#!/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 REPO_DIR=/opt/turnstone DATA_DIR=/opt/turnstone/data PATTERNS_DIR=/opt/turnstone/patterns 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/contrib2 # bash /opt/turnstone/podman-standalone.sh # # TURNSTONE_SOURCE_HOST is auto-detected from `hostname` — override if needed. # ── 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. # # 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 /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}" \ --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."