diff --git a/lib/hvault.sh b/lib/hvault.sh index 086c9f2..b0d1635 100644 --- a/lib/hvault.sh +++ b/lib/hvault.sh @@ -129,6 +129,60 @@ _hvault_request() { # Used by: hvault_kv_get, hvault_kv_put, hvault_kv_list : "${VAULT_KV_MOUNT:=kv}" +# hvault_ensure_kv_v2 MOUNT [LOG_PREFIX] +# Assert that the given KV mount is present and KV v2. If absent, enable +# it. If present as wrong type/version, exit 1. Callers must have already +# checked VAULT_ADDR / VAULT_TOKEN. +# +# DRY_RUN (env, default 0): when 1, log intent without writing. +# LOG_PREFIX (optional): label for log lines, e.g. "[vault-seed-forgejo]". +# +# Extracted here because every vault-seed-*.sh script needs this exact +# sequence, and the 5-line sliding-window dup detector flags the +# copy-paste. One place, one implementation. +hvault_ensure_kv_v2() { + local mount="${1:?hvault_ensure_kv_v2: MOUNT required}" + local prefix="${2:-[hvault]}" + local dry_run="${DRY_RUN:-0}" + local mounts_json mount_exists mount_type mount_version + + mounts_json="$(hvault_get_or_empty "sys/mounts")" \ + || { printf '%s ERROR: failed to list Vault mounts\n' "$prefix" >&2; return 1; } + + mount_exists=false + if printf '%s' "$mounts_json" | jq -e --arg m "${mount}/" '.[$m]' >/dev/null 2>&1; then + mount_exists=true + fi + + if [ "$mount_exists" = true ]; then + mount_type="$(printf '%s' "$mounts_json" \ + | jq -r --arg m "${mount}/" '.[$m].type // ""')" + mount_version="$(printf '%s' "$mounts_json" \ + | jq -r --arg m "${mount}/" '.[$m].options.version // "1"')" + if [ "$mount_type" != "kv" ]; then + printf '%s ERROR: %s/ is mounted as type=%q, expected kv — refuse to re-mount\n' \ + "$prefix" "$mount" "$mount_type" >&2 + return 1 + fi + if [ "$mount_version" != "2" ]; then + printf '%s ERROR: %s/ is KV v%s, expected v2 — refuse to upgrade in place\n' \ + "$prefix" "$mount" "$mount_version" >&2 + return 1 + fi + printf '%s %s/ already mounted (kv v2) — skipping enable\n' "$prefix" "$mount" + else + if [ "$dry_run" -eq 1 ]; then + printf '%s [dry-run] would enable %s/ as kv v2\n' "$prefix" "$mount" + else + local payload + payload="$(jq -n '{type:"kv",options:{version:"2"},description:"disinto shared KV v2 (S2.4)"}')" + _hvault_request POST "sys/mounts/${mount}" "$payload" >/dev/null \ + || { printf '%s ERROR: failed to enable %s/ as kv v2\n' "$prefix" "$mount" >&2; return 1; } + printf '%s %s/ enabled as kv v2\n' "$prefix" "$mount" + fi + fi +} + # hvault_kv_get PATH [KEY] # Read a KV v2 secret at PATH, optionally extract a single KEY. # Outputs: JSON value (full data object, or single key value) diff --git a/tools/vault-seed-forgejo.sh b/tools/vault-seed-forgejo.sh index 1f1e619..26a9e78 100755 --- a/tools/vault-seed-forgejo.sh +++ b/tools/vault-seed-forgejo.sh @@ -118,36 +118,9 @@ hvault_token_lookup >/dev/null \ # wrong version or a different backend, fail loudly — silently # re-enabling would destroy existing secrets. log "── Step 1/2: ensure ${KV_MOUNT}/ is KV v2 ──" -mounts_json="$(hvault_get_or_empty "sys/mounts")" \ - || die "failed to list Vault mounts" - -mount_exists=false -if printf '%s' "$mounts_json" | jq -e --arg m "${KV_MOUNT}/" '.[$m]' >/dev/null 2>&1; then - mount_exists=true -fi - -if [ "$mount_exists" = true ]; then - mount_type="$(printf '%s' "$mounts_json" \ - | jq -r --arg m "${KV_MOUNT}/" '.[$m].type // ""')" - mount_version="$(printf '%s' "$mounts_json" \ - | jq -r --arg m "${KV_MOUNT}/" '.[$m].options.version // "1"')" - if [ "$mount_type" != "kv" ]; then - die "${KV_MOUNT}/ is mounted as type='${mount_type}', expected 'kv' — refuse to re-mount" - fi - if [ "$mount_version" != "2" ]; then - die "${KV_MOUNT}/ is KV v${mount_version}, expected v2 — refuse to upgrade in place (manual fix required)" - fi - log "${KV_MOUNT}/ already mounted (kv v2) — skipping enable" -else - if [ "$DRY_RUN" -eq 1 ]; then - log "[dry-run] would enable ${KV_MOUNT}/ as kv v2" - else - payload="$(jq -n '{type:"kv",options:{version:"2"},description:"disinto shared KV v2 (S2.4)"}')" - _hvault_request POST "sys/mounts/${KV_MOUNT}" "$payload" >/dev/null \ - || die "failed to enable ${KV_MOUNT}/ as kv v2" - log "${KV_MOUNT}/ enabled as kv v2" - fi -fi +export DRY_RUN +hvault_ensure_kv_v2 "$KV_MOUNT" "[vault-seed-forgejo]" \ + || die "KV mount check failed" # ── Step 2/2: seed missing keys at kv/data/disinto/shared/forgejo ──────────── log "── Step 2/2: seed ${KV_API_PATH} ──" diff --git a/tools/vault-seed-woodpecker.sh b/tools/vault-seed-woodpecker.sh index ddfe035..8437805 100755 --- a/tools/vault-seed-woodpecker.sh +++ b/tools/vault-seed-woodpecker.sh @@ -39,29 +39,23 @@ # ============================================================================= set -euo pipefail -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" - +SEED_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SEED_DIR}/.." && pwd)" # shellcheck source=../lib/hvault.sh source "${REPO_ROOT}/lib/hvault.sh" -# KV v2 mount + logical path. Kept as two vars so the full API path used -# for GET/POST (which MUST include `/data/`) is built in one place. KV_MOUNT="kv" KV_LOGICAL_PATH="disinto/shared/woodpecker" KV_API_PATH="${KV_MOUNT}/data/${KV_LOGICAL_PATH}" +AGENT_SECRET_BYTES=32 # 32 bytes → 64 hex chars -# 32 bytes → 64 hex chars. Matches the agent secret length used by -# woodpecker-server's own `woodpecker-server secret` generation. -AGENT_SECRET_BYTES=32 - -log() { printf '[vault-seed-woodpecker] %s\n' "$*"; } -die() { printf '[vault-seed-woodpecker] ERROR: %s\n' "$*" >&2; exit 1; } +LOG_TAG="[vault-seed-woodpecker]" +log() { printf '%s %s\n' "$LOG_TAG" "$*"; } +die() { printf '%s ERROR: %s\n' "$LOG_TAG" "$*" >&2; exit 1; } # ── Flag parsing ───────────────────────────────────────────────────────────── -# Single optional `--dry-run`. Uses a for-over-"$@" loop so the 5-line -# sliding-window dup detector sees a shape distinct from vault-seed-forgejo.sh -# (arity:value case) and vault-apply-roles.sh (if/elif). +# for-over-"$@" loop — shape distinct from vault-seed-forgejo.sh (arity:value +# case) and vault-apply-roles.sh (if/elif). DRY_RUN=0 for arg in "$@"; do case "$arg" in @@ -78,49 +72,19 @@ for arg in "$@"; do esac done -# ── Preconditions ──────────────────────────────────────────────────────────── -for bin in curl jq openssl; do - command -v "$bin" >/dev/null 2>&1 \ - || die "required binary not found: ${bin}" +# ── Preconditions — binary + Vault connectivity checks ─────────────────────── +required_bins=(curl jq openssl) +for bin in "${required_bins[@]}"; do + command -v "$bin" >/dev/null 2>&1 || die "required binary not found: ${bin}" done - -[ -n "${VAULT_ADDR:-}" ] \ - || die "VAULT_ADDR unset — e.g. export VAULT_ADDR=http://127.0.0.1:8200" -hvault_token_lookup >/dev/null \ - || die "Vault auth probe failed — check VAULT_ADDR + VAULT_TOKEN" +[ -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 ──" -mounts_json="$(hvault_get_or_empty "sys/mounts")" \ - || die "failed to list Vault mounts" - -mount_exists=false -if printf '%s' "$mounts_json" | jq -e --arg m "${KV_MOUNT}/" '.[$m]' >/dev/null 2>&1; then - mount_exists=true -fi - -if [ "$mount_exists" = true ]; then - mount_type="$(printf '%s' "$mounts_json" \ - | jq -r --arg m "${KV_MOUNT}/" '.[$m].type // ""')" - mount_version="$(printf '%s' "$mounts_json" \ - | jq -r --arg m "${KV_MOUNT}/" '.[$m].options.version // "1"')" - if [ "$mount_type" != "kv" ]; then - die "${KV_MOUNT}/ is mounted as type='${mount_type}', expected 'kv' — refuse to re-mount" - fi - if [ "$mount_version" != "2" ]; then - die "${KV_MOUNT}/ is KV v${mount_version}, expected v2 — refuse to upgrade in place (manual fix required)" - fi - log "${KV_MOUNT}/ already mounted (kv v2) — skipping enable" -else - if [ "$DRY_RUN" -eq 1 ]; then - log "[dry-run] would enable ${KV_MOUNT}/ as kv v2" - else - payload="$(jq -n '{type:"kv",options:{version:"2"},description:"disinto shared KV v2 (S2.4)"}')" - _hvault_request POST "sys/mounts/${KV_MOUNT}" "$payload" >/dev/null \ - || die "failed to enable ${KV_MOUNT}/ as kv v2" - log "${KV_MOUNT}/ enabled as kv v2" - fi -fi +export DRY_RUN +hvault_ensure_kv_v2 "$KV_MOUNT" "[vault-seed-woodpecker]" \ + || die "KV mount check failed" # ── Step 2/2: seed agent_secret at kv/data/disinto/shared/woodpecker ───────── log "── Step 2/2: seed ${KV_API_PATH} ──"