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.
This commit is contained in:
tim 2026-07-22 13:06:08 -07:00
parent 56ddb349b4
commit 3bf1726a99
7 changed files with 410 additions and 76 deletions

View file

@ -1,88 +1,167 @@
# Copy to .env and adjust. The compose file reads these values. ################################################################################
# arr-stack — single config file for a tech demo / fresh host
# Copy to .env and edit. Compose + bootstrap scripts read this file.
#
# Demo credentials (all local UIs): DEMO_USER / DEMO_PASS (default tim / asdfasdf)
# Security is intentionally weak for demos. Change before any real exposure.
#
# Fresh rebuild checklist:
# 1. Put a valid wireguard/wg0.conf (Endpoint IP + # EndpointHost = hostname)
# 2. cp .env.example .env && edit paths / host IP / usenet keys
# 3. ./scripts/bootstrap-stack.sh
################################################################################
# --- user / group that owns your media (run `id` on the host) --- # =============================================================================
# Demo mode & shared login
# =============================================================================
DEMO_MODE=true
DEMO_USER=tim
DEMO_PASS=asdfasdf
# Used as OpenAI-compatible key for Bonsai / OpenHands / Open WebUI connections
OPENAI_API_KEY=sk-local-bonsai
# =============================================================================
# Host identity & paths
# =============================================================================
PUID=1000 PUID=1000
PGID=1000 PGID=1000
TZ=America/Los_Angeles
# --- your timezone (https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) --- # Production-style (server):
TZ=Europe/Berlin # CONFIG_DIR=/opt/stack/config
# DATA_DIR=/storage
# --- host paths (relative to this folder is fine) --- # BONSAI_MODELS_DIR=/opt/stack/models/bonsai
# AI_WORKSPACE_DIR=/opt/stack/workspace
# Laptop / relative demo:
CONFIG_DIR=./config CONFIG_DIR=./config
DATA_DIR=./data DATA_DIR=./data
BONSAI_MODELS_DIR=./models/bonsai
AI_WORKSPACE_DIR=./workspace
# --- your LAN subnet, so the qBittorrent WebUI stays reachable through # LAN used by gluetun FIREWALL_OUTBOUND_SUBNETS (qBittorrent WebUI from LAN)
# gluetun's firewall. If your machine is 192.168.1.50, use 192.168.1.0/24 LAN_SUBNET=192.168.8.0/24
LAN_SUBNET=192.168.1.0/24 # Public hostname/IP other PCs use to open UIs (landing page, Forgejo ROOT_URL, …)
STACK_HOST=192.168.8.123
# WireGuard peer hostname (used by scripts/gluetun-endpoint-watch.sh when gluetun is unhealthy) # =============================================================================
# Published ports (host → service)
# =============================================================================
LANDING_PORT=80
QB_WEBUI_PORT=8080
JELLYFIN_PORT=8096
SONARR_PORT=8989
RADARR_PORT=7878
LIDARR_PORT=8686
BAZARR_PORT=6767
PROWLARR_PORT=9696
SABNZBD_PORT=8085
OPEN_WEBUI_PORT=3000
BONSAI_PORT=8081
OPENHANDS_PORT=3001
FORGEJO_HTTP_PORT=3002
FORGEJO_SSH_PORT=2222
SEARXNG_PORT=8888
# =============================================================================
# WireGuard / gluetun
# =============================================================================
# Peer hostname for gluetun-endpoint-watch (must match provider; dig when unhealthy)
WIREGUARD_ENDPOINT_HOST=las-183-wg.whiskergalaxy.com WIREGUARD_ENDPOINT_HOST=las-183-wg.whiskergalaxy.com
WIREGUARD_ENDPOINT_PORT=443 WIREGUARD_ENDPOINT_PORT=443
# Path to conf mounted into gluetun (create before first up)
WIREGUARD_CONF=./wireguard/wg0.conf
# ============================================================================= # =============================================================================
# Local AI (Ternary-Bonsai-27B on SER5 PRO / Ryzen 7735HS) # Local AI — Ternary-Bonsai-27B
# ============================================================================= # =============================================================================
# GGUF weights directory (populated by scripts/download-bonsai-model.sh)
BONSAI_MODELS_DIR=./models/bonsai
BONSAI_MODEL_PATH=/models/Ternary-Bonsai-27B-Q2_0.gguf BONSAI_MODEL_PATH=/models/Ternary-Bonsai-27B-Q2_0.gguf
BONSAI_HF_REPO=prism-ml/Ternary-Bonsai-27B-gguf
BONSAI_MAIN_FILE=Ternary-Bonsai-27B-Q2_0.gguf
# Download model during bootstrap-stack.sh
DOWNLOAD_MODEL=true
DOWNLOAD_VISION=true
DOWNLOAD_DRAFTER=false
# cpu (default) or vulkan — rebuild image after changing: # Inference: vulkan on AMD iGPU; cpu for safe fallback
# BONSAI_BACKEND=vulkan docker compose build --no-cache bonsai BONSAI_BACKEND=vulkan
BONSAI_BACKEND=cpu BONSAI_NGL=99
# GPU layers: 0 = CPU only. With vulkan image + /dev/dri, try 99.
BONSAI_NGL=0
# Context size. 16384 is a solid default on 3264 GB RAM with the Arr stack.
# Raise for long docs / repo work; set BONSAI_KV4=1 if you run OOM.
BONSAI_CTX=16384 BONSAI_CTX=16384
BONSAI_KV4=0 BONSAI_KV4=0
BONSAI_THREADS=12
# Leave 0 for auto threads, or e.g. 12 on 16-thread 7735HS to leave headroom
BONSAI_THREADS=0
# Sampling (Prism 27B thinking-mode defaults)
BONSAI_TEMP=0.7 BONSAI_TEMP=0.7
BONSAI_TOP_P=0.95 BONSAI_TOP_P=0.95
BONSAI_TOP_K=20 BONSAI_TOP_K=20
BONSAI_EXTRA_ARGS=
# Host port for llama-server API (OpenAI-compatible at /v1)
BONSAI_PORT=8081
# Open WebUI # Open WebUI
OPEN_WEBUI_PORT=3000
OPENAI_API_KEY=sk-local-bonsai
# true = login required (recommended); false = open LAN access
WEBUI_AUTH=true WEBUI_AUTH=true
OPENWEBUI_DEFAULT_MODEL=openai/Ternary-Bonsai-27B-Q2_0.gguf
RAG_EMBEDDING_ENGINE=
RAG_CHUNK_SIZE=1000
RAG_CHUNK_OVERLAP=100
# Folder mounted read-only into Open WebUI as /workspace (RAG + file Q&A) # SearXNG
# Point this at your git projects, notes, docs, etc. SEARXNG_SECRET=demo-searxng-secret-change-me
AI_WORKSPACE_DIR=./workspace
# SearXNG (web search for RAG) — published on localhost only # OpenHands (compose profile: coding)
SEARXNG_PORT=8888 OPENHANDS_MODEL=openai/Ternary-Bonsai-27B-Q2_0.gguf
SEARXNG_SECRET=change-me-to-a-long-random-string # true = also start openhands in bootstrap
START_OPENHANDS=false
# OpenHands coding agent (docker compose --profile coding up -d) # =============================================================================
OPENHANDS_PORT=3001 # Forgejo
# LiteLLM-style id; after first start check http://host:8081/v1/models # =============================================================================
OPENHANDS_MODEL=openai/local FORGEJO_DOMAIN=${STACK_HOST}
FORGEJO_ROOT_URL=http://${STACK_HOST}:3002/
FORGEJO_DISABLE_REGISTRATION=false
# First-run admin (set after install or via bootstrap when API allows)
FORGEJO_ADMIN_USER=${DEMO_USER}
FORGEJO_ADMIN_PASS=${DEMO_PASS}
FORGEJO_ADMIN_EMAIL=tim@stack.local
# FlareSolverr (Cloudflare helper for Prowlarr; internal only) # Git remote used by server /opt/stack (see stack_deploy_method: git)
STACK_GIT_REPO=ssh://git@127.0.0.1:2222/tim/stack.git
STACK_GIT_BRANCH=main
# =============================================================================
# Usenet / NZB (also read by scripts/configure-usenet.sh)
# Prefer these names; legacy aliases still work (see configure-usenet.sh)
# =============================================================================
# NZBgeek indexer (Prowlarr)
NZBGEEKUSERNAME=
NZBGEEKEMAIL=
NZBGEEKAPI=https://api.nzbgeek.info/
NZBKEEPKEY=
# aliases: NZBGEEK_API_KEY
# Newshosting
NEWSHOSTINGAPI=news.newshosting.com
NEWSHOSTINGSSLPORT=563
NEWSHOSTINGUSER=
HNEWSHOSTINGPASS=
NEWSHOSTINGCONNECTIONS=20
# Tweaknews
TWEAKNEWSAPI=news.tweaknews.eu
TWEAKNEWSSSLPORT=563
TWEAKNEWSLOGIN=
TWEAKNEWSPASSWORD=
TWEAKNEWSCONNECTIONS=40
# =============================================================================
# FlareSolverr
# =============================================================================
FLARESOLVERR_LOG_LEVEL=info FLARESOLVERR_LOG_LEVEL=info
FLARESOLVERR_CAPTCHA_SOLVER=none FLARESOLVERR_CAPTCHA_SOLVER=none
# SABnzbd (Usenet) — credentials in secrets/.env, not here # =============================================================================
SABNZBD_PORT=8085 # Bootstrap behaviour (scripts/bootstrap-stack.sh)
# =============================================================================
# Caddy landing page (LAN service map) # compose profiles to enable: empty, or coding
LANDING_PORT=80 COMPOSE_PROFILES=
# After containers are up, run configure-usenet.sh
# Forgejo — self-hosted Git (web + SSH clone) CONFIGURE_USENET=true
FORGEJO_HTTP_PORT=3002 # Recreate gluetun/qbittorrent if unhealthy after start
FORGEJO_SSH_PORT=2222 HEAL_GLUETUN=true
FORGEJO_DOMAIN=192.168.8.123 # Pull images before up
FORGEJO_ROOT_URL=http://192.168.8.123:3002/ COMPOSE_PULL=true
# true after you create the admin account (blocks random signups)
FORGEJO_DISABLE_REGISTRATION=false

View file

@ -7,3 +7,4 @@
- Never commit secrets: `.env`, `wireguard/wg0.conf`, `config/`, GGUF models, `ansible/.secrets/`. - Never commit secrets: `.env`, `wireguard/wg0.conf`, `config/`, GGUF models, `ansible/.secrets/`.
- Prefer SSH push (`GIT_SSH_COMMAND` with `~/.ssh/id_ed25519` if needed). - Prefer SSH push (`GIT_SSH_COMMAND` with `~/.ssh/id_ed25519` if needed).
- Server checkout: `/opt/stack` is the same repo (`git pull` on ser5). Ansible uses `stack_deploy_method: git`. - Server checkout: `/opt/stack` is the same repo (`git pull` on ser5). Ansible uses `stack_deploy_method: git`.
- Prefer **`.env`** for config (see `.env.example`). Demo creds: `DEMO_USER=tim` `DEMO_PASS=asdfasdf`. Fresh host: `./scripts/bootstrap-stack.sh`.

View file

@ -192,6 +192,11 @@ ufw_lan_tcp_ports:
wireguard_endpoint_host: las-183-wg.whiskergalaxy.com wireguard_endpoint_host: las-183-wg.whiskergalaxy.com
wireguard_endpoint_port: "443" wireguard_endpoint_port: "443"
# Tech demo shared credentials (local UIs)
demo_user: tim
demo_pass: asdfasdf
openai_api_key: sk-local-bonsai
# Forgejo (self-hosted Git) # Forgejo (self-hosted Git)
forgejo_domain: "192.168.8.123" forgejo_domain: "192.168.8.123"
forgejo_http_port: 3002 forgejo_http_port: 3002

