Merge pull request 'fix: [nomad-step-5] S5.5 — wire --with edge,staging,chat + vault-runner + full deploy ordering (#992)' (#1002) from fix/issue-992-2 into main
This commit is contained in:
commit
8fc3ba5b59
4 changed files with 210 additions and 11 deletions
71
bin/disinto
71
bin/disinto
|
|
@ -82,7 +82,7 @@ Init options:
|
||||||
--ci-id <n> Woodpecker CI repo ID (default: 0 = no CI)
|
--ci-id <n> Woodpecker CI repo ID (default: 0 = no CI)
|
||||||
--forge-url <url> Forge base URL (default: http://localhost:3000)
|
--forge-url <url> Forge base URL (default: http://localhost:3000)
|
||||||
--backend <value> Orchestration backend: docker (default) | nomad
|
--backend <value> Orchestration backend: docker (default) | nomad
|
||||||
--with <services> (nomad) Deploy services: forgejo,woodpecker,agents[,...] (S1.3, S3.4, S4.2)
|
--with <services> (nomad) Deploy services: forgejo,woodpecker,agents,staging,chat,edge[,...] (S1.3, S3.4, S4.2, S5.2, S5.5)
|
||||||
--empty (nomad) Bring up cluster only, no jobs (S0.4)
|
--empty (nomad) Bring up cluster only, no jobs (S0.4)
|
||||||
--bare Skip compose generation (bare-metal setup)
|
--bare Skip compose generation (bare-metal setup)
|
||||||
--build Use local docker build instead of registry images (dev mode)
|
--build Use local docker build instead of registry images (dev mode)
|
||||||
|
|
@ -787,7 +787,7 @@ _disinto_init_nomad() {
|
||||||
# real-run path so dry-run output accurately represents execution order.
|
# real-run path so dry-run output accurately represents execution order.
|
||||||
# Build ordered deploy list: only include services present in with_services
|
# Build ordered deploy list: only include services present in with_services
|
||||||
local DEPLOY_ORDER=""
|
local DEPLOY_ORDER=""
|
||||||
for ordered_svc in forgejo woodpecker-server woodpecker-agent agents staging chat; do
|
for ordered_svc in forgejo woodpecker-server woodpecker-agent agents staging chat edge; do
|
||||||
if echo ",$with_services," | grep -q ",$ordered_svc,"; then
|
if echo ",$with_services," | grep -q ",$ordered_svc,"; then
|
||||||
DEPLOY_ORDER="${DEPLOY_ORDER:+${DEPLOY_ORDER} }${ordered_svc}"
|
DEPLOY_ORDER="${DEPLOY_ORDER:+${DEPLOY_ORDER} }${ordered_svc}"
|
||||||
fi
|
fi
|
||||||
|
|
@ -824,8 +824,19 @@ _disinto_init_nomad() {
|
||||||
echo "[deploy] dry-run complete"
|
echo "[deploy] dry-run complete"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Build custom images dry-run (if agents or chat services are included)
|
# Dry-run vault-runner (unconditionally, not gated by --with)
|
||||||
if echo ",$with_services," | grep -qE ",(agents|chat),"; then
|
echo ""
|
||||||
|
echo "── Vault-runner dry-run ───────────────────────────────────"
|
||||||
|
local vault_runner_path="${FACTORY_ROOT}/nomad/jobs/vault-runner.hcl"
|
||||||
|
if [ -f "$vault_runner_path" ]; then
|
||||||
|
echo "[deploy] vault-runner: [dry-run] nomad job validate ${vault_runner_path}"
|
||||||
|
echo "[deploy] vault-runner: [dry-run] nomad job run -detach ${vault_runner_path}"
|
||||||
|
else
|
||||||
|
echo "[deploy] vault-runner: jobspec not found, skipping"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build custom images dry-run (if agents, chat, or edge services are included)
|
||||||
|
if echo ",$with_services," | grep -qE ",(agents|chat|edge),"; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "── Build images dry-run ──────────────────────────────"
|
echo "── Build images dry-run ──────────────────────────────"
|
||||||
if echo ",$with_services," | grep -q ",agents,"; then
|
if echo ",$with_services," | grep -q ",agents,"; then
|
||||||
|
|
@ -834,6 +845,9 @@ _disinto_init_nomad() {
|
||||||
if echo ",$with_services," | grep -q ",chat,"; then
|
if echo ",$with_services," | grep -q ",chat,"; then
|
||||||
echo "[build] [dry-run] docker build -t disinto/chat:local -f ${FACTORY_ROOT}/docker/chat/Dockerfile ${FACTORY_ROOT}"
|
echo "[build] [dry-run] docker build -t disinto/chat:local -f ${FACTORY_ROOT}/docker/chat/Dockerfile ${FACTORY_ROOT}"
|
||||||
fi
|
fi
|
||||||
|
if echo ",$with_services," | grep -q ",edge,"; then
|
||||||
|
echo "[build] [dry-run] docker build -t disinto/edge:local -f ${FACTORY_ROOT}/docker/edge/Dockerfile ${FACTORY_ROOT}"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
@ -922,10 +936,10 @@ _disinto_init_nomad() {
|
||||||
echo "[import] no --import-env/--import-sops — skipping; set them or seed kv/disinto/* manually before deploying secret-dependent services"
|
echo "[import] no --import-env/--import-sops — skipping; set them or seed kv/disinto/* manually before deploying secret-dependent services"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Build custom images required by Nomad jobs (S4.2, S5.2) — before deploy.
|
# Build custom images required by Nomad jobs (S4.2, S5.2, S5.5) — before deploy.
|
||||||
# Single-node factory dev box: no multi-node pull needed, no registry auth.
|
# Single-node factory dev box: no multi-node pull needed, no registry auth.
|
||||||
# Can upgrade to approach B (registry push/pull) later if multi-node.
|
# Can upgrade to approach B (registry push/pull) later if multi-node.
|
||||||
if echo ",$with_services," | grep -qE ",(agents|chat),"; then
|
if echo ",$with_services," | grep -qE ",(agents|chat|edge),"; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "── Building custom images ─────────────────────────────"
|
echo "── Building custom images ─────────────────────────────"
|
||||||
if echo ",$with_services," | grep -q ",agents,"; then
|
if echo ",$with_services," | grep -q ",agents,"; then
|
||||||
|
|
@ -938,6 +952,11 @@ _disinto_init_nomad() {
|
||||||
echo "── Building $tag ─────────────────────────────"
|
echo "── Building $tag ─────────────────────────────"
|
||||||
docker build -t "$tag" -f "${FACTORY_ROOT}/docker/chat/Dockerfile" "${FACTORY_ROOT}" 2>&1 | tail -5
|
docker build -t "$tag" -f "${FACTORY_ROOT}/docker/chat/Dockerfile" "${FACTORY_ROOT}" 2>&1 | tail -5
|
||||||
fi
|
fi
|
||||||
|
if echo ",$with_services," | grep -q ",edge,"; then
|
||||||
|
local tag="disinto/edge:local"
|
||||||
|
echo "── Building $tag ─────────────────────────────"
|
||||||
|
docker build -t "$tag" -f "${FACTORY_ROOT}/docker/edge/Dockerfile" "${FACTORY_ROOT}" 2>&1 | tail -5
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Interleaved seed/deploy per service (S2.6, #928, #948).
|
# Interleaved seed/deploy per service (S2.6, #928, #948).
|
||||||
|
|
@ -948,9 +967,9 @@ _disinto_init_nomad() {
|
||||||
if [ -n "$with_services" ]; then
|
if [ -n "$with_services" ]; then
|
||||||
local vault_addr="${VAULT_ADDR:-http://127.0.0.1:8200}"
|
local vault_addr="${VAULT_ADDR:-http://127.0.0.1:8200}"
|
||||||
|
|
||||||
# Build ordered deploy list (S3.4, S4.2, S5.2): forgejo → woodpecker-server → woodpecker-agent → agents → staging → chat
|
# Build ordered deploy list (S3.4, S4.2, S5.2, S5.5): forgejo → woodpecker-server → woodpecker-agent → agents → staging → chat → edge
|
||||||
local DEPLOY_ORDER=""
|
local DEPLOY_ORDER=""
|
||||||
for ordered_svc in forgejo woodpecker-server woodpecker-agent agents staging chat; do
|
for ordered_svc in forgejo woodpecker-server woodpecker-agent agents staging chat edge; do
|
||||||
if echo ",$with_services," | grep -q ",$ordered_svc,"; then
|
if echo ",$with_services," | grep -q ",$ordered_svc,"; then
|
||||||
DEPLOY_ORDER="${DEPLOY_ORDER:+${DEPLOY_ORDER} }${ordered_svc}"
|
DEPLOY_ORDER="${DEPLOY_ORDER:+${DEPLOY_ORDER} }${ordered_svc}"
|
||||||
fi
|
fi
|
||||||
|
|
@ -1001,6 +1020,27 @@ _disinto_init_nomad() {
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Run vault-runner (unconditionally, not gated by --with) — infrastructure job
|
||||||
|
# vault-runner is always present since it's needed for vault action dispatch
|
||||||
|
echo ""
|
||||||
|
echo "── Running vault-runner ────────────────────────────────────"
|
||||||
|
local vault_runner_path="${FACTORY_ROOT}/nomad/jobs/vault-runner.hcl"
|
||||||
|
if [ -f "$vault_runner_path" ]; then
|
||||||
|
echo "[deploy] vault-runner: running Nomad job (infrastructure)"
|
||||||
|
local -a vault_runner_cmd=("$deploy_sh" "vault-runner")
|
||||||
|
if [ "$(id -u)" -eq 0 ]; then
|
||||||
|
"${vault_runner_cmd[@]}" || exit $?
|
||||||
|
else
|
||||||
|
if ! command -v sudo >/dev/null 2>&1; then
|
||||||
|
echo "Error: deploy.sh must run as root and sudo is not installed" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sudo -n -- "${vault_runner_cmd[@]}" || exit $?
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "[deploy] vault-runner: jobspec not found, skipping"
|
||||||
|
fi
|
||||||
|
|
||||||
# Print final summary
|
# Print final summary
|
||||||
echo ""
|
echo ""
|
||||||
echo "── Summary ────────────────────────────────────────────"
|
echo "── Summary ────────────────────────────────────────────"
|
||||||
|
|
@ -1157,14 +1197,25 @@ disinto_init() {
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Auto-include all dependencies when edge is requested (S5.5)
|
||||||
|
if echo ",$with_services," | grep -q ",edge,"; then
|
||||||
|
# Edge depends on all backend services
|
||||||
|
for dep in forgejo woodpecker-server woodpecker-agent agents staging chat; do
|
||||||
|
if ! echo ",$with_services," | grep -q ",${dep},"; then
|
||||||
|
echo "Note: --with edge implies --with ${dep} (edge depends on all backend services)"
|
||||||
|
with_services="${with_services},${dep}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
# Validate all service names are known
|
# Validate all service names are known
|
||||||
local IFS=','
|
local IFS=','
|
||||||
for _svc in $with_services; do
|
for _svc in $with_services; do
|
||||||
_svc=$(echo "$_svc" | xargs)
|
_svc=$(echo "$_svc" | xargs)
|
||||||
case "$_svc" in
|
case "$_svc" in
|
||||||
forgejo|woodpecker-server|woodpecker-agent|agents|staging|chat) ;;
|
forgejo|woodpecker-server|woodpecker-agent|agents|staging|chat|edge) ;;
|
||||||
*)
|
*)
|
||||||
echo "Error: unknown service '${_svc}' — known: forgejo, woodpecker-server, woodpecker-agent, agents, staging, chat" >&2
|
echo "Error: unknown service '${_svc}' — known: forgejo, woodpecker-server, woodpecker-agent, agents, staging, chat, edge" >&2
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
|
||||||
|
|
@ -405,3 +405,36 @@ hvault_token_lookup() {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# _hvault_seed_key — Seed a single KV key if it doesn't exist.
|
||||||
|
# Reads existing data and merges to preserve sibling keys (KV v2 replaces
|
||||||
|
# .data atomically). Returns 0=created, 1=unchanged, 2=API error.
|
||||||
|
# Args:
|
||||||
|
# path: KV v2 logical path (e.g. "disinto/shared/chat")
|
||||||
|
# key: key name within the path (e.g. "chat_oauth_client_id")
|
||||||
|
# generator: shell command that outputs a random value (default: openssl rand -hex 32)
|
||||||
|
# Usage:
|
||||||
|
# _hvault_seed_key "disinto/shared/chat" "chat_oauth_client_id"
|
||||||
|
# rc=$? # 0=created, 1=unchanged
|
||||||
|
_hvault_seed_key() {
|
||||||
|
local path="$1" key="$2" generator="${3:-openssl rand -hex 32}"
|
||||||
|
local existing
|
||||||
|
existing=$(hvault_kv_get "$path" "$key" 2>/dev/null) || true
|
||||||
|
if [ -n "$existing" ]; then
|
||||||
|
return 1 # unchanged
|
||||||
|
fi
|
||||||
|
|
||||||
|
local value
|
||||||
|
value=$(eval "$generator")
|
||||||
|
|
||||||
|
# Read existing data to preserve sibling keys (KV v2 replaces atomically)
|
||||||
|
local kv_api="${VAULT_KV_MOUNT}/data/${path}"
|
||||||
|
local raw existing_data payload
|
||||||
|
raw="$(hvault_get_or_empty "$kv_api")" || return 2
|
||||||
|
existing_data="{}"
|
||||||
|
[ -n "$raw" ] && existing_data="$(printf '%s' "$raw" | jq '.data.data // {}')"
|
||||||
|
payload="$(printf '%s' "$existing_data" \
|
||||||
|
| jq --arg k "$key" --arg v "$value" '{data: (. + {($k): $v})}')"
|
||||||
|
_hvault_request POST "$kv_api" "$payload" >/dev/null
|
||||||
|
return 0 # created
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -215,7 +215,7 @@ setup_file() {
|
||||||
run "$DISINTO_BIN" init placeholder/repo --backend=nomad --with unknown-service --dry-run
|
run "$DISINTO_BIN" init placeholder/repo --backend=nomad --with unknown-service --dry-run
|
||||||
[ "$status" -ne 0 ]
|
[ "$status" -ne 0 ]
|
||||||
[[ "$output" == *"unknown service"* ]]
|
[[ "$output" == *"unknown service"* ]]
|
||||||
[[ "$output" == *"known: forgejo, woodpecker-server, woodpecker-agent, agents, staging, chat"* ]]
|
[[ "$output" == *"known: forgejo, woodpecker-server, woodpecker-agent, agents, staging, chat, edge"* ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
# S3.4: woodpecker auto-expansion and forgejo auto-inclusion
|
# S3.4: woodpecker auto-expansion and forgejo auto-inclusion
|
||||||
|
|
|
||||||
115
tools/vault-seed-chat.sh
Executable file
115
tools/vault-seed-chat.sh
Executable file
|
|
@ -0,0 +1,115 @@
|
||||||
|
#!/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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue