thermograph/deploy/render-secrets.sh

61 lines
2.9 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
# Render /etc/thermograph.env from the SOPS-encrypted source of truth
# (deploy/secrets/<env>.yaml, committed encrypted) — sourced by deploy.sh and
# deploy-dev.sh, not run directly.
#
# The encrypted files are the single source of truth; /etc/thermograph.env becomes a
# rendered artifact that the existing seams (docker-compose `env_file:`, the systemd
# unit's EnvironmentFile, and entrypoint.sh's /run/secrets shim) keep consuming
# unchanged. Common secrets + the per-host overrides are concatenated with the host
# file LAST, so a host value wins (env_file and `source` both take the last
# occurrence of a duplicate key).
#
# Presence-detected: a host is "configured for SOPS" only when it has an age key at
# /etc/thermograph/age.key AND an environment name in /etc/thermograph/secrets-env
# (prod|beta|dev). A host without them keeps whatever /etc/thermograph.env it already
# has — so merging this is a safe no-op until a host is deliberately migrated. See
# deploy/secrets/README.md.
render_thermograph_secrets() {
local repo="${1:-.}" # repo root holding deploy/secrets
local key="${THERMOGRAPH_AGE_KEY:-/etc/thermograph/age.key}"
local marker="${THERMOGRAPH_SECRETS_ENV_FILE:-/etc/thermograph/secrets-env}"
local env_name
env_name=$(cat "$marker" 2>/dev/null || true)
# The per-host file is required; common.yaml is optional so the initial cutover can
# seed each host as an exact copy of its live env (byte-identical render, no value
# changes) and factor out shared secrets into common.yaml later.
if [ -z "$env_name" ] || [ ! -f "$key" ] \
|| [ ! -f "$repo/deploy/secrets/${env_name}.yaml" ]; then
echo "==> SOPS secrets not configured here; using existing /etc/thermograph.env"
return 0
fi
if ! command -v sops >/dev/null 2>&1; then
echo "!! sops not installed but this host is configured for SOPS secrets" >&2
return 1
fi
echo "==> Rendering /etc/thermograph.env from deploy/secrets (common + ${env_name})"
local tmp; tmp=$(mktemp)
: > "$tmp"
# set -e in the caller makes a decrypt failure fatal here (no partial env). common
# first, host second, so a host value overrides a shared one (last-wins).
if [ -f "$repo/deploy/secrets/common.yaml" ]; then
SOPS_AGE_KEY_FILE="$key" sops -d --input-type yaml --output-type dotenv \
"$repo/deploy/secrets/common.yaml" >> "$tmp"
fi
SOPS_AGE_KEY_FILE="$key" sops -d --input-type yaml --output-type dotenv \
"$repo/deploy/secrets/${env_name}.yaml" >> "$tmp"
# /etc/thermograph.env is in the root-owned /etc: write it directly if we can, else
# via sudo. Fail loudly rather than deploy against stale secrets.
if install -m 0640 "$tmp" /etc/thermograph.env 2>/dev/null; then :
elif sudo install -m 0640 "$tmp" /etc/thermograph.env 2>/dev/null; then :
else
rm -f "$tmp"
echo "!! cannot write /etc/thermograph.env (need write access or passwordless sudo)" >&2
return 1
fi
rm -f "$tmp"
}