Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

37 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

postoffice

Every shellcell package repository, published to https://dl.shellcell.dev with snailmail.

repository format serves URL
releases raw release binaries for every tool and platform dl.shellcell.dev/releases/
apt deb apt-get install for Debian and Ubuntu dl.shellcell.dev/apt/
yum rpm dnf install for Fedora and RHEL dl.shellcell.dev/yum/
alpine apk apk add for Alpine dl.shellcell.dev/alpine/
charts helm Helm charts dl.shellcell.dev/charts/

One repository per format, not per tool: raw publishes to <name>/<version>/<file> and deb pools by package name, so every shellcell tool lives in the same three.

How a release gets here

Tools do not publish to this repository. Each one builds its own artifacts, attaches them to a GitHub Release with a SHA256SUMS, and dispatches here:

shellcell/snailmail  tag v0.1.2 ──► GitHub Release + SHA256SUMS
                                          │  repository_dispatch
                                          ▼
postoffice: adopt (pins each digest) ──► plan ──► apply ──► gh-pages

bin/adopt-release.sh lists the release, reads whatever checksum file it publishes, and adopts each asset by digest, routing it by what it is: .deb to apt, .rpm to yum, .apk to alpine, .tgz to charts, archives to releases. An asset type with no rule fails rather than landing somewhere arbitrary.

The release is listed rather than read only through its checksum file. A producer that publishes digests for its tarballs but not its packages would otherwise adopt cleanly and publish half of itself.

SHA256SUMS, checksums.txt and the other common names are recognised; --checksums NAME says which to use when a release names it something else.

An asset with no published digest is refused by name. Adopting it means pinning the bytes that run downloads rather than a digest the producer stood behind, which is a weaker claim about what was released — --pin-downloads accepts that, and nothing else does it silently. The publish workflow exposes the same choice as an input, and only on a manual run: an automatic dispatch never makes it for you.

Adding a tool needs no change here. Give it a release workflow that produces conventionally named assets and dispatches release with its repository and tag, using a token with Contents: write on this repository (TAP_GITHUB_TOKEN).

Archives are the one format that cannot name itself. Raw reads <name>_<version>_<os>_<arch>.<ext> out of the filename; an archive written as tool-v1.2.3-linux-amd64.tar.gz is republished under the convention so its architecture is recorded, and anything unrecognisable is adopted under the tool and version alone.

Adopting by hand

bin/adopt-release.sh shellcell/snailmail v0.1.2
git diff repos/          # review what is about to be published
git commit -am 'adopt snailmail 0.1.2'
git push                 # the push publishes

GH_TOKEN is optional, and raises the anonymous API rate limit when listing the release.

Where things live

main holds desired state — snailmail.toml, the locks, the publication ledgers and deployment receipts. gh-pages holds the built site as a single orphan commit, force-pushed each time so published bytes never accumulate in history.

Artifact bytes are in neither. Each lock pins the origin on the producing repository's GitHub Release, and snailmail refetches from it and checks it against the committed digest. That is why this needs no object storage: your GitHub Releases already are the durable copy. It also means a release whose assets are deleted can no longer be rebuilt — keep them.

docs/ is generated by snailmail apply and is not committed.

Operating it

snailmail status                       # what is desired, and what is published
snailmail check --origins              # re-fetch pins and verify them
snailmail promote releases snailmail 0.1.2
snailmail yank --all releases snailmail 0.1.1
snailmail prune releases --keep 5      # bound how many versions stay visible

Every one of these edits a lock. Publishing is always plan then apply, run by the workflow on push.

Signing

The apt repository is signed by archive-signing, an OpenPGP RSA-4096 key. The private key lives outside the workspace in $XDG_DATA_HOME/snailmail/private-keys/<workspace-id>/, mode 0600; only the public forms are committed, as keys/archive-signing.gpg and .asc.

Publishing needs three settings on this repository:

setting value
vars.SNAILMAIL_SIGNING_KEY_REF 53b4b8e5c2289f98d8b4b7537f94ae3facbb03934dc5363e4608bd27703c2837/archive-signing
secrets.SNAILMAIL_SIGNING_PRIVATE_KEY contents of that .asc private key file
vars.SNAILMAIL_APK_KEY_REF 53b4b8e5…/alpine-signing
secrets.SNAILMAIL_APK_PRIVATE_KEY contents of that .asc private key file
secrets.SNAILMAIL_KEY_PASSPHRASE the passphrase, at least 24 bytes; one covers the store

apt and yum share the OpenPGP key: both verify OpenPGP, so one key serves them. alpine cannot — apk verifies a bare RSA key it holds by filename and can check no other — so it has its own, and keys attach refuses the wrong kind rather than letting it fail later at publication.

Locally, export SNAILMAIL_KEY_PASSPHRASE before plan or apply; without it they refuse to run rather than publish something unsigned.

Rotation is a first-class operation — snailmail keys rotate apt --successor <key>, then --advance once the overlap has been served long enough for clients to have refreshed. snailmail keys audit reports whether the current state is valid.

Attaching a key to a repository that already exists

snailmail setup only creates repositories, so signing one that was set up unsigned is its own command:

snailmail keys new archive-signing --expires-in 17520h
snailmail keys attach apt --key archive-signing
snailmail keys audit

attach derives the client keyring from the key the workspace already recorded, and refuses a repository that is already signed — replacing a live key is rotation, which serves both keys for an overlap so clients keep trusting the repository through the change.

Requirements

Publishing runs on a fresh runner every time, which needs

  • Pages serving the gh-pages branch, with dl.shellcell.dev as the custom domain and a CNAME DNS record pointing at shellcell.github.io
  • Docker on the runner, so a real apt verifies the Debian repository by installing from it
  • full history on checkout, since ledgers are validated against it

About

Snailmail releases repo

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages