167 lines
6.6 KiB
Bash
167 lines
6.6 KiB
Bash
|
|
#!/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}"
|