# ks4 — prod server setup & rebuild procedure Prod Incus host at OVH (`ks4.lutran.fr` → `193.70.35.17`, SSH on port **2233**). This is a scan-derived snapshot of how the box is currently laid out plus the steps to rebuild the host layer. Per-instance rebuild docs are still TODO — see [Instances](#instances). > Scanned 2026-08-09 as `julien` (in groups `sudo`, `incus`) with > passwordless `sudo incus …` only. Anything needing root beyond incus > (`zpool status`, host firewall dumps, container internals) is marked > **verify** below. ## Hardware / hosting - OVH dedicated server, hostname `ns3061243`, up ~90 days at scan time - CPU: Intel Xeon D-1521 @ 2.40 GHz (4c/8t) - RAM: 31 GiB (+1 GiB swap) - Disks: 2× 6 TB HGST `HUS726T6TALE6L1` (`sda`, `sdb`) - `sda1/2/3` + `sdb1/2/3` → **mdraid RAID1** mirrors: `md1` → `/boot/efi` (vfat), `md2` → `/boot` (ext4), `md3` → `/` (ext4, 40 GB) - `sda4` / `sdb4` → swap (512 MB each) - `sda5` (5.4 TB) → ZFS pool `data` (all Incus instance disks), single vdev `ata-HGST_…_V9K2UX7L-part5`, `ONLINE`, weekly scrub (Sundays, last clean run 2026-08-09) - `sdb5` (5.4 TB) → **unused** (no fstype) - Network: `eno3` up, `193.70.35.17/24`, gw `193.70.35.254`, IPv6 `2001:41d0:303:711::1/128`. `eno4` down. - OS: Debian 13 (trixie), kernel `6.12.86+deb13-amd64` ### ⚠️ Data pool is NOT mirrored Confirmed via `zpool status`: the OS (`md1/2/3`) is RAID1 across both disks, but the ZFS `data` pool is a **single vdev on `sda5`**. If `sda` dies, every instance disk is lost even though the OS survives on `sdb`. Decision (2026-08-09): instead of mirroring, `sdb5` hosts a dedicated `backup` zpool receiving nightly replicas + dumps, with a second off-site replica on nuc — see [incus-copy.md](incus-copy.md). (`zpool status` also notes some pool features are disabled — an optional `zpool upgrade data` would enable them, at the cost of compatibility with older ZFS.) ## Software layer - **Incus** from the Zabbly stable repo (`pkgs.zabbly.com/incus/stable`, suite `trixie`), packages `incus`, `incus-base`, `incus-client`, `incus-ui-canonical`, version `1:7.2-debian13-…`. - ZFS `2.4.3-1~bpo13+1` (`zfs-dkms`, `zfsutils-linux`, `zfs-zed`). - Incus daemon: `core.https_address: :8443` (web UI reachable; TLS auth), firewall driver **nftables**, storage driver **zfs**, not clustered. Repo + install (matches nuc): ```sh mkdir -p /etc/apt/keyrings curl -fsSL https://pkgs.zabbly.com/key.asc -o /etc/apt/keyrings/zabbly.asc cat > /etc/apt/sources.list.d/zabbly-incus-stable.sources < `~/incus-compose` on the host is a Go project (own GitLab CI) — a > docker-compose-style deployer for Incus that julien develops. The > `incus-compose-images` project is its image store. Not required to run > the existing containers. ## Instances All in the `default` project, on the `default` profile (`eth0` → `incusbr0`, `root` → pool `data`), unprivileged (`security.nesting=true`), with daily snapshots (`snapshots.schedule=@daily`, expiry 1w / 4w manual, ~7 kept). **Running:** | Name | IP | Role (inferred) | Base image | Notes | |---|---|---|---|---| | gateway | .2 | **nginx reverse proxy / TLS** — HTTP(S) ingress | Ubuntu 18.04 | proxy 80/443, PROXY protocol | | mail | .3 | **mail server** (Postfix/Dovecot stack) | Debian 9.9 (Apache/MariaDB/PHP) | proxy 25/587/993; ~20 GB | | nextcloud | .4 | **Nextcloud** | Debian stretch | ~726 GB data | | freshrss | .5 | **FreshRSS** reader | — | dedicated `root` disk device | | git | .6 | **Git server** (Gitea/GitLab-class) | Ubuntu 18.04 | SSH proxy host:2244→:22 | | seafile | .7 | **Seafile** file sync | — | ~950 GB, dedicated `root` disk | | databap | .8 | app (role unconfirmed — **verify**) | Ubuntu 18.04 | | | spot | .9 | app (role unconfirmed — **verify**; joal/torrent?) | Debian 9.9 | ~25 GB | | bitwarden | .15 | **Bitwarden / Vaultwarden** | Ubuntu 18.04 | ~23 GB | | solar | .17 | app (solar/PV monitoring? — **verify**) | Debian 9.9 | | | wireguard | .18 | **WireGuard VPN** | Debian trixie (rebuilt 2025-11-28) | proxy udp:51845; wg0 10.8.0.1/24 | | login | .19 | **SSO / auth** (runs Docker inside) | Debian 9.9 | `docker0`+bridge; nesting, syscall intercepts | | outline | .20 | **Outline** wiki (runs Docker inside) | Debian 9.9 | `docker0`+bridge; nesting, syscall intercepts | **Stopped (legacy / occasional):** `catc`, `mythoughts`, `qcm`, `wedding`, `wow` — no snapshots, kept around; roles not documented. > Most base images are old (Ubuntu 18.04 / Debian 9 "stretch", both EOL). > Treat these as pets to migrate, not reproduce. Per-instance install > docs (one `.md` each, per repo convention) still need to be written — > that requires shelling into each container, which the current > incus-only sudo grant doesn't allow. ## Backups Full picture, schedule and restore procedures: **[../backup-strategy.md](../backup-strategy.md)**. Drivers live in the [scripts repo](https://git.lutran.fr/julien/scripts) and are deployed to `/root/scripts`. Current **root** cron jobs — the safety net for the non-mirrored `data` pool: ```cron 0 1 * * * /root/scripts/incus-copy.sh -p backup -s backup >> /var/log/incus-copy.log 2>&1 0 5 * * * /root/scripts/restic-backup.sh >> /var/log/restic-backup.log 2>&1 0 14 * * 0 /root/scripts/restic-maintenance.sh >> /var/log/restic-maintenance.log 2>&1 ``` - **01:00 `incus-copy.sh`** → replicas of every instance in project `backup` on the sdb5 pool ([incus-copy.md](incus-copy.md), [local-backup-cron.md](local-backup-cron.md)); the S3 instance leg chains here once seeded. - **05:00 `restic-backup.sh`** → auto-discovered DB dumps + the data trees listed in [`scripts/restic-paths`](https://git.lutran.fr/julien/scripts/src/branch/main/restic-paths), into the `restic-data` bucket ([restic-backup.md](restic-backup.md)). - **Sun 14:00 `restic-maintenance.sh`** → prune + integrity checks. A `ks2` interim replica push (`incus-copy.sh -d ks2 -m push`, 02:00) runs until the nuc pull leg is seeded. Retired 2026-08: the `incus-backup.sh` rsync leg and the first S3 implementation ([plakar-s3-data.md](plakar-s3-data.md) — reference only). - **01:00 — `incus-copy.sh` → `ks2`**: differential `incus copy … \ --refresh --refresh-exclude-older --mode push` of every *running* instance to remote incus host `ks2` (a near-live replica). `ks2` must exist as an `incus remote`. - **04:00 — `incus-backup.sh` → `164.132.173.57`**: rsync-over-ssh (user `rsyncbackup`, key `/root/.ssh/id_rsyncbackup`, port 2233) of the incus local+global DB dumps, each container's `backup.yaml`, MariaDB dumps (`incus exec … mariadb-dump`), and selected rootfs paths into `/backup/ks4/` on the remote. **Which** containers/DBs/paths are backed up is driven by the JSON manifest `/root/scripts/incus-backup.db` — keep it current when adding a service. Both scripts run as root (they use `incus exec` / `incus admin sql`), not through the `julien` incus-only sudo grant. Logs: `/var/log/incus-copy.log`, `/var/log/incus-backup.log`. Manual export of a single instance: ```sh sudo incus export /path/to/.tar.gz # --instance-only to skip snapshots ``` ## Host rebuild outline OVH reinstalls provide Debian; the ks4-specific layer is: 1. **OS**: Debian 13, mdraid RAID1 (`md1`/`md2`/`md3`) across both disks for `/boot/efi`, `/boot`, `/` — OVH's guided partitioning, or restore the layout above. Leave `sda5` (and ideally `sdb5`) for ZFS. 2. **SSH**: move sshd to port **2233**; restore `/root/.ssh/authorized_keys` (incl. `id_rsa_claude.pub` from this repo) and the `julien` account (groups `sudo`, `incus`) + `/etc/sudoers.d/incus`. 3. **ZFS pool**: ```sh zpool create data /dev/disk/by-id/ # recommended: mirror it (see warning above) zpool attach data /dev/disk/by-id/ ``` 4. **Incus**: install from Zabbly (above), then `incus admin init` with pool `data` and bridge `incusbr0` (`192.168.1.1/24`, NAT): ```sh cat < eth0 ipv4.address 192.168.1.x`), and re-add the proxy devices from the [ingress table](#networking--public-ingress), e.g.: ```sh incus config device add gateway https proxy \ listen=tcp:0.0.0.0:443 connect=tcp:127.0.0.1:443 proxy_protocol=true ``` 6. **Re-enable** daily snapshots on each instance, restore root's crontab + `/root/scripts/` + `/root/.ssh/id_rsyncbackup`, add the `ks2` incus remote, and confirm both backup jobs run (see [Backups](#backups)).