#!/usr/bin/env bash # podman-standalone.sh — Turnstone rootful Podman setup (no Compose) # # For hosts running system Podman (non-rootless) with systemd. # Turnstone is a diagnostic log intelligence layer — glean service logs, # search by symptom, and view incidents in a lightweight web UI. # # ── Prerequisites ──────────────────────────────────────────────────────────── # 1. Clone the repo: # sudo git clone https://git.opensourcesolarpunk.com/Circuit-Forge/turnstone.git /opt/turnstone # sudo chown -R x:x /opt/turnstone # # 2. Build the image (requires Docker or Podman with BuildKit/multi-stage support): # cd /opt/turnstone && podman build -t localhost/turnstone:latest . # # 3. Create data and patterns directories, then copy config files: # mkdir -p /opt/turnstone/{data,patterns} # cp /opt/turnstone/patterns/default.yaml /opt/turnstone/patterns/ # cp /opt/turnstone/patterns/sources.yaml /opt/turnstone/patterns/ # # Edit sources.yaml if any paths differ on this host. # # 4. Run this script: # bash /opt/turnstone/podman-standalone.sh # # ── After setup — generate systemd unit file ───────────────────────────────── # sudo podman generate systemd --new --name turnstone \ # | sudo tee /etc/systemd/system/turnstone.service # sudo systemctl daemon-reload # sudo systemctl enable --now turnstone # # ── Gleaning logs ───────────────────────────────────────────────────────────── # All service logs under /opt are accessible inside the container. # Sources are configured in patterns/sources.yaml (bind-mounted at /patterns/). # # To glean all sources (run manually or via cron): # # sudo podman exec turnstone python scripts/glean_corpus.py \ # --sources /patterns/sources.yaml --db /data/turnstone.db # # Example cron (every 15 minutes, add to root's crontab with: sudo crontab -e): # */15 * * * * podman exec turnstone python scripts/glean_corpus.py \ # --sources /patterns/sources.yaml --db /data/turnstone.db >> /var/log/turnstone-glean.log 2>&1 # # To add a new log source: edit /opt/turnstone/patterns/sources.yaml — no restart needed. # # ── Adding Caddy reverse proxy ──────────────────────────────────────────────── # Add to /etc/caddy/Caddyfile: # # turnstone.example-node.tv { # import protected # reverse_proxy 10.0.0.10:8534 # import cloudflare # } # # Then: sudo systemctl reload caddy # # ── Ports ──────────────────────────────────────────────────────────────────── # Turnstone UI → http://localhost:8534/turnstone/ # set -euo pipefail # Auto-detect repo from script location — works whether cloned to /opt/turnstone # or to /Library/Development/CircuitForge/turnstone or any other path. REPO_DIR="${TURNSTONE_REPO_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" # Data and patterns live OUTSIDE the repo so they survive git pulls. DATA_DIR="${TURNSTONE_DATA_DIR:-/opt/turnstone-data}" PATTERNS_DIR="${TURNSTONE_PATTERNS_DIR:-${DATA_DIR}/patterns}" HF_CACHE_DIR="${TURNSTONE_HF_CACHE:-${DATA_DIR}/hf-cache}" TZ="${TZ:-America/Los_Angeles}" # ── Bundle push configuration ──────────────────────────────────────────────── # Set TURNSTONE_BUNDLE_ENDPOINT before running this script to enable the # "Send Bundle" button in the Incidents UI: # # export TURNSTONE_BUNDLE_ENDPOINT=https://turnstone.circuitforge.tech/turnstone/api/bundles # bash /opt/turnstone/podman-standalone.sh # # ── Orchard submission (opt-in telemetry) ──────────────────────────────────── # Set TURNSTONE_SUBMIT_ENDPOINT to push pattern-matched log entries to a CF # receiving instance after each glean run. Only matched entries are sent — # no raw log content. Used to build Avocet training data. # # export TURNSTONE_SUBMIT_ENDPOINT=https://harvest.circuitforge.tech/xander # bash /opt/turnstone/podman-standalone.sh # # TURNSTONE_SOURCE_HOST is auto-detected from `hostname` — override if needed. # # ── Multi-agent diagnose pipeline ──────────────────────────────────────────── # The 5-stage ML pipeline requires three env vars and a writable HF cache dir: # # TURNSTONE_MULTI_AGENT_DIAGNOSE=true — enable the pipeline # GPU_SERVER_URL=http://:7700 — cf-orch coordinator or Ollama base URL # # ML models are downloaded on first diagnose run and cached in HF_CACHE_DIR. # On a CPU-only host (no GPU) set TURNSTONE_EMBED_DEVICE=cpu (default). # # For Xander's instance (example-node.tv) — no WireGuard to Heimdall LAN, # use the public cf-orch endpoint instead: # export GPU_SERVER_URL=https://orch.circuitforge.tech # export TURNSTONE_MULTI_AGENT_DIAGNOSE=true # sudo bash /opt/turnstone/podman-standalone.sh # # For Daniel's instance (Huginn) — WireGuard reaches Heimdall LAN directly, # use docker-standalone.sh (not this script — Docker host): # export GPU_SERVER_URL=http://:7700 # export TURNSTONE_MULTI_AGENT_DIAGNOSE=true # bash ~/turnstone/docker-standalone.sh # ── Turnstone container ─────────────────────────────────────────────────────── # Image is built locally — no registry auto-update label. # Run this script after every `git pull` to rebuild and redeploy. # # /opt is mounted read-only so all service logs under /opt/*/config/logs/ are # accessible without per-service mounts. Add new sources to patterns/sources.yaml # — no container restart needed. # # Must be run as root (sudo bash podman-standalone.sh) — rootful Podman only. # # Bootstrap data and patterns dirs if this is a first run mkdir -p "${DATA_DIR}" "${PATTERNS_DIR}" "${HF_CACHE_DIR}" # Copy default patterns if the dir is empty (first run only) if [ -z "$(ls -A "${PATTERNS_DIR}")" ]; then cp "${REPO_DIR}/patterns/default.yaml" "${PATTERNS_DIR}/" # Copy host-specific sources if present, otherwise copy the generic template HOST_SOURCES="${REPO_DIR}/patterns/sources-$(hostname).yaml" if [ -f "${HOST_SOURCES}" ]; then cp "${HOST_SOURCES}" "${PATTERNS_DIR}/sources.yaml" echo "==> Installed host-specific sources: ${HOST_SOURCES}" else cp "${REPO_DIR}/patterns/sources.yaml" "${PATTERNS_DIR}/" echo "==> Installed default sources.yaml — edit ${PATTERNS_DIR}/sources.yaml for this host" fi fi # Build image from current source (bakes app/ code into the image) echo "Building Turnstone image..." podman build -t localhost/turnstone:latest "${REPO_DIR}" # Remove existing container if present (safe re-run) podman rm -f turnstone 2>/dev/null || true podman run -d \ --name=turnstone \ --restart=unless-stopped \ --net=host \ -v "${DATA_DIR}:/data:Z" \ -v "${PATTERNS_DIR}:/patterns:Z" \ -v "${HF_CACHE_DIR}:/hf-cache:Z" \ -v /opt:/opt:ro \ -v /var/log:/var/log:ro \ -e TURNSTONE_DB=/data/turnstone.db \ -e TURNSTONE_SOURCE_HOST="$(hostname)" \ -e TURNSTONE_BUNDLE_ENDPOINT="${TURNSTONE_BUNDLE_ENDPOINT:-}" \ -e TURNSTONE_SUBMIT_ENDPOINT="${TURNSTONE_SUBMIT_ENDPOINT:-}" \ -e PYTHONUNBUFFERED=1 \ -e TZ="${TZ}" \ -e TURNSTONE_MULTI_AGENT_DIAGNOSE="${TURNSTONE_MULTI_AGENT_DIAGNOSE:-false}" \ -e GPU_SERVER_URL="${GPU_SERVER_URL:-}" \ -e HF_HOME=/hf-cache \ -e TURNSTONE_AUTO_INCIDENT="${TURNSTONE_AUTO_INCIDENT:-true}" \ -e TURNSTONE_AUTO_INCIDENT_THRESHOLD="${TURNSTONE_AUTO_INCIDENT_THRESHOLD:-5}" \ -e TURNSTONE_AUTO_INCIDENT_WINDOW="${TURNSTONE_AUTO_INCIDENT_WINDOW:-600}" \ -e TURNSTONE_CLASSIFIER_MODEL="${TURNSTONE_CLASSIFIER_MODEL:-byviz/bylastic_classification_logs}" \ -e TURNSTONE_EMBED_BACKEND="${TURNSTONE_EMBED_BACKEND:-sentence_transformers}" \ -e TURNSTONE_EMBED_MODEL="${TURNSTONE_EMBED_MODEL:-sentence-transformers/all-MiniLM-L6-v2}" \ -e TURNSTONE_EMBED_DEVICE="${TURNSTONE_EMBED_DEVICE:-cpu}" \ --health-cmd="curl -f http://localhost:8534/turnstone/health || exit 1" \ --health-interval=30s \ --health-timeout=10s \ --health-start-period=20s \ --health-retries=3 \ localhost/turnstone:latest echo "" echo "Turnstone is starting up." echo " UI: http://localhost:8534/turnstone/" echo "" # Regenerate systemd unit so it references the freshly-built image. # The --new flag means systemd re-creates the container on each start # rather than binding to a specific container ID. if [ -d /etc/systemd/system ]; then echo "Regenerating systemd unit..." podman generate systemd --new --name turnstone \ | tee /etc/systemd/system/turnstone.service > /dev/null systemctl daemon-reload systemctl enable turnstone.service 2>/dev/null || true echo " systemd unit updated — run: sudo systemctl restart turnstone.service" echo "" fi echo "Check container health with:" echo " sudo podman ps" echo " sudo podman logs turnstone" echo "" echo "To glean all sources now:" echo " sudo podman exec turnstone python scripts/glean_corpus.py \\" echo " --sources /patterns/sources.yaml --db /data/turnstone.db" echo "" echo "To add a new source: edit /opt/turnstone/patterns/sources.yaml — no restart needed."