#!/usr/bin/env bash # ============================================================================= # tools/vault-seed-chat.sh — Idempotent seed for kv/disinto/shared/chat # # Part of the Nomad+Vault migration (S5.2, issue #989). Populates the KV v2 # path that nomad/jobs/chat.hcl reads from, so a clean-install factory # (no old-stack secrets to import) still has per-key values for # CHAT_OAUTH_CLIENT_ID, CHAT_OAUTH_CLIENT_SECRET, and FORWARD_AUTH_SECRET. # # Companion to tools/vault-import.sh (S2.2) — when that import runs against # a box with an existing stack, it overwrites these seeded values with the # real ones. Order doesn't matter: whichever runs last wins, and both # scripts are idempotent in the sense that re-running never rotates an # existing non-empty key. # # Uses _hvault_seed_key (lib/hvault.sh) for each key — the helper reads # existing data and merges to preserve sibling keys (KV v2 replaces .data # atomically). # # Preconditions: # - Vault reachable + unsealed at $VAULT_ADDR. # - VAULT_TOKEN set (env) or /etc/vault.d/root.token readable. # - The `kv/` mount is enabled as KV v2. # # Requires: VAULT_ADDR, VAULT_TOKEN, curl, jq, openssl # # Usage: # tools/vault-seed-chat.sh # tools/vault-seed-chat.sh --dry-run # # Exit codes: # 0 success (seed applied, or already applied) # 1 precondition / API / mount-mismatch failure # ============================================================================= set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" # shellcheck source=../lib/hvault.sh source "${REPO_ROOT}/lib/hvault.sh" KV_MOUNT="kv" KV_LOGICAL_PATH="disinto/shared/chat" # Keys to seed — array-driven loop (structurally distinct from forgejo's # sequential if-blocks and agents' role loop). SEED_KEYS=(chat_oauth_client_id chat_oauth_client_secret forward_auth_secret) LOG_TAG="[vault-seed-chat]" log() { printf '%s %s\n' "$LOG_TAG" "$*"; } die() { printf '%s ERROR: %s\n' "$LOG_TAG" "$*" >&2; exit 1; } # ── Flag parsing — [[ ]] guard + case: shape distinct from forgejo # (arity:value case), woodpecker (for-loop), agents (while/shift). DRY_RUN=0 if [[ $# -gt 0 ]]; then case "$1" in --dry-run) DRY_RUN=1 ;; -h|--help) printf 'Usage: %s [--dry-run]\n\n' "$(basename "$0")" printf 'Seed kv/disinto/shared/chat with random OAuth client\n' printf 'credentials and forward auth secret if missing.\n' printf 'Idempotent: existing non-empty values are preserved.\n\n' printf ' --dry-run Show what would be seeded without writing.\n' exit 0 ;; *) die "invalid argument: ${1} (try --help)" ;; esac fi # ── Preconditions — inline check-or-die (shape distinct from agents' array # loop and forgejo's continuation-line style) ───────────────────────────── command -v curl >/dev/null 2>&1 || die "curl not found" command -v jq >/dev/null 2>&1 || die "jq not found" command -v openssl >/dev/null 2>&1 || die "openssl not found" [ -n "${VAULT_ADDR:-}" ] || die "VAULT_ADDR unset — export VAULT_ADDR=http://127.0.0.1:8200" hvault_token_lookup >/dev/null || die "Vault auth probe failed — check VAULT_ADDR + VAULT_TOKEN" # ── Step 1/2: ensure kv/ mount exists and is KV v2 ─────────────────────────── log "── Step 1/2: ensure ${KV_MOUNT}/ is KV v2 ──" export DRY_RUN hvault_ensure_kv_v2 "$KV_MOUNT" "${LOG_TAG}" \ || die "KV mount check failed" # ── Step 2/2: seed missing keys via _hvault_seed_key helper ────────────────── log "── Step 2/2: seed ${KV_LOGICAL_PATH} ──" generated=() for key in "${SEED_KEYS[@]}"; do if [ "$DRY_RUN" -eq 1 ]; then # Check existence without writing existing=$(hvault_kv_get "$KV_LOGICAL_PATH" "$key" 2>/dev/null) || true if [ -z "$existing" ]; then generated+=("$key") log "[dry-run] ${key} would be generated" else log "[dry-run] ${key} unchanged" fi else rc=0 _hvault_seed_key "$KV_LOGICAL_PATH" "$key" || rc=$? case "$rc" in 0) generated+=("$key"); log "${key} generated" ;; 1) log "${key} unchanged" ;; *) die "API error seeding ${key} (rc=${rc})" ;; esac fi done if [ "${#generated[@]}" -eq 0 ]; then log "all keys present — no-op" else log "done — ${#generated[@]} key(s) seeded at kv/${KV_LOGICAL_PATH}" fi