Rotate operator secrets into agenix and deepen caches
Some checks failed
Build Rust / Cargo Test (push) Waiting to run
Build Site / Next.js Build (push) Waiting to run
Build Apple / Build App (iOS Simulator) (push) Failing after 52s
Build Apple / Build App (macOS) (push) Failing after 1m1s

This commit is contained in:
Conrad Kramer 2026-03-19 00:28:18 -07:00
parent 7039bf5aad
commit 03415e579b
28 changed files with 526 additions and 126 deletions

View file

@ -0,0 +1,78 @@
#!/usr/bin/env bash
set -euo pipefail
BURROW_SECRET_TMPFILES=()
burrow_cleanup_secret_tmpfiles() {
local path
for path in "${BURROW_SECRET_TMPFILES[@]:-}"; do
[[ -n "${path}" ]] && rm -f "${path}" >/dev/null 2>&1 || true
done
BURROW_SECRET_TMPFILES=()
}
burrow_decrypt_age_secret_to_temp() {
local repo_root="$1"
local secret_path="$2"
local tmp_file
if [[ ! -f "${secret_path}" ]]; then
echo "age secret not found: ${secret_path}" >&2
return 1
fi
tmp_file="$(mktemp "${TMPDIR:-/tmp}/burrow-secret.XXXXXX")"
nix --extra-experimental-features "nix-command flakes" run "${repo_root}#agenix" -- -d "${secret_path}" > "${tmp_file}"
chmod 600 "${tmp_file}"
BURROW_SECRET_TMPFILES+=("${tmp_file}")
printf '%s\n' "${tmp_file}"
}
burrow_resolve_secret_file() {
local repo_root="$1"
local explicit_path="$2"
local intake_path="$3"
local age_path="$4"
local fallback_path="${5:-}"
if [[ -n "${explicit_path}" ]]; then
if [[ ! -s "${explicit_path}" ]]; then
echo "required file missing or empty: ${explicit_path}" >&2
return 1
fi
printf '%s\n' "${explicit_path}"
return 0
fi
if [[ -n "${intake_path}" && -s "${intake_path}" ]]; then
printf '%s\n' "${intake_path}"
return 0
fi
if [[ -n "${age_path}" && -f "${age_path}" ]]; then
burrow_decrypt_age_secret_to_temp "${repo_root}" "${age_path}"
return 0
fi
if [[ -n "${fallback_path}" && -s "${fallback_path}" ]]; then
printf '%s\n' "${fallback_path}"
return 0
fi
return 1
}
burrow_encrypt_secret_from_file() {
local repo_root="$1"
local secret_path="$2"
local source_path="$3"
if [[ ! -s "${source_path}" ]]; then
echo "secret source missing or empty: ${source_path}" >&2
return 1
fi
SECRET_SOURCE_FILE="${source_path}" \
EDITOR="${repo_root}/Scripts/agenix-load-file.sh" \
nix --extra-experimental-features "nix-command flakes" run "${repo_root}#agenix" -- -e "${secret_path}"
}

View file

