2026-07-22 03:21:46 +00:00
|
|
|
#!/usr/bin/env bash
|
2026-07-26 06:56:38 +00:00
|
|
|
# Seed deploy/secrets/<env>.yaml by encrypting an environment's live env file
|
2026-07-22 03:21:46 +00:00
|
|
|
# ENTIRELY ON THE BOX. The plaintext never leaves the box; only the already-encrypted
|
|
|
|
|
# ciphertext is written back here. Encryption uses the age PUBLIC key (safe to hardcode)
|
|
|
|
|
# so the machine you run this from needs nothing but SSH access + the agent key — no
|
|
|
|
|
# sops, no age, no private key. sops is installed on the box on demand.
|
|
|
|
|
#
|
|
|
|
|
# Run from any checkout of this branch that can SSH to the target with the agent key:
|
|
|
|
|
# deploy/secrets/seed-encrypt-on-host.sh prod agent@169.58.46.181
|
2026-07-26 06:56:38 +00:00
|
|
|
# deploy/secrets/seed-encrypt-on-host.sh beta agent@169.58.46.181
|
|
|
|
|
# deploy/secrets/seed-encrypt-on-host.sh dev agent@75.119.132.91
|
|
|
|
|
#
|
|
|
|
|
# prod and beta share a box (vps2); they differ only by which env FILE is read
|
|
|
|
|
# (/etc/thermograph.env vs /etc/thermograph-beta.env), resolved from
|
|
|
|
|
# deploy/env-topology.sh. Hardcoding that path meant "seed beta" would encrypt
|
|
|
|
|
# PROD's live secrets into beta.yaml.
|
2026-07-22 03:21:46 +00:00
|
|
|
# Then commit + push the resulting deploy/secrets/<env>.yaml. Verify faithfulness with
|
|
|
|
|
# the key-gaps skill after. See README.md.
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
cd "$(dirname "$(readlink -f "$0")")/../.."
|
|
|
|
|
|
|
|
|
|
ENV_NAME="${1:?usage: seed-encrypt-on-host.sh <env> <ssh-target> [ssh-key]}"
|
|
|
|
|
SSH_TARGET="${2:?ssh target, e.g. agent@169.58.46.181}"
|
|
|
|
|
SSH_KEY="${3:-$HOME/.ssh/thermograph_agent_ed25519}"
|
|
|
|
|
OUT="deploy/secrets/${ENV_NAME}.yaml"
|
2026-07-26 06:56:38 +00:00
|
|
|
# shellcheck source=infra/deploy/env-topology.sh
|
|
|
|
|
. deploy/env-topology.sh
|
|
|
|
|
thermograph_topology "$ENV_NAME"
|
|
|
|
|
REMOTE_ENV_FILE="$TG_ENV_FILE"
|
2026-07-22 03:21:46 +00:00
|
|
|
|
|
|
|
|
# Public age recipient — NOT a secret (matches .sops.yaml). The private key never
|
|
|
|
|
# leaves the operator's machine / the hosts' /etc/thermograph/age.key.
|
|
|
|
|
AGE_PUB="age1xx4dzs0dxlwvkv9sjuqzsphl7lfrxannkfken374yu2qvvcte9sqzktqt2"
|
|
|
|
|
SOPS_URL="https://github.com/getsops/sops/releases/download/v3.13.2/sops-v3.13.2.linux.amd64"
|
|
|
|
|
|
2026-07-26 06:56:38 +00:00
|
|
|
echo "==> Encrypting ${SSH_TARGET}:${REMOTE_ENV_FILE} on-host -> ${OUT}"
|
|
|
|
|
ssh -i "$SSH_KEY" "$SSH_TARGET" "AGE_PUB='$AGE_PUB' SOPS_URL='$SOPS_URL' ENV_FILE='$REMOTE_ENV_FILE' bash -s" > "$OUT" <<'REMOTE'
|
2026-07-22 03:21:46 +00:00
|
|
|
set -euo pipefail
|
|
|
|
|
exec 3>&1 # real stdout carries ONLY the ciphertext
|
|
|
|
|
{ # setup noise -> stderr, so it can't corrupt the file
|
|
|
|
|
if ! command -v sops >/dev/null 2>&1; then
|
|
|
|
|
sudo curl -fsSL "$SOPS_URL" -o /usr/local/bin/sops
|
|
|
|
|
sudo chmod +x /usr/local/bin/sops
|
|
|
|
|
fi
|
|
|
|
|
} >&2
|
|
|
|
|
tmp="$(mktemp)"; tmy="$(mktemp)"; trap 'rm -f "$tmp" "$tmy"' EXIT
|
2026-07-26 06:56:38 +00:00
|
|
|
# $ENV_FILE comes from the ssh command line above (env-topology.sh resolved it
|
|
|
|
|
# for the named environment) — NOT hardcoded, because vps2 holds two of them.
|
|
|
|
|
sudo cat "$ENV_FILE" > "$tmp" # plaintext stays on this box only
|
2026-07-22 03:21:46 +00:00
|
|
|
python3 -c '
|
|
|
|
|
import json, sys
|
|
|
|
|
for line in open(sys.argv[1], encoding="utf-8"):
|
|
|
|
|
s = line.rstrip("\n").strip()
|
|
|
|
|
if not s or s.startswith("#") or "=" not in s:
|
|
|
|
|
continue
|
|
|
|
|
k, _, v = s.partition("=")
|
|
|
|
|
print(f"{k.strip()}: {json.dumps(v)}") # JSON-escaped scalar = valid YAML
|
|
|
|
|
' "$tmp" > "$tmy"
|
|
|
|
|
cd /tmp # no .sops.yaml here, so --age is honored
|
|
|
|
|
sops -e --age "$AGE_PUB" --input-type yaml --output-type yaml "$tmy" >&3
|
|
|
|
|
REMOTE
|
|
|
|
|
|
|
|
|
|
if grep -q 'ENC\[AES256_GCM' "$OUT"; then
|
|
|
|
|
echo "==> OK: ${OUT} written and encrypted ($(grep -cE '^[A-Za-z_][A-Za-z0-9_]*: ' "$OUT") keys)"
|
|
|
|
|
else
|
|
|
|
|
echo "!! ${OUT} is not encrypted (the on-host step failed) — removing" >&2
|
|
|
|
|
rm -f "$OUT"; exit 1
|
|
|
|
|
fi
|