Self-hosted personal services on a single Linux box, organized as modular podman-compose sections that share a small set of management scripts and a KDE tray indicator. The repo migrated from a media-only stack on 2026-05-02; today it hosts a media stack, an AI image-gen service, an LLM chat UI, and a launchpad dashboard — each in its own section directory.
Start here: docs/QUICK-REF.md for commands · docs/INDEX.md for full doc map.
# Each section is independent. Configure .env for the sections you want, then:
./scripts/up.sh media # Jellyfin + arr + qBittorrent + AirVPN
./scripts/up.sh forge # Stable Diffusion WebUI Forge
./scripts/up.sh sillytavern # LLM chat UI
./scripts/up.sh dashboard # Homarr launchpad
./scripts/up.sh ebooks # Calibre-Web Automated (e-book library)
./scripts/up.sh all # everything available
./scripts/down.sh <section> # stop
./scripts/logs.sh <section> -f # follow logsThe KDE tray indicator (scripts/tray.py, autostarts on login) gives 1-click control: 🟢 all up · 🟡 partial · ⚫ all down. Right-click for per-category start/stop, double-click for the dashboard.
| Section | Stack | Network | Endpoint |
|---|---|---|---|
| media/ | Jellyfin · Sonarr · Radarr · Bazarr · Prowlarr · qBittorrent · Gluetun (AirVPN WireGuard) · FlareSolverr | own (project default) | :8096 Jellyfin, :8080 qBT, etc. |
| forge/ | Stable Diffusion WebUI Forge | home-net |
:7860 (localhost-only) |
| sillytavern/ | SillyTavern + Forge image-gen integration | home-net |
:8000 (localhost-only) |
| dashboard/ | Homarr | home-net |
:7575 (LAN-accessible) |
| ebooks/ | Calibre-Web Automated (mounts existing Calibre library) | home-net |
:8083 (localhost-only) |
Each section has its own compose.yml, .env, data/ (gitignored), and README.md. The READMEs in forge/, sillytavern/, dashboard/, ebooks/ are canonical for section-specific details. For media, see docs/media/.
┌──────────────── home-server ────────────────┐
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ media/ │ │ forge/ │ │ ST/ │ │
│ │ (own net)│ │ ┌──────►│ │ home-net │ │
│ └────┬─────┘ │ │home-net│ └────┬─────┘ │
│ │ │ └──────┘ │ │
│ │ ┌┴───────────────────┴┐ │
│ └──────►│ dashboard (:7575) │ │
│ │ home-net + h.c.i. │ │
│ └─────────────────────┘ │
│ │
│ shared: scripts/ · tray.py · │
│ vram-guard.sh · _lib.sh │
└─────────────────────────────────────────────┘
↕ all routed by KDE tray (icon = stack state)
- AI sections (forge, sillytavern, dashboard) plus ebooks share
home-netPodman network → cross-container DNS works (http://home-forge:7860,http://home-ebooks:8083). - Media stack runs on its own network. Dashboard reaches media services via
host.containers.internal:<port>. - VRAM budget enforced by
scripts/vram-guard.sh(RTX 4070 Ti SUPER 16GB ceiling).
| OS | Linux with rootless Podman 4.0+. Tested on Fedora/Nobara. |
| Container | podman + podman-compose (or docker-compose with podman backend) |
| GPU | Optional but recommended: NVIDIA with nvidia-container-toolkit. CDI is auto-regenerated by _lib.sh. |
| Desktop | KDE Plasma for the tray indicator. CLI-only works fine without tray. |
| VPN | AirVPN account for media section's gluetun. See AIRVPN-VALIDATION-CHECKLIST.md. |
| Storage | NVMe recommended for data/ and media library. Configure MEDIA_ROOT in media/.env. |
-
Clone + configure
.envfor each section you wantcp media/.env.example media/.env cp forge/.env.example forge/.env # optional cp sillytavern/.env.example sillytavern/.env # optional cp dashboard/.env.example dashboard/.env # optional cp ebooks/.env.example ebooks/.env # set CALIBRE_LIBRARY_PATH
Edit each
.env— required vars are clearly marked. Media section needs AirVPN credentials (see AIRVPN-VALIDATION-CHECKLIST.md). -
Start sections
./scripts/up.sh dashboard # launchpad first — takes 30s to onboard ./scripts/up.sh media # then media ./scripts/up.sh forge # AI sections optional ./scripts/up.sh sillytavern
-
Tray autostart (KDE Plasma)
# If migrating from prior install, autostart is already configured. # Otherwise: cp -r ~/.config/autostart/home-server-tray.desktop ~/.config/autostart/ # or create manually
The
.desktopfile points toscripts/launcher.shwhich invokestray.py. -
Verify
- Dashboard: http://localhost:7575 (Homarr onboarding)
- Jellyfin: http://localhost:8096 (Jellyfin setup wizard)
- VPN:
podman exec gluetun wget -qO- https://ipinfo.ioshould show AirVPN exit IP
| Task | Command |
|---|---|
| Start a section | ./scripts/up.sh <section> |
| Stop a section | ./scripts/down.sh <section> |
| Follow logs | ./scripts/logs.sh <section> -f [services...] |
| Toggle a category | ./scripts/category.sh {ai|media|all} {toggle|up|down|status} |
| Dashboard backup | ./scripts/dashboard-backup.sh |
| Dashboard restore | ./scripts/dashboard-restore.sh <backup.tar.gz> |
| Ebooks backup | ./scripts/ebooks-backup.sh |
| Ebooks restore | ./scripts/ebooks-restore.sh <backup.tar.gz> |
| Media maintenance | ./media/maintenance/maintenance.sh health |
| Media quick-debug | ./media/maintenance/quick-debug.sh |
- Per-section structure:
<section>/{compose.yml, .env, .env.example, .gitignore, data/, README.md}. Thedata/dir holds container state and is gitignored. - Naming: containers use
home-<role>for AI services on shared network (home-forge,home-sillytavern,home-dashboard). Media stack uses bare names (jellyfin,gluetun, etc.). - Secrets: never commit
.envordata/. The repo.gitignoreis generic; per-section.gitignorehandles section-specific paths. - GPU: CDI device
nvidia.com/gpu=all(rootless-compatible)._lib.shauto-regenerates CDI on driver upgrade. - VRAM: don't hand-edit memory budgets without checking
scripts/vram-guard.sh. Heavy SDXL + Jellyfin transcoding can exceed 16GB.
- docs/INDEX.md — full doc map
- docs/QUICK-REF.md — daily commands, common issues
- docs/PODMAN.md — Podman fundamentals (rootless, SELinux, GPU)
- docs/AIRVPN-VALIDATION-CHECKLIST.md — AirVPN setup + chain-bug gotchas
- docs/media/ — media-section deep-dives (Jellyfin perf, qBT perf, GPU timing, boot investigation)
- Per-section READMEs in
forge/,sillytavern/,dashboard/
Personal project. Conventional Commits format (type(scope): description). Pre-commit hooks via git config core.hooksPath .githooks enforce shellcheck + commit message format.