From a1b9a5e46d1639efc08edaf5b3c4c693bab7f802 Mon Sep 17 00:00:00 2001 From: copilot Date: Wed, 30 Sep 2026 11:34:44 +0200 Subject: [PATCH] Add script to mint and upload Forgejo Obtainium release secrets. Generates a local upload keystore and pushes Actions secrets without printing values, so Obtainium APK builds can be signed on desktop-release. Co-authored-by: Cursor --- scripts/setup-forgejo-release-secrets.sh | 235 +++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100755 scripts/setup-forgejo-release-secrets.sh diff --git a/scripts/setup-forgejo-release-secrets.sh b/scripts/setup-forgejo-release-secrets.sh new file mode 100755 index 00000000..325bb5a1 --- /dev/null +++ b/scripts/setup-forgejo-release-secrets.sh @@ -0,0 +1,235 @@ +#!/usr/bin/env bash +# Generate Android release signing material + upload Forgejo Actions secrets for +# Obtainium APK builds — without printing secret values. +# +# Usage (from anywhere): +# bash /home/yuna/git/frosty/scripts/setup-forgejo-release-secrets.sh +# +# What it does: +# 1. Writes secrets to ~/.config/frosty/forgejo-release-secrets.env (mode 600) +# — generates a new upload keystore + passwords if the file is missing +# — merges CLIENT_ID / SECRET from frosty/.env when present (never echoes them) +# 2. PUTs each secret to Forgejo Actions on copilot/frosty +# +# Re-run safely: existing secrets file is reused (keystore not regenerated) unless +# FORCE_REGENERATE=1 bash …/setup-forgejo-release-secrets.sh +# +# Twitch Helix: if CLIENT_ID/SECRET are still empty after merge, the script stops +# before upload and tells you to edit the secrets file (add the two lines) then re-run. +set -euo pipefail + +REPO="${REPO:-copilot/frosty}" +FORGEJO_URL="${FORGEJO_URL:-https://forgejo.fsociety00.cc}" +TOKEN_FILE="${TOKEN_FILE:-${HOME}/.config/forgejo/token}" +SECRETS_DIR="${SECRETS_DIR:-${HOME}/.config/frosty}" +SECRETS_FILE="${SECRETS_FILE:-${SECRETS_DIR}/forgejo-release-secrets.env}" +KEYSTORE_FILE="${KEYSTORE_FILE:-${SECRETS_DIR}/upload-keystore.jks}" +FROSTY_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ENV_FILE="${ENV_FILE:-${FROSTY_ROOT}/.env}" +KEY_ALIAS_DEFAULT="${KEY_ALIAS_DEFAULT:-upload}" + +need_cmd() { + command -v "$1" >/dev/null 2>&1 || { + echo "ERROR: missing command: $1" >&2 + exit 1 + } +} + +need_cmd curl +need_cmd python3 +need_cmd openssl +need_cmd keytool +need_cmd base64 + +if [[ ! -f "$TOKEN_FILE" ]]; then + echo "ERROR: Forgejo token not found: $TOKEN_FILE" >&2 + exit 1 +fi +TOKEN="$(tr -d '[:space:]' <"$TOKEN_FILE")" +[[ -n "$TOKEN" ]] || { + echo "ERROR: empty token in $TOKEN_FILE" >&2 + exit 1 +} + +mkdir -p "$SECRETS_DIR" +chmod 700 "$SECRETS_DIR" + +rand_pw() { + # URL/shell-safe password, no quotes + openssl rand -base64 24 | tr -d '\n/+=\r' | head -c 32 +} + +write_secrets_file() { + local store_pw key_pw alias b64 + store_pw="$1" + key_pw="$2" + alias="$3" + b64="$4" + # Atomic write; never echo values + local tmp + tmp="$(mktemp "${SECRETS_DIR}/.secrets.XXXXXX")" + umask 077 + cat >"$tmp" <&2 + # Response may contain error text only — still avoid dumping request body + python3 -c 'import pathlib; p=pathlib.Path("/tmp/frosty-forgejo-secret-resp.json"); print(p.read_text()[:500] if p.exists() else "")' >&2 || true + exit 1 + fi + echo " set ${name} (HTTP ${code})" +} + +# --- generate if needed --- +if [[ ! -f "$SECRETS_FILE" || "${FORCE_REGENERATE:-0}" == "1" ]]; then + echo "→ Generating new upload keystore + passwords → ${SECRETS_FILE}" + STORE_PW="$(rand_pw)" + KEY_PW="$(rand_pw)" + ALIAS="$KEY_ALIAS_DEFAULT" + rm -f "$KEYSTORE_FILE" + keytool -genkeypair \ + -keystore "$KEYSTORE_FILE" \ + -storetype JKS \ + -storepass "$STORE_PW" \ + -keypass "$KEY_PW" \ + -alias "$ALIAS" \ + -keyalg RSA \ + -keysize 2048 \ + -validity 10000 \ + -dname "CN=Frosty Forgejo Release, OU=Newas, O=fsociety00, L=Home, ST=NA, C=DE" \ + >/dev/null 2>&1 + chmod 600 "$KEYSTORE_FILE" + B64="$(base64 -w0 "$KEYSTORE_FILE" 2>/dev/null || base64 "$KEYSTORE_FILE" | tr -d '\n')" + write_secrets_file "$STORE_PW" "$KEY_PW" "$ALIAS" "$B64" + # Drop locals + unset STORE_PW KEY_PW B64 + echo " wrote keystore ${KEYSTORE_FILE}" + echo " wrote secrets file (mode 600)" +else + echo "→ Reusing existing secrets file: ${SECRETS_FILE}" + echo " (set FORCE_REGENERATE=1 to mint a new keystore)" +fi + +echo "→ Merging Twitch CLIENT_ID/SECRET from ${ENV_FILE} if present…" +merge_twitch_from_dotenv || true + +if ! status="$(secrets_complete)"; then + echo >&2 + echo "Twitch Helix secrets still missing in ${SECRETS_FILE}." >&2 + echo "Edit that file and set CLIENT_ID=… and SECRET=… (from https://dev.twitch.tv/console)," >&2 + echo "or put them in ${ENV_FILE}, then re-run this script." >&2 + echo "Signing secrets are already generated; they will be uploaded together." >&2 + exit 3 +fi +echo "→ Secrets file complete (${status})" + +echo "→ Uploading Actions secrets to ${FORGEJO_URL}/${REPO} (values not printed)" +for name in RELEASE_KEYSTORE_BASE64 STORE_PASSWORD KEY_ALIAS KEY_PASSWORD CLIENT_ID SECRET; do + put_secret "$name" +done + +echo +echo "Done. Secrets live only in:" +echo " ${SECRETS_FILE}" +echo " ${KEYSTORE_FILE}" +echo " Forgejo Actions → ${REPO}" +echo +echo "Next: Forgejo → Actions → Release APK → Run workflow" +echo "Obtainium: https://forgejo.fsociety00.cc/api/v1/repos/copilot/frosty/releases" +echo "APK filter: frosty-arm64-v8a-v.*\\.apk"