name: Decrypt Release Secrets description: Decrypts age-sealed Burrow release secrets and exports CI paths/env. inputs: root: description: Repository root required: false default: . runs: using: composite steps: - shell: bash run: | set -euo pipefail ROOT_INPUT='${{ inputs.root }}' ROOT="$(cd "$ROOT_INPUT" && pwd)" resolve_decrypt_tool() { local candidate for candidate in \ "${AGE_BIN:-}" \ "$(command -v age 2>/dev/null || true)" \ /opt/homebrew/bin/age \ /usr/local/bin/age \ /usr/bin/age \ /run/current-system/sw/bin/age \ /etc/profiles/per-user/runner/bin/age \ "${HOME:-}/.nix-profile/bin/age" \ "${HOME:-}/.local/state/nix/profile/bin/age" \ /Users/runner/.nix-profile/bin/age \ /Users/runner/.local/state/nix/profile/bin/age \ /home/runner/.nix-profile/bin/age \ /home/runner/.local/state/nix/profile/bin/age \ ; do if [[ -n "$candidate" && -x "$candidate" ]]; then printf 'age:%s\n' "$candidate" return 0 fi done for candidate in \ "${AGENIX_BIN:-}" \ "$(command -v agenix 2>/dev/null || true)" \ ; do if [[ -n "$candidate" && -x "$candidate" ]]; then printf 'agenix:%s\n' "$candidate" return 0 fi done if command -v nix >/dev/null 2>&1; then local built built="$(nix build --no-link "$ROOT#agenix" --print-out-paths 2>/dev/null | tail -n 1 || true)" if [[ -n "$built" && -x "$built/bin/agenix" ]]; then printf 'agenix:%s\n' "$built/bin/agenix" return 0 fi fi echo "::error ::age or agenix is required to decrypt release secrets but neither is available." >&2 exit 1 } decrypt_tool="$(resolve_decrypt_tool)" decrypt_kind="${decrypt_tool%%:*}" decrypt_bin="${decrypt_tool#*:}" KEY="${AGE_IDENTITY_PATH:-}" if [[ -z "$KEY" || ! -s "$KEY" ]]; then echo "::error ::Missing age identity. Run Scripts/resolve-age-identity.sh before this action." >&2 exit 1 fi decrypt_age_file() { local file_path="$1" if [[ "$decrypt_kind" == "agenix" ]]; then "$decrypt_bin" --identity "$KEY" --decrypt "$file_path" else "$decrypt_bin" --decrypt -i "$KEY" "$file_path" fi } write_env() { local name="$1" local value="$2" echo "${name}=${value}" >> "$GITHUB_ENV" } mask_multiline() { local value="$1" echo "::add-mask::${value}" while IFS= read -r line; do [[ -n "$line" ]] || continue echo "::add-mask::${line}" done <<<"$value" } decrypt_env_optional() { local name="$1" local file="$2" if [[ ! -f "$file" ]]; then echo "::notice ::Skipping optional release secret ${file}" return 0 fi local value value="$(decrypt_age_file "$file")" mask_multiline "$value" { echo "${name}<> "$GITHUB_ENV" } decrypt_file_optional() { local name="$1" local file="$2" local suffix="$3" if [[ ! -f "$file" ]]; then echo "::notice ::Skipping optional release secret ${file}" return 0 fi local temp_dir="${RUNNER_TEMP:-/tmp}" local temp_file temp_file="$(mktemp "${temp_dir}/${name}.XXXXXX")" if [[ -n "$suffix" ]]; then local with_suffix="${temp_file}.${suffix}" mv "$temp_file" "$with_suffix" temp_file="$with_suffix" fi decrypt_age_file "$file" > "$temp_file" chmod 600 "$temp_file" write_env "$name" "$temp_file" } decrypt_dotenv_optional() { local file="$1" if [[ ! -f "$file" ]]; then echo "::notice ::Skipping optional release secret ${file}" return 0 fi local temp_file temp_file="$(mktemp "${RUNNER_TEMP:-/tmp}/burrow-release-dotenv.XXXXXX")" decrypt_age_file "$file" > "$temp_file" # shellcheck disable=SC1090 set -a source "$temp_file" set +a rm -f "$temp_file" if [[ -z "${MINIO_ROOT_USER:-}" || -z "${MINIO_ROOT_PASSWORD:-}" ]]; then echo "::error ::${file} must define MINIO_ROOT_USER and MINIO_ROOT_PASSWORD." >&2 exit 1 fi echo "::add-mask::${MINIO_ROOT_USER}" echo "::add-mask::${MINIO_ROOT_PASSWORD}" { echo "AWS_ACCESS_KEY_ID=${MINIO_ROOT_USER}" echo "AWS_SECRET_ACCESS_KEY=${MINIO_ROOT_PASSWORD}" echo "AWS_DEFAULT_REGION=${BLOB_REGION:-hel1}" echo "AWS_REGION=${BLOB_REGION:-hel1}" echo "AWS_S3_ENDPOINT_URL=https://${BLOB_ENDPOINT:-hel1.your-objectstorage.com}" } >> "$GITHUB_ENV" } decode_base64_to_file() { local output="$1" python3 -c 'import base64, pathlib, sys; pathlib.Path(sys.argv[1]).write_bytes(base64.b64decode(sys.stdin.buffer.read()))' "$output" } decrypt_appstore_connect() { local file="$ROOT/secrets/apple/appstore-connect.env.age" if [[ ! -f "$file" ]]; then echo "::notice ::Skipping optional release secret ${file}" return 0 fi local temp_file key_path key_value temp_file="$(mktemp "${RUNNER_TEMP:-/tmp}/burrow-asc-env.XXXXXX")" decrypt_age_file "$file" > "$temp_file" # shellcheck disable=SC1090 set -a source "$temp_file" set +a rm -f "$temp_file" if [[ -z "${ASC_API_KEY_ID:-}" || -z "${ASC_API_ISSUER_ID:-}" ]]; then echo "::error ::${file} must define ASC_API_KEY_ID and ASC_API_ISSUER_ID." >&2 exit 1 fi key_path="$(mktemp "${RUNNER_TEMP:-/tmp}/burrow-asc-key.XXXXXX.p8")" if [[ -n "${ASC_API_KEY_BASE64:-}" ]]; then printf '%s' "$ASC_API_KEY_BASE64" | decode_base64_to_file "$key_path" elif [[ -n "${ASC_API_KEY_P8_BASE64:-}" ]]; then printf '%s' "$ASC_API_KEY_P8_BASE64" | decode_base64_to_file "$key_path" elif [[ -n "${APPSTORE_KEY:-}" ]]; then printf '%s\n' "$APPSTORE_KEY" > "$key_path" elif [[ -n "${ASC_API_KEY:-}" ]]; then printf '%s\n' "$ASC_API_KEY" > "$key_path" else echo "::error ::${file} must define ASC_API_KEY_BASE64 or ASC_API_KEY." >&2 exit 1 fi chmod 600 "$key_path" key_value="$(cat "$key_path")" mask_multiline "$key_value" echo "::add-mask::${ASC_API_KEY_ID}" echo "::add-mask::${ASC_API_ISSUER_ID}" write_env ASC_API_KEY_ID "$ASC_API_KEY_ID" write_env ASC_API_ISSUER_ID "$ASC_API_ISSUER_ID" write_env ASC_API_KEY_PATH "$key_path" } decrypt_distribution_signing() { local file="$ROOT/secrets/apple/distribution-signing.env.age" if [[ ! -f "$file" ]]; then echo "::notice ::Skipping optional release secret ${file}" return 0 fi local temp_file cert_path cert_password cert_base64 temp_file="$(mktemp "${RUNNER_TEMP:-/tmp}/burrow-signing-env.XXXXXX")" decrypt_age_file "$file" > "$temp_file" # shellcheck disable=SC1090 set -a source "$temp_file" set +a rm -f "$temp_file" cert_base64="${APPLE_SIGNING_CERTIFICATE_BASE64:-${APPLE_DISTRIBUTION_CERTIFICATE_BASE64:-}}" cert_password="${APPLE_SIGNING_CERTIFICATE_PASSWORD:-${APPLE_DISTRIBUTION_CERTIFICATE_PASSWORD:-}}" if [[ -z "$cert_password" && -n "${APPLE_SIGNING_CERTIFICATE_PASSWORD_BASE64:-}" ]]; then cert_password="$(printf '%s' "$APPLE_SIGNING_CERTIFICATE_PASSWORD_BASE64" | python3 -c 'import base64,sys; print(base64.b64decode(sys.stdin.buffer.read()).decode(), end="")')" fi if [[ -z "$cert_base64" || -z "$cert_password" ]]; then echo "::error ::${file} must define APPLE_SIGNING_CERTIFICATE_BASE64 and APPLE_SIGNING_CERTIFICATE_PASSWORD." >&2 exit 1 fi cert_path="$(mktemp "${RUNNER_TEMP:-/tmp}/burrow-apple-signing.XXXXXX.p12")" printf '%s' "$cert_base64" | decode_base64_to_file "$cert_path" chmod 600 "$cert_path" echo "::add-mask::${cert_password}" write_env APPLE_SIGNING_CERTIFICATE_PATH "$cert_path" write_env APPLE_SIGNING_CERTIFICATE_PASSWORD "$cert_password" write_env APPLE_KEYCHAIN_PASSWORD "$cert_password" } decrypt_sparkle() { local file="$ROOT/secrets/apple/sparkle.env.age" if [[ ! -f "$file" ]]; then echo "::notice ::Skipping optional release secret ${file}" return 0 fi local temp_file key_path key_base64 key_value temp_file="$(mktemp "${RUNNER_TEMP:-/tmp}/burrow-sparkle-env.XXXXXX")" decrypt_age_file "$file" > "$temp_file" # shellcheck disable=SC1090 set -a source "$temp_file" set +a rm -f "$temp_file" key_base64="${SPARKLE_EDDSA_KEY_BASE64:-${SPARKLE_PRIVATE_KEY_BASE64:-}}" key_path="$(mktemp "${RUNNER_TEMP:-/tmp}/burrow-sparkle-eddsa.XXXXXX.key")" if [[ -n "$key_base64" ]]; then printf '%s' "$key_base64" | decode_base64_to_file "$key_path" elif [[ -n "${SPARKLE_EDDSA_KEY:-}" ]]; then printf '%s\n' "$SPARKLE_EDDSA_KEY" > "$key_path" elif [[ -n "${SPARKLE_PRIVATE_KEY:-}" ]]; then printf '%s\n' "$SPARKLE_PRIVATE_KEY" > "$key_path" else echo "::error ::${file} must define SPARKLE_EDDSA_KEY_BASE64 or SPARKLE_EDDSA_KEY." >&2 exit 1 fi chmod 600 "$key_path" key_value="$(cat "$key_path")" mask_multiline "$key_value" write_env SPARKLE_EDDSA_KEY_PATH "$key_path" } decrypt_appstore_connect decrypt_distribution_signing decrypt_sparkle decrypt_dotenv_optional "$ROOT/secrets/hetzner/object-storage.age"