feat(infra): production host bootstrap and bare-metal Stalwart scaffolding

Host provisioning for the single-server production target: SSH + firewall
hardening (nftables allowlist), k3s node registration, bare-metal Stalwart
install with systemd units and TLS cert-sync from the cluster secret, and
Restic encrypted backup/restore (primary + DR) with timer units. Host-specific
secrets live in config.env (gitignored); config.env.example is the template.
Also gitignores MemPalace per-project files.
This commit is contained in:
Ronni Baslund
2026-06-07 00:19:48 +02:00
parent 5ed3d2bc5f
commit 3831c85285
18 changed files with 1432 additions and 0 deletions
@@ -0,0 +1,59 @@
# ─────────────────────────────────────────────────────────────
# Dezky production host configuration
#
# Copy to `config.env` and fill in real values. `config.env` is
# gitignored — it holds host-specific values, not the repo's source
# of truth. Both bootstrap.sh and firewall/firewall.sh source this.
# ─────────────────────────────────────────────────────────────
# --- Management allowlist -------------------------------------------------
# Source addresses allowed to reach SSH (22) and the k3s API (6443).
# Everything else on those ports is dropped. Accepts a comma-separated
# list of single IPs and/or CIDRs (e.g. home + office, or a /29 block,
# or a v6 /64 prefix) — the firewall treats these as nftables interval sets.
#
# NOTE: residential IPs can change. If yours is dynamic, prefer a small
# prefix here, and remember Hetzner's KVM/LARA console is always reachable
# out-of-band if you ever lock yourself out (see README).
MGMT_ALLOW_V4="203.0.113.10, 203.0.113.11" # REQUIRED — management IPv4(s)/CIDR(s)
MGMT_ALLOW_V6="" # optional — management IPv6(s)/prefix (empty to skip)
# --- Server identity ------------------------------------------------------
SERVER_HOSTNAME="node1.dezky.eu" # FQDN set on the box
SERVER_PUBLIC_IPV4="" # AX41 primary IPv4 (fill after provisioning)
SERVER_PUBLIC_IPV6="" # AX41 primary IPv6 (fill after provisioning)
# --- Admin (non-root) user ------------------------------------------------
ADMIN_USER="dezky" # created with sudo; root SSH login is then disabled
ADMIN_SSH_PUBKEY="" # REQUIRED — your SSH public key (the WHOLE line, e.g. "ssh-ed25519 AAAA... you@home")
# --- SSH ------------------------------------------------------------------
SSH_PORT="22" # keep 22 unless you have a reason; obscurity is not security
# --- k3s networking (defaults; change ONLY if you customise k3s CIDRs) ----
K3S_POD_CIDR="10.42.0.0/16" # flannel pod network — accepted to/from host
K3S_SERVICE_CIDR="10.43.0.0/16" # cluster service network — accepted to/from host
# --- Rancher Custom-cluster registration (SECRET) -------------------------
# From Rancher → Cluster Management → <cluster> → Registration tab. Create the
# cluster with the **K3s** distribution first. Token + checksum are secrets.
RANCHER_SERVER_URL="https://rancher.example.com"
RANCHER_NODE_TOKEN="" # REQUIRED — node registration token
RANCHER_CA_CHECKSUM="" # REQUIRED — CA checksum from the same command
RANCHER_NODE_ROLES="--etcd --controlplane --worker" # single node = all three
RANCHER_INSECURE_FETCH="true" # true if Rancher is reached by IP / self-signed cert
# --- Stalwart mail (host service) -----------------------------------------
# SECRETS — platform-api (k3s) must use the SAME admin password + webhook secret.
STALWART_VERSION="latest" # pin to a release tag after first install
STALWART_ADMIN_PASSWORD="" # REQUIRED — openssl rand -hex 24
STALWART_WEBHOOK_SECRET="" # REQUIRED — openssl rand -hex 32
# --- Restic backups (host) ------------------------------------------------
# Storage Box is SSH/SFTP on PORT 23, key auth. STORE RESTIC_PASSWORD OFFLINE.
RESTIC_PASSWORD="" # REQUIRED — openssl rand -hex 32 (save offline!)
BACKUP_PRIMARY_REPO="" # sftp:<user>@<user>.your-storagebox.de:/dezky
BACKUP_DR_REPO="" # sftp:<user>@<user>.your-storagebox.de:/dezky (Helsinki box)
BACKUP_PATHS="/opt/stalwart/data /opt/stalwart/etc /var/lib/rancher/k3s/server/db/snapshots /var/lib/rancher/k3s/storage"
BACKUP_RETENTION="--keep-daily 7 --keep-weekly 4 --keep-monthly 6"
BACKUP_HEALTHCHECK_URL="" # optional dead-man's-switch base URL