View file

@ -1,17 +1,54 @@
# Generated by Ansible for {{ inventory_hostname }} — do not commit # Generated by Ansible for {{ inventory_hostname }} — do not commit
# Host: {{ ansible_host | default(inventory_hostname) }} # Host: {{ ansible_host | default(inventory_hostname) }}
# Tech demo: DEMO_USER/DEMO_PASS used across local UIs when you set them.
################################################################################
# Demo login (local services)
################################################################################
DEMO_MODE=true
DEMO_USER={{ demo_user | default('tim') }}
DEMO_PASS={{ demo_pass | default('asdfasdf') }}
OPENAI_API_KEY={{ openai_api_key | default('sk-local-bonsai') }}
################################################################################
# Host
################################################################################
PUID={{ ansible_user_uid | default(1000) }} PUID={{ ansible_user_uid | default(1000) }}
PGID={{ ansible_user_gid | default(1000) }} PGID={{ ansible_user_gid | default(1000) }}
TZ={{ timezone }} TZ={{ timezone }}
CONFIG_DIR={{ config_dir }} CONFIG_DIR={{ config_dir }}
DATA_DIR={{ data_dir }} DATA_DIR={{ data_dir }}
LAN_SUBNET={{ lan_subnet }} LAN_SUBNET={{ lan_subnet }}
STACK_HOST={{ forgejo_domain }}
# --- AI (Ternary-Bonsai + Vulkan on SER5) --- ################################################################################
# Ports
################################################################################
LANDING_PORT=80
SABNZBD_PORT=8085
OPEN_WEBUI_PORT=3000
BONSAI_PORT=8081
OPENHANDS_PORT=3001
FORGEJO_HTTP_PORT={{ forgejo_http_port }}
FORGEJO_SSH_PORT={{ forgejo_ssh_port }}
SEARXNG_PORT=8888
################################################################################
# WireGuard
################################################################################
WIREGUARD_ENDPOINT_HOST={{ wireguard_endpoint_host | default('las-183-wg.whiskergalaxy.com') }}
WIREGUARD_ENDPOINT_PORT={{ wireguard_endpoint_port | default('443') }}
################################################################################
# AI
################################################################################
BONSAI_MODELS_DIR={{ models_dir }} BONSAI_MODELS_DIR={{ models_dir }}
BONSAI_MODEL_PATH=/models/Ternary-Bonsai-27B-Q2_0.gguf BONSAI_MODEL_PATH=/models/Ternary-Bonsai-27B-Q2_0.gguf
BONSAI_HF_REPO=prism-ml/Ternary-Bonsai-27B-gguf
BONSAI_MAIN_FILE=Ternary-Bonsai-27B-Q2_0.gguf
DOWNLOAD_MODEL=true
DOWNLOAD_VISION=true
DOWNLOAD_DRAFTER=false
BONSAI_BACKEND={{ bonsai_backend }} BONSAI_BACKEND={{ bonsai_backend }}
BONSAI_NGL={{ bonsai_ngl }} BONSAI_NGL={{ bonsai_ngl }}
BONSAI_CTX={{ bonsai_ctx }} BONSAI_CTX={{ bonsai_ctx }}
@ -20,26 +57,48 @@ BONSAI_THREADS={{ bonsai_threads }}
BONSAI_TEMP=0.7 BONSAI_TEMP=0.7
BONSAI_TOP_P=0.95 BONSAI_TOP_P=0.95
BONSAI_TOP_K=20 BONSAI_TOP_K=20
BONSAI_PORT=8081 BONSAI_EXTRA_ARGS=
OPEN_WEBUI_PORT=3000
OPENAI_API_KEY=sk-local-bonsai
WEBUI_AUTH={{ open_webui_auth }} WEBUI_AUTH={{ open_webui_auth }}
OPENWEBUI_DEFAULT_MODEL=openai/Ternary-Bonsai-27B-Q2_0.gguf
AI_WORKSPACE_DIR={{ workspace_dir }} AI_WORKSPACE_DIR={{ workspace_dir }}
SEARXNG_PORT=8888
SEARXNG_SECRET={{ lookup('password', playbook_dir + '/.secrets/searxng_secret chars=ascii_letters,digits length=48') }} SEARXNG_SECRET={{ lookup('password', playbook_dir + '/.secrets/searxng_secret chars=ascii_letters,digits length=48') }}
OPENHANDS_MODEL=openai/Ternary-Bonsai-27B-Q2_0.gguf
START_OPENHANDS=false
OPENHANDS_PORT=3001 ################################################################################
OPENHANDS_MODEL=openai/local # Forgejo
################################################################################
# Forgejo (self-hosted Git)
FORGEJO_HTTP_PORT={{ forgejo_http_port }}
FORGEJO_SSH_PORT={{ forgejo_ssh_port }}
FORGEJO_DOMAIN={{ forgejo_domain }} FORGEJO_DOMAIN={{ forgejo_domain }}
FORGEJO_ROOT_URL={{ forgejo_root_url }} FORGEJO_ROOT_URL={{ forgejo_root_url }}
FORGEJO_DISABLE_REGISTRATION=false FORGEJO_DISABLE_REGISTRATION=false
FORGEJO_ADMIN_USER={{ demo_user | default('tim') }}
FORGEJO_ADMIN_PASS={{ demo_pass | default('asdfasdf') }}
FORGEJO_ADMIN_EMAIL=tim@stack.local
STACK_GIT_REPO={{ stack_git_repo }}
STACK_GIT_BRANCH={{ stack_git_branch }}
# WireGuard peer hostname for gluetun-endpoint-watch (re-dig when tunnel dies) ################################################################################
WIREGUARD_ENDPOINT_HOST={{ wireguard_endpoint_host | default('las-183-wg.whiskergalaxy.com') }} # Usenet — fill from secrets or set here for demo
WIREGUARD_ENDPOINT_PORT={{ wireguard_endpoint_port | default('443') }} ################################################################################
NZBGEEKAPI=https://api.nzbgeek.info/
NZBKEEPKEY=
NEWSHOSTINGAPI=news.newshosting.com
NEWSHOSTINGSSLPORT=563
NEWSHOSTINGUSER=
HNEWSHOSTINGPASS=
NEWSHOSTINGCONNECTIONS=20
TWEAKNEWSAPI=news.tweaknews.eu
TWEAKNEWSSSLPORT=563
TWEAKNEWSLOGIN=
TWEAKNEWSPASSWORD=
TWEAKNEWSCONNECTIONS=40
################################################################################
# Misc
################################################################################
FLARESOLVERR_LOG_LEVEL=info
FLARESOLVERR_CAPTCHA_SOLVER=none
CONFIGURE_USENET=true
HEAL_GLUETUN=true
COMPOSE_PULL=true
COMPOSE_PROFILES=