@ -3,6 +3,8 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
# shellcheck source=Scripts/_burrow-secrets.sh
source "${SCRIPT_DIR}/_burrow-secrets.sh"
usage() {
cat <<'EOF'
@ -10,27 +12,33 @@ Usage: Scripts/bootstrap-forge-intake.sh [options]
Copy the minimum Burrow forge bootstrap secrets onto the target host under
/var/lib/burrow/intake with the ownership expected by the NixOS services.
Legacy path only: the current forge runtime consumes agenix secrets directly.
Options:
--host <user@host> SSH target (default: root@git.burrow.net)
--ssh-key <path> SSH private key used to reach the host
(default: intake/agent_at_burrow_net_ed25519)
(default: secrets/forgejo/agent-ssh-key.age, then intake/)
--password-file <path> Forgejo admin bootstrap password file
(default: intake/forgejo_pass_contact_at_burrow_net.txt)
(default: secrets/forgejo/admin-password.age, then intake/)
--agent-key-file <path> Agent SSH private key copied for runner bootstrap
(default: intake/agent_at_burrow_net_ed25519)
(default: secrets/forgejo/agent-ssh-key.age, then intake/)
--no-verify Skip remote ls/stat verification after install
-h, --help Show this help text
EOF
}
HOST="${BURROW_FORGE_HOST:-root@git.burrow.net}"
SSH_KEY="${BURROW_FORGE_SSH_KEY:-${REPO_ROOT}/intake/agent_at_burrow_net_ed25519}"
PASSWORD_FILE="${BURROW_FORGE_PASSWORD_FILE:-${REPO_ROOT}/intake/forgejo_pass_contact_at_burrow_net.txt}"
AGENT_KEY_FILE="${BURROW_FORGE_AGENT_KEY_FILE:-${REPO_ROOT}/intake/agent_at_burrow_net_ed25519}"
SSH_KEY="${BURROW_FORGE_SSH_KEY:-}"
PASSWORD_FILE="${BURROW_FORGE_PASSWORD_FILE:-}"
AGENT_KEY_FILE="${BURROW_FORGE_AGENT_KEY_FILE:-}"
KNOWN_HOSTS_FILE="${BURROW_FORGE_KNOWN_HOSTS_FILE:-${HOME}/.cache/burrow/forge-known_hosts}"
VERIFY=1
cleanup() {
burrow_cleanup_secret_tmpfiles
}
trap cleanup EXIT
while [[ $# -gt 0 ]]; do
case "$1" in
--host)
@ -67,12 +75,29 @@ done
mkdir -p "$(dirname "${KNOWN_HOSTS_FILE}")"
for path in "${SSH_KEY}" "${PASSWORD_FILE}" "${AGENT_KEY_FILE}"; do
if [[ ! -s "${path}" ]]; then
echo "required file missing or empty: ${path}" >&2
exit 1
fi
done
SSH_KEY="$(
burrow_resolve_secret_file \
"${REPO_ROOT}" \
"${SSH_KEY}" \
"${REPO_ROOT}/intake/agent_at_burrow_net_ed25519" \
"${REPO_ROOT}/secrets/forgejo/agent-ssh-key.age" \
"${HOME}/.ssh/agent_at_burrow_net_ed25519"
)"
PASSWORD_FILE="$(
burrow_resolve_secret_file \
"${REPO_ROOT}" \
"${PASSWORD_FILE}" \
"${REPO_ROOT}/intake/forgejo_pass_contact_at_burrow_net.txt" \
"${REPO_ROOT}/secrets/forgejo/admin-password.age"
)"
AGENT_KEY_FILE="$(
burrow_resolve_secret_file \
"${REPO_ROOT}" \
"${AGENT_KEY_FILE}" \
"${REPO_ROOT}/intake/agent_at_burrow_net_ed25519" \
"${REPO_ROOT}/secrets/forgejo/agent-ssh-key.age" \
"${HOME}/.ssh/agent_at_burrow_net_ed25519"
)"
ssh_opts=(
-i "${SSH_KEY}"

View file

@ -3,6 +3,8 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
# shellcheck source=Scripts/_burrow-secrets.sh
source "${SCRIPT_DIR}/_burrow-secrets.sh"
usage() {
cat <<'EOF'
@ -12,17 +14,22 @@ Run a post-boot verification pass against the Burrow forge host.
Options:
--host <user@host> SSH target (default: root@git.burrow.net)
--ssh-key <path> SSH private key (default: intake/agent_at_burrow_net_ed25519)
--ssh-key <path> SSH private key (default: secrets/forgejo/agent-ssh-key.age, then intake/)
--expect-nsc Fail if forgejo-nsc services are not active
-h, --help Show this help text
EOF
}
HOST="${BURROW_FORGE_HOST:-root@git.burrow.net}"
SSH_KEY="${BURROW_FORGE_SSH_KEY:-${REPO_ROOT}/intake/agent_at_burrow_net_ed25519}"
SSH_KEY="${BURROW_FORGE_SSH_KEY:-}"
KNOWN_HOSTS_FILE="${BURROW_FORGE_KNOWN_HOSTS_FILE:-${HOME}/.cache/burrow/forge-known_hosts}"
EXPECT_NSC=0
cleanup() {
burrow_cleanup_secret_tmpfiles
}
trap cleanup EXIT
while [[ $# -gt 0 ]]; do
case "$1" in
--host)
@ -51,10 +58,17 @@ done
mkdir -p "$(dirname "${KNOWN_HOSTS_FILE}")"
if [[ ! -f "${SSH_KEY}" ]]; then
echo "forge SSH key not found: ${SSH_KEY}" >&2
SSH_KEY="$(
burrow_resolve_secret_file \
"${REPO_ROOT}" \
"${SSH_KEY}" \
"${REPO_ROOT}/intake/agent_at_burrow_net_ed25519" \
"${REPO_ROOT}/secrets/forgejo/agent-ssh-key.age" \
"${HOME}/.ssh/agent_at_burrow_net_ed25519"
)" || {
echo "forge SSH key could not be resolved" >&2
exit 1
fi
}
ssh \
-i "${SSH_KEY}" \

