stack/scripts/bootstrap-stack.sh
tim 3bf1726a99 Centralize demo config in .env and add bootstrap-stack.sh
Expand .env.example with ports, AI, Usenet, WireGuard, demo logins, and
bootstrap flags. One script creates dirs, downloads the model, starts
compose, heals gluetun, and configures SABnzbd from the same file.
2026-07-22 13:06:08 -07:00

166 lines
6.6 KiB
Bash
Executable file

#!/usr/bin/env bash
# Fresh / demo stack bring-up driven almost entirely by .env
#
# cp .env.example .env # fill usenet keys + STACK_HOST
# # drop wireguard/wg0.conf
# ./scripts/bootstrap-stack.sh
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
STACK_DIR="${STACK_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}"
cd "$STACK_DIR"
log() { echo "[bootstrap] $*"; }
die() { log "ERROR: $*"; exit 1; }
[[ -f "$STACK_DIR/.env" ]] || die "missing .env — cp .env.example .env and edit"
set -a
# shellcheck disable=SC1091
source "$STACK_DIR/.env"
set +a
# Expand nested ${STACK_HOST} style leftovers if someone left them literal
STACK_HOST="${STACK_HOST:-192.168.8.123}"
DEMO_USER="${DEMO_USER:-tim}"
DEMO_PASS="${DEMO_PASS:-asdfasdf}"
CONFIG_DIR="${CONFIG_DIR:-$STACK_DIR/config}"
DATA_DIR="${DATA_DIR:-$STACK_DIR/data}"
BONSAI_MODELS_DIR="${BONSAI_MODELS_DIR:-$STACK_DIR/models/bonsai}"
AI_WORKSPACE_DIR="${AI_WORKSPACE_DIR:-$STACK_DIR/workspace}"
DOWNLOAD_MODEL="${DOWNLOAD_MODEL:-true}"
CONFIGURE_USENET="${CONFIGURE_USENET:-true}"
HEAL_GLUETUN="${HEAL_GLUETUN:-true}"
COMPOSE_PULL="${COMPOSE_PULL:-true}"
START_OPENHANDS="${START_OPENHANDS:-false}"
BONSAI_BACKEND="${BONSAI_BACKEND:-vulkan}"
export STACK_DIR CONFIG_DIR DATA_DIR BONSAI_MODELS_DIR AI_WORKSPACE_DIR
log "STACK_DIR=$STACK_DIR host=$STACK_HOST demo_user=$DEMO_USER"
# ---------------------------------------------------------------------------
# Directories
# ---------------------------------------------------------------------------
log "Creating data / config trees"
mkdir -p \
"$CONFIG_DIR" \
"$BONSAI_MODELS_DIR" \
"$AI_WORKSPACE_DIR" \
"$DATA_DIR"/media/{movies,tv,music} \
"$DATA_DIR"/torrents/{movies,tv,music} \
"$DATA_DIR"/usenet/incomplete \
"$DATA_DIR"/usenet/complete/{movies,tv,music} \
"$STACK_DIR/wireguard" \
"$STACK_DIR/secrets"
# Prefer main .env for usenet if secrets/.env missing
if [[ ! -f "$STACK_DIR/secrets/.env" && -f "$STACK_DIR/.env" ]]; then
log "Using root .env as secrets source for usenet (DEMO_MODE)"
# configure-usenet already can source SECRETS_FILE; symlink for convenience
ln -sfn ../.env "$STACK_DIR/secrets/.env" 2>/dev/null || \
cp "$STACK_DIR/.env" "$STACK_DIR/secrets/.env"
fi
if [[ ! -f "$STACK_DIR/wireguard/wg0.conf" ]]; then
log "WARNING: wireguard/wg0.conf missing — gluetun/qbittorrent will stay unhealthy until you add it"
else
# Ensure EndpointHost comment for the watcher
if [[ -n "${WIREGUARD_ENDPOINT_HOST:-}" ]] && ! grep -q EndpointHost "$STACK_DIR/wireguard/wg0.conf"; then
sed -i "1a # EndpointHost = ${WIREGUARD_ENDPOINT_HOST}" "$STACK_DIR/wireguard/wg0.conf" || true
fi
fi
# ---------------------------------------------------------------------------
# Model download
# ---------------------------------------------------------------------------
if [[ "${DOWNLOAD_MODEL}" == "true" || "${DOWNLOAD_MODEL}" == "1" ]]; then
if [[ ! -f "$BONSAI_MODELS_DIR/${BONSAI_MAIN_FILE:-Ternary-Bonsai-27B-Q2_0.gguf}" ]]; then
log "Downloading Ternary-Bonsai GGUF (~7GB)…"
MODEL_DIR="$BONSAI_MODELS_DIR" \
BONSAI_HF_REPO="${BONSAI_HF_REPO:-prism-ml/Ternary-Bonsai-27B-gguf}" \
BONSAI_MAIN_FILE="${BONSAI_MAIN_FILE:-Ternary-Bonsai-27B-Q2_0.gguf}" \
DOWNLOAD_VISION="${DOWNLOAD_VISION:-1}" \
DOWNLOAD_DRAFTER="${DOWNLOAD_DRAFTER:-0}" \
"$SCRIPT_DIR/download-bonsai-model.sh"
else
log "Model already present — skip download"
fi
else
log "DOWNLOAD_MODEL=false — skip model download"
fi
# ---------------------------------------------------------------------------
# Docker compose
# ---------------------------------------------------------------------------
PROFILES=()
if [[ -n "${COMPOSE_PROFILES:-}" ]]; then
# shellcheck disable=SC2206
PROFILES=(--profile ${COMPOSE_PROFILES//,/ --profile })
fi
if [[ "$START_OPENHANDS" == "true" || "$START_OPENHANDS" == "1" ]]; then
PROFILES+=(--profile coding)
fi
if [[ "$COMPOSE_PULL" == "true" || "$COMPOSE_PULL" == "1" ]]; then
log "docker compose pull"
docker compose "${PROFILES[@]}" pull || true
fi
log "Building bonsai (BONSAI_BACKEND=$BONSAI_BACKEND)"
docker compose build bonsai
log "docker compose up -d"
docker compose "${PROFILES[@]}" up -d
# ---------------------------------------------------------------------------
# Heal gluetun if needed
# ---------------------------------------------------------------------------
if [[ "$HEAL_GLUETUN" == "true" || "$HEAL_GLUETUN" == "1" ]]; then
if [[ -x "$SCRIPT_DIR/gluetun-endpoint-watch.sh" ]]; then
log "Running gluetun endpoint watch (no-op if healthy)"
STACK_DIR="$STACK_DIR" "$SCRIPT_DIR/gluetun-endpoint-watch.sh" || log "gluetun watch returned non-zero (VPN may still need wg0.conf)"
fi
fi
# ---------------------------------------------------------------------------
# Usenet bootstrap
# ---------------------------------------------------------------------------
if [[ "$CONFIGURE_USENET" == "true" || "$CONFIGURE_USENET" == "1" ]]; then
log "Waiting for SABnzbd…"
for i in $(seq 1 60); do
if curl -fsS "http://127.0.0.1:${SABNZBD_PORT:-8085}/api?mode=version&output=json" >/dev/null 2>&1; then
break
fi
sleep 2
done
if [[ -x "$SCRIPT_DIR/configure-usenet.sh" ]]; then
SECRETS_FILE="${SECRETS_FILE:-$STACK_DIR/secrets/.env}" \
SAB_URL="http://127.0.0.1:${SABNZBD_PORT:-8085}" \
"$SCRIPT_DIR/configure-usenet.sh" || log "configure-usenet failed — check SABnzbd wizard / secrets"
fi
fi
# ---------------------------------------------------------------------------
# Summary
# ---------------------------------------------------------------------------
log ""
log "=== Stack is up (or starting) ==="
docker compose ps --format 'table {{.Name}}\t{{.Status}}\t{{.Ports}}' 2>/dev/null || docker compose ps
log ""
log "Landing: http://${STACK_HOST}/"
log "Jellyfin: http://${STACK_HOST}:8096"
log "Open WebUI: http://${STACK_HOST}:3000 (first user = admin; use ${DEMO_USER} / ${DEMO_PASS})"
log "Forgejo: http://${STACK_HOST}:3002 (install wizard → admin ${DEMO_USER} / ${DEMO_PASS})"
log "SABnzbd: http://${STACK_HOST}:${SABNZBD_PORT:-8085}"
log "Prowlarr: http://${STACK_HOST}:9696"
log "Bonsai: http://${STACK_HOST}:${BONSAI_PORT:-8081}/v1/models"
log ""
log "Still one-time UI wiring (APIs vary by version):"
log " • Prowlarr → NZBgeek + SABnzbd download client → sync to apps"
log " • Sonarr/Radarr/Lidarr → root folders /data/media/* + SABnzbd categories"
log " • qBittorrent → categories + password ${DEMO_PASS} after first login"
log " • Jellyfin → libraries under /data/media/*"
log ""
log "Demo login for apps you configure: user=${DEMO_USER} pass=${DEMO_PASS}"