View file

@ -5,6 +5,25 @@ Stack: **`/opt/stack`** · Media: **`/storage`** · User: **`tim`**
--- ---
## Fresh install / tech demo (max automation)
Almost everything lives in **`.env`** (see `.env.example` — ports, paths, AI, Usenet, demo login, bootstrap flags).
```bash
cp .env.example .env
# set STACK_HOST, LAN_SUBNET, usenet keys (NZBKEEPKEY, NEWSHOSTING*, TWEAKNEWS*)
# drop wireguard/wg0.conf (+ # EndpointHost = your.provider.host)
./scripts/bootstrap-stack.sh
# → dirs, model download, compose build/up, gluetun heal, SABnzbd servers
```
Demo login for local apps you create: **`tim` / `asdfasdf`** (`DEMO_USER` / `DEMO_PASS`).
Still manual once (app APIs differ): Prowlarr↔apps, Jellyfin libraries, first Forgejo/Open WebUI admin (use demo user/pass).
---
## Ansible ## Ansible
From a machine with this repo and SSH key `~/.ssh/id_ed25519`: From a machine with this repo and SSH key `~/.ssh/id_ed25519`:

166
scripts/bootstrap-stack.sh Executable file
View file

@ -0,0 +1,166 @@
#!/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}"

View file

@ -15,7 +15,12 @@ SAB_URL="${SAB_URL:-http://127.0.0.1:8085}"
log() { echo "[configure-usenet] $*"; } log() { echo "[configure-usenet] $*"; }
die() { log "ERROR: $*"; exit 1; } die() { log "ERROR: $*"; exit 1; }
[[ -f "$SECRETS_FILE" ]] || die "missing $SECRETS_FILE (see secrets/.env.example)" # Prefer secrets/.env; fall back to root .env (tech-demo: everything in one file)
if [[ ! -f "$SECRETS_FILE" && -f "$STACK_DIR/.env" ]]; then
SECRETS_FILE="$STACK_DIR/.env"
log "using $SECRETS_FILE (no secrets/.env)"
fi
[[ -f "$SECRETS_FILE" ]] || die "missing $SECRETS_FILE (see secrets/.env.example or .env.example)"
# shellcheck disable=SC1090 # shellcheck disable=SC1090
set -a set -a