Packaging runbook¶
What CI builds today¶
| Workflow | Trigger | Produces |
|---|---|---|
validate.yml | PR, push | scripts/validate.py result; old-name guard (clipster may not reappear outside an allow-list) |
packaging-ci.yml | PR, push to develop/main | shellcheck, desktop-file-validate, appstreamcli validate; tarballs; .deb .rpm .apk .pkg.tar.zst; install/status/uninstall smoke in debian:stable, fedora:latest, alpine:latest, archlinux; macOS .app/.pkg/.dmg build + bundled-CLI smoke |
release.yml | tag v* | guard (tag on main; VERSION = CHANGELOG top = CITATION.cff = tag; validator) → Linux build → macOS build (signed + notarized when Apple secrets exist) → SHA256SUMS, cosign keyless bundle, GitHub artifact attestation → GitHub Release with every asset, including spritesheet-v1.webp |
docs.yml | PR (build only), push to main, manual | mkdocs build --strict; deploy to gh-pages preserving reserved paths |
Local equivalents: make validate, make lint, make dist, make v1, ./packaging/linux/build.sh (needs nfpm), ./packaging/macos/build.sh (macOS), make docs / make docs-serve.
Layout¶
packaging/bin/clippy-pet shared POSIX CLI (@VERSION@ / @DATADIR@ substituted at build)
packaging/share/ man page, .desktop launcher + autostart, AppStream metainfo, icon
packaging/dist/make-tarballs.sh reproducible tarballs (SOURCE_DATE_EPOCH from the tag commit)
packaging/linux/ nfpm.yaml, build.sh, debian-copyright (DEP-5), debian-changelog
packaging/macos/ build.sh, postinstall, install.applescript, distribution.xml, resources/, .icns
packaging/keys/ public GPG (.gpg.asc) and apk (.rsa.pub) keys + README
scripts/install.sh bootstrap; published as /install.sh on Pages
scripts/build-v1-spritesheet.py v1 crop for web upload
Identifiers: package/CLI/repo clippy-pet; reverse-DNS app id io.github.adammatthewsteinberger.clippy_pet (underscore, since the scheme can't take both dots and hyphens; .installer / .pkg suffixes for the two macOS bundles); data dir <prefix>/share/clippy-pet/; Homebrew tap adammatthewsteinberger/homebrew-tap; Pages https://adammatthewsteinberger.github.io/clippy-pet/.
Design constraints worth remembering¶
- Pets are per-user; no package may write to
$HOME(Debian policy, Homebrew policy, common sense). Packages ship payload + CLI; the CLI (or the opt-out autostartsync) copies into the user's home. Only the macOS.app, the.pkgpostinstall (as the console user), andinstall.shtouch a home directory directly. - Source-based recipes fetch the runtime tarball from Releases, not the GitHub source archive (
source/andqa/areexport-ignore). - nFPM gotchas hit so far: env vars in
srcneedexpand: true;packager:for Arch goes in a top-levelarchlinux:block; don't declaredepends: sh(breaks Debian's solver); hand-writtenchangelog.Debian.gzbeats nfpm'schangelog:; run nfpm frompackaging/linux/. - macOS:
COPYFILE_DISABLE=1beforepkgbuild; PlistBuddyAdd … || Set …forCFBundleIdentifier; postinstall resolves the console user via${SUDO_USER:-$(stat -f%Su /dev/console)}, skipsroot/loginwindow/_*, and never fails the package. - Alpine's
cmpmay be missing; the CLI'sfiles_equalfalls back tocksum/md5sum.
Secrets and one-time setup¶
Set in the repository's Actions secrets. Private keys and passphrases are never committed; an offline backup exists outside the repository.
| Secret | Purpose | Status |
|---|---|---|
GPG_PRIVATE_KEY, GPG_PASSPHRASE | RSA-4096, apt/rpm repo signing; public key fingerprint 49B2 46A2 9CD4 0801 4D5C 1EEE 57F1 8C00 88A5 8920, expires 2028-08 | set |
APK_PRIVATE_KEY | Alpine signing (adam@matthewsteinberger.com-6a834891) | set |
AUR_SSH_PRIVATE_KEY | push to AUR | set (public key must still be attached to an AUR account) |
TAP_GITHUB_TOKEN | fine-grained PAT scoped to homebrew-tap only, Contents: read/write | needed |
MACOS_CERT_P12, MACOS_CERT_PASSWORD, APPLE_TEAM_NAME, APPLE_TEAM_ID, APPLE_ID, APPLE_APP_PASSWORD | Developer ID signing + notarization | needed (requires Apple Developer enrolment) |
COPR_API_TOKEN · OBS_USERNAME/OBS_PASSWORD · LAUNCHPAD_SSH_PRIVATE_KEY · SNAPCRAFT_STORE_CREDENTIALS | build services | needed (each requires an account) |
Accounts still to create, in order of value: Apple Developer ID → AUR → Launchpad (PPA) → COPR → OBS → Ubuntu One / Snap Store (snapcraft register clippy-pet) → GitLab (aports) → GURU access. Also: upload docs/assets/social-preview.png in the repository's Settings → Social preview (no API for that).
Release checklist (GitFlow)¶
release/x.y.zfromdevelop: bumpVERSION;CITATION.cffversion:anddate-released:; moveCHANGELOG.mdUnreleased to[x.y.z] - dateand fix compare links;packaging/linux/debian-changelogentry.- PR
release/x.y.z→main; merge; back-merge intodevelop. - Tag
vX.Y.Zonmain→release.ymlruns. - Check the Release page (all assets,
SHA256SUMS,.sigstore.json), thencurl -fsSL …/install.sh | shon a clean machine. - Downstream: tap formula, AUR, COPR, OBS, PPA, snap edge, Pages repos, as each is wired; open a tracking issue for the manual PRs (nixpkgs, MacPorts, aports, GURU, conda-forge, Flathub).
docs.ymlredeploys the site frommainautomatically; the announcement bar text lives inoverrides/main.html.
Docs and Pages¶
gh-pages hosts both this site and non-site paths. docs.yml:
pip install -r docs/requirements.txt && mkdocs build --strict- copies
scripts/install.sh→site/install.shandpackaging/keys/*→site/keys/, addssite/.nojekyll - checks out
gh-pagesandrsync -a --deletethe site into it excludingapt/ rpm/ alpine/ conda/ flatpak/(the package repos, owned by the release pipeline) - asserts those directories still exist if they existed before, then commits and pushes;
concurrency: gh-pagesserialises it against any repo publisher
mkdocs gh-deploy is deliberately not used because it force-replaces the branch.
Naming¶
Project and package: clippy-pet. Display: Clippy Pet. Old name clipster is allowed only in the migration code path, the changelog, and the guard's allow-list; validate.yml fails otherwise.