stack/scripts/bootstrap-stack.sh

167 lines
6.6 KiB
Bash
Raw Normal View History

#!/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}"