View file

@ -1,6 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
# shellcheck source=Scripts/_burrow-secrets.sh
source "${SCRIPT_DIR}/_burrow-secrets.sh"
usage() {
cat <<'EOF'
Usage: Scripts/cloudflare-upsert-a-record.sh --zone <zone> --name <fqdn> --ipv4 <address> [options]
@ -13,7 +18,7 @@ Options:
--name <fqdn> Fully-qualified DNS record name
--ipv4 <address> IPv4 address for the A record
--token-file <path> Cloudflare API token file
default: intake/cloudflare-token.txt
default: secrets/cloudflare/api-token.age, then intake/cloudflare-token.txt
--ttl <seconds|auto> Record TTL, or auto
default: auto
--proxied <true|false> Whether to proxy through Cloudflare
@ -25,10 +30,15 @@ EOF
ZONE_NAME=""
RECORD_NAME=""
IPV4=""
TOKEN_FILE="intake/cloudflare-token.txt"
TOKEN_FILE="${CLOUDFLARE_TOKEN_FILE:-}"
TTL_VALUE="auto"
PROXIED="false"
cleanup() {
burrow_cleanup_secret_tmpfiles
}
trap cleanup EXIT
while [[ $# -gt 0 ]]; do
case "$1" in
--zone)
@ -71,11 +81,16 @@ if [[ -z "${ZONE_NAME}" || -z "${RECORD_NAME}" || -z "${IPV4}" ]]; then
usage >&2
exit 2
fi
if [[ ! -f "${TOKEN_FILE}" ]]; then
echo "Cloudflare token file not found: ${TOKEN_FILE}" >&2
TOKEN_FILE="$(
burrow_resolve_secret_file \
"${REPO_ROOT}" \
"${TOKEN_FILE}" \
"${REPO_ROOT}/intake/cloudflare-token.txt" \
"${REPO_ROOT}/secrets/cloudflare/api-token.age"
)" || {
echo "Cloudflare token file could not be resolved" >&2
exit 1
fi
}
if [[ ! "${IPV4}" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
echo "Invalid IPv4 address: ${IPV4}" >&2

View file

@ -5,6 +5,8 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=Scripts/_burrow-flake.sh
source "${SCRIPT_DIR}/_burrow-flake.sh"
# shellcheck source=Scripts/_burrow-secrets.sh
source "${SCRIPT_DIR}/_burrow-secrets.sh"
usage() {
cat <<'EOF'
@ -18,7 +20,7 @@ Defaults:
Environment:
BURROW_FORGE_HOST root@git.burrow.net
BURROW_FORGE_SSH_KEY intake/agent_at_burrow_net_ed25519
BURROW_FORGE_SSH_KEY explicit path, otherwise secrets/forgejo/agent-ssh-key.age
EOF
}
@ -28,6 +30,7 @@ ALLOW_DIRTY=0
BURROW_FLAKE_TMPDIRS=()
cleanup() {
burrow_cleanup_secret_tmpfiles
burrow_cleanup_flake_tmpdirs
}
trap cleanup EXIT
@ -71,21 +74,17 @@ if [[ ${ALLOW_DIRTY} -ne 1 ]] && [[ -n "$(git status --short)" ]]; then
fi
FORGE_HOST="${BURROW_FORGE_HOST:-root@git.burrow.net}"
FORGE_SSH_KEY="${BURROW_FORGE_SSH_KEY:-}"
if [[ -z "${FORGE_SSH_KEY}" ]]; then
if [[ -f "${REPO_ROOT}/intake/agent_at_burrow_net_ed25519" ]]; then
FORGE_SSH_KEY="${REPO_ROOT}/intake/agent_at_burrow_net_ed25519"
else
FORGE_SSH_KEY="${HOME}/.ssh/agent_at_burrow_net_ed25519"
fi
fi
if [[ ! -f "${FORGE_SSH_KEY}" ]]; then
echo "Forge SSH key not found at ${FORGE_SSH_KEY}." >&2
echo "Set BURROW_FORGE_SSH_KEY or place the agent key in intake/." >&2
FORGE_SSH_KEY="$(
burrow_resolve_secret_file \
"${REPO_ROOT}" \
"${BURROW_FORGE_SSH_KEY:-}" \
"${REPO_ROOT}/intake/agent_at_burrow_net_ed25519" \
"${REPO_ROOT}/secrets/forgejo/agent-ssh-key.age" \
"${HOME}/.ssh/agent_at_burrow_net_ed25519"
)" || {
echo "Unable to resolve the forge SSH key." >&2
exit 1
fi
}
FORGE_KNOWN_HOSTS_FILE="${BURROW_FORGE_KNOWN_HOSTS_FILE:-${HOME}/.cache/burrow/forge-known_hosts}"
mkdir -p "$(dirname "${FORGE_KNOWN_HOSTS_FILE}")"

