Idempotent host configuration targeting Ubuntu 24.04 with ZFS on a dedicated second disk. Role-based platform-infra/ansible: - base: apt packages, timezone, unattended security upgrades. - zfs: install ZFS, create pool on a dedicated disk (guarded against wiping a non-empty disk), create platform datasets + customers parent per docs/03; docker dataset mounted at /var/lib/docker. - docker: Docker Engine + Compose plugin, daemon.json written before first start so the native zfs storage driver initializes on the ZFS data-root; per-site network address pool preconfigured. - firewall: nftables inbound default-deny in a dedicated table that never flushes Docker's rules; container outbound SMTP blocked via a DOCKER-USER jump applied by a systemd oneshot. - ssh_hardening: key-first SSH with an anti-lockout assertion, config validation gate, and the sftponly group for Phase 4 SFTP accounts. Includes ansible.cfg, requirements.yml, inventory example, group_vars with safety notes, and a run guide. Real inventory (hosts.yml) is git-ignored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
78 lines
3.3 KiB
Markdown
78 lines
3.3 KiB
Markdown
# Phase 1 — Host baseline (Ansible)
|
|
|
|
Idempotent host configuration for the heleos platform, targeting **Ubuntu 24.04
|
|
LTS**. Roles: base packages → ZFS pool/datasets → Docker (data-root on ZFS) →
|
|
nftables firewall + container egress filter → SSH hardening.
|
|
|
|
## Prerequisites
|
|
|
|
1. An Ubuntu 24.04 VM you can SSH into as a **sudo-capable user**.
|
|
2. A **dedicated second virtual disk** attached to the VM for the ZFS pool
|
|
(e.g. `/dev/sdb` or `/dev/vdb`) — separate from the OS disk.
|
|
3. Ansible installed on your workstation (`pipx install ansible` or apt).
|
|
|
|
## Configure
|
|
|
|
```bash
|
|
cd platform-infra/ansible
|
|
ansible-galaxy collection install -r requirements.yml
|
|
|
|
cp inventory/hosts.yml.example inventory/hosts.yml # edit host/user (git-ignored)
|
|
$EDITOR group_vars/all.yml # set zfs_pool_disk, keys, etc.
|
|
```
|
|
|
|
Key variables in `group_vars/all.yml`:
|
|
|
|
| Variable | Meaning |
|
|
|----------|---------|
|
|
| `zfs_pool_disk` | The dedicated disk for the pool. **Its contents will be destroyed.** |
|
|
| `zfs_pool_force` | Must be `true` to create a pool on a non-empty disk (safety gate). |
|
|
| `admin_authorized_keys` | Public keys for the admin — required before disabling passwords. |
|
|
| `ssh_disable_password_auth` | Leave `false` until key login is verified, then flip to `true`. |
|
|
| `smtp_relay_host` | Optional; if set, containers may reach SMTP only on this host. |
|
|
|
|
## Run
|
|
|
|
```bash
|
|
ansible-playbook site.yml --check # dry run (note: first run can't fully
|
|
# check tasks that depend on ZFS/Docker
|
|
# not yet present)
|
|
ansible-playbook site.yml # apply
|
|
```
|
|
|
|
Run a single layer with tags: `--tags zfs`, `--tags docker`, `--tags firewall`,
|
|
`--tags ssh`, `--tags base`.
|
|
|
|
## Safety notes
|
|
|
|
- **ZFS is destructive:** the play refuses to create a pool on a disk that
|
|
already has a filesystem/partition unless `zfs_pool_force: true`. Double-check
|
|
`zfs_pool_disk` points at the empty spare disk, not the OS disk. For production
|
|
prefer a stable `/dev/disk/by-id/...` path over `/dev/sdb`.
|
|
- **SSH lock-out:** the play asserts that `admin_authorized_keys` is non-empty
|
|
before it will disable password authentication. Verify you can log in with your
|
|
key **before** setting `ssh_disable_password_auth: true`.
|
|
- **Firewall coexistence:** host inbound rules live in a dedicated `inet heleos`
|
|
nftables table and never flush the global ruleset, so Docker's own iptables/nft
|
|
rules are left intact. Container SMTP egress is blocked via a `DOCKER-USER`
|
|
jump applied by the `heleos-docker-egress` service.
|
|
|
|
## What this sets up
|
|
|
|
- ZFS pool `tank` with platform datasets and the `customers` parent
|
|
(see [../../docs/03-naming-conventions.md](../../docs/03-naming-conventions.md)).
|
|
- Docker Engine + Compose plugin, data-root on `tank/platform/docker` using the
|
|
native `zfs` storage driver; per-site network address pool preconfigured.
|
|
- nftables default-deny inbound (allow SSH/80/443 + established + loopback +
|
|
ICMP); outbound SMTP blocked from containers.
|
|
- Hardened SSH (key-first, root prohibit-password) and the `sftponly` group that
|
|
per-customer SFTP accounts will join in Phase 4.
|
|
|
|
## Verify after running
|
|
|
|
```bash
|
|
zpool status && zfs list
|
|
docker info | grep -E 'Storage Driver|Docker Root Dir'
|
|
sudo nft list table inet heleos
|
|
sudo iptables -S DOCKER-USER
|
|
```
|