Conversation
Add two-channel APT repo (main=TPM, legacy=non-TPM) built with reprepro and GPG-signed during release. Hosted at tysmith.me/facelock/apt/ alongside existing docs site. Changes: - Add build-deb-tpm job (Debian trixie container, --features tpm) - Update build-deb for legacy channel (version suffix ~legacy1) - Add publish-apt job (reprepro + GPG signing via gpg-preset-passphrase) - Add trigger-pages job (gh workflow run after apt publish) - Update pages.yml to download apt-repo.tar.gz from latest release - Add dist/apt/conf/distributions (reprepro config) - Add test-apt-repo justfile recipe and APT secret preflight checks - Update docs (releasing.md, quickstart.md, README.md) with install instructions - Fix maintainer email to facelock@m.tysmith.me across all packaging files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds an automated, signed APT repository publishing flow for Facelock releases (two suites: TPM-enabled “main” and non-TPM “legacy”), hosted via GitHub Pages, and documents installation/releasing steps.
Changes:
- Add reprepro
distributionsconfig formainandlegacysuites. - Extend release workflow to build both Debian packages and publish a signed APT repo tarball to the GitHub Release, then trigger Pages.
- Update Pages workflow + docs/README/book quickstart with APT installation and release/key management instructions.
Reviewed changes
Copilot reviewed 6 out of 8 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
dist/apt/conf/distributions |
New reprepro suite definitions for main and legacy. |
.github/workflows/release.yml |
Adds TPM deb build, APT repo publish/signing, and Pages trigger; updates legacy deb versioning. |
.github/workflows/pages.yml |
Pulls apt-repo.tar.gz from latest release and publishes under _site/apt/. |
justfile |
Adds local test-apt-repo and expands release preflight secret checks. |
docs/releasing.md |
Documents two-channel Debian packaging and APT repo signing setup/rotation. |
book/src/quickstart.md |
Adds APT installation instructions for main/legacy channels. |
README.md |
Adds APT install section and updates release workflow description. |
dist/debian/changelog |
Updates maintainer email. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| REPO_DIR="$(pwd)/apt-repo" | ||
| gpg --export > "${REPO_DIR}/tysmith-archive-keyring.gpg" |
There was a problem hiding this comment.
gpg --export without a key selector exports all public keys in the keyring. To avoid accidentally shipping unrelated keys (or changing output if additional keys are present in the environment), export only the intended signing key (e.g., by fingerprint captured earlier) when generating tysmith-archive-keyring.gpg.
| REPO_DIR="$(pwd)/apt-repo" | |
| gpg --export > "${REPO_DIR}/tysmith-archive-keyring.gpg" | |
| set -euo pipefail | |
| REPO_DIR="$(pwd)/apt-repo" | |
| # Determine the fingerprint of the signing key (first secret key) | |
| SIGNING_FPR="$(gpg --list-secret-keys --with-colons | awk -F: '$1 == "sec" { print $5; exit }')" | |
| if [ -z "${SIGNING_FPR}" ]; then | |
| echo "Error: No secret GPG key found; cannot export signing keyring." >&2 | |
| exit 1 | |
| fi | |
| echo "Exporting public key for signing key fingerprint: ${SIGNING_FPR}" | |
| gpg --export "${SIGNING_FPR}" > "${REPO_DIR}/tysmith-archive-keyring.gpg" |
| if gh release download --pattern 'apt-repo.tar.gz' --dir /tmp/apt-download --repo "${{ github.repository }}" 2>/dev/null; then | ||
| echo "Downloaded apt-repo.tar.gz from latest release" | ||
| mkdir -p _site/apt | ||
| tar -xzf /tmp/apt-download/apt-repo.tar.gz -C _site/apt/ | ||
| echo "=== APT repo files included in site ===" | ||
| find _site/apt -type f | sort | ||
| else | ||
| echo "No apt-repo.tar.gz found in any release. Deploying docs only." |
There was a problem hiding this comment.
If gh release download fails (API rate limit/transient error), this workflow currently proceeds and deploys a site without _site/apt/, which would remove the previously-published APT repo from Pages. Consider treating "release exists but asset download failed" as a hard error (fail the job) or otherwise ensuring an existing APT repo isn't unintentionally wiped on deploy.
| if gh release download --pattern 'apt-repo.tar.gz' --dir /tmp/apt-download --repo "${{ github.repository }}" 2>/dev/null; then | |
| echo "Downloaded apt-repo.tar.gz from latest release" | |
| mkdir -p _site/apt | |
| tar -xzf /tmp/apt-download/apt-repo.tar.gz -C _site/apt/ | |
| echo "=== APT repo files included in site ===" | |
| find _site/apt -type f | sort | |
| else | |
| echo "No apt-repo.tar.gz found in any release. Deploying docs only." | |
| # First, query the latest release assets and capture any error. | |
| release_err_file="$(mktemp)" | |
| asset_names="$(gh release view --json assets --jq '.assets[].name' --repo "${{ github.repository }}" 2>"${release_err_file}" || true)" | |
| if [ -s "${release_err_file}" ]; then | |
| if grep -qi 'release not found' "${release_err_file}"; then | |
| echo "No releases found. Deploying docs only." | |
| echo "This is expected before the first stable release with APT publishing." | |
| rm -f "${release_err_file}" | |
| exit 0 | |
| else | |
| echo "ERROR: Failed to query latest release for APT assets. Aborting to avoid wiping existing APT repo." >&2 | |
| cat "${release_err_file}" >&2 || true | |
| rm -f "${release_err_file}" | |
| exit 1 | |
| fi | |
| fi | |
| rm -f "${release_err_file}" | |
| if printf '%s\n' "${asset_names}" | grep -qx 'apt-repo.tar.gz'; then | |
| echo "Found apt-repo.tar.gz asset in latest release. Attempting download..." | |
| if gh release download --pattern 'apt-repo.tar.gz' --dir /tmp/apt-download --repo "${{ github.repository }}"; then | |
| echo "Downloaded apt-repo.tar.gz from latest release" | |
| mkdir -p _site/apt | |
| tar -xzf /tmp/apt-download/apt-repo.tar.gz -C _site/apt/ | |
| echo "=== APT repo files included in site ===" | |
| find _site/apt -type f | sort | |
| else | |
| echo "ERROR: apt-repo.tar.gz asset exists but download failed. Aborting to avoid wiping existing APT repo." >&2 | |
| exit 1 | |
| fi | |
| else | |
| echo "No apt-repo.tar.gz asset found in latest release. Deploying docs only." |
| 5. Validates Nix flake evaluation | ||
| 6. Publishes to AUR (if `AUR_SSH_KEY` secret is configured) | ||
| 7. Triggers COPR rebuild (if `COPR_WEBHOOK_URL` secret is configured) | ||
| 8. Publishes signed APT repository (if `APT_GPG_PRIVATE_KEY` is configured) |
There was a problem hiding this comment.
The workflow summary says APT publishing happens if APT_GPG_PRIVATE_KEY is configured, but the implementation/documentation elsewhere require both APT_GPG_PRIVATE_KEY and APT_GPG_PASSPHRASE. Suggest updating this line to mention both secrets so operators don't end up with a partially-configured setup.
| 8. Publishes signed APT repository (if `APT_GPG_PRIVATE_KEY` is configured) | |
| 8. Publishes signed APT repository (if `APT_GPG_PRIVATE_KEY` and `APT_GPG_PASSPHRASE` secrets are configured) |
| sed -i '/^SignWith:/d' "${REPO_DIR}/conf/distributions" | ||
|
|
||
| # Find any .deb files (from just test-deb or CI artifacts) | ||
| DEB_FILES=$(find . -maxdepth 1 -name 'facelock_*.deb' -o -path './target/*.deb' 2>/dev/null | head -2) |
There was a problem hiding this comment.
test-apt-repo's find command uses -maxdepth 1, so the -path './target/*.deb' branch can never match (it prevents descending into ./target). This makes the recipe fail to pick up debs from target/ as intended; consider restructuring the find with parentheses and a depth that actually includes target/ (or run two finds).
| DEB_FILES=$(find . -maxdepth 1 -name 'facelock_*.deb' -o -path './target/*.deb' 2>/dev/null | head -2) | |
| DEB_FILES=$((find . -maxdepth 1 -name 'facelock_*.deb'; find ./target -maxdepth 1 -name '*.deb') 2>/dev/null | head -2) |
| run: | | ||
| if [ -z "$APT_GPG_PRIVATE_KEY" ]; then | ||
| echo "APT_GPG_PRIVATE_KEY secret not configured. Skipping APT publish." | ||
| echo "See docs/releasing.md for setup instructions." |
There was a problem hiding this comment.
This step says it's skipping when APT_GPG_PRIVATE_KEY is missing, but it exits with status 1, which will fail the publish-apt job (and block downstream jobs). If APT publishing is meant to be optional when secrets are not configured (like AUR/COPR), exit 0 (or gate the job on secret presence) and consider also validating APT_GPG_PASSPHRASE explicitly for a clearer failure mode.
| echo "See docs/releasing.md for setup instructions." | |
| echo "See docs/releasing.md for setup instructions." | |
| exit 0 | |
| fi | |
| if [ -z "$APT_GPG_PASSPHRASE" ]; then | |
| echo "APT_GPG_PASSPHRASE secret not configured, but APT_GPG_PRIVATE_KEY is set." | |
| echo "Refusing to publish APT repository without a passphrase. See docs/releasing.md for setup instructions." |
Summary
tysmith.me/facelock/apt/mainchannel: TPM-enabled build (Debian trixie container,--features tpm)legacychannel: non-TPM build (Ubuntu 24.04, version suffix~legacy1)build-deb-tpmjob in Debian trixie container with dynamic libtss2 dependency detectionpublish-aptjob using reprepro + GPG signing (viagpg-preset-passphrasefor non-interactive CI)apt-repo.tar.gzfrom latest release, assembles into_site/apt/facelock@m.tysmith.meacross all packaging filesVerification plan
v0.1.0-rc11): Testsbuild-deb-tpmand legacy versioning (apt publish skipped)Files changed
dist/apt/conf/distributions.github/workflows/release.ymlbuild-deb-tpm,publish-apt,trigger-pages; updatebuild-debfor legacy.github/workflows/pages.yml_site/apt/justfiletest-apt-reporecipe, APT secret checks in preflightdocs/releasing.mdbook/src/quickstart.mdREADME.mddist/debian/changelogTest plan
v0.1.0-rc11→build-deb-tpmsucceeds in trixie containerv0.1.0-rc11→build-debproduces~legacy1versioned debpublish-aptruns reprepro + GPG signingapt-repo.tar.gzappears in release assets/apt/pathapt updatewith facelock source succeeds without GPG warnings🤖 Generated with Claude Code