#!/usr/bin/env bash # Seed deploy/secrets/.yaml by encrypting an environment's live env file # 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 # 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. # Then commit + push the resulting deploy/secrets/.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 [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" # shellcheck source=infra/deploy/env-topology.sh . deploy/env-topology.sh thermograph_topology "$ENV_NAME" REMOTE_ENV_FILE="$TG_ENV_FILE" # 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" 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' 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 # $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 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