transition precondition enforcement + thin-client context poller
Plan #3 at 100%. Last three items resolved: 1. Transition precondition enforcement (Phase 5): - no-inbound-edges: blocks destroy when relationships exist - backups-verified, secrets-revoked, ingress-dns-removed: checks attrs - age-key-enrolled-if-needed, mesh-joined-if-needed: workstation checks - health-check-answering: verifies entity_status health - doc-page-complete: requires at least one linked document - Soft preconditions (inventory-entry, cancelled-note, etc.): operator confirmed via transition request itself - Parses {requires: [check-name]} from lifecycle_defs.transitions JSONB 2. bootstrap.sh: already thin-client (fetches only agent files, no git clone, calls POST /clients/enroll, embeds context poller) 3. tools/context-poller.sh: standalone version — polls GET /clients/{slug}/context, applies file/tool/sops deltas, re-runs changed setup scripts
This commit is contained in:
73
tools/context-poller.sh
Normal file
73
tools/context-poller.sh
Normal file
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env bash
|
||||
# context-poller.sh — lightweight deltas replacing git pull.
|
||||
# Polls GET /api/v1/clients/{slug}/context?since=<timestamp> every 5 min.
|
||||
# Installed by bootstrap.sh via launchd (macOS) or systemd timer (Linux).
|
||||
#
|
||||
# Dependencies: curl, jq
|
||||
set -euo pipefail
|
||||
|
||||
OIKOS_URL="${OIKOS_API_URL:-https://oikos.hubris.network/api/v1}"
|
||||
HNAME=$(scutil --get LocalHostName 2>/dev/null || hostname -s)
|
||||
CONTEXT_DIR="${HOMELAB_CONTEXT_DIR:-/opt/homelab}"
|
||||
RAW_URL="${HOMELAB_RAW_URL:-https://git.hubris.network/dtoro/Homelab-Docs/raw/main}"
|
||||
STATE_FILE="$CONTEXT_DIR/.context_since"
|
||||
|
||||
SINCE=""
|
||||
[ -f "$STATE_FILE" ] && SINCE=$(cat "$STATE_FILE")
|
||||
|
||||
API_RESP=$(curl -s --connect-timeout 10 \
|
||||
"$OIKOS_URL/clients/ws:${HNAME}/context?since=${SINCE}" 2>/dev/null || true)
|
||||
|
||||
if [ -z "$API_RESP" ] || ! echo "$API_RESP" | jq -e '.version' >/dev/null 2>&1; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
NEW_SINCE=$(echo "$API_RESP" | jq -r '.since // empty')
|
||||
VERSION=$(echo "$API_RESP" | jq -r '.version // 0')
|
||||
|
||||
CHANGED_FILES=$(echo "$API_RESP" | jq -r '.agent_files_changed // [] | .[]' 2>/dev/null || true)
|
||||
SOPS_CHANGED=$(echo "$API_RESP" | jq -r '.sops_config_changed // false' 2>/dev/null || true)
|
||||
TOOLS_CHANGED=$(echo "$API_RESP" | jq -r '.tools_changed // [] | .[]' 2>/dev/null || true)
|
||||
|
||||
APPLIED=0
|
||||
|
||||
# Fetch changed agent files
|
||||
for f in $CHANGED_FILES; do
|
||||
url="$RAW_URL/$f"
|
||||
dest="$CONTEXT_DIR/$f"
|
||||
mkdir -p "$(dirname "$dest")"
|
||||
if curl -fsSL --connect-timeout 10 "$url" -o "$dest.tmp" 2>/dev/null; then
|
||||
mv "$dest.tmp" "$dest"
|
||||
APPLIED=$((APPLIED + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
# Fetch changed tools and re-run them
|
||||
for t in $TOOLS_CHANGED; do
|
||||
url="$RAW_URL/$t"
|
||||
dest="$CONTEXT_DIR/$t"
|
||||
mkdir -p "$(dirname "$dest")"
|
||||
if curl -fsSL --connect-timeout 10 "$url" -o "$dest.tmp" 2>/dev/null; then
|
||||
mv "$dest.tmp" "$dest"
|
||||
chmod +x "$dest" 2>/dev/null || true
|
||||
if [ "$t" != "tools/post-pull.sh" ] && [ "$t" != "tools/context-poller.sh" ]; then
|
||||
bash "$dest" 2>/dev/null || true
|
||||
fi
|
||||
APPLIED=$((APPLIED + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
# Fetch .sops.yaml if changed
|
||||
if [ "$SOPS_CHANGED" = "true" ]; then
|
||||
url="$RAW_URL/.sops.yaml"
|
||||
dest="$CONTEXT_DIR/.sops.yaml"
|
||||
if curl -fsSL --connect-timeout 10 "$url" -o "$dest.tmp" 2>/dev/null; then
|
||||
mv "$dest.tmp" "$dest"
|
||||
APPLIED=$((APPLIED + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$APPLIED" -gt 0 ] && [ -n "$NEW_SINCE" ]; then
|
||||
echo "$NEW_SINCE" > "$STATE_FILE"
|
||||
echo "[oikos] context updated: version=$VERSION, applied=$APPLIED files ($(date -u +%Y-%m-%dT%H:%M:%SZ))"
|
||||
fi
|
||||
Reference in New Issue
Block a user