Files
lifegame/homelab-config.md
2026-07-21 16:17:12 -07:00

542 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Bumblesloth Homelab — System Reference
> **Purpose:** This file is shared project knowledge for all homelab-related conversations. It describes the full infrastructure so that any discussion (game servers, cloud, networking, etc.) has the context it needs.
---
## Hardware
| Component | Detail |
|-----------|--------|
| Machine | HP ProDesk 600 G6 SFF |
| CPU | Intel i5-10500 (6 cores / 12 threads) |
| RAM | 16 GB DDR4 UDIMM (288-pin desktop, not SO-DIMM) |
| Storage | 512 GB NVMe SSD (single drive) |
| GPU | Integrated Intel UHD 630 (no dedicated GPU) |
| Expansion | 1× free PCIe x16 slot, 1× 3.5" bay |
| Form factor | Small Form Factor (SFF) — limits internal expansion |
---
## Operating System
- **OS:** Ubuntu Server 24.04 LTS (HWE kernel, minimized install, no snaps)
- **User:** `bumblesloth`
- **Hostname:** `slothhive`
- **IPv6:** Disabled system-wide (`/etc/sysctl.d/99-disable-ipv6.conf`) and in Docker (`daemon.json`)
---
## Network Configuration
### Local Network
| Setting | Value |
|---------|-------|
| IP | `192.168.178.30` (DHCP with static lease via FRITZ!Box MAC binding) |
| Subnet | `192.168.178.x` |
| Netplan config | `/etc/netplan/50-cloud-init.yaml` (dhcp4: no — eno1 enslaved to br0) + `/etc/netplan/99-bridge.yaml` |
| Network bridge | `br0` — bridges `eno1` for VM LAN access; inherits `eno1` MAC (`e8:d8:d1:d3:05:bd`) |
### Router
| Setting | Value |
|---------|-------|
| Current router | FRITZ!Box 5690 Pro (Deutsche Telekom fiber, SFP module) |
| DHCP DNS | AdGuard Home pushed via DHCP to all LAN devices (`192.168.178.30`) |
| DynDNS | Configured directly on FRITZ!Box — auto-updates ipv64.net on IP change |
### Public Access
| Setting | Value |
|---------|-------|
| Public IP | Dynamic (DynDNS managed) |
| DynDNS provider | ipv64.net |
| Base domain | `bumblesloth.ipv64.net` |
| A record | `bumblesloth.ipv64.net` → public IP (auto-updated by FRITZ!Box) |
### DNS — AdGuard Home
- **Wildcard rewrite:** `*.bumblesloth.ipv64.net → 192.168.178.30` (covers all subdomains on LAN)
- **Desktop DNS (CachyOS):** received automatically via FRITZ!Box DHCP — no manual override needed
- All devices on LAN receive AdGuard Home as DNS via FRITZ!Box DHCP
### DNS Records (CNAME → bumblesloth.ipv64.net)
| Subdomain | Service |
|-----------|---------|
| `cloud.bumblesloth.ipv64.net` | Nextcloud |
| `photos.bumblesloth.ipv64.net` | Immich |
| `actual-budget.bumblesloth.ipv64.net` | Actual Budget |
| `adguard.bumblesloth.ipv64.net` | AdGuard Home |
| `mealie.bumblesloth.ipv64.net` | Mealie |
| `git.bumblesloth.ipv64.net` | Gitea |
| `foundry.bumblesloth.ipv64.net` | Foundry VTT |
| `matrix.bumblesloth.ipv64.net` | Matrix/Synapse |
| `jellyfin.bumblesloth.ipv64.net` | Jellyfin |
| `paperless.bumblesloth.ipv64.net` | Paperless-NGX |
| `homeassistant.bumblesloth.ipv64.net` | Home Assistant |
### Firewall (UFW)
```
22/tcp — SSH
80/tcp — HTTP (Nginx Proxy Manager)
443/tcp — HTTPS (Nginx Proxy Manager)
222/tcp — Gitea SSH
53/tcp+udp — AdGuard Home DNS
2456:2458/udp — Valheim
15636/udp — Enshrouded (query)
15637/udp — Enshrouded (game)
ufw route allow in on br0 — VM bridge traffic (required for KVM VM LAN access)
ufw route allow out on br0 — VM bridge traffic (required for KVM VM LAN access)
```
### Router Port Forwarding
| Port(s) | Protocol | Target | Service |
|---------|----------|--------|---------|
| 80 | TCP | 192.168.178.30 | NPM (HTTP) |
| 443 | TCP | 192.168.178.30 | NPM (HTTPS) |
| 222 | TCP | 192.168.178.30 | Gitea SSH |
| 24562458 | UDP | 192.168.178.30 | Valheim |
| 15636 | UDP | 192.168.178.30 | Enshrouded (query) |
| 15637 | UDP | 192.168.178.30 | Enshrouded (game) |
### Remote Access
- **Tailscale** mesh VPN installed on slothhive, iPhone, and MacBook; MagicDNS enabled for hostname-based access
- **CachyOS desktop:** `/etc/hosts` entry `192.168.178.30 slothhive` for local SSH
---
## KVM Virtualisation
Home Assistant OS runs as a KVM virtual machine on slothhive, alongside Docker services.
### Installation
```bash
sudo apt install qemu-kvm libvirt-daemon-system libvirt-clients virtinst bridge-utils ovmf
sudo usermod -aG libvirt bumblesloth
```
### Network Bridge
The VM needs a bridge interface to get its own LAN IP. Configured via `/etc/netplan/99-bridge.yaml`:
```yaml
network:
version: 2
ethernets:
eno1:
dhcp4: false
bridges:
br0:
interfaces: [eno1]
dhcp4: true
macaddress: e8:d8:d1:d3:05:bd
parameters:
stp: false
forward-delay: 0
```
`/etc/netplan/50-cloud-init.yaml` sets `eno1` to `dhcp4: false` to avoid conflict.
### Bridge Kernel Settings
```
/etc/sysctl.d/99-bridge.conf:
net.bridge.bridge-nf-call-iptables = 0
net.bridge.bridge-nf-call-ip6tables = 0
/etc/modules-load.d/br_netfilter.conf:
br_netfilter
/etc/sysctl.d/99-ipforward.conf:
net.ipv4.ip_forward = 1
```
### Common virsh Commands
```bash
virsh list --all # List all VMs and state
virsh start hass # Start HA VM
virsh shutdown hass # Graceful shutdown
virsh destroy hass # Force stop
virsh reboot hass # Reboot
virsh autostart hass # Enable autostart on boot
virsh dominfo hass # VM details
virsh domifaddr hass # VM IP address
virsh attach-device hass --file device.xml --persistent # Attach USB device
```
---
## Docker Infrastructure
### Installation
- Docker CE from official Docker repository (not distro packages)
- Docker Compose plugin (`docker compose`, not `docker-compose`)
- User `bumblesloth` in `docker` and `www-data` groups
### Networks
| Network | Type | Purpose |
|---------|------|---------|
| `proxy-net` | External, user-defined | Connects NPM to all web-facing services |
| `nc-internal` | Bridge (Nextcloud stack) | Isolates Nextcloud DB/Redis |
| `paperless-internal` | Bridge (Paperless stack) | Isolates Paperless DB |
| `matrix-internal` | Bridge (Matrix stack) | Isolates Synapse DB |
| `default` (per stack) | Auto-created | Internal communication within a compose stack |
> **Security model:** Services behind NPM on `proxy-net` don't need UFW rules or host port exposure — traffic flows 443/80 → NPM → container internally.
### Directory Structure
```
~/homelab/
├── nginx-proxy-manager/ # Reverse proxy + SSL
│ ├── docker-compose.yml
│ ├── data/
│ └── letsencrypt/
├── adguard-home/ # DNS resolver + ad blocking
│ └── docker-compose.yml
├── nextcloud/ # File sync & collaboration
│ ├── docker-compose.yml
│ ├── app-data/
│ ├── db-data/ # MariaDB data
│ └── user-data/
├── immich/ # Photo & video management
│ ├── docker-compose.yml
│ ├── .env
│ ├── library/
│ └── postgres/
├── paperless/ # Document management
│ ├── docker-compose.yml
│ └── db-data/ # PostgreSQL data
├── jellyfin/ # Media server
│ └── docker-compose.yml
├── mealie/ # Recipe manager
│ └── docker-compose.yml
├── gitea/ # Self-hosted Git
│ ├── docker-compose.yml
│ └── data/
├── actual-budget/ # Personal finance
│ ├── docker-compose.yml
│ └── data/
├── foundry/ # Foundry VTT
│ └── docker-compose.yml
├── matrix/ # Matrix/Synapse chat
│ └── docker-compose.yml
├── enshrouded/ # Enshrouded game server
│ ├── docker-compose.yml
│ └── enshrouded-data/
├── valheim/ # Valheim game server
│ ├── docker-compose.yml
│ ├── config/
│ │ ├── worlds_local/
│ │ ├── backups/
│ │ └── bepinex/
│ │ └── plugins/
│ └── data/
└── cups/ # AirPrint bridge for Epson ET-2810
└── docker-compose.yml
```
### Homelab Management Script
`/usr/local/bin/homelab` — supports `up`, `down`, `status`
**Design:** AdGuard Home and NPM are hardcoded as first/last. All other Docker services live in the `STACKS` array. The Home Assistant VM is managed separately via `virsh` commands within the same script.
**Startup order:** adguard-home → nginx-proxy-manager (3s delay) → HA VM → STACKS in order
**Shutdown order:** STACKS in reverse → HA VM → nginx-proxy-manager → adguard-home
### Auto-start on Boot
- **Docker services:** Systemd service at `/etc/systemd/system/homelab.service` — enabled, runs `homelab up` after Docker is ready on every boot.
- **HA VM:** `virsh autostart hass` — VM starts independently on boot via libvirt.
---
## Services
### Nginx Proxy Manager
| Setting | Value |
|---------|-------|
| Container | `jc21/nginx-proxy-manager:latest` |
| Ports | 80 (HTTP), 443 (HTTPS), 81 (admin, local only) |
| Network | `proxy-net` |
| Admin UI | `http://192.168.178.30:81` |
| Features | SSL via Let's Encrypt, Force SSL, HTTP/2, Block Common Exploits, WebSockets |
All web services are proxied through NPM. Game servers use direct UDP port forwarding, not NPM.
### AdGuard Home
| Setting | Value |
|---------|-------|
| Role | LAN DNS resolver, ad/tracker blocking |
| URL | `https://adguard.bumblesloth.ipv64.net` |
| Admin UI | `http://192.168.178.30:3000` |
| Port | 53 (bound to host) |
| Location | `~/homelab/adguard-home/` |
| Network | `proxy-net` |
| Wildcard rewrite | `*.bumblesloth.ipv64.net → 192.168.178.30` |
### Home Assistant
| Setting | Value |
|---------|-------|
| Role | Home automation, smart home integration |
| URL | `https://homeassistant.bumblesloth.ipv64.net` |
| Type | **KVM Virtual Machine** (not Docker) — runs Home Assistant OS 18.0 |
| VM name | `hass` |
| VM IP | `192.168.178.43` (DHCP via FRITZ!Box) |
| VM disk | `/var/lib/libvirt/images/haos.img` (64 GB) |
| VM RAM | 4096 MB |
| VM vCPUs | 2 |
| NPM forward | `192.168.178.43:8123` (by IP, not container name) |
| NPM features | WebSockets enabled, Force SSL |
| Autostart | `virsh autostart hass` |
**ZBT-2 USB Passthrough:**
| Setting | Value |
|---------|-------|
| Device | Nabu Casa ZBT-2 Thread/Matter border router |
| USB ID | `303a:831a` |
| Host path | `/dev/serial/by-id/usb-Nabu_Casa_ZBT-2_94A990D07314-if00``/dev/ttyACM0` |
| Passthrough method | `virsh attach-device hass ~/zbt2.xml --persistent` |
| Mode | Thread border router (OpenThread Border Router add-on in HA OS) |
**ZBT-2 passthrough XML (`~/zbt2.xml`):**
```xml
<hostdev mode='subsystem' type='usb' managed='yes'>
<source>
<vendor id='0x303a'/>
<product id='0x831a'/>
</source>
</hostdev>
```
**Integrations configured:**
- LG webOS TV (`192.168.178.22`) — added manually
- iPhone Companion app — connected via `https://homeassistant.bumblesloth.ipv64.net`
- ZBT-2 Thread border router — OpenThread Border Router add-on
**Notes:**
- HA OS runs as a KVM VM because the OpenThread Border Router add-on requires HA OS (not available in HA Container)
- VM uses `br0` bridge for LAN access — gets its own IP directly from FRITZ!Box DHCP
- NPM proxies to VM by IP (`192.168.178.43`) since the VM is not on `proxy-net`
- `trusted_proxies` config not needed in HA OS (handled differently than HA Container)
- mDNS auto-discovery doesn't work across Docker bridge — iOS Companion app must use manual URL
### Nextcloud
| Setting | Value |
|---------|-------|
| Role | Documents, file sync, collaboration |
| URL | `https://cloud.bumblesloth.ipv64.net` |
| Containers | `nextcloud-app` (Nextcloud), `nextcloud-db` (MariaDB), `nextcloud-redis` (Redis) |
| Networks | `proxy-net`, `nc-internal` |
| NPM forward | `nextcloud-app:80` |
| PHP memory | 1024M (`/usr/local/etc/php/conf.d/memory-limit.ini`) |
**Users:** root (admin, no data), kevin
**Cron:**
```
*/5 * * * * docker exec -u www-data nextcloud-app php -f /var/www/html/cron.php
```
Background jobs set to "Cron" in admin settings.
**Notes:**
- iOS/macOS CalDAV login can trigger brute-force protection; reset with `occ security:bruteforce:reset <ip>`
- `bumblesloth` user is in `www-data` group for host-level access to user-data directory
- Passwords in docker-compose.yml contain `$` special characters — escaped as `$$` to prevent Docker Compose variable interpolation warnings
### Immich
| Setting | Value |
|---------|-------|
| Role | Photo & video management, AI tagging, face recognition |
| URL | `https://photos.bumblesloth.ipv64.net` |
| Containers | `immich_server`, `immich_machine_learning`, `immich_redis`, `immich_postgres` |
| Networks | `proxy-net` + `default` (server), `default` only (others) |
| NPM forward | `immich_server:2283` |
**Users:** Kevin, mother
### Paperless-NGX
| Setting | Value |
|---------|-------|
| Role | Document management, OCR, tagging |
| URL | `https://paperless.bumblesloth.ipv64.net` |
| Containers | `paperless`, `paperless-db` (PostgreSQL), `paperless-redis`, `paperless-gotenberg`, `paperless-tika`, `paperless-gpt`, `ollama` |
| Networks | `proxy-net`, `paperless-internal` |
| NPM forward | `paperless:8000` |
**Backup:** PostgreSQL dump + document export → rsync to USB (`/mnt/backup-usb`, btrfs) nightly at 2 AM
**Manual sync to Proton Drive:** Fish function `paperless-backup` on CachyOS desktop (rclone automation pending upstream fix — PRs #9080/#9081)
**OCR workflow:**
- Automated metadata (title, tags, date) via paperless-gpt + `qwen2.5:7b`
- Manual OCR for difficult documents via Open WebUI + `llama3.2-vision:11b`; PDFs converted with `pdftoppm` (poppler)
- Claude (vision) as fallback for manual transcription
### Jellyfin
| Setting | Value |
|---------|-------|
| Role | Media streaming |
| URL | `https://jellyfin.bumblesloth.ipv64.net` |
| Network | `proxy-net` |
| NPM forward | `jellyfin:8096` |
| Hardware accel | Intel QSV via `/dev/dri` (Intel UHD 630) |
| Media path | `/srv/media/` (host mount) |
### Mealie
| Setting | Value |
|---------|-------|
| Role | Recipe manager |
| URL | `https://mealie.bumblesloth.ipv64.net` |
| Network | `proxy-net` |
| Backend | SQLite |
### Gitea
| Setting | Value |
|---------|-------|
| Role | Self-hosted Git |
| URL | `https://git.bumblesloth.ipv64.net` |
| Network | `proxy-net` |
| Backend | SQLite |
| SSH | Port 222 (UFW + FRITZ!Box port forwarding configured) |
| NPM forward | `gitea:3000` |
| Features | WebSockets enabled, Block Common Exploits enabled |
### Actual Budget
| Setting | Value |
|---------|-------|
| Role | Personal finance (replaced Firefly III) |
| URL | `https://actual-budget.bumblesloth.ipv64.net` |
| Network | `proxy-net` |
| NPM forward | `actual-budget:5006` |
### Foundry VTT
| Setting | Value |
|---------|-------|
| Role | TTRPG virtual tabletop |
| URL | `https://foundry.bumblesloth.ipv64.net` |
| Network | `proxy-net` |
| NPM | WebSockets must be enabled in NPM proxy host |
### Matrix / Synapse
| Setting | Value |
|---------|-------|
| Role | Private self-hosted chat |
| URL | `https://matrix.bumblesloth.ipv64.net` |
| Server name | `matrix.bumblesloth.ipv64.net` (permanent — chosen to avoid .well-known delegation) |
| User IDs | `@kevin:matrix.bumblesloth.ipv64.net` |
| Containers | `synapse`, `matrix-db` (PostgreSQL), `coturn` |
| Networks | `proxy-net`, `matrix-internal` |
| NPM | WebSockets must be enabled |
| Federation | Disabled |
| Registration | Manual only |
### Enshrouded (Game Server)
| Setting | Value |
|---------|-------|
| Image | `mornedhels/enshrouded-server:latest` |
| Container | `enshrouded` |
| Ports | UDP 15636 (query), UDP 15637 (game) |
| Backups | Every 6 hours, max 4 kept |
| Max players | 8 |
| Update cron | Mondays at 4 AM |
**Force update:** `docker compose exec enshrouded supervisorctl start enshrouded-force-update`
**Note:** Server version may lag behind client after patches — Keen Games publishes dedicated server updates separately. Use force update command if version mismatch occurs.
### Valheim (Game Server)
| Setting | Value |
|---------|-------|
| Image | `ghcr.io/lloesche/valheim-server` |
| Container | `valheim` |
| Ports | UDP 24562458 |
| Mod framework | BepInEx (disabled — glibc incompatibility; vanilla only) |
| Auto-update | Only when idle |
| Backups | Every 6 hours, max 10 kept, max 7 days |
| Public | false (private server) |
| World | `BumbleWorld` (in `./config/worlds_local/`) |
**World switching:** Change `WORLD_NAME` in docker-compose.yml, run `docker compose down && docker compose up -d`.
**Adding mods:** Drop `.dll` files in `./config/bepinex/plugins/`, restart container.
### CUPS / AirPrint Bridge
| Setting | Value |
|---------|-------|
| Role | AirPrint bridge for Epson ET-2810 printer |
| Image | `drpsychick/airprint-bridge:latest` |
| Container | `cups` |
| Location | `~/homelab/cups/` |
---
## Common Commands
### Docker
```bash
docker ps # List running containers
docker compose up -d # Start stack (from its directory)
docker compose down # Stop stack
docker compose logs -f # Follow logs
docker compose restart # Restart stack
docker stats # Live resource usage
```
### Homelab Script
```bash
homelab up # Start all services in order
homelab down # Stop all services in reverse order
homelab status # Show status of all stacks + HA VM
```
### KVM / Home Assistant VM
```bash
virsh list --all # Show all VMs and state
virsh start hass # Start HA VM
virsh shutdown hass # Graceful shutdown
virsh reboot hass # Reboot VM
virsh domifaddr hass # Show VM IP address
virsh dominfo hass # Show VM details
virsh attach-device hass ~/zbt2.xml --persistent # Attach ZBT-2 USB dongle
```
### Enshrouded
```bash
docker compose exec enshrouded supervisorctl start enshrouded-force-update # Force game update
docker compose exec enshrouded supervisorctl restart enshrouded-server # Restart server
```
### Nextcloud (OCC)
```bash
docker exec -u www-data nextcloud-app php occ [command]
# Useful commands:
# user:list, user:add
# files:scan --all / files:scan <user>
# files:cleanup
# maintenance:mode --on / --off
# maintenance:repair
# trashbin:cleanup --all-users
# app:list, app:enable <app>, app:disable <app>
# security:bruteforce:reset <ip>
```
### System
```bash
df -h / # Disk usage
htop # System resources
sudo ufw status # Firewall rules
crontab -l # Scheduled tasks
rsync -avh --progress # File transfers to slothhive over SSH
```
---
## Known Issues & Notes
1. **IPv6 disabled** — Docker Hub pulls fail over IPv6. Disabled system-wide in sysctl and in Docker daemon.json.
2. **BepInEx glibc error** — BepInEx doorstop requires GLIBC 2.33+; container has older version. Valheim runs vanilla.
3. **Nextcloud brute force** — iOS/macOS app login can trigger rate limiting. Reset with `occ security:bruteforce:reset <ip>`. Use app passwords if 2FA is enabled.
4. **Hairpin NAT** — Accessing public-facing services via domain from inside the LAN may not work on some routers. AdGuard wildcard rewrite handles this on the LAN.
5. **Single SSD** — All data on one 512 GB NVMe. Future plan: NAS for bulk storage, keep databases on SSD.
6. **Proton Drive rclone automation** — Blocked by upstream bug (PRs #9080/#9081 unmerged). Paperless backups synced manually via `paperless-backup` Fish function on CachyOS desktop.
7. **Matrix server name is permanent**`matrix.bumblesloth.ipv64.net` used directly; changing it would require a full rebuild.
8. **WebSockets** — Must be explicitly enabled in NPM for Foundry VTT and Matrix/Synapse (and Home Assistant).
9. **DDR4 UDIMM (288-pin)** — HP ProDesk 600 G6 SFF requires standard desktop DIMMs, not SO-DIMM. Important for future RAM upgrades.
10. **DynDNS auto-update** — FRITZ!Box handles updates automatically, but after a power outage verify the public IP matches ipv64.net if services are unreachable externally.
11. **Nextcloud DB passwords** — Contain `$` special characters; escaped as `$$` in docker-compose.yml to avoid Docker Compose variable interpolation warnings.
12. **HA OS requires KVM VM** — The OpenThread Border Router add-on (required for ZBT-2 Thread mode) is only available in HA OS, not HA Container. KVM VM coexists with Docker on the same host without conflict.
13. **HA VM network** — VM gets its IP via DHCP from FRITZ!Box through the `br0` bridge. NPM proxies to the VM by IP (`192.168.178.43`), not container name. If the VM IP changes, update NPM proxy host.
14. **br0 bridge required for VM** — KVM VM LAN access requires `br0` network bridge. UFW must allow routed traffic on `br0` (`ufw route allow in/out on br0`). Bridge netfilter must be disabled (`bridge-nf-call-iptables = 0`) to avoid Docker iptables conflicts.
15. **HA VM disk size** — VM disk expanded to 64 GB (from default 32 GB) to accommodate HA Core Docker image download during initial setup.
16. **mDNS limitations** — Auto-discovery (for iOS Companion app, LG TV, etc.) does not work across Docker bridge or into the KVM VM from LAN devices. Use manual IP/URL for all HA integrations.
---
## Future Plans
- [ ] Proton Drive rclone automation once upstream PRs merge
- [ ] NAS integration (NFS mount for bulk media/backup storage)
- [ ] Resolve BepInEx GLIBC compatibility for Valheim modding
- [ ] Navidrome (music streaming)
- [ ] Confirm macOS CalDAV resolution (brute force reset + app password)
- [ ] Add IKEA Matter over Thread devices via ZBT-2 border router