Static apt (.deb), dnf (.rpm) and apk (Alpine) repositories for the
shellcell CLIs, served from GitHub Pages at
https://packages.shellcell.dev. End-user install instructions live on that
page (generated from templates/index.html; the base URL is set once as
BASE_URL in the publish workflow).
This repository is stateless: no packages or indexes are committed. The source of truth is each project's latest GitHub release.
project release workflow ──repository_dispatch──▶ publish.yml
│
for each project in projects.txt: │
download *.deb *.rpm *.apk from latest release│
build + sign apt/rpm/apk indexes ◀─────────────┘
deploy site/ to GitHub Pages
scripts/build-site.sh— orchestrator; downloads assets, calls the three builders, exports public keys, rendersindex.html.scripts/build-apt.sh— reprepro-builtdists/+pool/, Release signed with the GPG key (SignWith: yes= default imported key).scripts/build-rpm.sh— per-arch dirs +createrepo_c, detached signature onrepomd.xml(repo_gpgcheck=1).scripts/build-apk.sh— per-arch dirs,APKINDEX.tar.gzgenerated and signed with the abuild RSA key inside analpinecontainer.
Every run rebuilds the whole site, so a burst of releases, a failed run, or a
key rotation is fixed by re-running the workflow. Only the latest release
of each project is served. Projects whose releases carry no package assets
are skipped, so listing a project in projects.txt before its packaging
exists is harmless.
| Secret | Used by | Contents |
|---|---|---|
GPG_PRIVATE_KEY |
this repo | armored GPG private key; signs apt Release and rpm repomd.xml |
APK_PRIVATE_KEY |
this repo | abuild RSA private key; signs APKINDEX.tar.gz |
TAP_GITHUB_TOKEN |
project repos | a PAT with access to this repo; authorizes the repository_dispatch that triggers publishing |
Keep the canonical copy of every private key and token in a password
manager — GitHub Actions secrets are write-only and cannot be recovered from
GitHub. Public keys are not secret: the abuild public key is committed under
keys/, and the GPG public key is exported from the private key on each run.
-
Enable Pages: repo Settings → Pages → Source: GitHub Actions.
-
Custom domain: add a DNS
CNAMErecordpackages.shellcell.dev → shellcell.github.io, setpackages.shellcell.devas the custom domain in Settings → Pages, and enable Enforce HTTPS once the certificate is issued. -
GPG key (signs apt + rpm metadata). Sign-only, no passphrase (CI signs unattended), no expiry (an expired repo key breaks installed users):
export GNUPGHOME="$(mktemp -d)" gpg --batch --quick-gen-key "shellcell packages <packages@shellcell.dev>" ed25519 sign never gpg --export-secret-keys --armor > gpg-private.asc # → password manager gpg --export --armor > gpg-public.asc # → password manager gh secret set GPG_PRIVATE_KEY --org shellcell --visibility selected --repos packages < gpg-private.asc shred -u gpg-private.asc && rm -rf "$GNUPGHOME"
-
abuild RSA key (signs the Alpine index; Alpine does not use GPG). The file name is load-bearing — see
keys/README.md:openssl genrsa -out packages@shellcell.rsa 4096 openssl rsa -in packages@shellcell.rsa -pubout -out packages@shellcell.rsa.pub # private key → password manager, then: gh secret set APK_PRIVATE_KEY --org shellcell --visibility selected --repos packages < packages@shellcell.rsa cp packages@shellcell.rsa.pub keys/ # commit this shred -u packages@shellcell.rsa
-
Dispatch token: a fine-grained PAT whose repository access includes this repo (Contents: read/write is sufficient), stored as an org secret named
TAP_GITHUB_TOKENavailable to the project repos. -
Wire the projects: each project's release workflow sends a dispatch after its release assets are uploaded:
publish-packages: needs: release runs-on: ubuntu-latest steps: - name: Trigger package repo publish env: GH_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN }} run: | gh api repos/shellcell/packages/dispatches \ -f event_type=publish-packages \ -f "client_payload[project]=${GITHUB_REPOSITORY#*/}" \ -f "client_payload[version]=${GITHUB_REF_NAME#v}"
(Guard with
if: startsWith(github.ref, 'refs/tags/')in workflows that also run on branch pushes.)
Publish manually: Actions → Publish → Run workflow. Safe to run any time; the site is rebuilt from scratch.
Local dry run (needs gh, reprepro, createrepo_c, gpg with the
signing key imported, and docker):
export GH_TOKEN=$(gh auth token)
./scripts/build-site.sh
python3 -m http.server -d site # point apt/dnf/apk at localhost to testAdd a project: add its name to projects.txt; make sure its release
workflow uploads .deb/.rpm/.apk assets (nfpm) and sends the dispatch
above.
Rotate a key: generate a new key as in setup, overwrite the org secret,
re-run Publish. The site serves the new public key immediately; existing
users must re-fetch it (one curl line from the install instructions), so
announce the rotation in the projects' READMEs or releases.