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

21 KiB
Raw Blame History

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

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:

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

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):

<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

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

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

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

docker compose exec enshrouded supervisorctl start enshrouded-force-update  # Force game update
docker compose exec enshrouded supervisorctl restart enshrouded-server       # Restart server

Nextcloud (OCC)

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

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 permanentmatrix.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