Files
doc/ks4/install.md
T
Julien LutranandClaude Fable 5 ee9eaee889 Initial import: nuc and ks4 infrastructure documentation
README with instance tables and nuc<->ks4 network flow chart;
per-container install/troubleshooting docs for nuc (jellyfin
server/client, transmission-bt, bare-metal reinstall) and the
ks4 two-leg backup scheme (incus-copy over wireguard).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 22:50:07 +02:00

233 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 <<EOF
Enabled: yes
Types: deb
URIs: https://pkgs.zabbly.com/incus/stable
Suites: trixie
Components: main
Architectures: amd64
Signed-By: /etc/apt/keyrings/zabbly.asc
EOF
apt update && apt install -y incus zfs-dkms zfsutils-linux zfs-zed
```
## Networking / public ingress
Unlike nuc (macvlan → real LAN IPs), ks4 uses a **NAT bridge**: all
instances sit on `incusbr0` (`192.168.1.1/24`) with static
`192.168.1.x` addresses. Public traffic reaches them through Incus
**proxy devices** on the host's public IP:
| Host port(s) | → Instance | Proxy notes |
|---|---|---|
| 80, 443/tcp | `gateway` (192.168.1.2) | `proxy_protocol=true` → nginx reverse proxy fans out to the app containers |
| 25, 587, 993/tcp | `mail` (192.168.1.3) | `proxy_protocol=true` (SMTP/submission/IMAPS) |
| 2244/tcp | `git` (192.168.1.6) | → container `:22` (git-over-SSH) |
| 51845/udp | `wireguard` (192.168.1.18) | → WireGuard; VPN subnet `10.8.0.0/24` (server `wg0` 10.8.0.1) |
| 2233/tcp | host `sshd` | admin SSH (not a proxy device) |
| 8443/tcp | host `incus` daemon | remote API + web UI |
So **`gateway` is the single HTTP/S entry point** and terminates TLS for
the web apps; the app containers are not directly exposed. The `gateway`
and `mail` proxies use the PROXY protocol, so nginx/postfix inside must
be configured to expect it.
Other bridges present: `incusbr-1001` (`10.183.142.1/24`) and
`incusbr-1002` (`10.10.112.1/24`), one per restricted user project
(see below). The host also runs a DNS resolver (dnsmasq) on each bridge
IP `:53`.
## Incus projects
| Project | Purpose |
|---|---|
| `default` | all the service containers below |
| `user-1001` | restricted project for `julien` (own bridge + quota) |
| `user-1002` | restricted project for `franzz` |
| `incus-compose-images` | image cache for the `incus-compose` tool (see note) |
> `~/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
> **Migration in progress** (2026-08-09): the `ks2` destination below is
> being replaced by a local `backup` zpool on `sdb5` + off-site pull
> from nuc — plan and setup in [incus-copy.md](incus-copy.md). The
> cron blocks here describe what runs **today**; update on cutover.
Two **root** cron jobs (this repo's [`scripts/`](../scripts/) are the
copies that run here) — the primary safety net for the non-mirrored
`data` pool:
```cron
0 1 * * * /root/scripts/incus-copy.sh -d ks2 -m push
0 4 * * * /root/scripts/incus-backup.sh -d 164.132.173.57 -u rsyncbackup \
-i /root/.ssh/id_rsyncbackup -p 2233 \
-f /root/scripts/incus-backup.db -s data
```
- **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 <name> /path/to/<name>.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/<sda5-id>
# recommended: mirror it (see warning above)
zpool attach data <sda5-id> /dev/disk/by-id/<sdb5-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 <<EOF | incus admin init --preseed
config:
core.https_address: :8443
storage_pools:
- name: data
driver: zfs
config: {source: data}
networks:
- name: incusbr0
type: bridge
config: {ipv4.address: 192.168.1.1/24, ipv4.nat: "true", ipv6.address: none}
profiles:
- name: default
devices:
eth0: {name: eth0, network: incusbr0, type: nic}
root: {path: /, pool: data, type: disk}
EOF
```
5. **Restore instances** from exports, re-pin each static IP
(`incus config device set <name> 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)).