Skip to content

Repository files navigation

snailmail

relaxed package delivery

One tool to create, host, sign and operate package repositories from a git-backed workspace. Builds are deterministic, plans are reviewable, artifacts are immutable, and before anything is published a real client — pip, apt, dnf, apk, helm — installs from the built tree in a container.

MIT licensed. See LICENSE and NOTICE.md.

Six formats today. Debian (apt), RPM (dnf/yum), Alpine (apk), PyPI (pip), Helm, and raw for artifacts that carry no ecosystem metadata. Debian, RPM, Alpine and Helm are signed with the scheme their own clients verify.

Four hosts today. A local directory, a directory reached over ssh, GitHub Pages, and S3-compatible object storage — though not every format on every host; see the table below.

Planned, not built: npm, OCI, Cargo, Go, Maven and Nix repositories, and publishing into registries you do not own (AUR, Homebrew, nixpkgs, npmjs, PyPI, ghcr). PLAN.md has the design; none of it works yet.

Quickstart

From an empty directory to a published PyPI repository:

go run ./cmd/snailmail init --name example   # creates the Git repository if there is none
go run ./cmd/snailmail setup pypi --name python --output public/python
go run ./cmd/snailmail add python ./dist/*.whl
git add .gitignore snailmail.toml repos/python.lock.toml
git commit -m "configure Python repository"
go run ./cmd/snailmail plan
go run ./cmd/snailmail apply

plan writes what it intends to do; apply does it and records a publication ledger entry. Nothing reaches a host until a plan has been applied.

Each command prints the one that usually comes next, so the sequence can be followed without this page. The git commit in the middle is not incidental: desired state is reviewed as a diff, and plan reads committed state only.

apply reports each phase as it runs — a publication takes minutes, most of it building and running a real client per format — and writes that narration to stderr, so --json still emits exactly one document on stdout.

apply --dry-run builds, verifies and checks every gate, then stops before writing anything to a host. It is not the same as --structural-only, which only skips client verification and still publishes.

Every command that takes --workspace also accepts --root.

What runs where

Host pypi deb rpm apk helm raw
local directory yes yes yes yes yes yes
ssh directory (rsync) yes yes yes yes yes yes
GitHub Pages yes yes yes yes yes yes
S3 / R2 / GCS yes unsigned only yes yes

An object store makes a revision live by writing one object, so it can serve a format only where a single path switches. Debian needs a Release and its detached signature to become live together; Alpine has one index per architecture; and a signed yum repository has to switch repomd.xml with the repomd.xml.asc that signs it. Those are structural limits, not missing work — a local directory, an ssh directory or GitHub Pages commits a whole tree at once and serves all six.

The rsync host serves every format but publishes no preview site, so it works under the auto gate and is refused under pr and approval, which exist to review one. Publishing over ssh has the detail.

One caveat for object storage: the browsable index.html is generated fresh for every revision, so it is kept with the release rather than at the repository root. Clients are unaffected — they fetch simple/, index.yaml, repodata/ or SHA256SUMS — but there is no human-browsable page at the root of a bucket-hosted repository yet.

Status

Phase 2 is complete and Phase 3 signing is implemented. Concretely: git-backed workspaces with init, setup, add, plan and apply; immutable blobs; reviewable plans; publication ledgers; per-repository managed release switching; auto, pr and signed-approval gates; encrypted local RSA4096 OpenPGP keys with keys new|publish|audit|rotate; receipt-backed Debian key rotation; and placement-level promote and yank.

Review gates read GitHub, GitLab, Forgejo and Gitea, and snailmail ci emits a GitHub Actions or GitLab CI pipeline derived from the workspace.

Replacing an existing managed release needs an atomic directory-entry exchange, so that path is implemented on Linux and macOS; creating a first release is portable. There is no Windows build yet.

Additional key backends, more formats, and an interactive setup remain Phase 3 work; import is done and has its own page.

Documentation

The front page is the shape of the thing. Everything else is one page per question, because a reference manual and an introduction want different orders and this file was trying to be both.

Where repositories are published ssh, object storage, GitHub Pages, and what each can serve
Deciding what a repository publishes promote, yank, prune, collect, rollback, and raw artifacts
Signing and review keys, rotation, and the gates that hold a publication for a person
Taking on a repository that already exists import, adopt, and inspecting somebody else's
Running it status, check, exit codes, concurrency, and how large a workspace can get
Shipping snailmail itself the container image, generated CI, and binary size

ARCHITECTURE.md is the implementation contract and PLAN.md the broader design.

About

Relaxed package delivery

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages