From 3bf1726a99eb3f13560e102a86ccc915b9eeb8c6 Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 22 Jul 2026 13:06:08 -0700 Subject: [PATCH] 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. --- .env.example | 195 +++++++++++++++++++-------- AGENTS.md | 1 + ansible/group_vars/all.yml | 5 + ansible/roles/stack/templates/env.j2 | 93 ++++++++++--- quickstart.md | 19 +++ scripts/bootstrap-stack.sh | 166 +++++++++++++++++++++++ scripts/configure-usenet.sh | 7 +- 7 files changed, 410 insertions(+), 76 deletions(-) create mode 100755 scripts/bootstrap-stack.sh diff --git a/.env.example b/.env.example index 52eb9b6..54bfd19 100644 --- a/.env.example +++ b/.env.example @@ -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 PGID=1000 +TZ=America/Los_Angeles -# --- your timezone (https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) --- -TZ=Europe/Berlin - -# --- host paths (relative to this folder is fine) --- +# Production-style (server): +# CONFIG_DIR=/opt/stack/config +# DATA_DIR=/storage +# BONSAI_MODELS_DIR=/opt/stack/models/bonsai +# AI_WORKSPACE_DIR=/opt/stack/workspace +# Laptop / relative demo: CONFIG_DIR=./config DATA_DIR=./data +BONSAI_MODELS_DIR=./models/bonsai +AI_WORKSPACE_DIR=./workspace -# --- your LAN subnet, so the qBittorrent WebUI stays reachable through -# gluetun's firewall. If your machine is 192.168.1.50, use 192.168.1.0/24 -LAN_SUBNET=192.168.1.0/24 +# LAN used by gluetun FIREWALL_OUTBOUND_SUBNETS (qBittorrent WebUI from LAN) +LAN_SUBNET=192.168.8.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_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_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: -# BONSAI_BACKEND=vulkan docker compose build --no-cache bonsai -BONSAI_BACKEND=cpu - -# GPU layers: 0 = CPU only. With vulkan image + /dev/dri, try 99. -BONSAI_NGL=0 - -# Context size. 16384 is a solid default on 32–64 GB RAM with the Arr stack. -# Raise for long docs / repo work; set BONSAI_KV4=1 if you run OOM. +# Inference: vulkan on AMD iGPU; cpu for safe fallback +BONSAI_BACKEND=vulkan +BONSAI_NGL=99 BONSAI_CTX=16384 BONSAI_KV4=0 - -# 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_THREADS=12 BONSAI_TEMP=0.7 BONSAI_TOP_P=0.95 BONSAI_TOP_K=20 - -# Host port for llama-server API (OpenAI-compatible at /v1) -BONSAI_PORT=8081 +BONSAI_EXTRA_ARGS= # Open WebUI -OPEN_WEBUI_PORT=3000 -OPENAI_API_KEY=sk-local-bonsai -# true = login required (recommended); false = open LAN access 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) -# Point this at your git projects, notes, docs, etc. -AI_WORKSPACE_DIR=./workspace +# SearXNG +SEARXNG_SECRET=demo-searxng-secret-change-me -# SearXNG (web search for RAG) — published on localhost only -SEARXNG_PORT=8888 -SEARXNG_SECRET=change-me-to-a-long-random-string +# OpenHands (compose profile: coding) +OPENHANDS_MODEL=openai/Ternary-Bonsai-27B-Q2_0.gguf +# true = also start openhands in bootstrap +START_OPENHANDS=false -# OpenHands coding agent (docker compose --profile coding up -d) -OPENHANDS_PORT=3001 -# LiteLLM-style id; after first start check http://host:8081/v1/models -OPENHANDS_MODEL=openai/local +# ============================================================================= +# Forgejo +# ============================================================================= +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_CAPTCHA_SOLVER=none -# SABnzbd (Usenet) — credentials in secrets/.env, not here -SABNZBD_PORT=8085 - -# Caddy landing page (LAN service map) -LANDING_PORT=80 - -# Forgejo — self-hosted Git (web + SSH clone) -FORGEJO_HTTP_PORT=3002 -FORGEJO_SSH_PORT=2222 -FORGEJO_DOMAIN=192.168.8.123 -FORGEJO_ROOT_URL=http://192.168.8.123:3002/ -# true after you create the admin account (blocks random signups) -FORGEJO_DISABLE_REGISTRATION=false +# ============================================================================= +# Bootstrap behaviour (scripts/bootstrap-stack.sh) +# ============================================================================= +# compose profiles to enable: empty, or coding +COMPOSE_PROFILES= +# After containers are up, run configure-usenet.sh +CONFIGURE_USENET=true +# Recreate gluetun/qbittorrent if unhealthy after start +HEAL_GLUETUN=true +# Pull images before up +COMPOSE_PULL=true diff --git a/AGENTS.md b/AGENTS.md index 82d8c2a..8fe9930 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,3 +7,4 @@ - Never commit secrets: `.env`, `wireguard/wg0.conf`, `config/`, GGUF models, `ansible/.secrets/`. - 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`. +- Prefer **`.env`** for config (see `.env.example`). Demo creds: `DEMO_USER=tim` `DEMO_PASS=asdfasdf`. Fresh host: `./scripts/bootstrap-stack.sh`. diff --git a/ansible/group_vars/all.yml b/ansible/group_vars/all.yml index 1bbd05e..10b6812 100644 --- a/ansible/group_vars/all.yml +++ b/ansible/group_vars/all.yml @@ -192,6 +192,11 @@ ufw_lan_tcp_ports: wireguard_endpoint_host: las-183-wg.whiskergalaxy.com 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_domain: "192.168.8.123" forgejo_http_port: 3002 diff --git a/ansible/roles/stack/templates/env.j2 b/ansible/roles/stack/templates/env.j2 index 677b962..31c11cd 100644 --- a/ansible/roles/stack/templates/env.j2 +++ b/ansible/roles/stack/templates/env.j2 @@ -1,17 +1,54 @@ # Generated by Ansible for {{ inventory_hostname }} — do not commit # 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) }} PGID={{ ansible_user_gid | default(1000) }} TZ={{ timezone }} - CONFIG_DIR={{ config_dir }} DATA_DIR={{ data_dir }} 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_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_NGL={{ bonsai_ngl }} BONSAI_CTX={{ bonsai_ctx }} @@ -20,26 +57,48 @@ BONSAI_THREADS={{ bonsai_threads }} BONSAI_TEMP=0.7 BONSAI_TOP_P=0.95 BONSAI_TOP_K=20 -BONSAI_PORT=8081 - -OPEN_WEBUI_PORT=3000 -OPENAI_API_KEY=sk-local-bonsai +BONSAI_EXTRA_ARGS= WEBUI_AUTH={{ open_webui_auth }} +OPENWEBUI_DEFAULT_MODEL=openai/Ternary-Bonsai-27B-Q2_0.gguf AI_WORKSPACE_DIR={{ workspace_dir }} - -SEARXNG_PORT=8888 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 (self-hosted Git) -FORGEJO_HTTP_PORT={{ forgejo_http_port }} -FORGEJO_SSH_PORT={{ forgejo_ssh_port }} +################################################################################ +# Forgejo +################################################################################ FORGEJO_DOMAIN={{ forgejo_domain }} FORGEJO_ROOT_URL={{ forgejo_root_url }} 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') }} -WIREGUARD_ENDPOINT_PORT={{ wireguard_endpoint_port | default('443') }} +################################################################################ +# Usenet — fill from secrets or set here for demo +################################################################################ +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= diff --git a/quickstart.md b/quickstart.md index 1afc74a..cbf3742 100644 --- a/quickstart.md +++ b/quickstart.md @@ -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 From a machine with this repo and SSH key `~/.ssh/id_ed25519`: diff --git a/scripts/bootstrap-stack.sh b/scripts/bootstrap-stack.sh new file mode 100755 index 0000000..706a07b --- /dev/null +++ b/scripts/bootstrap-stack.sh @@ -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}" diff --git a/scripts/configure-usenet.sh b/scripts/configure-usenet.sh index e6aeb47..547c469 100755 --- a/scripts/configure-usenet.sh +++ b/scripts/configure-usenet.sh @@ -15,7 +15,12 @@ SAB_URL="${SAB_URL:-http://127.0.0.1:8085}" log() { echo "[configure-usenet] $*"; } 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 set -a