feat(signing): unified GPG release key, verification guide, and org secrets tooling

This commit is contained in:
John Smith
2026-09-13 12:30:06 -04:00
parent 075b7c33be
commit 89b010fa8d
5 changed files with 577 additions and 13 deletions
+65
View File
@@ -0,0 +1,65 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
h2eyLaSysuCo47WOvwuN
=7J/k
-----END PGP PUBLIC KEY BLOCK-----
+93
View File
@@ -0,0 +1,93 @@
# Release Signing & Cryptographic Verification
This document describes how to verify official releases, binaries, and packages published by:
- [project-dorado](https://github.com/project-dorado)
- [Heretek-Games](https://github.com/Heretek-Games)
- [Heretek-AI](https://github.com/Heretek-AI)
All official release artifacts are signed using **Heretek Release Signing** GPG key and authenticated with **GitHub Artifact Attestations (Sigstore SLSA Provenance)**.
---
## 1. GPG Public Key
The ASCII-armored public key is available at:
- Repository file: [`HERETEK_RELEASE_SIGNING_KEY.asc`](./HERETEK_RELEASE_SIGNING_KEY.asc)
- Direct URL: `https://raw.githubusercontent.com/project-dorado/.github/main/HERETEK_RELEASE_SIGNING_KEY.asc`
### Key Details
- **User ID**: `Heretek Release Signing (Heretek & Dorado Unified Release Signing Key) <[email protected]>`
- **Key ID**: `BCBC85E3B15DC872`
- **Algorithm**: `RSA 4096-bit`
- **Fingerprint**:
```text
11EE 4B06 9B95 02EB E5EF 6531 BCBC 85E3 B15D C872
```
### Importing the Public Key
```bash
# Direct import from GitHub:
curl -sSL https://raw.githubusercontent.com/project-dorado/.github/main/HERETEK_RELEASE_SIGNING_KEY.asc | gpg --import
# Or via keyserver (once synchronized):
gpg --keyserver keys.openpgp.org --recv-keys 11EE4B069B9502EBE5EF6531BCBC85E3B15DC872
```
---
## 2. Verifying Release Assets with GPG
Every release publishes a `SHA256SUMS` file and an ASCII-armored detached signature `SHA256SUMS.asc`.
### Step-by-Step Verification
```bash
# 1. Download the release asset(s), SHA256SUMS, and SHA256SUMS.asc
curl -LO https://github.com/project-dorado/dorado/releases/download/<tag>/Dorado-Linux-x64.tar.gz
curl -LO https://github.com/project-dorado/dorado/releases/download/<tag>/SHA256SUMS
curl -LO https://github.com/project-dorado/dorado/releases/download/<tag>/SHA256SUMS.asc
# 2. Verify the GPG detached signature on the checksum file
gpg --verify SHA256SUMS.asc SHA256SUMS
# Expected output includes:
# gpg: Good signature from "Heretek Release Signing ... <[email protected]>"
# Primary key fingerprint: 11EE 4B06 9B95 02EB E5EF 6531 BCBC 85E3 B15D C872
# 3. Verify the checksum of the downloaded file
sha256sum --check --ignore-missing SHA256SUMS
```
---
## 3. Verifying with GitHub Artifact Attestations (Sigstore)
Releases are also signed with GitHub's native Sigstore infrastructure using OpenID Connect (OIDC). This provides cryptographically verifiable build provenance linking the artifact directly to the exact workflow run, repository, and git commit.
### Verification using GitHub CLI (`gh`):
```bash
# Verify binary against project-dorado repository
gh attestation verify Dorado-Linux-x64.tar.gz --owner project-dorado
# Or verify against Heretek-Games or Heretek-AI
gh attestation verify <game-package.zip> --owner Heretek-Games
gh attestation verify <package.tar.gz> --owner Heretek-AI
```
---
## 4. Verifying Android APK Signatures
Android application packages (`.apk`) are signed with the official Heretek release keystore (APK Signature Scheme v1, v2, v3, and v4).
To verify on any machine with the Android SDK installed:
```bash
apksigner verify --verbose --print-certs dorado-hd-<sha>.apk
```
Expected signer:
- **Owner**: `CN=Heretek Release Signing, OU=Engineering, O=Heretek, C=US`
- **Key Algorithm**: `4096-bit RSA`
+22 -13
View File
@@ -1,13 +1,15 @@
# Dorado — Program Status
**Last updated:** 2026-09-11
**Last updated:** 2026-09-13
A consolidated view of what has been completed and what remains across the
organization. Legend: ✅ done · 🚧 in progress · ⏳ pending
> **Single source of truth.** This file is the canonical program status for the
> organization; repository READMEs and the website link here. Figures are as of
> the date above.
> organization; repository READMEs, in-repo working notes (e.g.
> `dorado-hd/docs/parity-roadmap.md`, `docs/ui-parity-audit.md`) and the website
> defer to it. On any conflict of figures or status, this file wins — update it
> when a change moves counts or state, and date the change above.
All figures below were **re-verified against `origin/main` and executed test
suites on 2026-09-10** (commands in [Verification](#verification)). Every
@@ -18,14 +20,22 @@ repository has a clean working tree and is **0 ahead / 0 behind** its remote.
| Repository | Purpose | State |
|---|---|---|
| [dorado](https://github.com/project-dorado/dorado) | Zune 4.8 desktop re-creation (.NET 8 / Avalonia) | ✅ pushed · 433/433 tests (incl. golden-image gate) · **~88%** weight parity ([audited 2026-09-11](https://github.com/project-dorado/dorado/blob/main/docs/parity/audit-2026-09-11.md), remediated M1–M3 + follow-on) |
| [dorado-hd](https://github.com/project-dorado/dorado-hd) | Zune HD Android client (Kotlin / Compose) | ✅ pushed · **1,602/1,602 tests** (JDK 21) · **M16: all 62 official apps implemented** + engine3d core · **UI-parity program complete** (audit + gap register, 63-app smoke/layout/back/golden suites, emulator crawl 63/63) · M4, M7–M9, M12–M14 done; **next-steps Track B/C shipped**: fade-through crossfade, live FFT visualizer, sleep timer, richer lock-screen, Smart DJ, podcast search, artist photo grid, radio/picture Now Playing, **audiobooks (D4)**; M15 social inbox wired; M10 widget · UI/UX deep audit done ([audit](https://github.com/project-dorado/dorado-hd/blob/main/docs/ui-ux-audit.md)) |
| [dorado-cloud](https://github.com/project-dorado/dorado-cloud) | Community cloud services (.NET 8) | ✅ pushed · M0–M12 done · 195/195 tests · M6 media legal-gated (model ready) |
| [dorado-hd](https://github.com/project-dorado/dorado-hd) | Zune HD Android client (Kotlin / Compose) | ✅ pushed · **1,603/1,603 tests** (JDK 21) · **M16: all 62 official apps implemented** + engine3d core · **UI-parity program complete** (audit + gap register, 63-app smoke/layout/back/golden suites, emulator crawl 63/63) · M4, M7–M9, M12–M14 done; **next-steps Track B/C shipped**: fade-through crossfade, live FFT visualizer, sleep timer, richer lock-screen, Smart DJ, podcast search, artist photo grid, radio/picture Now Playing, **audiobooks (D4)**; M15 social inbox wired; M10 widget · UI/UX deep audit done ([audit](https://github.com/project-dorado/dorado-hd/blob/main/docs/ui-ux-audit.md)) |
| [dorado-cloud](https://github.com/project-dorado/dorado-cloud) | Community cloud services (.NET 8) | ✅ pushed · M0–M12 done · 200/200 tests · M6 media legal-gated (model ready) |
| [dorado-emu](https://github.com/project-dorado/dorado-emu) | Zune HD `.zcp`/`.ccgame` XNA emulator core | ✅ pushed · M0–M1 + ZCSTFS volume reader + **official-app runtime** (XNB pipeline, ZDK bridge) · **79/79 tests** · **50/54 framework-path titles run headless** · DRM key seam (no keys shipped) |
| [project-dorado.github.io](https://github.com/project-dorado/project-dorado.github.io) | Organization website (dorado.org.uk) | ✅ published |
| [.github](https://github.com/project-dorado/.github) | Org profile + community health files | ✅ published |
| [.github](https://github.com/project-dorado/.github) | Org profile + community health files | ✅ published · Release signing keys & org secrets provisioned |
## Completed
### Release signing & cryptographic provenance ✅ complete
- Cross-organization release signing infrastructure established across `project-dorado`, `Heretek-Games`, and `Heretek-AI`.
- 4096-bit RSA GPG release signing key and PKCS12 Android release keystore provisioned across organization secrets (`GPG_PRIVATE_KEY`, `GPG_PASSPHRASE`, `GPG_KEY_ID`, `ANDROID_KEYSTORE_BASE64`, `ANDROID_KEYSTORE_PASSWORD`, `ANDROID_KEY_ALIAS`, `ANDROID_KEY_PASSWORD`).
- Desktop (`dorado`) workflow enhanced with `SHA256SUMS` generation, detached GPG signatures (`SHA256SUMS.asc`), and Sigstore SLSA build provenance attestations (`actions/attest-build-provenance@v2`).
- Android client (`dorado-hd`) Gradle build wired with production release signing configuration, and workflow enhanced with signed APK packaging, detached GPG checksum signatures, and Sigstore build provenance attestations.
- Public key documentation published in `.github/KEYS.md` with ASCII-armored key in `.github/HERETEK_RELEASE_SIGNING_KEY.asc`.
### Repository migration & rebrand
- `Heretek-AI/not-zune` → **project-dorado/dorado** and
`Heretek-AI/xune-HD` → **project-dorado/dorado-hd**; histories preserved,
@@ -73,7 +83,7 @@ repository has a clean working tree and is **0 ahead / 0 behind** its remote.
stream only from external, untracked corpora and fail closed (`404`) when
unset; `commerce.zune.net` purchase and Zune-Pass DRM/license endpoints are
**not** implemented (no DRM circumvention); login is disabled by default.
- 195/195 tests; Release build 0 warnings (`/warnaserror`).
- 200/200 tests; Release build 0 warnings (`/warnaserror`).
### Legacy client enablement + hardening (M8–M12) ✅
- **M8 — Legacy client enablement:** gated `login.zune.net` bridge now issues the
@@ -139,7 +149,7 @@ repository has a clean working tree and is **0 ahead / 0 behind** its remote.
51.4 GB, per-file MD5); decompiled app tree verifies **7,843/7,844** files
(one upstream gap); `color-spill` remains corpus-blocked (GUID collision
with Reversi) with provisional genre constants.
- Gates: `assembleDebug` + `testDebugUnitTest` (1,602) + `lintDebug` green;
- Gates: `assembleDebug` + `testDebugUnitTest` (1,603) + `lintDebug` green;
`DesignInvariantTest` 0 violations.
### Dorado-HD modern listening + always-on (M9/M10) ✅
@@ -201,13 +211,13 @@ done
# .NET suites
dotnet test # dorado → 433 passed (14 Domain + 418 Application + 1 visual golden gate)
dotnet test DoradoCloud.sln # cloud → 195 passed (178 integration + 17 client)
dotnet test DoradoCloud.sln # cloud → 200 passed (182 integration + 18 client)
dotnet test Dorado.sln # dorado-emu → 79 passed (incl. official-app smoke)
native/zdk-bridge/build.sh # dorado-emu → libZDK.so (image/font/ZDKGL bridge)
python3 tools/smoke_official.py # dorado-emu → 50/54 framework-path titles ran
# Android suite (JDK 21 required; JDK 26 breaks Robolectric)
JAVA_HOME=/home/linuxbrew/.linuxbrew/opt/openjdk@21/libexec ./gradlew testDebugUnitTest # → 1,602 passed (unit + UI parity suites)
JAVA_HOME=/home/linuxbrew/.linuxbrew/opt/openjdk@21/libexec ./gradlew testDebugUnitTest # → 1,603 passed (unit + UI parity suites)
JAVA_HOME=/home/linuxbrew/.linuxbrew/opt/openjdk@21/libexec ./gradlew assembleDebug lintDebug # → green
# official-app corpus (external, untracked; never committed)
@@ -244,10 +254,9 @@ python3 dorado-hd/tools/zune_app_corpus.py --verify-only # 7,843/7,844 MD5 ver
redirect, CSRF coverage, pgvector fallback); desktop **MPRIS2** shipped
(i18n at 20 locales; remaining locales deferred per
`docs/parity/deferred_registry.md`).
- **Gates:** HD 1,602/1,602 + lintDebug + ui_lint; emulator entry crawl
- **Gates:** HD 1,603/1,603 + lintDebug + ui_lint; emulator entry crawl
63/63 and interactive flow crawl 63/63 (a launch-thread P0 was caught and
fixed by the crawl).
- **Documented N/A:** live-radio ±5-min rewind (no time-shifting server);
cloud inbox mark-read (server read-only); org repository pinning (manual
profile step — descriptions/topics are set).
org repository pinning (manual profile step — descriptions/topics are set).
+70
View File
@@ -0,0 +1,70 @@
# ==============================================================================
# Reusable Template: Sign & Attest GitHub Release Assets
#
# Usage in repositories across Heretek-Games, Heretek-AI, and project-dorado:
#
# Include this pattern in your release or publish job. It uses the organization-level
# secrets (GPG_PRIVATE_KEY, GPG_PASSPHRASE) automatically available to all repos.
# ==============================================================================
name: Sign & Attest Release Assets Example
on:
workflow_dispatch:
push:
tags: ['v*']
permissions:
contents: write
id-token: write
attestations: write
jobs:
publish-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# (Insert build/download artifact steps here to produce distribution files)
# e.g., dist/myapp-linux.tar.gz, dist/myapp-windows.zip, etc.
- name: Generate Checksums
working-directory: dist
run: |
sha256sum * > SHA256SUMS
- name: Import GPG Key
if: env.HAS_GPG == 'true'
uses: crazy-max/ghaction-import-gpg@v6
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.GPG_PASSPHRASE }}
env:
HAS_GPG: ${{ secrets.GPG_PRIVATE_KEY != '' }}
- name: Sign Checksums (Detached ASCII-armored signature)
if: env.HAS_GPG == 'true'
working-directory: dist
run: |
gpg --batch --yes --detach-sign --armor SHA256SUMS
- name: Attest Build Provenance (Sigstore)
uses: actions/attest-build-provenance@v2
with:
subject-path: |
dist/*
dist/SHA256SUMS
dist/SHA256SUMS.asc
- name: Publish Release to GitHub
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: Release ${{ github.ref_name }}
generate_release_notes: true
files: |
dist/*
dist/SHA256SUMS
dist/SHA256SUMS.asc
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+327
View File
@@ -0,0 +1,327 @@
#!/usr/bin/env bash
# ==============================================================================
# setup-org-signing-secrets.sh
# Unified Release Signing Key Generation & GitHub Org Secrets Provisioning
#
# Target Organizations:
# - project-dorado (https://github.com/project-dorado)
# - Heretek-Games (https://github.com/Heretek-Games)
# - Heretek-AI (https://github.com/Heretek-AI)
#
# Generated Credentials:
# - GPG Signing Key (RSA 4096, 3-year validity)
# - Android Keystore (PKCS12, RSA 4096, 10,000-day validity)
#
# Organizational Secrets Created:
# - GPG_PRIVATE_KEY
# - GPG_PASSPHRASE
# - GPG_KEY_ID
# - ANDROID_KEYSTORE_BASE64
# - ANDROID_KEYSTORE_PASSWORD
# - ANDROID_KEY_ALIAS
# - ANDROID_KEY_PASSWORD
# ==============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GITHUB_REPO_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
BACKUP_DIR="${HERETEK_BACKUP_DIR:-${HOME}/.heretek-signing-backup}"
TARGET_ORGS=("project-dorado" "Heretek-Games" "Heretek-AI")
KEY_NAME="${HERETEK_KEY_NAME:-Heretek Release Signing}"
KEY_EMAIL="${HERETEK_KEY_EMAIL:-releases@heretek.net}"
KEY_COMMENT="${HERETEK_KEY_COMMENT:-Heretek & Dorado Unified Release Signing Key}"
KEY_VALIDITY="${HERETEK_KEY_VALIDITY:-3y}"
KEYSTORE_ALIAS="${HERETEK_KEYSTORE_ALIAS:-heretek-release}"
KEYSTORE_VALIDITY="${HERETEK_KEYSTORE_VALIDITY:-10000}"
KEYSTORE_DNAME="${HERETEK_KEYSTORE_DNAME:-CN=Heretek Release Signing, OU=Engineering, O=Heretek, C=US}"
# Color output helpers
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
BOLD='\033[1m'
NC='\033[0m' # No Color
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
}
check_prerequisites() {
log_info "Checking prerequisites..."
local missing=()
for cmd in gpg keytool openssl base64 gh; do
if ! command -v "$cmd" >/dev/null 2>&1; then
missing+=("$cmd")
fi
done
if [ ${#missing[@]} -gt 0 ]; then
log_error "Missing required utilities: ${missing[*]}"
log_error "Please install them before continuing."
exit 1
fi
log_success "All prerequisites are installed."
}
init_backup_dir() {
if [ ! -d "$BACKUP_DIR" ]; then
mkdir -p "$BACKUP_DIR"
chmod 700 "$BACKUP_DIR"
log_info "Created secure backup directory: $BACKUP_DIR (permissions 0700)"
else
chmod 700 "$BACKUP_DIR"
fi
}
generate_credentials() {
init_backup_dir
local gpg_key_file="${BACKUP_DIR}/heretek-gpg-private.asc"
local jks_file="${BACKUP_DIR}/heretek-release.jks"
local env_file="${BACKUP_DIR}/secrets.env"
if [ -f "$gpg_key_file" ] && [ -f "$jks_file" ] && [ -f "$env_file" ]; then
log_warn "Credentials already exist in $BACKUP_DIR."
log_warn "Skipping generation. Using existing credentials."
return 0
fi
log_info "Generating new cryptographic keys..."
# 1. Passphrases
local gpg_passphrase="${HERETEK_GPG_PASSPHRASE:-$(openssl rand -base64 24)}"
local keystore_pass="${HERETEK_KEYSTORE_PASSWORD:-$(openssl rand -base64 24)}"
local key_pass="$keystore_pass"
# 2. GPG Key Generation in isolated GNUPGHOME
log_info "Generating 4096-bit RSA GPG release signing key..."
local gpg_temp_home
gpg_temp_home="$(mktemp -d)"
chmod 700 "$gpg_temp_home"
cat <<EOF > "$gpg_temp_home/gen-key.conf"
Key-Type: RSA
Key-Length: 4096
Subkey-Type: RSA
Subkey-Length: 4096
Name-Real: ${KEY_NAME}
Name-Email: ${KEY_EMAIL}
Name-Comment: ${KEY_COMMENT}
Expire-Date: ${KEY_VALIDITY}
Passphrase: ${gpg_passphrase}
%commit
EOF
GNUPGHOME="$gpg_temp_home" gpg --batch --generate-key "$gpg_temp_home/gen-key.conf" >/dev/null 2>&1
local gpg_key_id
gpg_key_id="$(GNUPGHOME="$gpg_temp_home" gpg --with-colons --list-secret-keys "$KEY_EMAIL" | awk -F: '$1 == "sec" {print $5; exit}')"
local gpg_fingerprint
gpg_fingerprint="$(GNUPGHOME="$gpg_temp_home" gpg --with-colons --fingerprint "$KEY_EMAIL" | awk -F: '$1 == "fpr" {print $10; exit}')"
log_success "Generated GPG Key: $gpg_key_id (Fingerprint: $gpg_fingerprint)"
# Export ASCII-armored private and public keys
GNUPGHOME="$gpg_temp_home" gpg --armor --batch --pinentry-mode loopback --passphrase "$gpg_passphrase" --export-secret-keys "$gpg_key_id" > "$gpg_key_file"
GNUPGHOME="$gpg_temp_home" gpg --armor --export "$gpg_key_id" > "${BACKUP_DIR}/heretek-gpg-public.asc"
chmod 600 "$gpg_key_file" "${BACKUP_DIR}/heretek-gpg-public.asc"
rm -rf "$gpg_temp_home"
# Also place public key into repo for distribution
cp "${BACKUP_DIR}/heretek-gpg-public.asc" "${GITHUB_REPO_DIR}/HERETEK_RELEASE_SIGNING_KEY.asc"
log_success "Published public key to ${GITHUB_REPO_DIR}/HERETEK_RELEASE_SIGNING_KEY.asc"
# 3. Android Keystore Generation
log_info "Generating 4096-bit RSA PKCS12 Android release keystore..."
keytool -genkeypair -v \
-keystore "$jks_file" \
-storetype PKCS12 \
-alias "$KEYSTORE_ALIAS" \
-keyalg RSA \
-keysize 4096 \
-validity "$KEYSTORE_VALIDITY" \
-storepass "$keystore_pass" \
-keypass "$key_pass" \
-dname "$KEYSTORE_DNAME" >/dev/null 2>&1
chmod 600 "$jks_file"
local keystore_base64
keystore_base64="$(base64 -w 0 "$jks_file")"
echo "$keystore_base64" > "${BACKUP_DIR}/android-keystore.base64"
chmod 600 "${BACKUP_DIR}/android-keystore.base64"
log_success "Generated Android Keystore: $jks_file (Alias: $KEYSTORE_ALIAS)"
# 4. Write secrets dotenv file for backup and gh secret import
cat <<EOF > "$env_file"
GPG_KEY_ID=${gpg_key_id}
GPG_FINGERPRINT=${gpg_fingerprint}
GPG_PASSPHRASE=${gpg_passphrase}
ANDROID_KEYSTORE_PASSWORD=${keystore_pass}
ANDROID_KEY_ALIAS=${KEYSTORE_ALIAS}
ANDROID_KEY_PASSWORD=${key_pass}
EOF
chmod 600 "$env_file"
log_success "All credentials generated and safely stored in $BACKUP_DIR"
}
check_gh_org_permissions() {
log_info "Verifying GitHub CLI authentication and organization access..."
if ! gh auth status >/dev/null 2>&1; then
log_error "GitHub CLI is not authenticated. Please run 'gh auth login'."
return 1
fi
# Test access to org secrets API on project-dorado
local test_res
if ! test_res="$(gh secret list --org project-dorado 2>&1)"; then
if echo "$test_res" | grep -q "403"; then
log_warn "GitHub token does not currently have 'admin:org' permission to manage org secrets."
echo ""
echo -e "${YELLOW}Please run the following command in your terminal to grant org secret permissions:${NC}"
echo -e " ${BOLD}gh auth refresh -s admin:org${NC}"
echo ""
return 2
fi
log_error "Error checking organization secrets: $test_res"
return 1
fi
log_success "GitHub CLI has required 'admin:org' permissions."
return 0
}
deploy_org_secrets() {
local gpg_key_file="${BACKUP_DIR}/heretek-gpg-private.asc"
local jks_file="${BACKUP_DIR}/heretek-release.jks"
local env_file="${BACKUP_DIR}/secrets.env"
if [ ! -f "$gpg_key_file" ] || [ ! -f "$jks_file" ] || [ ! -f "$env_file" ]; then
log_error "Missing credentials in $BACKUP_DIR. Run '$0 generate' first."
exit 1
fi
# Source parameters
# shellcheck disable=SC1090
source "$env_file"
local gpg_private_key
gpg_private_key="$(cat "$gpg_key_file")"
local keystore_base64
keystore_base64="$(cat "${BACKUP_DIR}/android-keystore.base64")"
log_info "Deploying secrets across organizations: ${TARGET_ORGS[*]}"
for org in "${TARGET_ORGS[@]}"; do
echo ""
log_info "=== Setting secrets for organization: $org ==="
# GPG Secrets
echo -n "$gpg_private_key" | gh secret set GPG_PRIVATE_KEY --org "$org" --visibility all
gh secret set GPG_PASSPHRASE --org "$org" --visibility all --body "$GPG_PASSPHRASE"
gh secret set GPG_KEY_ID --org "$org" --visibility all --body "$GPG_KEY_ID"
# Android Keystore Secrets
gh secret set ANDROID_KEYSTORE_BASE64 --org "$org" --visibility all --body "$keystore_base64"
gh secret set ANDROID_KEYSTORE_PASSWORD --org "$org" --visibility all --body "$ANDROID_KEYSTORE_PASSWORD"
gh secret set ANDROID_KEY_ALIAS --org "$org" --visibility all --body "$ANDROID_KEY_ALIAS"
gh secret set ANDROID_KEY_PASSWORD --org "$org" --visibility all --body "$ANDROID_KEY_PASSWORD"
log_success "Successfully configured 7 release secrets for organization: $org"
gh secret list --org "$org"
done
}
show_status() {
echo ""
echo -e "${BOLD}Heretek & Dorado Release Signing Status${NC}"
echo "--------------------------------------------------"
if [ -d "$BACKUP_DIR" ] && [ -f "${BACKUP_DIR}/secrets.env" ]; then
# shellcheck disable=SC1090
source "${BACKUP_DIR}/secrets.env"
echo -e "Backup Directory: ${GREEN}${BACKUP_DIR}${NC} (chmod 0700)"
echo -e "GPG Key ID: ${BOLD}${GPG_KEY_ID}${NC}"
echo -e "GPG Fingerprint: ${BOLD}${GPG_FINGERPRINT}${NC}"
echo -e "Android Key Alias: ${BOLD}${ANDROID_KEY_ALIAS}${NC}"
echo -e "Public Key Artifact: ${GREEN}${GITHUB_REPO_DIR}/HERETEK_RELEASE_SIGNING_KEY.asc${NC}"
else
echo -e "Status: ${YELLOW}No credentials generated yet.${NC}"
echo "Run '$0 generate' to create them."
fi
echo ""
}
print_manual_instructions() {
local env_file="${BACKUP_DIR}/secrets.env"
if [ ! -f "$env_file" ]; then
return
fi
# shellcheck disable=SC1090
source "$env_file"
echo ""
echo -e "${BOLD}Manual GitHub Settings Web UI Links:${NC}"
for org in "${TARGET_ORGS[@]}"; do
echo " - https://github.com/organizations/${org}/settings/secrets/actions"
done
echo ""
echo -e "Required Secrets to enter if not using CLI:"
echo " 1. GPG_PRIVATE_KEY -> File content of ${BACKUP_DIR}/heretek-gpg-private.asc"
echo " 2. GPG_PASSPHRASE -> From ${BACKUP_DIR}/secrets.env"
echo " 3. GPG_KEY_ID -> ${GPG_KEY_ID}"
echo " 4. ANDROID_KEYSTORE_BASE64 -> File content of ${BACKUP_DIR}/android-keystore.base64"
echo " 5. ANDROID_KEYSTORE_PASSWORD -> From ${BACKUP_DIR}/secrets.env"
echo " 6. ANDROID_KEY_ALIAS -> ${ANDROID_KEY_ALIAS}"
echo " 7. ANDROID_KEY_PASSWORD -> From ${BACKUP_DIR}/secrets.env"
echo ""
}
# Main Command Dispatch
cmd="${1:-all}"
case "$cmd" in
generate)
check_prerequisites
generate_credentials
show_status
;;
deploy)
check_prerequisites
if check_gh_org_permissions; then
deploy_org_secrets
else
print_manual_instructions
fi
;;
status)
show_status
;;
all)
check_prerequisites
generate_credentials
show_status
if check_gh_org_permissions; then
deploy_org_secrets
else
print_manual_instructions
fi
;;
*)
echo "Usage: $0 [generate|deploy|status|all]"
exit 1
;;
esac