#!/usr/bin/env bash set -euo pipefail REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" STATE_DIR="$REPO_ROOT/state/stacks" VAULT_ADDR="${VAULT_ADDR:-https://vault.viktorbarzin.me}" cmd="${1:-help}" stack="${2:-}" # optional: operate on single stack # Check if Vault token is valid vault_available() { VAULT_ADDR="$VAULT_ADDR" vault token lookup &>/dev/null 2>&1 } # Per-stack Transit key URI transit_uri() { local stack_name="$1" echo "${VAULT_ADDR}/v1/transit/keys/sops-state-${stack_name}" } # Extract stack name from directory path stack_name_from_dir() { basename "$1" } # Read age recipients from .sops.yaml AGE_RECIPIENTS="$(python3 -c " import yaml, sys with open('$REPO_ROOT/.sops.yaml') as f: c = yaml.safe_load(f) for r in c.get('creation_rules', []): age = r.get('age', '') if age: print(age.replace('\n', '').strip()) break " 2>/dev/null || echo "")" encrypt_state() { local dir="$1" local src="$dir/terraform.tfstate" local dst="$dir/terraform.tfstate.enc" local name name="$(stack_name_from_dir "$dir")" [ -f "$src" ] || return 0 # Only re-encrypt if state is newer than encrypted version if [ ! -f "$dst" ] || [ "$src" -nt "$dst" ]; then sops -e --input-type json --output-type json \ --hc-vault-transit "$(transit_uri "$name")" \ --age "$AGE_RECIPIENTS" \ "$src" > "$dst" fi } decrypt_state() { local dir="$1" local src="$dir/terraform.tfstate.enc" local dst="$dir/terraform.tfstate" [ -f "$src" ] || return 0 if vault_available; then # Vault Transit — per-stack key, no local key needed sops -d --input-type json --output-type json "$src" > "$dst" elif [ -f "${SOPS_AGE_KEY_FILE:-$HOME/.config/sops/age/keys.txt}" ]; then # Fallback: age key on disk (bootstrap / Vault down) echo "state-sync: Vault unavailable, falling back to age key" >&2 SOPS_AGE_KEY_FILE="${SOPS_AGE_KEY_FILE:-$HOME/.config/sops/age/keys.txt}" \ sops -d --input-type json --output-type json "$src" > "$dst" else echo "state-sync: ERROR — no Vault token and no age key at ~/.config/sops/age/keys.txt" >&2 return 1 fi } case "$cmd" in encrypt) if [ -n "$stack" ]; then encrypt_state "$STATE_DIR/$stack" else for dir in "$STATE_DIR"/*/; do encrypt_state "$dir" done fi ;; decrypt) if [ -n "$stack" ]; then decrypt_state "$STATE_DIR/$stack" else for dir in "$STATE_DIR"/*/; do decrypt_state "$dir" done fi ;; commit) # Encrypt all changed state, then git add + commit "$0" encrypt cd "$REPO_ROOT" git add state/stacks/*/terraform.tfstate.enc if ! git diff --cached --quiet; then git commit -m "state: update encrypted terraform state" fi ;; help) echo "Usage: state-sync {encrypt|decrypt|commit} [stack-name]" echo "Encrypt uses per-stack Vault Transit key (transit/keys/sops-state-)." echo "Decrypt uses Vault Transit if logged in, falls back to age key." ;; esac