Skip to content

Signing the APT repository

The APT repository at https://gh.vietanh.dev/ThinkUtils/apt is published by the Update APT repository step in .github/workflows/release.yml. That step signs the Release file if a signing key is configured, and publishes unsigned with a loud warning if one is not.

Until the key below exists, the repository is unsigned and users have to install with [trusted=yes].

Why this matters here

[trusted=yes] tells apt to skip signature verification entirely. For most repositories that is merely bad practice. For this one it is worse than average:

  • the package installs a fan-control helper that runs as root, plus a polkit rule that grants access to it
  • dpkg maintainer scripts run as root at install time

HTTPS authenticates the host for the duration of the transfer. It says nothing about whether the bytes sitting on the gh-pages branch, or in any cache in front of it, are the ones CI built. A signature is what carries that guarantee from the builder to the user's machine.

Generating the key

Do this once, on a trusted machine — not in CI.

bash
# A dedicated key, used for nothing else.
gpg --quick-gen-key "ThinkUtils Archive Signing Key <you@example.com>" \
    default default never

# Note the key ID from the output, then export both halves.
KEYID=<key-id-from-above>
gpg --armor --export-secret-keys "$KEYID" > thinkutils-apt-private.asc
gpg --armor --export "$KEYID"             > thinkutils-apt-public.asc

Keep thinkutils-apt-private.asc offline. It is the credential that lets anyone publish a package your users' machines will install as root.

Configuring CI

Add two repository secrets (Settings → Secrets and variables → Actions):

SecretValue
APT_GPG_PRIVATE_KEYfull contents of thinkutils-apt-private.asc
APT_GPG_PASSPHRASEthe key's passphrase (omit if the key has none)

The next tagged release will then publish InRelease, Release.gpg, and thinkutils-archive-keyring.asc alongside the packages, and the generated index.html will switch to signed-by= instructions automatically.

What users run once it is signed

bash
curl -fsSL https://gh.vietanh.dev/ThinkUtils/apt/thinkutils-archive-keyring.asc \
  | sudo gpg --dearmor -o /usr/share/keyrings/thinkutils-archive-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/thinkutils-archive-keyring.gpg] https://gh.vietanh.dev/ThinkUtils/apt ./" \
  | sudo tee /etc/apt/sources.list.d/thinkutils.list

sudo apt update
sudo apt install thinkutils

signed-by= scopes the key to this one repository, so it cannot be used to vouch for packages from anywhere else in the user's sources.

Rotating or removing the key

Changing the key changes what users must trust, so treat it as a release event: publish the new public key, and expect apt update to fail for anyone still pinning the old one until they re-import.

Removing the secrets makes the next release publish unsigned again. The workflow deletes any previously published InRelease, Release.gpg, and keyring file when it does — a stale signature next to a regenerated Release breaks apt update outright, which is worse for users than being unsigned.

Verifying by hand

bash
curl -fsSL https://gh.vietanh.dev/ThinkUtils/apt/thinkutils-archive-keyring.asc \
  | gpg --import
curl -fsSLO https://gh.vietanh.dev/ThinkUtils/apt/Release
curl -fsSLO https://gh.vietanh.dev/ThinkUtils/apt/Release.gpg
gpg --verify Release.gpg Release

Released under the LGPL v3 License.