#!/usr/bin/env bash # bootstrap/sops/install-age-key.sh — install the age PRIVATE key into the cluster as the # `sops-age` Secret in flux-system, so Flux can decrypt SOPS-encrypted manifests (ADR-0007, # R-SEC-01). This is the scripted form of the runbook in age-key-secret.md, and the entrypoint # the Ansible flux_bootstrap role calls (flux_bootstrap_sops_cmd) BEFORE bootstrap.sh. # # The private key NEVER lives in git. It is read from AGE_KEY_FILE (default: the standard sops # age path) and passed to kubectl from a file (not argv). Run at bootstrap by the OPERATOR — not # by any agent, and not during author-only Phase 1. set -euo pipefail AGE_KEY_FILE="${AGE_KEY_FILE:-${HOME}/.config/sops/age/keys.txt}" NAMESPACE="${FLUX_NAMESPACE:-flux-system}" SECRET_NAME="${SOPS_SECRET_NAME:-sops-age}" [ -f "$AGE_KEY_FILE" ] || { echo "ERROR: age key not found at ${AGE_KEY_FILE} — run age-keygen.sh first." >&2; exit 1; } grep -q 'AGE-SECRET-KEY-' "$AGE_KEY_FILE" || { echo "ERROR: ${AGE_KEY_FILE} contains no AGE-SECRET-KEY." >&2; exit 1; } kubectl get namespace "$NAMESPACE" >/dev/null 2>&1 || kubectl create namespace "$NAMESPACE" # Flux reads keys ending in `.agekey` from the sops-age Secret. Idempotent create-or-update; # the key is streamed from the file, never placed on the command line / in shell history. kubectl create secret generic "$SECRET_NAME" \ --namespace "$NAMESPACE" \ --from-file=age.agekey="$AGE_KEY_FILE" \ --dry-run=client -o yaml | kubectl apply -f - echo "Installed Secret ${NAMESPACE}/${SECRET_NAME} for Flux SOPS decryption."