Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions public.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

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
rdtB4A==
=lrzs
-----END PGP PUBLIC KEY BLOCK-----
112 changes: 112 additions & 0 deletions scripts/setup-gpg-keys.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#!/usr/bin/env bash
set -euo pipefail

KEY_NAME="Zurvan Linux Archive"
KEY_EMAIL="archive@zurvanlinux.org"
KEY_LENGTH=4096

if gpg --list-keys "${KEY_EMAIL}" >/dev/null 2>&1; then
FPR=$(gpg --list-keys --with-colons "${KEY_EMAIL}" | awk -F: '/^pub:/ {print $10; exit}')
echo "ERROR: A key for ${KEY_EMAIL} already exists in this keyring."
echo " Aborting to avoid overwriting. Remove it first if intended:"
echo " gpg --batch --yes --delete-secret-keys ${FPR}"
echo " gpg --batch --yes --delete-keys ${FPR}"
exit 1
fi

cat > /tmp/gpg-batch-primary-$$ <<EOF
%no-protection
Key-Type: RSA
Key-Length: ${KEY_LENGTH}
Name-Real: ${KEY_NAME}
Name-Email: ${KEY_EMAIL}
Expire-Date: 0
%commit
EOF

echo "[*] Generating offline 4096-bit RSA primary key for ${KEY_EMAIL} ..."
gpg --batch --gen-key /tmp/gpg-batch-primary-$$

PRIMARY_KEYID=$(gpg --list-keys --with-colons "${KEY_EMAIL}" | awk -F: '/^pub:/ {print $5; exit}')
echo "=== Primary key: ${PRIMARY_KEYID} ==="

cat > /tmp/gpg-addkey-$$.exp <<EOF
#!/usr/bin/env expect -f
set timeout 30
set keyid [lindex \$argv 0]
spawn gpg --pinentry-mode loopback --passphrase '' --edit-key \$keyid
expect "gpg> "
send "addkey\r"
expect "keyt"
send "4\r"
expect "keys"
send "${KEY_LENGTH}\r"
expect "expire"
send "0\r"
expect "y/N"
send "y\r"
expect "y/N"
send "y\r"
expect "gpg> "
send "save\r"
expect eof
EOF
chmod +x /tmp/gpg-addkey-$$.exp

echo "[*] Adding signing-only subkey ..."
/tmp/gpg-addkey-$$.exp "${PRIMARY_KEYID}"

SUBKEY_FPR=$(gpg --list-keys --with-colons "${KEY_EMAIL}" | awk -F: '/^sub:/ {print $5; exit}')
echo "=== Signing subkey fingerprint: ${SUBKEY_FPR} ==="

OUT_DIR="$(cd "$(dirname "$0")/.." && pwd)"

echo "[*] Exporting public key -> ${OUT_DIR}/public.key"
gpg --armor --export "${KEY_EMAIL}" > "${OUT_DIR}/public.key"

echo "[*] Exporting secret subkey -> ${OUT_DIR}/secret-subkey.asc"
gpg --armor --export-secret-subkeys "${PRIMARY_KEYID}" > "${OUT_DIR}/secret-subkey.asc" || true
if [[ ! -s "${OUT_DIR}/secret-subkey.asc" ]]; then
echo "ERROR: Failed to export secret subkey." >&2
exit 1
fi

chmod 600 "${OUT_DIR}/public.key" "${OUT_DIR}/secret-subkey.asc"

cat <<EOF

=============================================================
GPG key generation complete
=============================================================

Primary key (OFFLINE): ${PRIMARY_KEYID}
Signing subkey: ${SUBKEY_FPR}
Public key: ${OUT_DIR}/public.key
Secret subkey: ${OUT_DIR}/secret-subkey.asc

NEXT STEPS (per apt-repository/README.md):
----------------------------------------
1. Move ONLY ${OUT_DIR}/public.key and ${OUT_DIR}/secret-subkey.asc
to your secure workstation.

2. Commit public.key to the apt-repository repository root:
git add public.key && git commit -m "chore(apt): add APT repo signing public key"

3. In GitHub, add these secrets to the apt-repository repo:
Settings -> Secrets and variables -> Actions -> New repository secret

Name: APT_SIGNING_SUBKEY
Value: contents of secret-subkey.asc (the whole armored block)

Name: APT_SIGNING_PASSPHRASE
Value: leave blank / unset unless the exported subkey is passphrase-protected
(the publish-repo.yml worklow invokes gpg with --pinentry-mode loopback
and APT_SIGNING_PASSPHRASE at signing time).

4. Keep the primary key offline / air-gapped permanently.
If the subkey leaks, revoke it from the offline primary key and
generate a new signing subkey.

5. Re-run .github/workflows/publish-repo.yml to verify signing works.
=============================================================
EOF
Loading