# 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 | | 2456–2458 | 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 ``` **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 ` - `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 2456–2458 | | 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 # files:cleanup # maintenance:mode --on / --off # maintenance:repair # trashbin:cleanup --all-users # app:list, app:enable , app:disable # security:bruteforce:reset ``` ### 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 `. 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