stack/quickstart.md
tim 3bf1726a99 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.
2026-07-22 13:06:08 -07:00

10 KiB
Raw Blame History

Quickstart

Homelab host: 192.168.8.123 (SER5, Ubuntu 24.04)
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).

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:

cd ansible
ansible-galaxy collection install -r requirements.yml

ansible-playbook site.yml -K          # -K only if sudo still asks once; playbook installs NOPASSWD for tim

What it does: OS updates, tools (incl. Microsoft edit), Docker, AMD/Vulkan, UFW (LAN only), /opt/stack + /storage, .env, arr-stack.service, and tim as local admin (passwordless sudo, docker/video/render/…, owns stack + storage, permissive GPU nodes).

Code on the server: /opt/stack is a git clone of Forgejo tim/stack (stack_deploy_method: git).
Day-to-day updates on ser5:

ssh tim@192.168.8.123
cd /opt/stack
git pull
docker compose up -d   # if compose/images changed

Ansible can also refresh code: ansible-playbook site.yml -K --tags stack,deploy (runs git fetch + checkout).
Local-only (not in git): .env, config/, models/**/*.gguf, wireguard/wg0.conf.

Before first full stack start (on ser5):

ssh tim@192.168.8.123
# new login so docker/video/render groups apply
cd /opt/stack
# wireguard/wg0.conf must exist (no IPv6; Endpoint = IP not hostname)
./scripts/download-bonsai-model.sh    # ~7.2 GB GGUF
docker compose build bonsai
docker compose up -d
# or: sudo systemctl enable --now arr-stack
docker compose ps

Compose GPU notes

  • Keep devices/group_add only in docker-compose.override.yml (not also in the main file).
  • Use numeric GIDs for group_add, not names (video/render fail inside the image). Or omit group_add if /dev/dri is mode 0666.

Re-run playbook anytime: ansible-playbook site.yml -K
Tags: --tags common,docker,amd,firewall,stack


Remotes

LAN URLs (UFW allows 192.168.8.0/24). Replace host if needed.

Service URL
Landing page http://192.168.8.123/
Jellyfin http://192.168.8.123:8096
qBittorrent http://192.168.8.123:8080
Prowlarr http://192.168.8.123:9696
Sonarr http://192.168.8.123:8989
Radarr http://192.168.8.123:7878
Bazarr http://192.168.8.123:6767
Lidarr http://192.168.8.123:8686
SABnzbd http://192.168.8.123:8085
Open WebUI http://192.168.8.123:3000
Bonsai API http://192.168.8.123:8081/v1
OpenHands http://192.168.8.123:3001
Forgejo http://192.168.8.123:3002
Forgejo Git SSH ssh://git@192.168.8.123:2222/user/repo.git

OpenHands only runs with: docker compose --profile coding up -d (or stack_compose_profiles: [coding] in Ansible).
Images are from GHCR (ghcr.io/all-hands-ai/...); the old docker.all-hands.dev host often does not resolve.

Manual config (once)

qBittorrent — temp password in docker logs qbittorrent; user admin.
Advanced → Network interface: tun0.
Depends on gluetun healthy. If gluetun is unhealthy with lookup … i/o timeout, WireGuard is not passing traffic — usually a stale Endpoint IP in wireguard/wg0.conf.

Auto-heal: scripts/gluetun-endpoint-watch.sh + systemd timer every 5 minutes (installed by Ansible). When gluetun is not healthy it: digs WIREGUARD_ENDPOINT_HOST → updates Endpoint in wg0.conf → recreates gluetun → recreates qbittorrent → tests exit IP.
Manual run: cd /opt/stack && ./scripts/gluetun-endpoint-watch.sh
Logs: journalctl -u gluetun-endpoint-watch.service -n 50
Set host in .env: WIREGUARD_ENDPOINT_HOST=las-183-wg.whiskergalaxy.com

Default save path + categories (WebUI):

  1. Tools → Options → Downloads

    • Default Save Path: /data/torrents
    • Keep “Keep incomplete torrents in” off (or a subfolder under /data/torrents if you prefer).
  2. Categories (same Options → Downloads page, or rightclick empty area in the left “Categories” panel → Add category):

    Category name Save path
    movies /data/torrents/movies
    tv /data/torrents/tv
    music /data/torrents/music

    For each: Add category → name exactly movies / tv / music (lowercase, matches Sonarr/Radarr/Lidarr) → set Save path to the path above → OK.

  3. Sonarr/Radarr/Lidarr download-client “Category” fields must match those names so torrents land in the right folder for hardlinks.

Sonarr / Radarr / Lidarr — Download client qBittorrent: host gluetun, port 8080, categories tv / movies / music.
Root folders: /data/media/tv, /data/media/movies, /data/media/music (hardlinks on).

Prowlarr — Add indexers. Apps: Sonarr http://sonarr:8989, Radarr http://radarr:7878, Lidarr http://lidarr:8686 + each API key (Settings → General).

Usenet (NZBgeek + Newshosting + Tweaknews)
Credentials live in secrets/.env (not git). Example keys: see secrets/.env.example.

# on ser5
cd /opt/stack && git pull
mkdir -p /storage/usenet/{incomplete,complete/{movies,tv,music}}
# copy secrets if needed:  scp secrets/.env tim@stack:/opt/stack/secrets/
docker compose up -d sabnzbd
# open http://192.168.8.123:8085 once if first-run wizard appears, then:
./scripts/configure-usenet.sh
  1. SABnzbd (http://host:8085) — Config → Servers: Newshosting news.newshosting.com:563 SSL; Tweaknews news.tweaknews.eu:563 SSL. Test both green.
    Folders: incomplete /data/usenet/incomplete, complete /data/usenet/complete (categories movies/tv/music).
  2. Prowlarr → Indexers → NZBgeek → API key from secrets (NZBKEEPKEY / NZBGEEK_API_KEY).
    Download Clients → SABnzbd → host sabnzbd, port 8080, API key from SABnzbd Config → General.
    Sync download client to apps (or add SABnzbd in each *arr).
  3. Sonarr/Radarr/Lidarr → Download Clients → SABnzbd: host sabnzbd, port 8080, categories tv / movies / music.

Usenet traffic is not forced through gluetun (SSL to provider). Torrents still go via VPN.

FlareSolverr (Cloudflare / JS challenges for some indexers) — container flaresolverr on arr-net, no public port by default.
Prowlarr → Settings → Indexers → FlareSolverr host: http://flaresolverr:8191.

Bazarr — Sonarr/Radarr URLs + API keys; paths /data/media/tv, /data/media/movies.

Jellyfin — Libraries: Movies /data/media/movies, Shows /data/media/tv, Music /data/media/music.

Open WebUI — Create admin on first visit. Model connection is pre-set to Bonsai (http://bonsai:8080/v1). Enable web search in chat if desired. RAG: Workspace → Knowledge (uploads) or files under /workspace.

Bonsai — No UI login. Smoke test: curl -s http://192.168.8.123:8081/v1/models. Needs GGUF in models/bonsai/ and docker compose build bonsai once.

OpenHands — On the server (/opt/stack): docker compose --profile coding up -d openhands.
UI: http://192.168.8.123:3001

LLM (required once)
Settings → Advanced: base URL http://bonsai:8080/v1, API key sk-local-bonsai, model openai/<id from /v1/models>.

Connect to Forgejo (pick A and/or B)

A — Provider (browse / open repos in OpenHands)

  1. Forgejo → avatar → Settings → Applications → generate a token (scopes: read:repository, write:repository, read:user — or full access on a trusted LAN).
  2. OpenHands → Settings → Git / Integrations (or Secrets / provider list):
    • Provider: Forgejo or Gitea (same API)
    • Base URL: http://forgejo:3000 (Docker DNS on arr-net; use this from OpenHands)
      From a browser on your PC you use http://192.168.8.123:3002 — that is not what the OpenHands container should use.
    • Token: paste the Forgejo token
  3. Start a conversation → choose repository → tim/stack.

B — Workspace mount (most reliable for editing + git push)
Compose already mounts ${AI_WORKSPACE_DIR} (default /opt/stack/workspace) into the agent sandbox as /workspace.
On the server:

# HTTPS with token (one-time; token in URL — prefer SSH below)
# git clone http://tim:TOKEN@192.168.8.123:3002/tim/stack.git /opt/stack/workspace/stack

# SSH (key already on Forgejo for user tim)
git clone ssh://git@192.168.8.123:2222/tim/stack.git /opt/stack/workspace/stack

Then in OpenHands: work in /workspace/stack (or set the conversation workspace to that path).
Agent can git commit / git push if the host clone has credentials (SSH agent or stored remote URL).

If OpenHands cannot reach forgejo by name, ensure both services are on arr-net (docker compose up -d forgejo openhands).

Forgejohttp://192.168.8.123:3002
If connection refused: crash-loop from dual SSH; compose uses START_SSH_SERVER=false. Recreate: docker compose up -d forgejo --force-recreate.


Locals

Bound to loopback on the host (not LAN-reachable as published):

Service URL / path
SearXNG http://127.0.0.1:8888

Open WebUI talks to SearXNG on the Docker network (http://searxng:8080); you only need the loopback URL for direct browser checks on the server.

Manual config

SearXNG — None for normal use. Settings: /opt/stack/searxng/settings.yml. Secret comes from .env (SEARXNG_SECRET).

WireGuard — File only: /opt/stack/wireguard/wg0.conf (not a web UI). Must be ready before gluetun is healthy.

Host shellssh tim@192.168.8.123 · stack logs: docker compose -f /opt/stack/docker-compose.yml logs -f · unit: systemctl status arr-stack.