diff --git a/nix/modules/hive-forge.nix b/nix/modules/hive-forge.nix index ce420e58..538ae634 100644 --- a/nix/modules/hive-forge.nix +++ b/nix/modules/hive-forge.nix @@ -290,9 +290,12 @@ in THEMES = "catppuccin-vibec0re,forgejo-auto,forgejo-light,forgejo-dark,gitea-auto,gitea-light,gitea-dark"; }; # Point forgejo at the GPG key generated by the - # forgejo-gpg-init oneshot below. "default" resolves to - # the first secret key found in GNUPGHOME. GNUPGHOME - # must be absolute and writeable by the forgejo user. + # forgejo-gpg-init service below. SIGNING_KEY = "default" + # resolves via the forgejo process's git config + # (`user.signingkey`) — which forgejo-gpg-init sets to the + # generated key — not by scanning GNUPGHOME. GNUPGHOME is + # the keyring forgejo signs from; must be absolute + + # writeable by the forgejo user. "repository.signing" = { SIGNING_KEY = "default"; GNUPGHOME = "/var/lib/forgejo/.gnupg"; @@ -321,42 +324,67 @@ in pkgs.gnupg ]; - # Generate a GPG signing key for Forgejo on first boot so UI - # merges produce signed commits instead of erroring "no key to - # sign with". The key lives in forgejo's persistent state dir - # (/var/lib/forgejo/.gnupg) and survives container restarts. - # The stamp file prevents re-generation on subsequent boots. - # Service runs as the forgejo user so file ownership is correct. + # Ensure Forgejo has a usable GPG signing key so UI merges / CRUD + # commits are signed instead of erroring "does not have a signing + # key". This service (a) generates a key in forgejo's persistent + # keyring iff one isn't already present — keyed on the actual + # secret key, NOT a stamp file, so a partial state wipe that loses + # the key still regenerates it — and (b) points the forgejo user's + # git config at it (`user.signingkey` + commit/tag gpgsign), which + # is how `SIGNING_KEY = "default"` actually resolves. Runs as the + # forgejo user before forgejo on each start; idempotent (the keygen + # is guarded, the git-config is a cheap re-set). systemd.services.forgejo-gpg-init = { - description = "generate GPG signing key for Forgejo (once)"; - # Start before forgejo so the key is ready when forgejo reads - # repository.signing config on startup. + description = "ensure Forgejo's GPG signing key + git signing config"; + # Start before forgejo so the key + signing config are ready when + # forgejo reads repository.signing on startup. wantedBy = [ "forgejo.service" ]; before = [ "forgejo.service" ]; - unitConfig.ConditionPathExists = "!/var/lib/forgejo/.gnupg/hive-key-init.stamp"; serviceConfig = { Type = "oneshot"; - RemainAfterExit = true; User = "forgejo"; Group = "forgejo"; }; - environment.GNUPGHOME = "/var/lib/forgejo/.gnupg"; + # GNUPGHOME = the keyring forgejo signs from; HOME so + # `git config --global` lands where the forgejo process reads it. + environment = { + GNUPGHOME = "/var/lib/forgejo/.gnupg"; + HOME = "/var/lib/forgejo"; + }; path = [ pkgs.gnupg + pkgs.git + pkgs.gnugrep + pkgs.gawk pkgs.coreutils ]; script = '' - mkdir -p "$GNUPGHOME" - chmod 700 "$GNUPGHOME" - gpg --batch --gen-key <<'EOF' - %no-protection - Key-Type: RSA - Key-Length: 4096 - Name-Real: HyperHive Forge - Name-Email: forgejo@hive - Expire-Date: 0 - EOF - touch "$GNUPGHOME/hive-key-init.stamp" + set -euo pipefail + mkdir -p "$GNUPGHOME" + chmod 700 "$GNUPGHOME" + + # Generate only if no secret key is present (key-based guard, + # not a stamp — a stamp can outlive the key after a state wipe + # and wrongly suppress regeneration). + if ! gpg --list-secret-keys --with-colons 2>/dev/null | grep -q '^sec:'; then + printf '%s\n' \ + '%no-protection' \ + 'Key-Type: RSA' \ + 'Key-Length: 4096' \ + 'Name-Real: HyperHive Forge' \ + 'Name-Email: forgejo@hive' \ + 'Expire-Date: 0' \ + | gpg --batch --gen-key + fi + + # Point git (hence Forgejo's SIGNING_KEY="default") at the key. + KEYID=$(gpg --list-secret-keys --keyid-format long --with-colons \ + | awk -F: '/^sec:/ { print $5; exit }') + if [ -n "$KEYID" ]; then + git config --global user.signingkey "$KEYID" + git config --global commit.gpgsign true + git config --global tag.gpgsign true + fi ''; }; };