View file

@ -6,12 +6,14 @@ REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
# shellcheck source=Scripts/_burrow-flake.sh
source "${SCRIPT_DIR}/_burrow-flake.sh"
# shellcheck source=Scripts/_burrow-secrets.sh
source "${SCRIPT_DIR}/_burrow-secrets.sh"
DEFAULT_CONFIG="burrow-forge"
DEFAULT_FLAKE="."
DEFAULT_LOCATION="hel1"
DEFAULT_ARCHITECTURE="x86"
DEFAULT_TOKEN_FILE="${REPO_ROOT}/intake/hetzner-api-token.txt"
DEFAULT_TOKEN_FILE=""
CONFIG="${HCLOUD_IMAGE_CONFIG:-${DEFAULT_CONFIG}}"
FLAKE="${HCLOUD_IMAGE_FLAKE:-${DEFAULT_FLAKE}}"
@ -30,6 +32,13 @@ NIX_BUILD_FLAGS=()
BURROW_FLAKE_TMPDIRS=()
LOCAL_STORE_DIR=""
cleanup() {
burrow_cleanup_secret_tmpfiles
burrow_cleanup_flake_tmpdirs
}
trap cleanup EXIT
usage() {
cat <<'EOF'
Usage: Scripts/hcloud-upload-nixos-image.sh [options]
@ -42,7 +51,7 @@ Options:
--location <code> Hetzner location for the temporary upload server (default: hel1)
--architecture <x86|arm> CPU architecture of the image (default: x86)
--server-type <name> Hetzner server type for the temporary upload server
--token-file <path> Hetzner API token file (default: intake/hetzner-api-token.txt)
--token-file <path> Hetzner API token file (default: secrets/hetzner/api-token.age, then intake/hetzner-api-token.txt)
--artifact-path <path> Prebuilt raw image artifact to upload directly
--output-hash <hash> Stable hash label for --artifact-path uploads
--builder-spec <string> Complete builders string passed to nix build
@ -125,6 +134,17 @@ while [[ $# -gt 0 ]]; do
esac
done
TOKEN_FILE="$(
burrow_resolve_secret_file \
"${REPO_ROOT}" \
"${TOKEN_FILE}" \
"${REPO_ROOT}/intake/hetzner-api-token.txt" \
"${REPO_ROOT}/secrets/hetzner/api-token.age"
)" || {
echo "Hetzner API token file could not be resolved" >&2
exit 1
}
cleanup() {
burrow_cleanup_flake_tmpdirs
if [[ -n "${LOCAL_STORE_DIR}" && -d "${LOCAL_STORE_DIR}" ]]; then

View file

@ -2,6 +2,9 @@
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
# shellcheck source=Scripts/_burrow-secrets.sh
source "${SCRIPT_DIR}/_burrow-secrets.sh"
usage() {
cat <<'EOF'
@ -31,7 +34,7 @@ Options:
-h, --help Show this help text.
Environment:
HCLOUD_TOKEN_FILE Defaults to intake/hetzner-api-token.txt
HCLOUD_TOKEN_FILE Defaults to secrets/hetzner/api-token.age, then intake/hetzner-api-token.txt
EOF
}
@ -43,10 +46,15 @@ IMAGE="ubuntu-24.04"
CONFIG="burrow-forge"
FLAKE="."
UPLOAD_LOCATION=""
TOKEN_FILE="${HCLOUD_TOKEN_FILE:-intake/hetzner-api-token.txt}"
TOKEN_FILE="${HCLOUD_TOKEN_FILE:-}"
YES=0
SSH_KEYS=("contact@burrow.net" "agent@burrow.net")
cleanup() {
burrow_cleanup_secret_tmpfiles
}
trap cleanup EXIT
if [[ $# -gt 0 ]]; then
case "$1" in
show|create|delete|recreate|build-image|create-from-image|recreate-from-image)
@ -110,10 +118,16 @@ while [[ $# -gt 0 ]]; do
esac
done
if [[ ! -f "${TOKEN_FILE}" ]]; then
echo "Hetzner API token file not found: ${TOKEN_FILE}" >&2
TOKEN_FILE="$(
burrow_resolve_secret_file \
"${REPO_ROOT}" \
"${TOKEN_FILE}" \
"${REPO_ROOT}/intake/hetzner-api-token.txt" \
"${REPO_ROOT}/secrets/hetzner/api-token.age"
)" || {
echo "Hetzner API token file could not be resolved" >&2
exit 1
fi
}
if [[ -z "${UPLOAD_LOCATION}" ]]; then
UPLOAD_LOCATION="${LOCATION}"

View file

@ -6,11 +6,13 @@ REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
# shellcheck source=Scripts/_burrow-flake.sh
source "${SCRIPT_DIR}/_burrow-flake.sh"
# shellcheck source=Scripts/_burrow-secrets.sh
source "${SCRIPT_DIR}/_burrow-secrets.sh"
CONFIG="${HCLOUD_IMAGE_CONFIG:-burrow-forge}"
FLAKE="${HCLOUD_IMAGE_FLAKE:-.}"
LOCATION="${HCLOUD_IMAGE_LOCATION:-hel1}"
TOKEN_FILE="${HCLOUD_TOKEN_FILE:-${REPO_ROOT}/intake/hetzner-api-token.txt}"
TOKEN_FILE="${HCLOUD_TOKEN_FILE:-}"
NSC_SSH_HOST="${NSC_SSH_HOST:-ssh.ord2.namespace.so}"
NSC_MACHINE_TYPE="${NSC_MACHINE_TYPE:-linux/amd64:32x64}"
NSC_BUILDER_DURATION="${NSC_BUILDER_DURATION:-4h}"
@ -26,6 +28,13 @@ EXTRA_LABELS=()
BURROW_FLAKE_TMPDIRS=()
BUILDER_ID=""
cleanup() {
burrow_cleanup_secret_tmpfiles
burrow_cleanup_flake_tmpdirs
}
trap cleanup EXIT
usage() {
cat <<'EOF'
Usage: Scripts/nsc-build-and-upload-image.sh [options]
@ -37,7 +46,7 @@ Options:
--config <name> images.<name>-raw output to build (default: burrow-forge)
--flake <path> Flake path to build from (default: .)
--location <code> Hetzner upload location (default: hel1)
--token-file <path> Hetzner API token file (default: intake/hetzner-api-token.txt)
--token-file <path> Hetzner API token file (default: secrets/hetzner/api-token.age, then intake/hetzner-api-token.txt)
--machine-type <type> Namespace machine type (default: linux/amd64:32x64)
--ssh-host <host> Namespace SSH endpoint (default: ssh.ord2.namespace.so)
--duration <ttl> Namespace builder lifetime (default: 4h)
@ -126,6 +135,17 @@ while [[ $# -gt 0 ]]; do
esac
done
TOKEN_FILE="$(
burrow_resolve_secret_file \
"${REPO_ROOT}" \
"${TOKEN_FILE}" \
"${REPO_ROOT}/intake/hetzner-api-token.txt" \
"${REPO_ROOT}/secrets/hetzner/api-token.age"
)" || {
echo "Hetzner API token file could not be resolved" >&2
exit 1
}
cleanup() {
if [[ -n "${BUILDER_ID}" && -n "${NSC_BIN}" ]]; then
"${NSC_BIN}" destroy "${BUILDER_ID}" --force >/dev/null 2>&1 || true

View file

@ -6,31 +6,35 @@ REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
# shellcheck source=Scripts/_burrow-flake.sh
source "${SCRIPT_DIR}/_burrow-flake.sh"
# shellcheck source=Scripts/_burrow-secrets.sh
source "${SCRIPT_DIR}/_burrow-secrets.sh"
usage() {
cat <<'EOF'
Usage: Scripts/provision-forgejo-nsc.sh [options]
Generate Burrow forgejo-nsc runtime inputs in intake/ and optionally refresh the
Namespace token from the currently logged-in namespace account.
Generate Burrow forgejo-nsc runtime inputs and refresh the authoritative
`secrets/forgejo/*.age` files, optionally refreshing the Namespace token from
the currently logged-in namespace account.
Options:
--host <user@host> SSH target used to mint the Forgejo PAT.
Default: root@git.burrow.net
--ssh-key <path> SSH private key for the forge host.
Default: intake/agent_at_burrow_net_ed25519
Default: secrets/forgejo/agent-ssh-key.age, then intake/
--nsc-bin <path> Override the nsc binary.
--no-refresh-token Reuse intake/forgejo_nsc_token.txt if it already exists.
--no-refresh-token Reuse the existing encrypted Namespace token if it already exists.
--token-name <name> Forgejo PAT name prefix (default: forgejo-nsc)
--contact-user <name> Forgejo username used for PAT creation (default: contact)
--scope-owner <name> Forgejo org/user owner for the default NSC scope (default: hackclub)
--scope-name <name> Forgejo repository name for the default NSC scope (default: burrow)
--write-intake Also write plaintext runtime inputs to intake/ for local debugging.
-h, --help Show this help text.
EOF
}
HOST="${BURROW_FORGE_HOST:-root@git.burrow.net}"
SSH_KEY="${BURROW_FORGE_SSH_KEY:-${REPO_ROOT}/intake/agent_at_burrow_net_ed25519}"
SSH_KEY="${BURROW_FORGE_SSH_KEY:-}"
NSC_BIN="${NSC_BIN:-}"
KNOWN_HOSTS_FILE="${BURROW_FORGE_KNOWN_HOSTS_FILE:-${HOME}/.cache/burrow/forge-known_hosts}"
REFRESH_TOKEN=1
@ -39,8 +43,12 @@ CONTACT_USER="${FORGEJO_CONTACT_USER:-contact}"
SCOPE_OWNER="${FORGEJO_SCOPE_OWNER:-hackclub}"
SCOPE_NAME="${FORGEJO_SCOPE_NAME:-burrow}"
BURROW_FLAKE_TMPDIRS=()
WRITE_INTAKE=0
TMP_DIR=""
cleanup() {
[[ -n "${TMP_DIR}" ]] && rm -rf "${TMP_DIR}" >/dev/null 2>&1 || true
burrow_cleanup_secret_tmpfiles
burrow_cleanup_flake_tmpdirs
}
trap cleanup EXIT
@ -79,6 +87,10 @@ while [[ $# -gt 0 ]]; do
SCOPE_NAME="${2:?missing value for --scope-name}"
shift 2
;;
--write-intake)
WRITE_INTAKE=1
shift
;;
-h|--help)
usage
exit 0
@ -97,13 +109,15 @@ burrow_require_cmd nix
burrow_require_cmd ssh
burrow_require_cmd python3
if [[ ! -f "${SSH_KEY}" ]]; then
echo "forge SSH key not found: ${SSH_KEY}" >&2
exit 1
fi
mkdir -p "${REPO_ROOT}/intake"
chmod 700 "${REPO_ROOT}/intake"
SSH_KEY="$(
burrow_resolve_secret_file \
"${REPO_ROOT}" \
"${SSH_KEY}" \
"${REPO_ROOT}/intake/agent_at_burrow_net_ed25519" \
"${REPO_ROOT}/secrets/forgejo/agent-ssh-key.age" \
"${HOME}/.ssh/agent_at_burrow_net_ed25519"
)"
TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/burrow-forgejo-nsc.XXXXXX")"
flake_ref="$(burrow_prepare_flake_ref "${REPO_ROOT}")"
if [[ -z "${NSC_BIN}" ]]; then
@ -128,13 +142,16 @@ if [[ ! -x "${NSC_BIN}" ]]; then
exit 1
fi
token_file="${REPO_ROOT}/intake/forgejo_nsc_token.txt"
dispatcher_out="${REPO_ROOT}/intake/forgejo_nsc_dispatcher.yaml"
autoscaler_out="${REPO_ROOT}/intake/forgejo_nsc_autoscaler.yaml"
token_file="${TMP_DIR}/forgejo_nsc_token.txt"
dispatcher_out="${TMP_DIR}/forgejo_nsc_dispatcher.yaml"
autoscaler_out="${TMP_DIR}/forgejo_nsc_autoscaler.yaml"
dispatcher_src="${REPO_ROOT}/services/forgejo-nsc/deploy/dispatcher.yaml"
autoscaler_src="${REPO_ROOT}/services/forgejo-nsc/deploy/autoscaler.yaml"
token_secret="${REPO_ROOT}/secrets/forgejo/nsc-token.age"
dispatcher_secret="${REPO_ROOT}/secrets/forgejo/nsc-dispatcher-config.age"
autoscaler_secret="${REPO_ROOT}/secrets/forgejo/nsc-autoscaler-config.age"
if [[ "${REFRESH_TOKEN}" -eq 1 || ! -s "${token_file}" ]]; then
if [[ "${REFRESH_TOKEN}" -eq 1 ]]; then
"${NSC_BIN}" auth check-login --duration 20m >/dev/null
raw_token_file="$(mktemp)"
trap 'rm -f "${raw_token_file}"; cleanup' EXIT
@ -155,7 +172,13 @@ Path(os.environ["TOKEN_FILE"]).write_text(
PY
rm -f "${raw_token_file}"
chmod 600 "${token_file}"
elif [[ -s "${token_file}" ]]; then
elif [[ -f "${token_secret}" ]]; then
burrow_decrypt_age_secret_to_temp "${REPO_ROOT}" "${token_secret}" > "${token_file}"
elif [[ -s "${REPO_ROOT}/intake/forgejo_nsc_token.txt" ]]; then
cp "${REPO_ROOT}/intake/forgejo_nsc_token.txt" "${token_file}"
fi
if [[ -s "${token_file}" ]]; then
TOKEN_FILE="${token_file}" python3 - <<'PY'
import json
import os
@ -271,6 +294,24 @@ PY
chmod 600 "${dispatcher_out}" "${autoscaler_out}"
echo "Rendered intake/forgejo_nsc_token.txt, intake/forgejo_nsc_dispatcher.yaml, and intake/forgejo_nsc_autoscaler.yaml."
echo "Re-encrypt them into secrets/forgejo/{nsc-token,nsc-dispatcher-config,nsc-autoscaler-config}.age before deploying the forge host."
burrow_encrypt_secret_from_file "${REPO_ROOT}" "${token_secret}" "${token_file}"
burrow_encrypt_secret_from_file "${REPO_ROOT}" "${dispatcher_secret}" "${dispatcher_out}"
burrow_encrypt_secret_from_file "${REPO_ROOT}" "${autoscaler_secret}" "${autoscaler_out}"
if [[ "${WRITE_INTAKE}" -eq 1 ]]; then
mkdir -p "${REPO_ROOT}/intake"
chmod 700 "${REPO_ROOT}/intake"
cp "${token_file}" "${REPO_ROOT}/intake/forgejo_nsc_token.txt"
cp "${dispatcher_out}" "${REPO_ROOT}/intake/forgejo_nsc_dispatcher.yaml"
cp "${autoscaler_out}" "${REPO_ROOT}/intake/forgejo_nsc_autoscaler.yaml"
chmod 600 \
"${REPO_ROOT}/intake/forgejo_nsc_token.txt" \
"${REPO_ROOT}/intake/forgejo_nsc_dispatcher.yaml" \
"${REPO_ROOT}/intake/forgejo_nsc_autoscaler.yaml"
fi
echo "Updated secrets/forgejo/{nsc-token,nsc-dispatcher-config,nsc-autoscaler-config}.age."
if [[ "${WRITE_INTAKE}" -eq 1 ]]; then
echo "Also refreshed intake/forgejo_nsc_{token,dispatcher,autoscaler} for local debugging."
fi
echo "Minted Forgejo PAT ${token_name} for ${CONTACT_USER} on ${HOST}."

View file

@ -5,12 +5,12 @@ usage() {
cat <<'EOF'
Usage: Scripts/sync-forgejo-nsc-config.sh [options]
Copy Burrow forgejo-nsc runtime inputs from intake/ onto the forge host and
Copy Burrow forgejo-nsc runtime inputs from age secrets or intake/ onto the forge host and
restart the dispatcher/autoscaler units.
Options:
--host <user@host> SSH target (default: root@git.burrow.net)
--ssh-key <path> SSH private key (default: intake/agent_at_burrow_net_ed25519)
--ssh-key <path> SSH private key (default: secrets/forgejo/agent-ssh-key.age, then intake/)
--rotate-pat Re-render the intake files before syncing.
--no-restart Copy files only.
-h, --help Show this help text.
@ -19,12 +19,21 @@ EOF
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
# shellcheck source=Scripts/_burrow-secrets.sh
source "${SCRIPT_DIR}/_burrow-secrets.sh"
HOST="${BURROW_FORGE_HOST:-root@git.burrow.net}"
SSH_KEY="${BURROW_FORGE_SSH_KEY:-${REPO_ROOT}/intake/agent_at_burrow_net_ed25519}"
SSH_KEY="${BURROW_FORGE_SSH_KEY:-}"
KNOWN_HOSTS_FILE="${BURROW_FORGE_KNOWN_HOSTS_FILE:-${HOME}/.cache/burrow/forge-known_hosts}"
ROTATE_PAT=0
NO_RESTART=0
TMP_DIR=""
cleanup() {
[[ -n "${TMP_DIR}" ]] && rm -rf "${TMP_DIR}" >/dev/null 2>&1 || true
burrow_cleanup_secret_tmpfiles
}
trap cleanup EXIT
while [[ $# -gt 0 ]]; do
case "$1" in
@ -68,18 +77,41 @@ burrow_require_cmd() {
burrow_require_cmd ssh
burrow_require_cmd scp
if [[ ! -f "${SSH_KEY}" ]]; then
echo "forge SSH key not found: ${SSH_KEY}" >&2
exit 1
fi
SSH_KEY="$(
burrow_resolve_secret_file \
"${REPO_ROOT}" \
"${SSH_KEY}" \
"${REPO_ROOT}/intake/agent_at_burrow_net_ed25519" \
"${REPO_ROOT}/secrets/forgejo/agent-ssh-key.age" \
"${HOME}/.ssh/agent_at_burrow_net_ed25519"
)"
if [[ "${ROTATE_PAT}" -eq 1 ]]; then
"${SCRIPT_DIR}/provision-forgejo-nsc.sh" --host "${HOST}" --ssh-key "${SSH_KEY}"
fi
token_file="${REPO_ROOT}/intake/forgejo_nsc_token.txt"
dispatcher_file="${REPO_ROOT}/intake/forgejo_nsc_dispatcher.yaml"
autoscaler_file="${REPO_ROOT}/intake/forgejo_nsc_autoscaler.yaml"
TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/burrow-nsc-sync.XXXXXX")"
token_file="$(
burrow_resolve_secret_file \
"${REPO_ROOT}" \
"" \
"${REPO_ROOT}/intake/forgejo_nsc_token.txt" \
"${REPO_ROOT}/secrets/forgejo/nsc-token.age"
)"
dispatcher_file="$(
burrow_resolve_secret_file \
"${REPO_ROOT}" \
"" \
"${REPO_ROOT}/intake/forgejo_nsc_dispatcher.yaml" \
"${REPO_ROOT}/secrets/forgejo/nsc-dispatcher-config.age"
)"
autoscaler_file="$(
burrow_resolve_secret_file \
"${REPO_ROOT}" \
"" \
"${REPO_ROOT}/intake/forgejo_nsc_autoscaler.yaml" \
"${REPO_ROOT}/secrets/forgejo/nsc-autoscaler-config.age"
)"
for path in "${token_file}" "${dispatcher_file}" "${autoscaler_file}"; do
if [[ ! -s "${path}" ]]; then
@ -96,12 +128,12 @@ ssh_opts=(
)
remote_tmp="$(ssh "${ssh_opts[@]}" "${HOST}" "mktemp -d")"
cleanup() {
cleanup_remote() {
if [[ -n "${remote_tmp:-}" ]]; then
ssh "${ssh_opts[@]}" "${HOST}" "rm -rf '${remote_tmp}'" >/dev/null 2>&1 || true
fi
}
trap cleanup EXIT
trap 'cleanup_remote; cleanup' EXIT
scp "${ssh_opts[@]}" \
"${token_file}" \