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>
64 lines
2.1 KiB
YAML
64 lines
2.1 KiB
YAML
---
|
|
- name: Install ZFS userland + kernel module
|
|
ansible.builtin.apt:
|
|
name: zfsutils-linux
|
|
state: present
|
|
|
|
- name: Ensure the ZFS kernel module is loaded
|
|
community.general.modprobe:
|
|
name: zfs
|
|
state: present
|
|
|
|
# --- Pool creation (guarded & destructive) ----------------------------------
|
|
- name: Check whether the ZFS pool already exists
|
|
ansible.builtin.command: "zpool list -H -o name {{ zfs_pool_name }}"
|
|
register: zpool_check
|
|
changed_when: false
|
|
failed_when: false
|
|
|
|
- name: Record pool existence
|
|
ansible.builtin.set_fact:
|
|
zpool_exists: "{{ zpool_check.rc == 0 }}"
|
|
|
|
- name: Probe the target disk for existing data
|
|
ansible.builtin.command: "lsblk -nro FSTYPE,MOUNTPOINT,PARTTYPE {{ zfs_pool_disk }}"
|
|
register: disk_probe
|
|
changed_when: false
|
|
when: not zpool_exists
|
|
|
|
- name: Refuse to create a pool on a non-empty disk unless forced
|
|
ansible.builtin.assert:
|
|
that:
|
|
- (disk_probe.stdout | trim | length == 0) or zfs_pool_force
|
|
fail_msg: >-
|
|
{{ zfs_pool_disk }} appears to already contain data
|
|
({{ disk_probe.stdout | trim }}). Refusing to create the pool. Verify you
|
|
picked the right disk, then set zfs_pool_force=true to override.
|
|
when: not zpool_exists
|
|
|
|
- name: Create the ZFS pool on the dedicated disk
|
|
ansible.builtin.command: >-
|
|
zpool create {{ '-f ' if zfs_pool_force else '' }}-o ashift=12
|
|
-O compression={{ zfs_compression }}
|
|
-O atime=off
|
|
-O xattr=sa
|
|
-O acltype=posixacl
|
|
-O mountpoint=/{{ zfs_pool_name }}
|
|
{{ zfs_pool_name }} {{ zfs_pool_disk }}
|
|
when: not zpool_exists
|
|
|
|
# --- Datasets ---------------------------------------------------------------
|
|
- name: List existing datasets
|
|
ansible.builtin.command: "zfs list -H -o name"
|
|
register: zfs_existing
|
|
changed_when: false
|
|
|
|
- name: Create platform + customers datasets
|
|
ansible.builtin.command: >-
|
|
zfs create -p
|
|
{% if item.mountpoint is defined %}-o mountpoint={{ item.mountpoint }}{% endif %}
|
|
{{ zfs_pool_name }}/{{ item.path }}
|
|
loop: "{{ zfs_child_datasets }}"
|
|
loop_control:
|
|
label: "{{ zfs_pool_name }}/{{ item.path }}"
|
|
when: (zfs_pool_name ~ '/' ~ item.path) not in zfs_existing.stdout_lines
|