fix: [nomad-step-5] edge dispatcher task: Missing vault.read(kv/data/disinto/bots/vault) on fresh init (#1035)
Some checks failed
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/nomad-validate Pipeline was successful
ci/woodpecker/pr/ci Pipeline failed
ci/woodpecker/pr/nomad-validate Pipeline was successful
ci/woodpecker/pr/secret-scan Pipeline was successful
ci/woodpecker/pr/smoke-init Pipeline failed

This commit is contained in:
Agent 2026-04-19 09:35:27 +00:00
parent 7c543c9a16
commit cd778c4775
3 changed files with 153 additions and 2 deletions

View file

@ -802,6 +802,7 @@ _disinto_init_nomad() {
woodpecker-server|woodpecker-agent) seed_name="woodpecker" ;;
agents) seed_name="agents" ;;
chat) seed_name="chat" ;;
edge) seed_name="ops-repo" ;;
esac
local seed_script="${FACTORY_ROOT}/tools/vault-seed-${seed_name}.sh"
if [ -x "$seed_script" ]; then
@ -983,6 +984,7 @@ _disinto_init_nomad() {
woodpecker-server|woodpecker-agent) seed_name="woodpecker" ;;
agents) seed_name="agents" ;;
chat) seed_name="chat" ;;
edge) seed_name="ops-repo" ;;
esac
local seed_script="${FACTORY_ROOT}/tools/vault-seed-${seed_name}.sh"
if [ -x "$seed_script" ]; then

View file

@ -221,10 +221,10 @@ EOT
change_mode = "restart"
error_on_missing_key = false
data = <<EOT
{{- with secret "kv/data/disinto/bots/vault" -}}
{{- with secret "kv/data/disinto/shared/ops-repo" -}}
FORGE_TOKEN={{ .Data.data.token }}
{{- else -}}
# WARNING: kv/disinto/bots/vault is empty run tools/vault-seed-agents.sh
# WARNING: kv/disinto/shared/ops-repo is empty run tools/vault-seed-ops-repo.sh
FORGE_TOKEN=seed-me
{{- end }}
EOT

149
tools/vault-seed-ops-repo.sh Executable file
View file

@ -0,0 +1,149 @@
#!/usr/bin/env bash
# =============================================================================
# tools/vault-seed-ops-repo.sh — Idempotent seed for kv/disinto/shared/ops-repo
#
# Part of the Nomad+Vault migration (S5.1, issue #1035). Populates the KV v2
# path that nomad/jobs/edge.hcl dispatcher task reads from, so the edge
# proxy has FORGE_TOKEN for ops repo access.
#
# Seeds from kv/disinto/bots/vault (the vault bot credentials) — copies the
# token field to kv/disinto/shared/ops-repo. This is the "service" path that
# dispatcher uses, distinct from the "agent" path (bots/vault) used by
# agent tasks under the service-agents policy.
#
# Idempotency contract:
# - Key present with non-empty value → leave untouched, log "token unchanged".
# - Key missing or empty → copy from bots/vault, log "token copied".
# - If bots/vault is also empty → generate a random value, log "token generated".
#
# 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 (e.g. http://127.0.0.1:8200)
# - VAULT_TOKEN (env OR /etc/vault.d/root.token, resolved by lib/hvault.sh)
# - curl, jq, openssl
#
# Usage:
# tools/vault-seed-ops-repo.sh
# tools/vault-seed-ops-repo.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 v2 mount + logical paths
KV_MOUNT="kv"
OPS_REPO_PATH="disinto/shared/ops-repo"
VAULT_BOT_PATH="disinto/bots/vault"
OPS_REPO_API="${KV_MOUNT}/data/${OPS_REPO_PATH}"
VAULT_BOT_API="${KV_MOUNT}/data/${VAULT_BOT_PATH}"
log() { printf '[vault-seed-ops-repo] %s\n' "$*"; }
die() { printf '[vault-seed-ops-repo] ERROR: %s\n' "$*" >&2; exit 1; }
# ── Flag parsing ─────────────────────────────────────────────────────────────
DRY_RUN=0
case "$#:${1-}" in
0:)
;;
1:--dry-run)
DRY_RUN=1
;;
1:-h|1:--help)
printf 'Usage: %s [--dry-run]\n\n' "$(basename "$0")"
printf 'Seed kv/disinto/shared/ops-repo with FORGE_TOKEN.\n\n'
printf 'Copies token from kv/disinto/bots/vault if present;\n'
printf 'otherwise generates a random value. Idempotent:\n'
printf 'existing non-empty values are left untouched.\n\n'
printf ' --dry-run Print planned actions without writing.\n'
exit 0
;;
*)
die "invalid arguments: $* (try --help)"
;;
esac
# ── Preconditions ────────────────────────────────────────────────────────────
for bin in curl jq openssl; 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"
# ── 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" "[vault-seed-ops-repo]" \
|| die "KV mount check failed"
# ── Step 2/2: seed ops-repo from vault bot ───────────────────────────────────
log "── Step 2/2: seed ${OPS_REPO_API} ──"
# Read existing ops-repo value
existing_raw="$(hvault_get_or_empty "${OPS_REPO_API}")" \
|| die "failed to read ${OPS_REPO_API}"
existing_token=""
if [ -n "$existing_raw" ]; then
existing_token="$(printf '%s' "$existing_raw" | jq -r '.data.data.token // ""')"
fi
desired_token="$existing_token"
action=""
if [ -z "$existing_token" ]; then
# Token missing — try to copy from vault bot
bot_raw="$(hvault_get_or_empty "${VAULT_BOT_API}")" || true
if [ -n "$bot_raw" ]; then
bot_token="$(printf '%s' "$bot_raw" | jq -r '.data.data.token // ""')"
if [ -n "$bot_token" ]; then
desired_token="$bot_token"
action="copied"
fi
fi
# If still no token, generate one
if [ -z "$desired_token" ]; then
if [ "$DRY_RUN" -eq 1 ]; then
action="generated (dry-run)"
else
desired_token="$(openssl rand -hex 32)"
action="generated"
fi
fi
fi
if [ -z "$action" ]; then
log "all keys present at ${OPS_REPO_API} — no-op"
log "token unchanged"
exit 0
fi
if [ "$DRY_RUN" -eq 1 ]; then
log "[dry-run] ${OPS_REPO_PATH}: would ${action} token"
exit 0
fi
# Write the token
payload="$(jq -n --arg t "$desired_token" '{data: {token: $t}}')"
_hvault_request POST "${OPS_REPO_API}" "$payload" >/dev/null \
|| die "failed to write ${OPS_REPO_API}"
log "${OPS_REPO_PATH}: ${action} token"
log "done — ${OPS_REPO_API} seeded"