stack/scripts/bootstrap-stack.sh

166 lines
6.7 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 profile=${STACK_PROFILE:-unset}"
# Optional: re-apply named profile before bootstrap
if [[ -n "${STACK_PROFILE:-}" && -f "$STACK_DIR/profiles/${STACK_PROFILE}.env" ]]; then
log "Re-applying profile ${STACK_PROFILE}"
"$SCRIPT_DIR/apply-profile.sh" "$STACK_PROFILE"
set -a
# shellcheck disable=SC1091
source "$STACK_DIR/.env"
set +a
fi
# ---------------------------------------------------------------------------
# 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
log "Downloading models for profile (see DOWNLOAD_BONSAI27 / DOWNLOAD_QWEN)…"
MODEL_DIR="$BONSAI_MODELS_DIR" "$SCRIPT_DIR/download-models.sh"
else
log "DOWNLOAD_MODEL=false — skip model download"
fi
# ---------------------------------------------------------------------------
# Docker compose — always deploy the full stack (no optional profiles)
# ---------------------------------------------------------------------------
if [[ "$COMPOSE_PULL" == "true" || "$COMPOSE_PULL" == "1" ]]; then
log "docker compose pull"
docker compose pull || true
fi
log "Building bonsai (BONSAI_BACKEND=$BONSAI_BACKEND NGL=${BONSAI_NGL:-} model=${BONSAI_MAIN_FILE:-})"
docker compose build bonsai
log "docker compose up -d (all services)"
docker compose 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
# ---------------------------------------------------------------------------
# Cloudflare DDNS (optional — only if secrets present)
if [[ -f "$STACK_DIR/secrets/.env" ]] && grep -qE '^CLOUDFLAREAPIKEY=.+' "$STACK_DIR/secrets/.env" 2>/dev/null; then
log "Rendering Cloudflare DDNS env + starting container"
"$SCRIPT_DIR/render-cloudflare-ddns-env.sh" || log "cloudflare-ddns render failed"
docker compose up -d cloudflare-ddns || log "cloudflare-ddns start failed"
fi
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}"