From daf5ffc8d0103b25eb5bac09dab7458039d067ba Mon Sep 17 00:00:00 2001 From: karim Date: Thu, 4 Jun 2026 02:15:42 +0200 Subject: [PATCH] doc: konsolidierte Homelab-Dokumentation + Original-Plan --- README.md | 5 +- doc/README.md | 144 ++++++++++++ doc/plan/.env.example | 5 + doc/plan/docker-compose.yml | 111 +++++++++ doc/plan/homelab-anleitung.md | 261 ++++++++++++++++++++++ doc/plan/homelab-manifest.md | 232 +++++++++++++++++++ doc/plan/homelab-projekt-kontext.md | 81 +++++++ doc/plan/mariadb-tuning.cnf | 11 + doc/plan/migrations-backup-runbook.md | 148 ++++++++++++ doc/plan/nginx-nextcloud.conf | 54 +++++ doc/plan/pbs-und-tank-backup-anleitung.md | 163 ++++++++++++++ doc/plan/php-custom.ini | 22 ++ doc/plan/setup-pbs.sh | 136 +++++++++++ 13 files changed, 1372 insertions(+), 1 deletion(-) create mode 100644 doc/README.md create mode 100644 doc/plan/.env.example create mode 100644 doc/plan/docker-compose.yml create mode 100644 doc/plan/homelab-anleitung.md create mode 100644 doc/plan/homelab-manifest.md create mode 100644 doc/plan/homelab-projekt-kontext.md create mode 100644 doc/plan/mariadb-tuning.cnf create mode 100644 doc/plan/migrations-backup-runbook.md create mode 100644 doc/plan/nginx-nextcloud.conf create mode 100644 doc/plan/pbs-und-tank-backup-anleitung.md create mode 100644 doc/plan/php-custom.ini create mode 100644 doc/plan/setup-pbs.sh diff --git a/README.md b/README.md index 1afd1c9..5fee13f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ # homelab -openbureau Homelab — Dokumentation & Config-as-Code \ No newline at end of file +Dokumentation & Config-as-Code für das **openbureau Homelab** (Proxmox „tanin" + VPS). + +📖 **Komplette Doku:** [doc/README.md](./doc/README.md) +📋 Ursprünglicher Plan: [doc/plan/](./doc/plan/) diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..040a810 --- /dev/null +++ b/doc/README.md @@ -0,0 +1,144 @@ +# openbureau Homelab — Dokumentation + +> Stand: 2026-06-04 · Aktueller Ist-Zustand des fertig aufgebauten Homelabs. +> Der ursprüngliche Plan liegt unter [`plan/`](./plan/) (kann von der Realität abweichen). + +--- + +## 1. Architektur in einem Bild + +``` + Internet + │ + ┌───────▼─────────┐ WireGuard-Reverse-Tunnel ┌──────────────────────────┐ + │ VPS 46.225.70.71│◄════════════════════════════════════════│ Heim-Gateway CT101 │ + │ (Caddy + Mail) │ VPS wählt NICHT an — das HEIM dialt │ 10.10.0.2 / wählt VPS an │ + │ *.kgva.ch │ (Heim öffnet KEINE Ports) └────────────┬──────────────┘ + │ Let's Encrypt │ │ + └──────────────────┘ Heim-LAN 192.168.1.0/24 + Proxmox "tanin" 192.168.1.2 + (LXC-Container, ZFS) +``` + +- **Heim (Proxmox „tanin", 192.168.1.2):** Hypervisor. Ein Dienst = ein LXC-Container. ZFS: `rpool` (System, NVMe), `tank` (Daten, 2×4 TB Mirror). +- **VPS (46.225.70.71):** **Caddy** terminiert HTTPS für alle Domains (Let's-Encrypt-Wildcard `*.kgva.ch`) und proxyt via Tunnel ins Heim-LAN. Hostet zusätzlich den **Mailserver** (docker-mailserver). +- **WireGuard-Reverse-Tunnel:** Das Heim-Gateway (CT101) baut den Tunnel zum VPS auf → **das Heim muss KEINE Ports öffnen**. Der VPS routet `192.168.1.0/24` durch den Tunnel. +- **AdGuard (CT103):** netzweiter **DHCP** (.10–.99) + **DNS** + Adblock. +- **Zugang von außen:** + - *Einzelne Dienste:* öffentliche Domain → VPS-Caddy → Heim. Kein VPN nötig. + - *Komplettes Heimnetz:* **WireGuard-Client** (Laptop/Handy-Config, nutzt AdGuard als DNS). + +--- + +## 2. Dienst-Inventar + +> **IP-Konvention:** `192.168.1.` (außerhalb des DHCP-Bereichs). Ausnahmen: alte Instanzen behalten .177/.178. + +| CT | IP | Dienst | Domain(s) | Zweck | +|----|------|--------|-----------|-------| +| 100 | .100 | **PBS** | — (intern :8007) | Proxmox Backup Server | +| 101 | .101 | **wg-gateway** | — | WireGuard-Reverse-Tunnel zum VPS | +| 102 | .102 | **fileserver** | — (Samba) | Samba-Shares `kgva` + `private` (in Nextcloud eingebunden) | +| 103 | .103 | **adguard** | — (:80) | DHCP + DNS + Adblock | +| 110 | .110 | **nextcloud** (neu) | cloud.kgva.ch | Hauptcloud (Rieger-Setup, shibui-Theme) | +| 111 | .111 | **immich** | immich.kgva.ch | Fotos/Videos | +| 112 | .112 | **vaultwarden** | vault.kgva.ch · vault.gabrielevarano.ch | Passwort-Tresor | +| 113 | .113 | **paperless** | — (:8000) | Dokumente + OCR | +| 114 | .114 | **dashboard** | — (:8080, intern/VPN) | Glance-Dashboard | +| 115 | .115 | **collabora** | collabora.kgva.ch (:9980) · whiteboard.kgva.ch (:3002) | Office + Whiteboard | +| 116 | .116 | **stirling** | pdf.kgva.ch | Stirling-PDF (PDF-Werkzeuge) | +| 120 | .120 | **gitea** | git.openbureau.ch · git.kgva.ch | Git (DIESES Repo) | +| 130 | .130 | **kgva-website** | karimgabrielevarano.xyz | Website | +| 132 | .132 | **rapport-website** | rapport.openbureau.ch | Website | +| 133 | .133 | **dossier-website** | dossier.openbureau.ch | Website | +| 134 | .134 | **openbureau-dev** | dev.openbureau.ch | Dev (Supabase-style :8000 / Web :8080) | +| 135 | .135 | **trattoriamarina.ch** | (Website) | Marinas Trattoria | +| 140 | .140 | **studio** | studio.kgva.ch (:8080) · api.studio.kgva.ch (:8000) | **Supabase** (Social-Media-Backend) — *von anderer Claude-Instanz betreut* | +| 800 | **.177** | **nextcloud-old** | cloud.gabrielevarano.ch | ALTE Nextcloud (Altdaten, 400 GB-Quota) | +| 802 | .14 (DHCP) | vaultwarden-old | — | alt, läuft sporadisch | +| 805/807/810/811/910/919/990 | — | *-old | — | alte Pre-Rebuild-Gäste (meist gestoppt) | + +**VPS-Dienste (auf 46.225.70.71):** +| Domain | Dienst | +|--------|--------| +| mail.kgva.ch | docker-mailserver (Postfix/Dovecot/Rspamd, DKIM) | +| webmail.kgva.ch | SnappyMail | +| mailadmin.kgva.ch | DMS Admin-UI | + +**Mail-Konten:** u. a. `karim@gabrielevarano.ch` (von hosttech migriert), kgva.ch-Adressen. MX → `mail.kgva.ch`, SPF/DKIM/DMARC gesetzt. + +--- + +## 3. Backup + +- **PBS (CT100)** · Datastore **`backup`** auf der **Seagate Expansion HDD** (USB, xfs, `/datastore-hdd`, bind in CT100). +- **PVE-Storage:** `pbs` → Datastore `backup`. +- **Job:** täglich **02:30 + 14:30** (2×/Tag), alle Gäste, `mode snapshot`, **inkrementell + dedupliziert**. +- **Retention:** `keep-last=4, keep-daily=7, keep-weekly=4, keep-monthly=3`. +- **Kingston XS1000 SSD** wurde entfernt/freigeräumt (war der alte Pre-Wipe-Speicher). +- **Reboot-Fix:** `/etc/tmpfiles.d/zz-pbs-runtime.conf` in CT100 (sonst `/run/proxmox-backup`-EACCES nach Reboot). +- **Wiederherstellung:** PBS-WebUI (CT100:8007) oder `qmrestore`/`pct restore` aus dem `pbs`-Storage. + +--- + +## 4. Netzwerk / DNS + +- **DHCP-Bereich (AdGuard):** 192.168.1.**10–99** → statische IPs immer ≥ .100 wählen. +- **Statische IP für eine CT:** PVE `net0` auf `ip=192.168.1.X/24,gw=192.168.1.1` **UND** intern `/etc/network/interfaces` → `iface eth0 inet static` (sonst zieht der Container trotzdem DHCP!). +- **AdGuard-DNS-Rewrites:** aktuell keine (immich.gabrielevarano.ch wurde entfernt → Immich nur noch über `immich.kgva.ch`). +- **Wildcard `*.kgva.ch`** → VPS. `gabrielevarano.ch` hat eine Wildcard, `openbureau.ch`/`karimgabrielevarano.xyz` eigene A-Records. + +--- + +## 5. Zugang & Passwörter + +- **SSH:** `~/.ssh/proxmox_homelab` → Host .2 · `~/.ssh/vps_kgva` → VPS. +- **Proxmox-UI:** https://192.168.1.2:8006 (root@pam). Subscription-Nag ist dauerhaft gepatcht (siehe How-Tos). +- **Glance-Dashboard:** http://192.168.1.114:8080 (nur intern/VPN). +- **Service-Logins:** sollen alle in **Vaultwarden** zentralisiert werden (Rotations-Skript: `/root/rotate-passwords.py` auf dem Host → erzeugt Bitwarden-Import-CSV). + - ⚠️ **Sicherheits-To-do:** Viele Dienste nutzen aktuell noch `Rationalism2025!`. **AdGuard:** `admin` / `Rationalism2026!`. → bei Gelegenheit rotieren. + +--- + +## 6. How-Tos + +### Neuen Dienst hinzufügen +1. CT anlegen (Debian-Template, unprivilegiert, `features nesting=1,keyctl=1`, static `ip=192.168.1.X/24,gw=…`). +2. `/etc/network/interfaces` intern auf **static** stellen. +3. Docker installieren (`curl -fsSL https://get.docker.com | sh`) + Dienst per compose. +4. Im **VPS-Caddyfile** (`/opt/dms-stack/caddy/Caddyfile`) Block ergänzen: + ``` + meinedienst.kgva.ch { + reverse_proxy 192.168.1.X:PORT + } + ``` + dann `docker restart dms-caddy` (Caddy holt das Cert automatisch, da `*.kgva.ch`-Wildcard). +5. Optional: ins Glance-Dashboard (Monitor + Bookmark) aufnehmen. + +### Proxmox-Subscription-Nag (dauerhaft entfernt) +- Patch-Script: `/usr/local/bin/pve-no-nag.sh` (setzt `!== 'active'` → `=== 'active'` in `proxmoxlib.js`). +- apt-Hook `/etc/apt/apt.conf.d/no-nag-script` re-patcht nach **jedem** Update. +- Kommt der Nag mal wieder: nur Browser-Cache (Strg+Shift+R), der Server ist gepatcht. + +### Glance-Dashboard +- CT114, Docker, Config in `/opt/glance/{config/glance.yml, assets/}`. +- **Wichtig:** Custom-CSS muss unter `server.assets-path` (`/app/assets`) liegen und als `/assets/custom.css` referenziert werden (aus `/app/config` liefert Glance es mit 404). +- Daten-API für die Widgets: Python-Dienst auf dem **Host** (`/opt/zfs-api/zfs_api.py`, systemd `zfs-api`, Port 9099) → `/pools /guests /errors /backup /sysinfo /adguard`. Plus **Glances** auf dem Host (systemd `glances`, Port 61208, `-w --disable-webui`). + +### Whiteboard / Collabora (Nextcloud) +- Beide laufen in CT115. WOPI-Allowlist auf `0.0.0.0/0,::/0` (Tunnel-Routing macht IP-Härtung unpraktikabel). +- Whiteboard-Verify-Button meldet fälschlich „timeout" — die Echtzeit-Collaboration funktioniert trotzdem. + +--- + +## 7. Offene Punkte / To-do + +- [ ] **openbureau.ch (CT131)** — Caddy zeigt auf .131:80, Container existiert noch nicht → 502. openbureau-Suite bauen (Website/dev/app/rapport-server). +- [ ] **Passwörter rotieren** (`/root/rotate-passwords.py --apply`) + in Vaultwarden importieren. +- [ ] Master-Passwörter (Vaultwarden) + `Rationalism2025!` ablösen. +- [ ] Entscheiden: Flarum (CT810) / Obsidian-LiveSync (CT805) zurückbringen? +- [ ] Alte „-old"-Container aufräumen (NACH der Suite-Migration; CT800 mit Altdaten behalten). + +--- + +*Diese Doku wird manuell gepflegt. Bei Änderungen am Setup hier nachziehen.* diff --git a/doc/plan/.env.example b/doc/plan/.env.example new file mode 100644 index 0000000..75b2cd5 --- /dev/null +++ b/doc/plan/.env.example @@ -0,0 +1,5 @@ +DB_ROOT_PW=change-me-root +DB_PW=change-me-nc +REDIS_PW=change-me-redis +COLLABORA_PW=change-me-collabora +WHITEBOARD_JWT=change-me-langes-zufalls-secret diff --git a/doc/plan/docker-compose.yml b/doc/plan/docker-compose.yml new file mode 100644 index 0000000..408bff5 --- /dev/null +++ b/doc/plan/docker-compose.yml @@ -0,0 +1,111 @@ +# nextcloud stack — rieger-getuned, caddy-ready, mit collabora + whiteboard +# läuft im LXC 110. rootfs/app auf NVMe, daten auf tank (/srv/nc_data = tank/nextcloud). +# caddy terminiert TLS und proxyt cloud.deinedomain.ch -> web:80 +# office.deinedomain.ch -> collabora:9980 +# whiteboard.deinedomain.ch -> whiteboard:3002 + +services: + db: + image: mariadb:11.4 + restart: unless-stopped + command: --transaction-isolation=READ-COMMITTED --log-bin=binlog --binlog-format=ROW + volumes: + - db:/var/lib/mysql + - ./config/mariadb-tuning.cnf:/etc/mysql/conf.d/99-nextcloud.cnf:ro + environment: + - MARIADB_ROOT_PASSWORD=${DB_ROOT_PW} + - MARIADB_DATABASE=nextcloud + - MARIADB_USER=nextcloud + - MARIADB_PASSWORD=${DB_PW} + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + interval: 10s + retries: 5 + + redis: + image: redis:7-alpine + restart: unless-stopped + command: redis-server --requirepass ${REDIS_PW} + volumes: + - redis:/data + + app: + image: nextcloud:fpm + restart: unless-stopped + depends_on: + db: + condition: service_healthy + redis: + condition: service_started + volumes: + - nextcloud:/var/www/html + - /srv/nc_data:/var/www/html/data # <- tank/nextcloud mount im LXC + - ./config/php-custom.ini:/usr/local/etc/php/conf.d/zz-nextcloud.ini:ro + environment: + - MYSQL_HOST=db + - MYSQL_DATABASE=nextcloud + - MYSQL_USER=nextcloud + - MYSQL_PASSWORD=${DB_PW} + - REDIS_HOST=redis + - REDIS_HOST_PASSWORD=${REDIS_PW} + - NEXTCLOUD_TRUSTED_DOMAINS=cloud.deinedomain.ch + - TRUSTED_PROXIES=10.0.0.100 # <- Caddy-IP + - OVERWRITEPROTOCOL=https + - OVERWRITECLIURL=https://cloud.deinedomain.ch + - PHP_MEMORY_LIMIT=1024M + - PHP_UPLOAD_LIMIT=16G + + web: + image: nginx:alpine + restart: unless-stopped + depends_on: [app] + volumes: + - nextcloud:/var/www/html:ro + - /srv/nc_data:/var/www/html/data:ro + - ./config/nginx-nextcloud.conf:/etc/nginx/conf.d/default.conf:ro + ports: + - "8110:80" # <- Caddy proxyt hierauf + + cron: + image: nextcloud:fpm + restart: unless-stopped + depends_on: [db, redis] + volumes: + - nextcloud:/var/www/html + - /srv/nc_data:/var/www/html/data + entrypoint: /cron.sh + + collabora: + image: collabora/code:latest + restart: unless-stopped + cap_add: [MKNOD] + environment: + - aliasgroup1=https://cloud\.deinedomain\.ch:443 + - server_name=office.deinedomain.ch + - username=admin + - password=${COLLABORA_PW} + - extra_params=--o:ssl.enable=false --o:ssl.termination=true + ports: + - "9980:9980" # <- Caddy proxyt office.deinedomain.ch hierauf + + whiteboard: + image: ghcr.io/nextcloud-releases/whiteboard:stable + restart: unless-stopped + environment: + - NEXTCLOUD_URL=https://cloud.deinedomain.ch + - JWT_SECRET_KEY=${WHITEBOARD_JWT} + ports: + - "3002:3002" # <- Caddy proxyt whiteboard.deinedomain.ch hierauf + + # optional, aber rieger-im-geiste: schnellere previews + imaginary: + image: nextcloud/aio-imaginary:latest + restart: unless-stopped + cap_add: [SYS_NICE] + environment: + - PORT=9000 + +volumes: + nextcloud: + db: + redis: diff --git a/doc/plan/homelab-anleitung.md b/doc/plan/homelab-anleitung.md new file mode 100644 index 0000000..b7d547e --- /dev/null +++ b/doc/plan/homelab-anleitung.md @@ -0,0 +1,261 @@ +# homelab — aufbau-anleitung + +Schritt-für-Schritt nach dem Manifest. Reihenfolge ist bewusst: erst Fundament (Pools, Datasets), dann Netzgerüst (Caddy/WireGuard/DNS), dann Apps. Domains und IPs hier als Platzhalter (`deinedomain.ch`, `10.0.0.x`) — anpassen. + +--- + +## 1 — proxmox & rpool + +Proxmox-Installer von USB. Bei der Disk-Auswahl die NVMe nehmen, Filesystem **zfs (RAID0)** (= Single-Disk ZFS), `ashift=12`, compression `lz4`. Das wird dein `rpool`. + +Nach dem ersten Boot: Enterprise-Repo raus, No-Subscription-Repo rein, updaten. + +```bash +zpool status # rpool sollte ONLINE sein +zfs set atime=off rpool +``` + +--- + +## 2 — tank (mirror) anlegen + +Disk-IDs holen (nie /dev/sdX verwenden — die wandern): + +```bash +ls -l /dev/disk/by-id/ | grep -i ata # die zwei 4TB-Platten identifizieren + +zpool create -o ashift=12 \ + -O compression=lz4 -O atime=off -O xattr=sa \ + tank mirror /dev/disk/by-id/PLATTE1 /dev/disk/by-id/PLATTE2 + +zpool status tank # mirror, beide ONLINE +``` + +Monatlichen Scrub einrichten: + +```bash +echo '0 3 1 * * root /sbin/zpool scrub tank' > /etc/cron.d/zfs-scrub +``` + +--- + +## 3 — datasets & quotas + +```bash +zfs create -o quota=1300G tank/business +zfs create -o quota=300G tank/private +zfs create -o quota=600G -o recordsize=1M tank/immich +zfs create -o quota=150G tank/paperless +zfs create -o quota=150G tank/nextcloud +zfs create -o quota=150G tank/dump +zfs create -o quota=50G tank/gitea + +zfs list -o name,quota,used tank # kontrollieren +``` + +`tank/dump` als Proxmox-Backup-Storage eintragen: Datacenter → Storage → Add → Directory, Pfad `/tank/dump`, Inhalt "VZDump backup file". + +--- + +## 4 — LXC-muster & mountpoints + +Pro Container: rootfs auf `rpool`, Bulk-Daten auf `tank`. Beispiel Nextcloud (CTID 110), exklusives Dataset → verwalteter Mountpoint: + +```bash +pct set 110 -mp0 tank/nextcloud,mp=/data,backup=0 +``` + +Fileserver (102) braucht die **geteilten** Datasets als Bind-Mount (mehrere Dienste im selben LXC): + +```bash +pct set 102 -mp0 /tank/business,mp=/srv/business +pct set 102 -mp1 /tank/private,mp=/srv/private +``` + +**Hinweis unprivilegierte LXC:** in-container UID wird um 100000 verschoben. Damit Schreiben klappt, dem Dataset die verschobene UID/GID geben, z.B. für in-container `1000`: + +```bash +chown -R 101000:101000 /tank/business /tank/private +``` + +Tipp: alle drei Zugriffsdienste (Samba/Filebrowser/Syncthing) **im selben LXC 102** halten — dann gibt es keine container-übergreifenden UID-Probleme. + +--- + +## 5 — fileserver: samba + filebrowser + syncthing (LXC 102) + +Debian-LXC, dann: + +**Samba** (zwei Shares): + +```bash +apt install -y samba +groupadd buro; useradd -M -G buro karim; smbpasswd -a karim +``` + +`/etc/samba/smb.conf` (Auszug): + +```ini +[business] + path = /srv/business + valid users = @buro + read only = no + force group = buro + create mask = 0660 + directory mask = 0770 + +[private] + path = /srv/private + valid users = karim + read only = no + create mask = 0600 + directory mask = 0700 +``` + +```bash +systemctl restart smbd +``` + +Mounten am Mac: Finder → ⌘K → `smb://10.0.0.102/business`. Linux: `mount -t cifs //10.0.0.102/business /mnt/business -o user=karim`. + +**Filebrowser** (Web + Share-Links), rooted über beide Shares: + +```bash +curl -fsSL https://raw.githubusercontent.com/filebrowser/get/master/get.sh | bash +filebrowser config init +filebrowser config set --root /srv --address 0.0.0.0 --port 8080 +filebrowser users add karim DEINPASSWORT --perm.admin +``` + +Als systemd-Service einrichten (Unit anlegen, enable). Caddy proxyt später `files.deinedomain.ch` → `10.0.0.102:8080`. + +**Syncthing** (das "Drive"): `apt install syncthing`, als User-Service starten, Web-UI auf `:8384`, Ordner `/srv/business` (oder gezielte Unterordner) freigeben und auf dem Laptop verbinden. Da Syncthing plain files schreibt, bleibt SMB parallel nutzbar. + +--- + +## 6 — ordnerstruktur anlegen + +```bash +# business +mkdir -p /srv/business/{_vorlagen,_buro,_bibliothek/{details,cad-blocks,materialien,referenzen},projekte} +mkdir -p /srv/business/projekte/2026-001_beispiel/{00_admin,01_grundlagen,02_vorstudien,03_vorprojekt,04_bauprojekt,05_bewilligung,06_ausschreibung,07_ausfuhrung,08_abschluss,cad,plaene,visualisierung,fotos} + +# private +mkdir -p /srv/private/{dokumente/2026,finanzen,studium,projekte,scans,archiv} + +chown -R 101000:101000 /srv/business /srv/private +``` + +Für neue Projekte das `2026-001_beispiel`-Gerüst als Vorlage kopieren (oder ein kleines Skript in `_vorlagen/` ablegen). + +--- + +## 7 — caddy (LXC 100) + +`apt install caddy`, dann `/etc/caddy/Caddyfile`: + +``` +files.deinedomain.ch { reverse_proxy 10.0.0.102:8080 } +cloud.deinedomain.ch { reverse_proxy 10.0.0.110:80 } +fotos.deinedomain.ch { reverse_proxy 10.0.0.111:2283 } +www.deinedomain.ch { root * /srv/www; file_server } +``` + +```bash +systemctl reload caddy +``` + +Caddy holt TLS automatisch. Am Router nur 80/443 auf Caddy forwarden. + +--- + +## 8 — wireguard (LXC 101, wg-easy) + +Docker im LXC, wg-easy gibt dir eine GUI für Peers: + +```bash +docker run -d --name wg-easy --cap-add NET_ADMIN --cap-add SYS_MODULE \ + -e WG_HOST=vpn.deinedomain.ch -e PASSWORD_HASH='...' \ + -p 51820:51820/udp -p 51821:51821/tcp \ + -v ~/.wg-easy:/etc/wireguard --restart unless-stopped \ + ghcr.io/wg-easy/wg-easy +``` + +Am Router **nur** UDP 51820 forwarden. Peers in der UI anlegen, QR scannen. Im Tunnel erreichst du das interne Subnetz → alle privaten Dienste, ohne sie je öffentlich zu machen. + +--- + +## 9 — adguard home (LXC 103) + +Installieren, als DNS im Router (oder im WireGuard-Profil) setzen. Interne A-Records anlegen, damit `cloud.deinedomain.ch` etc. intern auf die LXC-IPs zeigen → gleiche Hostnamen drinnen wie draussen. + +--- + +## 10 — app-stacks (NC, immich, paperless) + +Pro Stack ein Docker-LXC, compose-File ins Gitea. Muster für **Nextcloud** (110): + +- compose mit `app`, `db` (Postgres), `redis`. Volumes für App/DB auf rootfs (NVMe). +- Daten-Volume auf das gemountete `/data` (= `tank/nextcloud`). +- Caddy proxyt `cloud.deinedomain.ch`. + +**Immich** (111) und **Paperless-ngx** (113) analog — offizielle compose-Files nehmen (ändern sich regelmässig, drum upstream statt hier einfrieren) und nur den Daten-Pfad auf das jeweilige `tank/...`-Mount zeigen. Paperless' Consume-Ordner auf `/srv/private/scans` zeigen lassen → Scans landen automatisch im Archiv. + +--- + +## 11 — monitoring + +- **Uptime Kuma** (140): jeden Dienst als HTTP-Check anlegen, Push aufs Handy (ntfy/Telegram). +- **Beszel** (141): Agent auf dem Proxmox-Host, Pool-Auslastung im Blick. +- **Pool-Alert <85%** als simpler Cron: + +```bash +cat > /etc/cron.hourly/zfs-capacity << 'EOF' +#!/bin/bash +cap=$(zpool list -H -o capacity tank | tr -d '%') +[ "$cap" -ge 85 ] && echo "tank $cap%" | ntfy publish homelab +EOF +chmod +x /etc/cron.hourly/zfs-capacity +``` + +--- + +## 12 — backup (sanoid + restic) + +**Snapshots** (sanoid) — `/etc/sanoid/sanoid.conf`: + +```ini +[tank/business] + use_template = prod +[tank/private] + use_template = prod +[tank/immich] + use_template = prod + +[template_prod] + hourly = 24 + daily = 30 + monthly = 6 + autosnap = yes + autoprune = yes +``` + +**Offsite** zur Hetzner Storage Box (Restic über SFTP): + +```bash +export RESTIC_REPOSITORY="sftp:uXXXXX@uXXXXX.your-storagebox.de:/backup" +export RESTIC_PASSWORD_FILE=/root/.restic-pw +restic init +restic backup /tank/business /tank/private /tank/nextcloud /tank/immich /tank/paperless /tank/gitea +restic forget --keep-daily 14 --keep-weekly 8 --keep-monthly 12 --prune +``` + +In einen täglichen Cron packen. + +**Restore-Test (Pflicht):** einmal `restic restore latest --target /tmp/restoretest --include /tank/private/dokumente` und prüfen ob die Files stimmen. Ein ungetestetes Backup ist kein Backup. + +--- + +## reihenfolge auf einen blick + +1. Proxmox + rpool → 2. tank Mirror + Scrub → 3. Datasets + Quotas → 4. Fileserver (Samba/Filebrowser/Syncthing) + Ordner → 5. Caddy + WireGuard + AdGuard → 6. Kern-Apps → 7. Monitoring → 8. Backup + Restore-Test → 9. Erweiterungen nach Lust. diff --git a/doc/plan/homelab-manifest.md b/doc/plan/homelab-manifest.md new file mode 100644 index 0000000..f63ce79 --- /dev/null +++ b/doc/plan/homelab-manifest.md @@ -0,0 +1,232 @@ +# homelab manifest + +> Referenz für Aufbau und Betrieb meines Proxmox-Servers. +> Architekturarbeit + privat. Ein Mini-PC, 1× 1TB NVMe, 2× 4TB, 1× 2TB externe HDD. +> An dieses Dokument richte ich die Maschine aus — nicht umgekehrt. + +--- + +## prinzipien + +1. **Filesystem-first.** Das Dateisystem ist die Wahrheit. Web-UIs, SMB und Sync sind nur Clients darauf. Keine App "besitzt" die Daten allein. +2. **FOSS und self-hosted**, config-as-code. Jede Konfiguration (Caddyfile, compose, ZFS-Layout, Samba) liegt versioniert im Gitea. +3. **Öffentlich = Caddy, privat = WireGuard-only.** Nach aussen offen: nur der WireGuard-Port plus 80/443 für die öffentlichen Websites. Alles andere erreiche ich nur durch den Tunnel. +4. **RAID ist kein Backup.** 3-2-1: 3 Kopien, 2 Medien, 1 offsite. +5. **ZFS-Pool nie über ~85%.** Copy-on-Write braucht Luft. Auslastung wird überwacht, nicht geschätzt. +6. **Grosse Daten ≠ vzdump.** Bulk-Mounts laufen mit `backup=0`, separat gesichert per `zfs send`/Restic. vzdump nur für die schlanken rootfs. +7. **Bewusster Hybrid.** Single-purpose Infra → native LXC. Echte Multi-Container-App-Stacks → ein Docker-LXC pro Stack. Nie "ein Docker pro LXC für alles". +8. **Ein Werkzeug pro Aufgabe.** Lieber ein schlankes Tool das eine Sache gut macht als ein Monolith der alles halb kann. +9. **Reserve ist eingeplant**, nicht zufällig. Quotas sind Sicherheitsdeckel, kein starres Partitionieren. + +--- + +## hardware & pools + +| Pool | Medium | Topologie | nutzbar | Zweck | +|------|--------|-----------|---------|-------| +| `rpool` | 1× 1TB NVMe | Single-Disk ZFS | ~900 GiB | OS, alle rootfs, **Datenbanken**, oft gelesene Dienste | +| `tank` | 2× 4TB | **ZFS Mirror (RAID1)** | ~3.6 TiB | schwere Daten: Shares, Nextcloud, Immich, Paperless | +| — | 1× 2TB ext. HDD | — | ~1.8 TiB | lokales Backup-Ziel (`zfs recv` / Restic) | + +ZFS-Settings beide Pools: `ashift=12`, `compression=lz4`, `atime=off`. Mirror überlebt einen Plattentod und heilt Bitrot beim Scrub (monatlich). NVMe ist Einzelplatte → keine Redundanz, aber nichts Unersetzliches liegt nur dort. + +--- + +## dienste & container + +Native LXC für Infra, ein Docker-LXC pro echtem App-Stack. CTID nach Rolle in Blöcken. +**[K]** = Kern (zuerst), **[+]** = Erweiterung (nach Bedarf). + +| CTID | Dienst | Typ | rootfs | Daten | für | | +|------|--------|-----|--------|-------|-----|--| +| 100 | Caddy (Reverse Proxy) | native | 8G | — | beide | K | +| 101 | WireGuard (wg-easy) | docker | 4G | — | beide | K | +| 102 | Fileserver — Samba + Filebrowser + Syncthing | native | 8G | `tank/business`, `tank/private` | beide | K | +| 103 | AdGuard Home (DNS + Adblock) | native | 4G | — | beide | + | +| 110 | Nextcloud (app+db+redis) | docker | 40G | `tank/nextcloud` | privat | K | +| 111 | Immich (server+ML+db+redis) | docker | 60G | `tank/immich` | privat | K | +| 112 | Vaultwarden | native | 4G | — | beide | K | +| 113 | Paperless-ngx (DMS + OCR) | docker | 30G | `tank/paperless` | Arbeit | + | +| 114 | Stirling-PDF (PDF-Toolkit) | docker | 8G | — | Arbeit | + | +| 115 | Collabora Online (Office in NC) | docker | 16G | — | Arbeit | + | +| 116 | BookStack (Büro-Wiki / Detailbibliothek) | docker | 8G | — | Arbeit | + | +| 120 | Gitea | native | 8G | `tank/gitea` | Arbeit | K | +| 121 | CI-Runner (Forgejo/Woodpecker) | docker | 16G | — | Arbeit | + | +| 122 | Penpot (Open-Source Figma) | docker | 16G | — | Arbeit | + | +| 130+ | Websites (Hugo/static via Caddy) | native | 8G ea. | — | Arbeit | K | +| 140 | Uptime Kuma (Monitoring/Status) | docker | 8G | — | Betrieb | + | +| 141 | Beszel (System-/Pool-Metriken) | docker | 8G | — | Betrieb | + | +| 142 | Homepage (Dashboard / "Launcher") | docker | 4G | — | Betrieb | + | +| 160 | Jellyfin (Medien) | docker | 16G | `tank/media`* | privat | + | +| 161 | Navidrome (Musik) | native | 8G | `tank/media`* | privat | + | +| 162 | Linkwarden (Referenz-/Bookmark-Archiv) | docker | 16G | — | beide | + | +| 163 | Miniflux (RSS) | docker | 4G | — | privat | + | + +Nummernschema: 100–109 Infra/Netz · 110–119 Cloud-Apps · 120–129 Dev · 130–139 Web · 140–149 Betrieb · 160–169 Medien/Privat. Lücken bleiben. + +\* Medienbibliothek braucht eine bewusste Budget-Entscheidung — bei echter Film-Sammlung eher eine zusätzliche Platte einplanen statt den Tank zu sprengen. + +--- + +## app-katalog: was sonst noch sinn macht + +**Fürs Büro (stark empfohlen)** +- **Paperless-ngx** — Dokumentenarchiv mit OCR und Volltextsuche. Löst das Papierproblem: Verträge, Rechnungen, Baugesuche, Korrespondenz scannen → durchsuchbar. Watch-Ordner zieht Scans automatisch ein. +- **Stirling-PDF** — alles für PDFs: zusammenführen, teilen, OCR, komprimieren, signieren. Du lebst in Plan-PDFs und Eingaben. +- **Collabora Online** — Office-Dokumente direkt im Browser/in Nextcloud editieren. XLSX-kompatibel für Raumlisten. +- **BookStack** — Büro-Wiki: Detailbibliothek-Doku, Standards, Vorlagen, Projektwissen an einem Ort. + +**Für den Betrieb des Homelabs** +- **Uptime Kuma** — weisst sofort wenn ein Dienst down ist (inkl. Push aufs Handy). +- **Beszel** (oder Netdata) — System- und Pool-Metriken → erfüllt die <85%-Alert-Regel. +- **Homepage** — eine Startseite die alle Dienste verlinkt, das DSM-Launcher-Gefühl. + +**Privat** +- **AdGuard Home** — netzweiter Tracker-/Werbeblocker + interner DNS (löst gleich die internen Hostnamen). +- **Linkwarden** — visuelles Referenz-/Bookmark-Archiv. Beruflich top für Präzedenzfälle, Materialien, Detail-Ideen. +- **Jellyfin** — Filme/Serien, ergänzt Navidrome (Musik). +- **Miniflux** — minimalistischer RSS-Reader. + +**Für deine Software (openbureau)** +- **CI-Runner** (Forgejo Actions / Woodpecker) — Tests + Auto-Deploy der Hugo-Site, Builds für Rapport/Dossier/Pendenz. +- **Penpot** — Open-Source Figma für UI/Mockups. + +--- + +## der zugriff auf den share (die synology-erfahrung, nachgebaut) + +Synologys Magie ist ein Bündel von Zugriffswegen auf *dieselben* Dateien. Filesystem-first nachgebaut, alles im Fileserver-LXC (102): + +- **Samba** → klassisches Netzlaufwerk auf Mac/Linux/Windows. Voller nativer Speed für Rhino/CAD vom Desktop. Der Hauptzugang für die Arbeit. +- **Filebrowser** → Web-"File Station": browsen, hoch-/runterladen, **Share-Links** mit Passwort/Ablauf. Liest das FS live, kein DB-Desync. Auch im Handy-Browser brauchbar (PWA). +- **Syncthing** → das "Synology Drive": ausgewählte Ordner kontinuierlich auf Laptop syncen, offline arbeiten. Files bleiben *plain* auf der Platte → SMB funktioniert weiter parallel. +- **Homepage** → eine Kachel-Startseite über alles, der Launcher. + +> Bewusst *nicht* Seafile: das wäre der bessere Sync-Client, speichert Dateien aber in eigenem Block-Format → dann ginge SMB auf denselben Daten nicht mehr. Verletzt filesystem-first. + +--- + +## shares & datenstruktur + +**Zwei getrennte Datasets** (= zwei Samba-Shares): saubere Trennung von Rechten, Snapshots und Backup-Priorität. Ein künftiger Mitarbeiter/Praktikant kriegt `business`, nie `private`. + +### `tank/business` — Architekturbüro + +Projektbezogen, Phasen nach SIA 102. Unterstriche sortieren Nicht-Projekt-Ordner nach oben. + +``` +business/ +├── _vorlagen/ Planköpfe, Dokumentvorlagen, CAD-Templates +├── _bibliothek/ +│ ├── details/ wiederverwendbare Konstruktionsdetails +│ ├── cad-blocks/ +│ ├── materialien/ +│ └── referenzen/ Präzedenzfälle, Vorbilder +├── _buro/ Administration, Buchhaltung, Marketing +└── projekte/ + └── 2026-001_projektname/ + ├── 00_admin/ Verträge, Honorar, Protokolle, Korrespondenz + ├── 01_grundlagen/ Bestand, Vermessung, Gutachten, Fotos + ├── 02_vorstudien/ SIA 21 — Machbarkeit, Wettbewerb + ├── 03_vorprojekt/ SIA 31 + ├── 04_bauprojekt/ SIA 32 + ├── 05_bewilligung/ SIA 33 — Baugesuch + ├── 06_ausschreibung/ SIA 41 + ├── 07_ausfuhrung/ SIA 51/52/53 — Werkplanung, Baustelle + ├── 08_abschluss/ Abnahme, Revision, Doku + ├── cad/ Rhino .3dm (working) + ├── plaene/ ausgegebene PDF-Pläne + ├── visualisierung/ Renderings + └── fotos/ Baustellenfotos +``` + +Projektnummer `JJJJ-NNN_name` → sortierbar, gleiche Logik wie die CTIDs. + +### `tank/private` — persönlich + +``` +private/ +├── dokumente/ Verträge, Versicherung, Behörden +│ └── 2026/ +├── finanzen/ +├── studium/ HSLU Master — Module, Arbeiten, Thesis +├── projekte/ persönliche Projekte +├── scans/ Eingang für Paperless +└── archiv/ +``` + +Nextcloud bleibt schlank für Kalender/Kontakte/Mobile und leichtes Teilen (eigenes `tank/nextcloud`). Optional kann es `tank/private` als External Storage einhängen, damit die App am Handy auch an die privaten Files kommt — mit dem bekannten Scan-Vorbehalt. + +--- + +## speicher-aufteilung (thin + quota, mit reserve) + +### tank — ~3.6 TiB nutzbar + +| Dataset | Quota | Zweck | +|---------|-------|-------| +| `tank/business` | **1.3 TiB** | Architekturarbeit (SMB + Filebrowser + Syncthing) | +| `tank/immich` | **600 GiB** | Foto-/Videobibliothek | +| `tank/private` | **300 GiB** | persönliche Files (SMB + Filebrowser + Syncthing) | +| `tank/paperless` | **150 GiB** | Dokumentenarchiv | +| `tank/nextcloud` | **150 GiB** | Sync/Mobile/Teilen | +| `tank/dump` | **150 GiB** | vzdump der rootfs | +| `tank/gitea` | **50 GiB** | Repos | +| **Summe Quotas** | **~2.7 TiB** | = ~72% bei voll ausgereizten Quotas | +| **freie Reserve** | **~0.9 TiB** | bewusst unallokiert → Pool bleibt unter 85% | + +> Quotas sind Deckel, nicht Reservierungen. Sie dürfen den Pool in Summe überschreiten — hier tun sie's bewusst *nicht*, damit selbst bei vollen Quotas Luft bleibt. Medien (Jellyfin) würden diese Reserve fressen → eigene Platte einplanen. + +### rpool — ~900 GiB nutzbar + +OS + ARC + alle rootfs (Kern + Erweiterungen): grob **~350 GiB**. Datenbanken liegen in den rootfs auf der NVMe (schnelle Random-IO). **Reserve ~550 GiB** — bewusst grosszügig: Snapshots, künftige Dienste, Option ein heisses Dataset (z.B. Immich-Thumbnails) auf die NVMe zu pinnen. + +--- + +## mount-konventionen + +Zwei Fälle, bewusst unterschiedlich: + +- **Exklusive Service-Daten** (Nextcloud, Immich, Paperless, Gitea) → Proxmox-verwalteter Mountpoint, eigenes subvol: + ``` + mp0: tank/nextcloud,mp=/data,backup=0,size=150G + ``` +- **Geteilte Datasets** (`business`, `private`, von Samba+Filebrowser+Syncthing im selben LXC genutzt) → **Bind-Mount** des Host-Pfads: + ``` + mp0: /tank/business,mp=/srv/business + mp1: /tank/private,mp=/srv/private + ``` + +`backup=0` auf allen grossen Mounts. Dataset-Settings nach Inhalt: `recordsize=1M` für Immich/Renderings, Default 128K sonst. + +--- + +## netz & zugriff + +- **Öffentlich:** Router → nur 80/443 → Caddy (100), macht TLS und routet zu den öffentlichen Websites. Sonst nichts nach aussen. +- **Privat:** Router → nur WireGuard-UDP → wg-easy (101). Proxmox-UI, Nextcloud, Immich, Vaultwarden, Filebrowser, Paperless: **nur** durch den Tunnel über interne Hostnamen. +- **Interne Namen** über AdGuard (103) als DNS + Caddy als internen Proxy → keine IPs merken. + +--- + +## backup-strategie (3-2-1) + +1. **Snapshots** (sanoid) auf allen `tank`-Datendatasets — Schutz gegen "ups, gelöscht". +2. **Lokal:** `zfs send`/Restic der wichtigen Datasets + `tank/dump` → 2TB externe HDD. +3. **Offsite:** Restic der Unersetzlichen (`business`, `private`, `immich`, `nextcloud`, `paperless`, `gitea`, `vaultwarden`) → Hetzner Storage Box, verschlüsselt + versioniert. + +> RAID gegen Plattentod. Snapshots gegen Fehler. Offsite gegen Brand und Ransomware. Alle drei. **Restore mindestens einmal testen** — ein ungetestetes Backup ist kein Backup. + +--- + +## checkliste + +- [ ] Proxmox auf NVMe, `rpool` als Single-Disk ZFS +- [ ] `tank` als Mirror, Settings setzen, Scrub-Cron +- [ ] Datasets + Quotas anlegen +- [ ] Fileserver-LXC (102): Samba (2 Shares) + Filebrowser + Syncthing +- [ ] Ordnerstruktur business/private anlegen +- [ ] Caddy + WireGuard + AdGuard (Netzgerüst) +- [ ] Kern-Apps (NC, Immich, Vaultwarden, Gitea), dann Erweiterungen nach Bedarf +- [ ] Monitoring (Uptime Kuma, Beszel) + Pool-Alert <85% +- [ ] sanoid + Restic offsite, **Restore testen** diff --git a/doc/plan/homelab-projekt-kontext.md b/doc/plan/homelab-projekt-kontext.md new file mode 100644 index 0000000..0f94a7b --- /dev/null +++ b/doc/plan/homelab-projekt-kontext.md @@ -0,0 +1,81 @@ +# homelab rebuild — projekt-kontext / handoff + +> Dieses Dokument bündelt den Stand des Homelab-Rebuild-Projekts, damit ein neuer Assistent (z.B. Claude Code) sofort im Bild ist. Stand: Juni 2026. + +## ziel + +Sauberer Komplett-Neuaufbau des Proxmox-Homelabs: PVE neu installieren mit **ZFS-Root**, den ZFS-Mirror `tank` neu anlegen, alles am geschriebenen **homelab-manifest** ausrichten. Vorher: backup-first Migration (alles sichern, verifizieren, *dann* wipen). + +## kontext zur person + +Architekt (Masterstudium berufsbegleitend, Luzern) mit kleinem Büro-Kontext, FOSS-/Linux-Mensch, entwickelt eigene Software-Suite **openbureau** (Rapport, Dossier, Pendenz) mit Claude Code. Vor-Architektur-Hintergrund: Elektroinstallation + BIM-Projektleitung. Werte: Datensouveränität, config-as-code, self-hosted. + +## hardware + +- Mini-PC Server +- 1× 1TB NVMe — **aktuell LVM-Thin (`local-lvm`) Root**, soll nach Reinstall **ZFS-Root (`rpool`)** werden +- 2× 4TB — **ZFS Mirror `tank`** (~3.6 TiB nutzbar, die Bulk-Daten) +- 1× 2TB externe HDD — `ext-hdd01` unter `/mnt/pve/ext-hdd01` (Directory, Backup-Ziel) +- 1× 2TB USB-C NVMe SSD — neu, für den **PBS-Datastore** (Pfad ist beim User zu bestätigen, Annahme `/mnt/pve/pbs-ssd`) + +## aktuelles inventar (echte CTIDs/VMIDs) + +LXC: 100 nextcloud · 101 immich · 102 vaultwarden · 103 mailserver · 105 obsidian-livesync · 106 gitea · 107 openbureau · 110 flarum · 111 nginx-proxy-manager · 200 kgva-website · 210 openbureau-web · 211 rapport-website · 212 dossier-website · 219 dev.openbureau · 220 trattoriamarina.ch · 900 rapport-server +VM: 500 windows · 800 homeassistant-os + +## getroffene entscheidungen + +**Architektur-Prinzipien:** filesystem-first (das FS ist die Wahrheit, Web/SMB/Sync sind Clients); FOSS + config-as-code in Gitea; öffentlich = Caddy / privat = WireGuard-only; RAID ≠ Backup (3-2-1); ZFS-Pool nie über 85%; grosse Daten mit `backup=0` + separat sichern; bewusster Hybrid (single-purpose Infra = native LXC, echte Multi-Container-Apps = ein Docker-LXC pro Stack). + +**ZFS:** tank = Mirror (einzige sinnvolle Redundanz bei 2 Platten). rpool = Single-Disk-ZFS für Einheitlichkeit. ashift=12, lz4, atime=off. + +**Speicher:** thin provisioning mit Quotas als Sicherheitsdeckel (nicht starr partitionieren). Reserve bewusst eingeplant. + +**Reverse Proxy:** Caddy statt NPM (Binary, auto-HTTPS, Caddyfile config-as-code). Edge-Härtung (TLS, Header, CrowdSec) bei Caddy, nicht in den App-Containern. + +**Nextcloud:** Docker-compose-Stack (NICHT native Rieger, NICHT AIO) — aber mit Rieger-Tuning-Werten (php opcache/JIT, APCu, Redis-Locking, MariaDB READ-COMMITTED). fpm + nginx-Sidecar. Inkl. Collabora + Whiteboard-Backend. Compose-Datei liegt vor (siehe Dateien). + +**Supabase:** nur für die eigenen openbureau-Apps als eigener, getrennter Stack — NICHT als Nextcloud-DB. NC bekommt eine nackte MariaDB/Postgres. NC- und Supabase-DBs strikt getrennt. + +**Shares:** zwei getrennte Datasets/Samba-Shares — `tank/business` (projektbasiert nach SIA-Phasen 00–08) und `tank/private` (nach Lebensbereichen). Synology-artiger Zugriff via Samba + Filebrowser (Web + Share-Links) + Syncthing (das "Drive"). Bewusst kein Seafile (sperrt Files weg → SMB ginge nicht mehr). + +**Architektur-spezifische Erweiterungen empfohlen:** Paperless-ngx (DMS+OCR), Stirling-PDF, BookStack-Wiki, Speckle (BIM-Datenhub, bei BIM-Arbeit), Authentik (SSO sobald mehr Leute). Plus Betrieb: Uptime Kuma, Beszel, Homepage. Privat: AdGuard Home, Linkwarden, Jellyfin/Navidrome, Miniflux. + +**Backup-Kette:** + +| Schicht | Werkzeug | Lokal | Offsite | +|---------|----------|-------|---------| +| Container + VMs | PBS | neue SSD | (aus Gitea reproduzierbar) | +| Tank-Daten | Restic | ext-hdd01 (HDD) | Hetzner Storage Box | + +PBS läuft als privileged LXC, Datastore auf der SSD (überlebt den Wipe). Nach Reinstall: frisches PBS → bestehenden Datastore wieder einhängen → Gäste zurück. Tank: neu anlegen → Restic zurück. Verschlüsselungs-Key (falls genutzt) MUSS off-box gesichert werden. + +## migrations-reihenfolge + +1. PBS aufsetzen (Skript vorhanden), Datastore auf SSD. +2. Alle 18 Gäste per `vzdump --mode stop --storage pbs` sichern. +3. Stateful-Dienste app-level dumpen (mailserver-Backup + DKIM/DNS notieren; DB-Dumps). +4. Tank-Daten per Restic auf ext-hdd01 + Hetzner. +5. **Verifizieren + je 1 Container- und 1 Tank-Restore testen.** +6. Erst dann wipen: PVE neu auf NVMe (ZFS-root, NUR NVMe als Ziel), tank neu als Mirror. +7. Zurückspielen: PBS-Datastore wieder einhängen → Gäste; tank-Datasets nach Manifest → Restic zurück. Mailserver zuletzt. +8. Laufendes Backup-System einrichten (PBS-Job + sanoid + Restic-Cron). + +## offene punkte (vom user noch zu liefern/bestätigen) + +- [ ] Discovery-Output: `zfs list -o name,used,mountpoint -r tank` + `pct config ` aller Container → um die echten Restic-Pfade und `backup=0/1`-Flags zu fixieren. **Wichtig:** App-Daten liegen in `subvol-*`-Mountpoints (z.B. `subvol-100-disk-0` = Nextcloud-Data) — nicht über Dataset-Namen auto-erkennbar. +- [ ] Was ist `103 mailserver` genau (docker-mailserver / Mailcow)? → bestimmt Backup-Kommando + DKIM-Handling. +- [ ] Stateful-Bestätigung: 107 openbureau & 900 rapport-server = Postgres? 105 obsidian-livesync = CouchDB? 219 dev.openbureau = ? +- [ ] Pfad/Name der neuen SSD bestätigen (`cat /etc/pve/storage.cfg`). +- [ ] Hetzner Storage Box vorhanden oder Offsite später? +- [ ] PBS-Setup-Skript gelaufen? PBS sauber in PVE sichtbar? + +## produzierte dateien + +- `homelab-manifest.md` — die Referenz-Architektur (Pools, Dienste, CTID-Schema, Speicher-Quotas, Shares+Struktur, Netz, Backup, App-Katalog). +- `homelab-anleitung.md` — Aufbau-Anleitung Schritt 1–12 (Proxmox → Pools → Datasets → Fileserver → Caddy/WireGuard/AdGuard → Apps → Monitoring → Backup). +- `nextcloud-stack/` — docker-compose.yml + config/ (php-custom.ini, mariadb-tuning.cnf, nginx-nextcloud.conf) + .env.example. +- `migrations-backup-runbook.md` — Backup-vor-dem-Wipe Runbook mit echtem Inventar. +- `pbs-und-tank-backup-anleitung.md` — PBS aufsetzen + Tank sichern, Schritt für Schritt. +- `setup-pbs.sh` — interaktives, ungefährliches Skript das den PBS-LXC provisioniert. +- `homelab-projekt-kontext.md` — dieses Dokument. diff --git a/doc/plan/mariadb-tuning.cnf b/doc/plan/mariadb-tuning.cnf new file mode 100644 index 0000000..b3e679f --- /dev/null +++ b/doc/plan/mariadb-tuning.cnf @@ -0,0 +1,11 @@ +[mariadb] +transaction_isolation = READ-COMMITTED +binlog_format = ROW +innodb_file_per_table = 1 +innodb_buffer_pool_size = 1G +innodb_io_capacity = 4000 +innodb_flush_log_at_trx_commit = 2 +character-set-server = utf8mb4 +collation-server = utf8mb4_general_ci +skip-character-set-client-handshake +max_connections = 200 diff --git a/doc/plan/migrations-backup-runbook.md b/doc/plan/migrations-backup-runbook.md new file mode 100644 index 0000000..df46f28 --- /dev/null +++ b/doc/plan/migrations-backup-runbook.md @@ -0,0 +1,148 @@ +# migrations-backup runbook — vor dem full-wipe + +> Ziel: alles sichern → verifizieren → NVMe + Tank wipen → Proxmox & Tank frisch nach Manifest → zurückspielen. +> **Regel: solange das Backup nicht getestet ist, wird nichts gewiped.** Nach dem Wipe gibt es keinen `zpool import` mehr — das Backup ist die einzige Kopie. + +## ausgangslage (aus deinen screenshots) + +- Root liegt auf **local-lvm (LVM-Thin)**, nicht ZFS → Reinstall ist der einzige Weg zum ZFS-Root. +- **tank** = ZFS-Mirror (die Bulk-Daten). +- **ext-hdd01** (`/mnt/pve/ext-hdd01`, Directory, Backup-fähig) = Backup-Ziel, überlebt den Wipe. + +## dein inventar + +| ID | Name | Typ | Annahme: stateful? | +|----|------|-----|--------------------| +| 100 | nextcloud | LXC | DB + Files | +| 101 | immich | LXC | Postgres + Files | +| 102 | vaultwarden | LXC | Daten/SQLite | +| 103 | mailserver | LXC | **KRITISCH** — Mail + DB + DKIM | +| 105 | obsidian-livesync | LXC | CouchDB? | +| 106 | gitea | LXC | DB + Repos | +| 107 | openbureau | LXC | Postgres? | +| 110 | flarum | LXC | DB | +| 111 | nginx-proxy-manager | LXC | Config/Certs | +| 200 | kgva-website | LXC | meist statisch | +| 210 | openbureau-web | LXC | meist statisch | +| 211 | rapport-website | LXC | meist statisch | +| 212 | dossier-website | LXC | meist statisch | +| 219 | dev.openbureau | LXC | ? | +| 220 | trattoriamarina.ch | LXC | meist statisch | +| 900 | rapport-server | LXC | Postgres/Supabase? | +| 500 | windows | **VM** | grösster Brocken — Platz checken | +| 800 | homeassistant-os | **VM** | Config | + +> Die „stateful?"-Spalte sind **Annahmen** — bitte bestätigen/korrigieren (siehe Schritt 0). + +--- + +## schritt 0 — discovery (mountpoints + backup-flags aufdecken) + +```bash +# was ist wo gemountet, und ist backup=1 oder backup=0? +for id in $(pct list | awk 'NR>1{print $1}'); do + echo "=== CT $id ==="; pct config $id | grep -E '^(rootfs|mp[0-9]+):' +done + +# was liegt auf dem tank? +zfs list -r tank + +# freier platz auf der backup-platte? +df -h /mnt/pve/ext-hdd01 +``` + +Das zeigt, welche Daten schon in den vzdumps stecken (rootfs + `backup=1`-Mounts) und welche **separat per Restic** müssen (`backup=0`-Mounts auf dem Tank). +→ **Diese Ausgabe brauche ich, um die exakten Restic-Pfade einzusetzen.** + +--- + +## schritt 1 — stateful dienste app-level dumpen (portabel über den rebuild) + +Macht die Wiederherstellung sauber unabhängig von Block-Konsistenz. Dumps in einen Pfad schreiben, der mitgesichert wird (rootfs oder ein `backup=1`-Mount). + +- **103 mailserver** — sein eigenes Backup nutzen. Bei docker-mailserver: `setup backup` bzw. das Mailcow-Backup-Skript. **DKIM-Keys + DNS-Records (SPF/DMARC/MX) separat notieren** — der häufigste Migrations-Schmerz. +- **DB-Dienste** (100/101/106/110/107/900 — je nach Engine): + ```bash + # Postgres (im docker-container): + pct exec -- docker exec pg_dumpall -U postgres > /pfad/dump.sql + # MariaDB/MySQL: + pct exec -- docker exec mysqldump --all-databases -uroot -p > /pfad/dump.sql + ``` +- **102 vaultwarden / 105 obsidian-livesync** — vor dem Sichern stoppen, dann ist der Datenstand konsistent. + +--- + +## schritt 2 — container + VMs sichern (vzdump → ext-hdd01) + +Weil wir eh wipen, ist Downtime okay → `--mode stop` = sauber konsistent. + +```bash +# alle CTs und VMs: +for id in $(pct list | awk 'NR>1{print $1}') $(qm list | awk 'NR>1{print $1}'); do + vzdump $id --mode stop --compress zstd --storage ext-hdd01 +done +``` + +> Achtung: **500 (windows)** ist vermutlich der grösste Brocken. Vorher `df -h /mnt/pve/ext-hdd01` prüfen. + +--- + +## schritt 3 — tank-daten sichern (restic → ext-hdd01 UND hetzner) + +Pfade aus Schritt 0 einsetzen. Zwei unabhängige Kopien, weil es nach dem Wipe die einzige Quelle ist. + +```bash +export RESTIC_PASSWORD_FILE=/root/.restic-pw + +# lokal auf die externe platte +export RESTIC_REPOSITORY=/mnt/pve/ext-hdd01/restic-tank +restic init +restic backup /tank/ /tank/ /tank/<...> + +# offsite nach hetzner +export RESTIC_REPOSITORY=sftp:uXXXX@uXXXX.your-storagebox.de:/tank +restic init +restic backup /tank/ /tank/ /tank/<...> +``` + +Restic als root → Eigentümer/Rechte (inkl. Idmap-Offsets der Shares) kommen 1:1 zurück. + +--- + +## schritt 4 — verifizieren + restore-test (PFLICHT) + +```bash +restic -r /mnt/pve/ext-hdd01/restic-tank check + +# einen container testweise auf freie ID zurückholen: +pct restore 999 /mnt/pve/ext-hdd01/dump/vzdump-lxc-102-*.tar.zst --storage local-lvm +pct start 999 # läuft er? dann wieder weg: +pct stop 999 && pct destroy 999 + +# einen tank-pfad testweise restoren: +restic -r /mnt/pve/ext-hdd01/restic-tank restore latest --target /tmp/rtest --include /tank/ +ls -la /tmp/rtest/tank/ +``` + +--- + +## schritt 5 — wipe-freigabe (erst wenn ALLES grün) + +- [ ] vzdump aller 18 Gäste auf ext-hdd01 vorhanden +- [ ] Tank-Restic auf ext-hdd01 **und** Hetzner, `restic check` ok +- [ ] DB-/Mail-Dumps vorhanden und im Backup enthalten +- [ ] je 1 Container- **und** 1 Tank-Restore erfolgreich getestet +- [ ] DKIM-Keys + DNS-Records notiert +- [ ] beim Reinstall wird **ext-hdd01 NICHT** als Ziel gewählt + +--- + +## schritt 6 — nach dem rebuild: reihenfolge + +1. Proxmox neu auf NVMe, **ZFS-root (rpool)** — nur NVMe als Ziel. +2. `tank` neu als Mirror + Datasets/Quotas nach Manifest. +3. ext-hdd01 wieder mounten. +4. Tank-Daten per `restic restore` zurück in die frischen Datasets. +5. Container/VMs aus vzdump zurück: `pct restore --storage ...` bzw. `qmrestore ` für die VMs. Dabei kannst du gleich auf das neue Manifest-Schema umnummerieren. +6. **mailserver zuletzt** und mit Ruhe; DKIM/DNS prüfen, Testmail. +7. PBS + sanoid + Restic als laufendes System einrichten (permanentes Backup). diff --git a/doc/plan/nginx-nextcloud.conf b/doc/plan/nginx-nextcloud.conf new file mode 100644 index 0000000..c593beb --- /dev/null +++ b/doc/plan/nginx-nextcloud.conf @@ -0,0 +1,54 @@ +upstream php-handler { server app:9000; } + +server { + listen 80; + server_name _; + root /var/www/html; + + client_max_body_size 16G; + client_body_timeout 3600s; + fastcgi_buffers 64 4K; + fastcgi_hide_header X-Powered-By; + + index index.php index.html /index.php$request_uri; + + location = / { if ($http_user_agent ~ ^DavClnt) { return 302 /remote.php/webdav/$is_args$args; } } + location = /robots.txt { allow all; log_not_found off; access_log off; } + + location ^~ /.well-known { + location = /.well-known/carddav { return 301 /remote.php/dav/; } + location = /.well-known/caldav { return 301 /remote.php/dav/; } + location /.well-known/acme-challenge { try_files $uri $uri/ =404; } + location /.well-known/pki-validation { try_files $uri $uri/ =404; } + return 301 /index.php$request_uri; + } + + location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) { return 404; } + location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { return 404; } + + location ~ \.php(?:$|/) { + rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|ocs-provider\/.+|.+\/richdocumentscode\/proxy) /index.php$request_uri; + fastcgi_split_path_info ^(.+?\.php)(/.*)$; + set $path_info $fastcgi_path_info; + try_files $fastcgi_script_name =404; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $path_info; + fastcgi_param modHeadersAvailable true; + fastcgi_param front_controller_active true; + fastcgi_pass php-handler; + fastcgi_intercept_errors on; + fastcgi_request_buffering off; + fastcgi_max_temp_file_size 0; + } + + location ~ \.(?:css|js|svg|gif|png|jpg|ico|wasm|tflite|map|ttf|bcmap|mp4|webm|ogg|flac)$ { + try_files $uri /index.php$request_uri; + access_log off; + expires 6M; + } + location ~ \.woff2?$ { try_files $uri /index.php$request_uri; expires 7d; access_log off; } + + location /remote { return 301 /remote.php$request_uri; } + location / { try_files $uri $uri/ /index.php$request_uri; } +} diff --git a/doc/plan/pbs-und-tank-backup-anleitung.md b/doc/plan/pbs-und-tank-backup-anleitung.md new file mode 100644 index 0000000..17a7f43 --- /dev/null +++ b/doc/plan/pbs-und-tank-backup-anleitung.md @@ -0,0 +1,163 @@ +# anleitung: PBS aufsetzen + tank sichern + +Zwei Teile: erst Proxmox Backup Server installieren & konfigurieren (Datastore auf der neuen SSD), dann den Tank sichern. + +--- + +## variablen — oben ausfüllen + +```text +PBS_DATASTORE_PATH = /mnt/pve/pbs-ssd # pfad deiner neuen SSD -> prüfen mit: cat /etc/pve/storage.cfg +PBS_CTID = 145 # freie container-ID +PBS_IP / GW = /24 / # in deinem bridge-subnetz +ETT_HDD = /mnt/pve/ext-hdd01 # die alte HDD = restic-ziel für den tank +HETZNER = uXXXX@uXXXX.your-storagebox.de +``` + +Pfad der neuen SSD bestätigen: +```bash +cat /etc/pve/storage.cfg # zeigt den namen + pfad der neu angelegten directory-storage +``` + +--- + +# TEIL 1 — PBS installieren & konfigurieren + +## 1.1 — Debian-13-Template holen (auf dem PVE-Host) +```bash +pveam update +pveam available | grep debian-13 +pveam download local debian-13-standard_13.0-1_amd64.tar.zst # name ggf. anpassen +``` + +## 1.2 — PBS-LXC anlegen (privileged), SSD reinmounten +```bash +pct create 145 local:vztmpl/debian-13-standard_13.0-1_amd64.tar.zst \ + --hostname pbs --cores 2 --memory 2048 --swap 512 \ + --rootfs local-lvm:16 --unprivileged 0 \ + --net0 name=eth0,bridge=vmbr0,ip=/24,gw= \ + --password + +# die neue SSD als datastore in den container binden: +pct set 145 -mp0 /mnt/pve/pbs-ssd,mp=/datastore +pct start 145 +``` + +## 1.3 — PBS im Container installieren +```bash +pct enter 145 + +wget https://enterprise.proxmox.com/debian/proxmox-archive-keyring-trixie.gpg \ + -O /usr/share/keyrings/proxmox-archive-keyring.gpg + +cat > /etc/apt/sources.list.d/pbs.sources << 'EOF' +Types: deb +URIs: http://download.proxmox.com/debian/pbs +Suites: trixie +Components: pbs-no-subscription +Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg +EOF + +apt update && apt install -y proxmox-backup-server +``` +Bei der Postfix-Frage „No configuration" wählen. + +## 1.4 — Web-UI + Datastore anlegen +- Öffne `https://:8007`, Login `root` + Container-Passwort (Self-signed-Warnung ist normal). +- **Datastore → Add Datastore**: Name `homelab`, Backing Path `/datastore`. + +## 1.5 — Aufräum- & Prüf-Jobs (im PBS) +- Datastore `homelab` → **Prune & GC**: + - Prune: keep-last 3, keep-daily 7, keep-weekly 4, keep-monthly 6 + - GC-Schedule: wöchentlich +- **Verify Jobs** → Add: wöchentlich (prüft die Backups gegen Bitrot). + +## 1.6 — PBS in PVE als Backup-Ziel einbinden +- Im PBS: Dashboard → **Show Fingerprint** kopieren. +- Im PVE: Datacenter → Storage → **Add → Proxmox Backup Server**: + - ID `pbs`, Server ``, Datastore `homelab`, Username `root@pam`, Passwort, Fingerprint einfügen. + +## 1.7 — Alle Gäste sichern +**Einmalig jetzt (vor dem Wipe), konsistent per stop-modus:** +```bash +for id in $(pct list | awk 'NR>1{print $1}') $(qm list | awk 'NR>1{print $1}'); do + vzdump $id --mode stop --storage pbs +done +``` +Das sichert deine 16 LXC (100–900) + die 2 VMs (500 windows, 800 homeassistant) ins PBS. +> 500 (windows) ist der grösste Brocken — vorher Platz auf der SSD prüfen: `df -h /mnt/pve/pbs-ssd`. + +**Für den laufenden Betrieb** (nach dem Rebuild): Datacenter → Backup → Add → täglicher Job, alle Gäste, Storage `pbs`, Modus Snapshot. + +--- + +# TEIL 2 — tank sichern + +PBS deckt die **Container/VMs** ab. Die **Bulk-Daten auf dem Tank** sind eine eigene Schicht — die sicherst du mit Restic auf die alte HDD (`ext-hdd01`) **und** offsite nach Hetzner. + +## 2.1 — Discovery: was liegt wo, und steckt es schon im PBS? +```bash +# alle datasets + mountpoints auf dem tank: +zfs list -o name,used,mountpoint -r tank + +# pro container: mountpoints + backup-flag: +for id in $(pct list | awk 'NR>1{print $1}'); do + echo "=== CT $id ==="; pct config $id | grep -E '^(rootfs|mp[0-9]+):' +done +``` +Entscheidend ist das `backup=`-Flag jedes Daten-Mountpoints: +- **`backup=1`** → die Daten sind schon in den PBS-Sicherungen aus 1.7. Restic ist dann nur noch für die **Offsite-Kopie** nötig. +- **`backup=0`** → die Daten sind **nicht** im PBS → Restic ist hier Pflicht, lokal *und* offsite. + +> Schick mir die Ausgabe dieser beiden Befehle, dann setze ich unten die echten Pfade ein. Bis dahin: in den Restic-Befehl kommen die `mountpoint`-Pfade der Daten-Datasets aus der `zfs list` (deine App-Daten wie nc_data, immich-library usw. — **nicht** die rootfs-Subvols der Websites, die deckt PBS ab). + +## 2.2 — Restic-Repos initialisieren +```bash +export RESTIC_PASSWORD_FILE=/root/.restic-pw # vorher anlegen: ein starkes passwort reinschreiben, chmod 600 + +# lokal auf die HDD: +export RESTIC_REPOSITORY=/mnt/pve/ext-hdd01/restic-tank +restic init + +# offsite nach hetzner: +export RESTIC_REPOSITORY=sftp:uXXXX@uXXXX.your-storagebox.de:/tank +restic init +``` + +## 2.3 — Tank sichern (kalt, weil Migration → Dienste eh gestoppt) +Pfade aus 2.1 einsetzen: +```bash +# lokal +export RESTIC_REPOSITORY=/mnt/pve/ext-hdd01/restic-tank +restic backup /tank/ /tank/ /tank/<...> + +# offsite +export RESTIC_REPOSITORY=sftp:uXXXX@uXXXX.your-storagebox.de:/tank +restic backup /tank/ /tank/ /tank/<...> +``` +Restic als root → Eigentümer/Rechte (inkl. Idmap-Offsets) kommen 1:1 zurück. + +## 2.4 — Verifizieren + Restore-Test (PFLICHT vor dem Wipe) +```bash +restic -r /mnt/pve/ext-hdd01/restic-tank check +restic -r /mnt/pve/ext-hdd01/restic-tank restore latest \ + --target /tmp/rtest --include /tank/ +ls -la /tmp/rtest/tank/ # stimmen die dateien? +``` + +## 2.5 — Für den laufenden Betrieb +Nach dem Rebuild beide Restic-Läufe (lokal + Hetzner) in einen täglichen Cron, plus Pruning: +```bash +restic forget --keep-daily 14 --keep-weekly 8 --keep-monthly 12 --prune +``` + +--- + +## die ganze kette auf einen blick + +| Schicht | Werkzeug | Lokal | Offsite | +|---------|----------|-------|---------| +| Container + VMs | PBS | SSD (`pbs-ssd`) | — (aus Gitea reproduzierbar) | +| Tank-Daten | Restic | HDD (`ext-hdd01`) | Hetzner | + +Beide externen Platten + die Hetzner-Box überleben den Wipe. Nach dem Rebuild: PBS-Datastore auf der SSD wieder einhängen → Gäste zurück; Tank neu anlegen → Restic zurück. diff --git a/doc/plan/php-custom.ini b/doc/plan/php-custom.ini new file mode 100644 index 0000000..2c73648 --- /dev/null +++ b/doc/plan/php-custom.ini @@ -0,0 +1,22 @@ +; rieger-spirit php-tuning für nextcloud +memory_limit = 1024M +upload_max_filesize = 16G +post_max_size = 16G +max_execution_time = 3600 +max_input_time = 3600 +output_buffering = 0 +date.timezone = Europe/Zurich + +; opcache + JIT +opcache.enable = 1 +opcache.enable_cli = 1 +opcache.interned_strings_buffer = 32 +opcache.max_accelerated_files = 10000 +opcache.memory_consumption = 256 +opcache.save_comments = 1 +opcache.revalidate_freq = 60 +opcache.jit = 1255 +opcache.jit_buffer_size = 128M + +; APCu als lokaler cache (im offiziellen image enthalten) +apc.enable_cli = 1 diff --git a/doc/plan/setup-pbs.sh b/doc/plan/setup-pbs.sh new file mode 100644 index 0000000..3ff5f35 --- /dev/null +++ b/doc/plan/setup-pbs.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +# +# setup-pbs.sh — richtet einen Proxmox Backup Server als LXC ein. +# Datastore landet auf der angegebenen (bereits eingebundenen) SSD. +# +# Macht NUR additive Dinge: LXC anlegen, PBS installieren, Datastore anlegen, +# in PVE als Storage einbinden. Fasst KEINE Platten an, loescht nichts, +# startet KEINE Backups. Das machst du danach bewusst selbst. +# +# Auf dem Proxmox-VE-Host als root ausfuehren. Vorher durchlesen. + +set -euo pipefail + +[[ $EUID -eq 0 ]] || { echo "Bitte als root ausfuehren."; exit 1; } +command -v pct >/dev/null || { echo "Kein Proxmox-VE-Host (pct fehlt)."; exit 1; } + +echo "=== PBS-LXC Setup ===" +echo + +# --- datastore-pfad (die neue SSD) --- +echo "Eingebundene Directory-Storages (Pfad-Spalte):" +awk '/^dir:/{name=$2} /path /{print " "name" -> "$2}' /etc/pve/storage.cfg || true +echo +read -rp "Pfad der SSD fuer den Datastore [/mnt/pve/pbs-ssd]: " DS_PATH +DS_PATH=${DS_PATH:-/mnt/pve/pbs-ssd} +[[ -d $DS_PATH ]] || { echo "FEHLER: $DS_PATH existiert nicht. Erst die SSD einbinden."; exit 1; } + +# --- container-ID (naechste freie als default) --- +NEXTID=$(pvesh get /cluster/nextid) +read -rp "Container-ID [$NEXTID]: " CTID +CTID=${CTID:-$NEXTID} + +# --- netzwerk (gateway autodetektiert) --- +GW_DETECT=$(ip route | awk '/default/{print $3; exit}') +read -rp "Gateway [$GW_DETECT]: " GW +GW=${GW:-$GW_DETECT} +read -rp "Bridge [vmbr0]: " BRIDGE +BRIDGE=${BRIDGE:-vmbr0} +read -rp "Feste IP fuer PBS inkl. Maske (z.B. 192.168.1.50/24): " CTIP +[[ -n ${CTIP:-} ]] || { echo "FEHLER: IP erforderlich."; exit 1; } +IPONLY=${CTIP%%/*} + +# --- rootfs-storage --- +read -rp "Storage fuer die Container-rootfs [local-lvm]: " ROOTSTORE +ROOTSTORE=${ROOTSTORE:-local-lvm} + +# --- root-passwort fuer den container (= PBS-Login root@pam) --- +read -rsp "Root-Passwort fuer den PBS-Container: " CTPW; echo +[[ -n ${CTPW:-} ]] || { echo "FEHLER: Passwort erforderlich."; exit 1; } + +# --- zusammenfassung + bestaetigung --- +cat < im Container /datastore + Datastore-Name homelab + +SUMMARY +read -rp "So anlegen? [y/N]: " OK +[[ ${OK,,} == y ]] || { echo "Abgebrochen."; exit 0; } + +# --- debian-13-template sicherstellen --- +echo ">> Template pruefen/holen ..." +pveam update >/dev/null 2>&1 || true +TMPL=$(pveam available --section system | awk '/debian-13-standard/{print $2}' | sort | tail -n1) +[[ -n $TMPL ]] || { echo "FEHLER: kein debian-13-Template gefunden."; exit 1; } +pveam list local | grep -q "$TMPL" || pveam download local "$TMPL" + +# --- LXC anlegen --- +echo ">> Erstelle LXC $CTID ..." +pct create "$CTID" "local:vztmpl/$TMPL" \ + --hostname pbs --cores 2 --memory 2048 --swap 512 \ + --rootfs "${ROOTSTORE}:16" --unprivileged 0 \ + --net0 "name=eth0,bridge=${BRIDGE},ip=${CTIP},gw=${GW}" \ + --onboot 1 + +# datastore reinmounten + starten +pct set "$CTID" -mp0 "${DS_PATH},mp=/datastore" +pct start "$CTID" +sleep 5 +pct exec "$CTID" -- bash -c "echo 'root:${CTPW}' | chpasswd" + +# --- PBS installieren --- +echo ">> Installiere PBS im Container ..." +pct exec "$CTID" -- bash -c ' + set -e + wget -qO /usr/share/keyrings/proxmox-archive-keyring.gpg \ + https://enterprise.proxmox.com/debian/proxmox-archive-keyring-trixie.gpg + cat > /etc/apt/sources.list.d/pbs.sources <> Lege Datastore 'homelab' an ..." +pct exec "$CTID" -- proxmox-backup-manager datastore create homelab /datastore 2>/dev/null \ + || echo " (Datastore existiert evtl. schon - ok)" + +# --- in PVE als storage einbinden (fingerprint automatisch) --- +echo ">> Binde PBS in PVE ein ..." +FP=$(pct exec "$CTID" -- proxmox-backup-manager cert info | awk -F': ' '/[Ff]ingerprint/{print $2; exit}') +if [[ -n ${FP:-} ]]; then + pvesm add pbs pbs --server "$IPONLY" --datastore homelab \ + --username root@pam --password "$CTPW" --fingerprint "$FP" 2>/dev/null \ + && echo " PVE-Storage 'pbs' angelegt." \ + || echo " (Storage 'pbs' existiert evtl. schon - ok)" +else + echo " Fingerprint nicht automatisch gefunden - bitte manuell einbinden (siehe unten)." +fi + +cat < Prune & GC: keep-last 3, daily 7, weekly 4, monthly 6; GC woechentlich + - Verify Jobs -> woechentlich + +Backups starten (bewusst, separat) - alle Gaeste einmalig ins PBS: + for id in \$(pct list | awk 'NR>1{print \$1}') \$(qm list | awk 'NR>1{print \$1}'); do + vzdump \$id --mode stop --storage pbs + done + +DONE