tapesctl
Package main provides reproducible lint, test, and cross-platform releasebuilds locally and in GitHub Actions.
tapesctl is a pure-Rust CLI (no Apple frameworks, no C deps), so the
Linux→macOS cross-compile uses `cargo-zigbuild` — zig bundles the macOS
libSystem stubs, so no Apple SDK and no osxcross build are needed (unlike a
workspace with Apple-framework code, which forces the heavier osxcross path).
To wire up after first checkout:
cd tapesctl
dagger develop # regenerates go.mod + dagger codegen
dagger call lint # cargo fmt --check + clippy
dagger call test # cargo test --workspace
dagger call build-release export --path ./build
Installation
dagger install github.com/papercomputeco/tapesctl@b69d0aa69ef05215b1ae839b8dca83891f75b471Entrypoint
Return Type
Tapesctl !Arguments
| Name | Type | Default Value | Description |
|---|---|---|---|
| source | Directory | - | Project source directory. |
Example
dagger -m github.com/papercomputeco/tapesctl@b69d0aa69ef05215b1ae839b8dca83891f75b471 call \
func (m *MyModule) Example() *dagger.Tapesctl {
return dag.
Tapesctl()
}@function
def example() -> dagger.Tapesctl:
return (
dag.tapesctl()
)@func()
example(): Tapesctl {
return dag
.tapesctl()
}Types
Tapesctl 🔗
Tapesctl is the main module for the tapesctl CI/CD pipeline.
assertStampedIdentity() 🔗
AssertStampedIdentity runs a built binary and fails unless it reports the identity this pipeline stamped into it.
This is a pipeline step rather than a workflow step because what it protects
is publication. The release and nightly functions sync artifacts to the
public download prefix that install.sh and the documented URLs read from,
and a check that runs after that has already let a mis-stamped binary out —
blocking the GitHub release afterwards leaves the wrong binary live on the
download host, which is where nearly everyone gets one. Run from in here it
is ordered before publication by construction, whatever order a workflow
happens to put its steps in.
The linux/amd64 artifact is the one asked. All four come out of one build container holding one set of variables, so an injection either reached that container or reached none of them, and a static musl binary needs nothing but a kernel to answer.
Return Type
Void !Arguments
| Name | Type | Default Value | Description |
|---|---|---|---|
| artifacts | Directory ! | - | Artifact tree to check, laid out as //tapesctl. |
| version | String | "" | Release tag or channel name the artifacts were stamped with. |
| commit | String | "" | Full commit the artifacts were stamped with. |
| date | String | "" | RFC 3339 build timestamp the artifacts were stamped with. |
Example
dagger -m github.com/papercomputeco/tapesctl@b69d0aa69ef05215b1ae839b8dca83891f75b471 call \
assert-stamped-identity --artifacts DIR_PATHfunc (m *MyModule) Example(ctx context.Context, artifacts *dagger.Directory) {
return dag.
Tapesctl().
Assertstampedidentity(ctx, artifacts)
}@function
async def example(artifacts: dagger.Directory) -> None:
return await (
dag.tapesctl()
.assertstampedidentity(artifacts)
)@func()
async example(artifacts: Directory): Promise<void> {
return dag
.tapesctl()
.assertStampedIdentity(artifacts)
}build() 🔗
Build cross-compiles the tapesctl binary for all supported platforms using cargo-zigbuild. Linux targets are static musl builds (curl-and-run, like the old CGO_ENABLED=0 Go binaries); macOS targets link against zig’s bundled libSystem stubs — no Apple SDK required.
The identity arguments are what make a released binary able to name itself. They are optional because this function also serves plain CI builds, which have no release to name: the source arrives here without a .git directory (see the module’s +ignore), so an unstamped build genuinely knows nothing about its own provenance and says so rather than guessing.
Return Type
Directory !Arguments
| Name | Type | Default Value | Description |
|---|---|---|---|
| version | String | "" | Release tag or channel name to stamp into the binaries, for example v1.0.0 or nightly. |
| commit | String | "" | Full commit the binaries are built from. |
| date | String | "" | RFC 3339 timestamp of the build. |
Example
dagger -m github.com/papercomputeco/tapesctl@b69d0aa69ef05215b1ae839b8dca83891f75b471 call \
buildfunc (m *MyModule) Example() *dagger.Directory {
return dag.
Tapesctl().
Build()
}@function
def example() -> dagger.Directory:
return (
dag.tapesctl()
.build()
)@func()
example(): Directory {
return dag
.tapesctl()
.build()
}buildRelease() 🔗
BuildRelease compiles release binaries and adds SHA256 checksums.
It does not check what the binaries report about themselves: that belongs to whatever is about to publish them, which is the only thing that both knows the identity it asked for and can still withhold the result. See [Tapesctl.AssertStampedIdentity].
Return Type
Directory !Arguments
| Name | Type | Default Value | Description |
|---|---|---|---|
| version | String | "" | Release tag or channel name to stamp into the binaries, for example v1.0.0 or nightly. |
| commit | String | "" | Full commit the binaries are built from. |
| date | String | "" | RFC 3339 timestamp of the build. |
Example
dagger -m github.com/papercomputeco/tapesctl@b69d0aa69ef05215b1ae839b8dca83891f75b471 call \
build-releasefunc (m *MyModule) Example() *dagger.Directory {
return dag.
Tapesctl().
Buildrelease()
}@function
def example() -> dagger.Directory:
return (
dag.tapesctl()
.buildrelease()
)@func()
example(): Directory {
return dag
.tapesctl()
.buildRelease()
}lint() 🔗
Lint runs cargo fmt --all -- --check and cargo clippy --workspace
--all-targets -- -D warnings. Both are the same gates make lint runs
locally, so a green Dagger lint matches a green local lint.
Return Type
String ! Example
dagger -m github.com/papercomputeco/tapesctl@b69d0aa69ef05215b1ae839b8dca83891f75b471 call \
lintfunc (m *MyModule) Example(ctx context.Context) string {
return dag.
Tapesctl().
Lint(ctx)
}@function
async def example() -> str:
return await (
dag.tapesctl()
.lint()
)@func()
async example(): Promise<string> {
return dag
.tapesctl()
.lint()
}nightly() 🔗
Nightly builds and uploads nightly artifacts.
Return Type
Directory !Arguments
| Name | Type | Default Value | Description |
|---|---|---|---|
| commit | String | "" | Full commit the nightly is built from. It is what distinguishes one nightly from the next, since they all carry the same version name. |
| endpoint | Secret ! | - | Bucket endpoint URL. |
| bucket | Secret ! | - | Bucket name. |
| accessKeyId | Secret ! | - | Bucket access key ID. |
| secretAccessKey | Secret ! | - | Bucket secret access key. |
Example
dagger -m github.com/papercomputeco/tapesctl@b69d0aa69ef05215b1ae839b8dca83891f75b471 call \
nightly --endpoint env:MYSECRET --bucket env:MYSECRET --access-key-id env:MYSECRET --secret-access-key env:MYSECRETfunc (m *MyModule) Example(endpoint *dagger.Secret, bucket *dagger.Secret, accessKeyId *dagger.Secret, secretAccessKey *dagger.Secret) *dagger.Directory {
return dag.
Tapesctl().
Nightly(endpoint, bucket, accessKeyId, secretAccessKey)
}@function
def example(endpoint: dagger.Secret, bucket: dagger.Secret, accesskeyid: dagger.Secret, secretaccesskey: dagger.Secret) -> dagger.Directory:
return (
dag.tapesctl()
.nightly(endpoint, bucket, accesskeyid, secretaccesskey)
)@func()
example(endpoint: Secret, bucket: Secret, accessKeyId: Secret, secretAccessKey: Secret): Directory {
return dag
.tapesctl()
.nightly(endpoint, bucket, accessKeyId, secretAccessKey)
}releaseLatest() 🔗
ReleaseLatest builds and uploads versioned and latest release artifacts.
Return Type
Directory !Arguments
| Name | Type | Default Value | Description |
|---|---|---|---|
| version | String ! | - | Version string, for example v1.0.0. Names the upload prefix and is stamped into the binaries, so a released tapesctl reports the release it was downloaded from. Written without backquotes on purpose: the help renderer takes a backquoted word as the flag’s placeholder, so a quoted command name here would be printed where the argument’s type belongs. |
| commit | String | "" | Full commit the release is built from. |
| endpoint | Secret ! | - | Bucket endpoint URL. |
| bucket | Secret ! | - | Bucket name. |
| accessKeyId | Secret ! | - | Bucket access key ID. |
| secretAccessKey | Secret ! | - | Bucket secret access key. |
Example
dagger -m github.com/papercomputeco/tapesctl@b69d0aa69ef05215b1ae839b8dca83891f75b471 call \
release-latest --version string --endpoint env:MYSECRET --bucket env:MYSECRET --access-key-id env:MYSECRET --secret-access-key env:MYSECRETfunc (m *MyModule) Example(version string, endpoint *dagger.Secret, bucket *dagger.Secret, accessKeyId *dagger.Secret, secretAccessKey *dagger.Secret) *dagger.Directory {
return dag.
Tapesctl().
Releaselatest(version, endpoint, bucket, accessKeyId, secretAccessKey)
}@function
def example(version: str, endpoint: dagger.Secret, bucket: dagger.Secret, accesskeyid: dagger.Secret, secretaccesskey: dagger.Secret) -> dagger.Directory:
return (
dag.tapesctl()
.releaselatest(version, endpoint, bucket, accesskeyid, secretaccesskey)
)@func()
example(version: string, endpoint: Secret, bucket: Secret, accessKeyId: Secret, secretAccessKey: Secret): Directory {
return dag
.tapesctl()
.releaseLatest(version, endpoint, bucket, accessKeyId, secretAccessKey)
}test() 🔗
Test runs the workspace unit tests. --locked fails if Cargo.lock is stale,
which subsumes the go.mod-tidy check the Go pipeline had.
Return Type
String ! Example
dagger -m github.com/papercomputeco/tapesctl@b69d0aa69ef05215b1ae839b8dca83891f75b471 call \
testfunc (m *MyModule) Example(ctx context.Context) string {
return dag.
Tapesctl().
Test(ctx)
}@function
async def example() -> str:
return await (
dag.tapesctl()
.test()
)@func()
async example(): Promise<string> {
return dag
.tapesctl()
.test()
}uploadInstallSh() 🔗
UploadInstallSh uploads the install script under the tapesctl namespace.
Return Type
Void !Arguments
| Name | Type | Default Value | Description |
|---|---|---|---|
| endpoint | Secret ! | - | Bucket endpoint URL. |
| bucket | Secret ! | - | Bucket name. |
| accessKeyId | Secret ! | - | Bucket access key ID. |
| secretAccessKey | Secret ! | - | Bucket secret access key. |
Example
dagger -m github.com/papercomputeco/tapesctl@b69d0aa69ef05215b1ae839b8dca83891f75b471 call \
upload-install-sh --endpoint env:MYSECRET --bucket env:MYSECRET --access-key-id env:MYSECRET --secret-access-key env:MYSECRETfunc (m *MyModule) Example(ctx context.Context, endpoint *dagger.Secret, bucket *dagger.Secret, accessKeyId *dagger.Secret, secretAccessKey *dagger.Secret) {
return dag.
Tapesctl().
Uploadinstallsh(ctx, endpoint, bucket, accessKeyId, secretAccessKey)
}@function
async def example(endpoint: dagger.Secret, bucket: dagger.Secret, accesskeyid: dagger.Secret, secretaccesskey: dagger.Secret) -> None:
return await (
dag.tapesctl()
.uploadinstallsh(endpoint, bucket, accesskeyid, secretaccesskey)
)@func()
async example(endpoint: Secret, bucket: Secret, accessKeyId: Secret, secretAccessKey: Secret): Promise<void> {
return dag
.tapesctl()
.uploadInstallSh(endpoint, bucket, accessKeyId, secretAccessKey)
}