# Mikkel's Homelab Infrastructure

## Overview

This document describes the complete homelab infrastructure spanning Hetzner cloud servers, home NAS, and future expansion plans.

---

## Core Infrastructure

### core.georgsen.dk (Primary Server)

| Attribute | Value |
|-----------|-------|
| Provider | Hetzner (AX52) |
| Location | Helsinki, Finland |
| CPU | AMD Ryzen 7 3700X |
| RAM | 64GB ECC |
| Storage | 2x 1TB NVMe (RAID0) - ZFS (rpool) |
| OS | Proxmox VE |
| Public IP | 65.108.14.165/26 |
| IPv6 | 2a01:4f9:6a:4a4f::/64 |

**Warning:** Storage is RAID0 — no local redundancy. PBS backups to Synology are critical.

### Network Bridges

| Bridge | Purpose | Subnet | Gateway |
|--------|---------|--------|---------|
| vmbr0 | Public internet | 65.108.14.165/26 | 65.108.14.129 |
| vmbr1 | Internal services | 10.5.0.0/24 | 10.5.0.254 |
| vmbr2 | Hetzner vSwitch | 10.9.1.0/24 | 10.9.1.1 |

### vSwitch Details
- Cloud Network: #11828854
- Subnet: 10.9.1.0/24
- Routes to: 10.9.0.0/16 via 10.9.1.1
- MTU: 1400

---

## Network Configuration

### /etc/network/interfaces (core)

```
auto lo
iface lo inet loopback

auto enp9s0
iface enp9s0 inet manual

auto enp9s0.4000
iface enp9s0.4000 inet manual
    mtu 1400

auto vmbr0
iface vmbr0 inet static
    address 65.108.14.165/26
    gateway 65.108.14.129
    bridge-ports enp9s0
    bridge-stp off
    bridge-fd 0

iface vmbr0 inet6 static
    address 2a01:4f9:6a:4a4f::/64
    gateway fe80::1

auto vmbr1
iface vmbr1 inet static
    address 10.5.0.254/24
    bridge-ports none
    bridge-stp off
    bridge-fd 0

auto vmbr2
iface vmbr2 inet static
    address 10.9.1.2/24
    bridge-ports enp9s0.4000
    bridge-stp off
    bridge-fd 0
    mtu 1400
    up ip route add 10.9.0.0/16 via 10.9.1.1 dev vmbr2
    down ip route del 10.9.0.0/16 via 10.9.1.1 dev vmbr2
```

### Port Forwarding (iptables)

| External Port | Internal Destination | Purpose |
|---------------|---------------------|---------|
| 80 | 10.5.0.1:80 | NPM HTTP |
| 443 | 10.5.0.1:443 | NPM HTTPS |

NAT masquerade enabled for 10.5.0.0/24 → vmbr0

### Firewall Rules (INPUT on vmbr0)

**Home IP (83.89.248.247) is whitelisted - always allowed.**

| Protocol | Port | Action | Purpose |
|----------|------|--------|---------|
| ALL | * | ACCEPT | Allow home IP (83.89.248.247) |
| TCP/UDP | 111 | DROP | Block portmapper from internet |
| TCP/UDP | 53 | DROP | Block DNS (prevent amplification attacks) |
| TCP | 3128 | DROP | Block spiceproxy |
| TCP | 8006 | DROP | Block Proxmox UI (use home IP or Tailscale) |
| TCP | 8008 | DROP | Block Proxmox console |

Saved with: `netfilter-persistent save`

### DHCP (dnsmasq)
- Range: 10.5.0.100 - 10.5.0.200
- Lease time: 24h

---

## Virtual Machines

### VM 200: mail.georgsen.dk (Stalwart Mail Server)

| Attribute | Value |
|-----------|-------|
| Type | VM (KVM) |
| IP | 65.108.14.164 (dedicated public IP) |
| Bridge | vmbr0 (direct) |
| Software | Stalwart Mail Server |
| Webmail | Snappymail (via dockge) |

**Current domains:** dataloes.dk (building reputation before adding more)

**Planned domains:** georgsen.dk, microsux.dk, dataloes.dk

---

## LXC Containers

### Container Overview

| VMID | Name | IP | Purpose | Status |
|------|------|-----|---------|--------|
| 100 | npm | 10.5.0.1 | Nginx Proxy Manager | Running |
| 101 | dockge | 10.5.0.10 | Docker Compose Manager | Running |
| 102 | mgmt | 10.5.0.108 | Management/Automation (Claude Code) | Running |
| 103 | postgresql01 | DHCP | PostgreSQL (community) | Running |
| 104 | redis01 | DHCP | Redis (community) | Running |
| 105 | sentry | DHCP | Defense Intelligence System | Running |
| 106 | pbs | 10.5.0.6 | Proxmox Backup Server | Running |
| 107 | pve-scripts-local | DHCP | Community Scripts Web UI | Running |
| 108 | jukebox | DHCP (→10.5.0.184) | Music Player (custom project) | Running |
| 110 | sense.microsux.dk | DHCP | CBD Vendor Locator | Stopped |
| 111 | dev | DHCP | Development container | Running |
| 112 | dataloes | 10.5.0.112 | dataloes.dk website | Stopped |
| 113 | general | 10.5.0.113 | Decomissioned | Stopped |
| 114 | forgejo | 10.5.0.14 | Git server (Forgejo) | Running |
| 115 | dns | 10.5.0.2 | DNS server (Technitium) | Running |
| 1000 | tailscale | 10.5.0.x + 10.9.1.10 | Tailscale relay | Running |

### Container Details

#### 100: NPM (Nginx Proxy Manager)

- **Purpose:** Reverse proxy with automatic Let's Encrypt certificates
- **IP:** 10.5.0.1
- **Ports:** 80, 443, 81 (admin)
- **SSL:** Let's Encrypt (HTTP challenge)
- **Runtime:** Docker (docker-compose at /opt/npm)

**Update NPM:**
```bash
cd /opt/npm && docker compose pull && docker compose up -d
```

**Proxy Hosts:**

| Domain | Destination | SSL |
|--------|-------------|-----|
| dataloes.dk, www.dataloes.dk | http://10.5.0.112:80 | Let's Encrypt |
| dev01.georgsen.dk | http://10.5.0.113:3001 | Let's Encrypt |
| dns.georgsen.dk | http://10.5.0.2:5380 | Let's Encrypt |
| dockge.georgsen.dk | http://10.5.0.10:5001 | Let's Encrypt |
| git.georgsen.dk | http://10.5.0.14:3000 | Let's Encrypt |
| jukebox.georgsen.dk | http://10.5.0.184:4000 | Let's Encrypt |
| pbs.georgsen.dk | https://10.5.0.6:8007 | Let's Encrypt |
| status.georgsen.dk | http://10.5.0.10:3001 | Let's Encrypt |
| webmail.georgsen.dk | http://10.5.0.10:8888 | Let's Encrypt |
| dashboard.georgsen.dk | http://10.5.0.10:8090 | Let's Encrypt |

#### 101: Dockge

- **Purpose:** Docker Compose stack management
- **IP:** 10.5.0.10
- **Port:** 5001

**Running Stacks:**
```yaml
# Snappymail (webmail for Stalwart)
services:
  snappymail:
    image: djmaze/snappymail:latest
    container_name: snappymail
    restart: unless-stopped
    ports:
      - 8888:8888
    volumes:
      - ./data:/var/lib/snappymail

# Uptime Kuma (status monitoring)
services:
  uptime-kuma:
    image: louislam/uptime-kuma:1
    container_name: uptime-kuma
    restart: unless-stopped
    ports:
      - 3001:3001
    volumes:
      - ./data:/app/data

# Beszel (server monitoring dashboard)
services:
  beszel:
    image: henrygd/beszel:latest
    container_name: beszel
    restart: unless-stopped
    ports:
      - 8090:8090
    volumes:
      - ./data:/beszel_data
```

#### 105: Sentry (Defense Intelligence)

- **Purpose:** Custom defense intelligence system for monitoring military contracting opportunities
- **Focus:** Ground equipment (EV conversion, diesel upgrades, specialty vehicles, bomb loaders, Humvee engines)
- **Databases:** Uses postgresql01 and redis01

#### 106: PBS (Proxmox Backup Server)

- **Purpose:** Centralized backup for all PVE nodes
- **IP (vmbr1):** 10.5.0.6
- **Tailscale IP:** 100.115.85.120
- **Web UI:** https://10.5.0.6:8007

**Configuration:**
- Container type: Privileged (required for NFS/CIFS)
- Features: nesting=1
- AppArmor: unconfined
- TUN device enabled (for Tailscale)

**Datastore:**
- Name: `synology`
- Path: `/mnt/synology/datastore`
- Backend: Synology NAS via CIFS over Tailscale

**Namespaces:**
- `core.georgsen.dk`
- `pve01.warradejendomme.dk`
- `pve02.warradejendomme.dk`

**Retention Policy:**
- Keep Daily: 7
- Keep Weekly: 4
- Keep Monthly: 3

**Schedule:**
- Prune: 21:00
- GC: 22:30
- core backup: 01:00
- pve01 backup: 01:30
- pve02 backup: 02:00

#### 108: JukeBox

- **Purpose:** Custom music player
- **Source:** https://github.com/mikl0s/JukeBox
- **Port:** 4000

#### 114: Forgejo (Git Server)

- **Purpose:** Self-hosted Git server (GitHub alternative)
- **IP:** 10.5.0.14
- **Port:** 3000
- **URL:** https://git.georgsen.dk
- **Software:** Forgejo 10.0.1
- **Storage:** 60GB

#### 115: Technitium DNS

- **Purpose:** Local DNS server with web UI and API
- **IP:** 10.5.0.2
- **Ports:** 53 (DNS), 5380 (Web UI)
- **URL:** https://dns.georgsen.dk
- **Features:** Authoritative DNS for internal zone, upstream forwarder

#### 1000: Tailscale

- **Purpose:** Tailscale access to vmbr1 and vmbr2 networks
- **IPs:** vmbr1 (DHCP) + 10.9.1.10 (vmbr2)
- **Tailscale IP:** 100.99.33.100

---

## Backup Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│  BACKUP FLOW                                                    │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  core.georgsen.dk ────────┐                                     │
│  (01:00 daily)            │                                     │
│                           ▼                                     │
│  pve01.warradejendomme.dk ──► PBS (10.5.0.6)                    │
│  (01:30 daily)            │        │                            │
│                           │        │ CIFS over Tailscale        │
│  pve02.warradejendomme.dk ┘        ▼                            │
│  (02:00 daily)                Synology NAS                      │
│                               (100.105.26.130)                  │
│                                                                 │
│  Retention: 7 daily, 4 weekly, 3 monthly                        │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
```

### PBS Container Config (/etc/pve/lxc/106.conf)

```
arch: amd64
cores: 2
features: nesting=1
hostname: pbs
memory: 2048
net0: name=eth0,bridge=vmbr1,gw=10.5.0.254,hwaddr=BC:24:11:BF:BD:74,ip=10.5.0.6/24,type=veth
onboot: 1
ostype: debian
rootfs: local-zfs:subvol-106-disk-0,size=50G
swap: 512
lxc.cgroup2.devices.allow: c 10:200 rwm
lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file
lxc.cap.drop:
lxc.apparmor.profile: unconfined
```

### PBS fstab (/etc/fstab on PBS)

```
//100.105.26.130/pbs-backup /mnt/synology cifs credentials=/etc/pbs-smb.creds,uid=34,gid=34,vers=3.0,_netdev,x-systemd.automount,x-systemd.after=tailscaled.service,x-systemd.requires=tailscaled.service 0 0
```

### PBS Systemd Dependencies

```
# /etc/systemd/system/proxmox-backup-proxy.service.d/wait-for-mount.conf
[Unit]
After=mnt-synology.mount tailscaled.service
Requires=mnt-synology.mount

# /etc/systemd/system/proxmox-backup.service.d/wait-for-mount.conf
[Unit]
After=mnt-synology.mount tailscaled.service
Requires=mnt-synology.mount
```

### Synology NFS/CIFS Settings

- Share: `/volume1/pbs-backup`
- Protocol: CIFS (SMB 3.0)
- Squash: Map all users to admin
- Tailscale IP allowed: 100.115.85.120

---

## Other PVE Nodes

### pve01.warradejendomme.dk

- **Purpose:** Friend's server (former job), needs backups
- **Backup namespace:** `pve01.warradejendomme.dk`
- **PBS connection:** Via Tailscale (100.115.85.120:8007)

### pve02.warradejendomme.dk

- **Purpose:** Friend's server (former job), needs backups
- **Backup namespace:** `pve02.warradejendomme.dk`
- **PBS connection:** Via Tailscale (100.115.85.120:8007)

---

## Tailscale Network

| Device | Tailscale IP | Notes |
|--------|--------------|-------|
| pbs-1 (PBS) | 100.115.85.120 | PBS container |
| nas01 (Synology) | 100.105.26.130 | Home NAS, backup target |
| tailscale-pve01 | 100.99.33.100 | core's Tailscale LXC |
| pve01 | 100.99.118.54 | Friend's server |
| pve02 | 100.82.87.108 | Friend's server |
| dev | 100.85.227.17 | |
| sentry | 100.83.236.113 | |
| mge-t14 | 100.112.71.15 | |
| mikflix | 100.84.253.6 | |
| xanderryzen | 100.71.118.78 | |
| nvr01 | 100.118.17.103 | Exit node |
| tailscalemg | 100.115.101.65 | Exit node |

**Tailscale config:** SSH enabled on all devices where possible

---

## DNS

### External DNS
**Provider:** dns.services (Danish, free, has API for future automation)

**Domains:**
- georgsen.dk
- dataloes.dk
- microsux.dk
- warradejendomme.dk

### Internal DNS (Technitium)
- **Server:** 10.5.0.2
- **Web UI:** https://dns.georgsen.dk or http://10.5.0.2:5380
- **API:** http://10.5.0.2:5380/api/
- **Internal zone:** lab.georgsen.dk
- **Upstream:** Cloudflare (1.1.1.1), Google (8.8.8.8), Quad9 (9.9.9.9)

**Internal DNS Records (lab.georgsen.dk):**

| Name | IP |
|------|-----|
| npm | 10.5.0.1 |
| dns | 10.5.0.2 |
| pbs | 10.5.0.6 |
| dockge | 10.5.0.10 |
| forgejo | 10.5.0.14 |
| git | 10.5.0.14 |
| mgmt | 10.5.0.108 |

---

## Home Infrastructure

### Synology NAS (nas01)

- **Tailscale IP:** 100.105.26.130
- **Purpose:** Backup target, general NAS
- **PBS Share:** /volume1/pbs-backup
- **Note:** To be replaced in 2026 (noisy)

### Future: Intel NUC Cluster (3 nodes)

Planned home PVE cluster:

| Node | RAM | NVMe | SATA SSD | HDD |
|------|-----|------|----------|-----|
| Node 1 | 16GB | 500GB | 512GB | - |
| Node 2 | 16GB | 500GB | 512GB | - |
| Node 3 | 16GB | 2TB | 512GB | 5TB (CCTV) |

---

## User Conventions

### Standard User Setup

When creating containers/VMs:

```bash
# Create group and user with UID/GID 1000
groupadd -g 1000 georgsen
useradd -u 1000 -g 1000 -m -s /bin/bash mikkel

# Passwordless sudo
apt install -y sudo
echo "mikkel ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/mikkel
chmod 440 /etc/sudoers.d/mikkel

# SSH key
mkdir -p /root/.ssh /home/mikkel/.ssh
echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIOQrK06zVkfY6C1ec69kEZYjf8tC98icCcBju4V751i mikkel@georgsen.dk" | tee /root/.ssh/authorized_keys /home/mikkel/.ssh/authorized_keys
chmod 700 /root/.ssh /home/mikkel/.ssh
chmod 600 /root/.ssh/authorized_keys /home/mikkel/.ssh/authorized_keys
chown -R mikkel:georgsen /home/mikkel/.ssh

# Enable ping in unprivileged containers (required because cap_net_raw is dropped)
# Without this, ping fails with "Operation not permitted"
# Must be re-applied if iputils-ping package is upgraded
setcap cap_net_raw+ep /bin/ping
```

---

## Projects

### Sentry (Defense Intelligence)

Custom system monitoring military contracting news for business opportunities.
- Focus: Ground equipment (NOT aircraft/naval)
- Services: EV conversion, diesel upgrades, specialty vehicles, bomb loaders, Humvee engine upgrades

### JukeBox

Custom music player: https://github.com/mikl0s/JukeBox

### Sense Vendor Locator

CBD retailer locator for Denmark (sense.microsux.dk)

### dataloes.dk

Personal company website

---

## Preferences

- **Coding:** Python, Batch
- **UI Style:** 256-color terminal retro aesthetic
- **Development:** Ask clarifying questions, prefer understanding over workarounds
- **Tools:** Claude Code for development projects
- **Home IP:** 83.89.248.247 (static)

---

## Security

### NPM Access Lists

| ID | Name | IPs | Applied To |
|----|------|-----|------------|
| 1 | home_only | 83.89.248.247 | dns.georgsen.dk, dockge.georgsen.dk, pbs.georgsen.dk |

### Fail2ban

**core.georgsen.dk (PVE host):**
- Config: `/etc/fail2ban/jail.local`
- Jail: sshd
- Max retries: 5
- Ban time: 24 hours
- Whitelisted: 127.0.0.1, 10.5.0.0/24, 83.89.248.247

**Forgejo (VMID 114):**
- Config: `/etc/fail2ban/jail.local`
- Jail: forgejo
- Max retries: 5
- Ban time: 24 hours
- Log: `/var/lib/forgejo/log/forgejo.log`

---

## Maintenance Notes

### Cleanup Tasks

1. **Remove orphaned iptables rules** (Coolify is gone):
   ```bash
   iptables -t nat -D PREROUTING -i vmbr0 -p tcp --dport 6001 -j DNAT --to-destination 10.5.0.1:6001
   iptables -t nat -D PREROUTING -i vmbr0 -p tcp --dport 6002 -j DNAT --to-destination 10.5.0.1:6002
   netfilter-persistent save
   ```

2. **Containers to evaluate:**
   - 110 (sense.microsux.dk) - Consider consolidating
   - 112 (dataloes) - Stopped
   - 113 (general) - Decomissioned, can remove

3. **DHCP vs Static IPs:**
   - Containers .112 and .113 have static IPs inside DHCP range (100-200)
   - Consider moving static assignments to .2-.99 range

---

## Quick Reference

### Service URLs

| Service | URL |
|---------|-----|
| PVE (core) | https://65.108.14.165:8006 |
| PBS | https://pbs.georgsen.dk or https://10.5.0.6:8007 |
| NPM Admin | http://10.5.0.1:81 |
| Dockge | https://dockge.georgsen.dk |
| DNS Admin | https://dns.georgsen.dk or http://10.5.0.2:5380 |
| Forgejo | https://git.georgsen.dk or http://10.5.0.14:3000 |
| Status | https://status.georgsen.dk |
| Webmail | https://webmail.georgsen.dk |
| JukeBox | https://jukebox.georgsen.dk |
| Dashboard | https://dashboard.georgsen.dk or http://10.5.0.10:8090 |

### Important IPs

| Resource | IP |
|----------|-----|
| core public | 65.108.14.165 |
| mail public | 65.108.14.164 |
| NPM | 10.5.0.1 |
| DNS | 10.5.0.2 |
| PBS | 10.5.0.6 |
| Dockge | 10.5.0.10 |
| Forgejo | 10.5.0.14 |
| Synology (Tailscale) | 100.105.26.130 |
| PBS (Tailscale) | 100.115.85.120 |

### PBS Credentials

- **Web UI:** root@pam
- **API Token:** root@pam!pve
- **Fingerprint:** f6:5d:3f:63:0e:15:38:10:38:7a:33:82:3a:c0:b0:59:98:a7:04:7d:8d:ed:64:7d:fc:85:ab:8f:87:a4:48:eb

### Uptime Kuma API (from mgmt container)

- **Host:** 10.5.0.10:3001 (on Dockge container)
- **URL:** https://status.georgsen.dk
- **Config:** ~/homelab/uptime-kuma/credentials (symlinked to ~/.config/uptime-kuma)
- **Helper:** ~/bin/kuma (list, info, add-http, add-port, add-ping, delete, pause, resume)
- **Library:** uptime-kuma-api (Python, installed in ~/venv)

### Beszel Dashboard API (from mgmt container)

- **Host:** 10.5.0.10:8090 (Docker in Dockge)
- **URL:** https://dashboard.georgsen.dk
- **Config:** ~/homelab/beszel/credentials (symlinked to ~/.config/beszel)
- **Helper:** ~/bin/beszel (list, status, add, delete, alerts)
- **Library:** pocketbase (Python, installed in ~/venv)
- **Retention:** 30 days (automatic)
- **Agents:** core (10.5.0.254), pbs (10.5.0.6), dockge (10.5.0.10 + Docker stats)

### PVE API (from mgmt container)

- **Token:** root@pam!mgmt
- **Config:** ~/homelab/pve/credentials (symlinked to ~/.config/pve)
- **Helper:** ~/bin/pve (list, status, start, stop, create-ct)

### NPM API (from mgmt container)

- **Config:** ~/homelab/npm/npm-api.conf (symlinked)
- **Helper:** ~/bin/npm-api (--host-list, --host-create, --host-delete, --cert-list)

### DNS API (from mgmt container)

- **Config:** ~/homelab/dns/credentials (symlinked to ~/.config/dns)
- **Helper:** ~/bin/dns (list, records, add, delete, lookup)
- **Default zone:** lab.georgsen.dk

### Forgejo API (from mgmt container)

- **Config:** ~/homelab/forgejo/credentials (symlinked to ~/.config/forgejo)
- **Repo:** git@10.5.0.14:mikkel/homelab.git

---

*Last updated: 2026-01-14*
