feat(release): sign release checksums (ed25519), enforce + bake pubkey

Releases were shipping UNSIGNED: ship.sh never invoked sign-checksums, the
goreleaser pubkey ldflag defaulted to empty, and publish-cli-release.sh did not
upload a .sig — so the self-updater's signature check was silently skipped
(1.0.0-beta had no checksums.txt.sig). Make signing unconditional:

- internal/upgrade/signature.go: bake the canonical release public key as the
  compiled-in default (public, safe to commit; removes the empty-env footgun).
- .goreleaser.yml: drop the pubkey ldflag (committed default is authoritative)
  + add a signs: block that runs scripts/sign-checksums over checksums.txt.
  sign-checksums requires -key, so an unset RELEASE_SIGNING_KEY fails the build
  instead of shipping unsigned.
- scripts/ship.sh: source RELEASE_SIGNING_KEY from ~/.config/unarr-release/signing.key
  (or the env), die if absent, and assert checksums.txt.sig was produced.

Private key lives outside the repo (gitignored keyfile + operator's vault);
public key verified to match (priv[32:] == baked pubkey).
This commit is contained in:
Deivid Soto 2026-06-03 19:23:19 +02:00
parent 547b0d4e37
commit 1757bdabf5
3 changed files with 59 additions and 18 deletions

View file

@ -26,10 +26,10 @@ builds:
- -s -w
- -X github.com/torrentclaw/unarr/internal/cmd.Version={{.Version}}
- -X github.com/torrentclaw/unarr/internal/sentry.dsn={{ .Env.SENTRY_DSN }}
# Release-signing public key — verified by the self-updater against
# checksums.txt.sig. Empty when not configured; in that case
# signature verification is skipped and a warning is logged.
- -X github.com/torrentclaw/unarr/internal/upgrade.releasePubKeyBase64={{ .Env.RELEASE_SIGNING_PUBKEY }}
# The release-signing PUBLIC key is compiled in as the canonical default
# in internal/upgrade/signature.go (it's public — committing it removes
# the "empty env var → unsigned binary" footgun). No ldflag override:
# every build bakes the same key and verifies checksums.txt.sig.
archives:
- formats: [tar.gz]
@ -51,6 +51,28 @@ archives:
checksum:
name_template: "checksums.txt"
# Sign checksums.txt with the release ed25519 private key → checksums.txt.sig,
# verified by the self-updater against the compiled-in public key. Releases are
# signed UNCONDITIONALLY: sign-checksums requires -key, so an unset/empty
# RELEASE_SIGNING_KEY makes this step (and the whole `goreleaser release`) fail
# rather than silently shipping an unsigned release. ship.sh sources the key
# from ~/.config/unarr-release/signing.key (or the RELEASE_SIGNING_KEY env).
signs:
- id: checksums
cmd: go
args:
- run
- ./scripts/sign-checksums
- -key
- "{{ .Env.RELEASE_SIGNING_KEY }}"
- -in
- "${artifact}"
- -out
- "${signature}"
signature: "${artifact}.sig"
artifacts: checksum
output: true
changelog:
sort: asc
filters: