Initial commit: arr-stack homelab + local AI

Docker Compose media stack (gluetun, *arr, Jellyfin), Ternary-Bonsai AI
(Open WebUI, Forgejo, optional OpenHands), and Ansible bootstrap for the
SER5 Ubuntu host.
This commit is contained in:
tim 2026-07-22 06:28:49 -07:00
commit 3645d1314c
33 changed files with 2673 additions and 0 deletions

138
quickstart.md Normal file
View file

@ -0,0 +1,138 @@
# Quickstart
Homelab host: **`192.168.8.123`** (SER5, Ubuntu 24.04)
Stack: **`/opt/stack`** · Media: **`/storage`** · User: **`tim`**
---
## Ansible
From a machine with this repo and SSH key `~/.ssh/id_ed25519`:
```bash
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).
**Before first full stack start** (on ser5):
```bash
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 |
|---------|-----|
| 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 |
| 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`.
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*).
**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`.
Settings → Advanced: base URL `http://bonsai:8080/v1`, API key `sk-local-bonsai` (or `.env` value), model `openai/<id from /v1/models>`.
**Forgejo** — First visit http://192.168.8.123:3002 → install wizard (SQLite is pre-selected via env) → create **admin**.
If connection refused: container crash-looped (built-in SSH + image OpenSSH both on :22). Compose sets `START_SSH_SERVER=false`; recreate with `docker compose up -d forgejo --force-recreate`.
Then: **+ → New Repository** → clone into workspace:
```bash
# HTTPS
git clone http://192.168.8.123:3002/YOU/repo.git /opt/stack/workspace/repo
# SSH (port 2222 — host :22 stays for machine login)
git clone ssh://git@192.168.8.123:2222/YOU/repo.git /opt/stack/workspace/repo
# or: git remote add origin ssh://git@192.168.8.123:2222/YOU/repo.git
```
Point OpenHands / Open WebUI `AI_WORKSPACE_DIR` at those clones. After admin exists, set `FORGEJO_DISABLE_REGISTRATION=true` in `.env` and recreate: `docker compose up -d forgejo`.
---
## 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 shell** — `ssh tim@192.168.8.123` · stack logs: `docker compose -f /opt/stack/docker-compose.yml logs -f` · unit: `systemctl status arr-stack`.