fix(O1): scaffold docker_host role so make lint passes on main

playbooks/site.yml imports the docker_host role, but it didn't exist, so
ansible-lint's syntax-check failed on a clean checkout — breaking CLAUDE.md's
"main must always work" / "Never skip lint" (top open finding O1 from the
2026-06-11 review).

Scaffold docker_host as a proper placeholder via the prescribed mechanism
(make new-role): filled meta/main.yml + README, an honest no-task tasks/main.yml
documenting planned scope (Docker engine + Compose, daemon hardening, nftables.d
container rules per ADR-004/020), and the standard molecule scenario. This
preserves site.yml's full-standard-state intent rather than dropping the play.

Update STATUS.md (docker_host moves from "Not in git" to "scaffolded, no tasks")
and the role/playbook READMEs to match.

make lint: 0 failures, 0 warnings; check-tags OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
sjat 2026-06-11 14:53:55 +02:00
parent 1da117d65b
commit 03d33f83dd
12 changed files with 118 additions and 9 deletions

View file

@ -35,14 +35,14 @@ _Last reviewed: 2026-06-11._
| Thing | State |
|---|---|
| `roles/base/` | **Partially built.** The `firewall` concern is implemented (nftables: catalog-driven default-deny + east-west allowlist + auto-rollback apply; ADR-020) with pytest + Molecule render/syntax tests. Other concerns (SSH hardening, fail2ban, auditd, packages, users) are **not** built yet, so `make deploy PLAYBOOK=site` has no real content to apply (the make target itself now works — see "Real and working today"). |
| `roles/docker_host/` | Not in git. Same. |
| `roles/docker_host/` | **Scaffolded, no tasks.** In git (meta/README/molecule filled), wired into `playbooks/site.yml` so the standard state is expressed end-to-end and `make lint` covers it, but it has no tasks yet — applying it is a no-op. Planned scope (Docker engine + Compose, daemon hardening, `nftables.d` container rules) in ADR-004/ADR-020. |
| `inventories/*/hosts.yml` | Structured stubs with empty host maps (`hosts: {}`); regenerated by `make tf-inventory` once Terraform has hosts |
| `inventories/production/group_vars/{docker_hosts,proxmox_hosts}/` | Empty dirs |
So `make deploy PLAYBOOK=site` has no real content to apply — `base` is only partially
built (its `firewall` concern only) and the `docker_host` role does not exist yet. (The
`make check`/`deploy` machinery itself now works — first proven by applying `dev_env` via
`playbooks/workstation.yml`.)
built (its `firewall` concern only) and the `docker_host` role is scaffolded but has no
tasks yet. (The `make check`/`deploy` machinery itself now works — first proven by
applying `dev_env` via `playbooks/workstation.yml`.)
## Designed but not built

View file

@ -5,7 +5,7 @@ Top-level orchestration playbooks. No inline vars — configuration comes from
- `site.yml` — full standard state: applies `base` to all hosts and `docker_host`
to docker hosts. **Note:** `base` is only partially built (its `firewall` concern)
and `docker_host` does not exist yet, so this is incomplete — see `STATUS.md`.
and `docker_host` is scaffolded with no tasks yet, so this is incomplete — see `STATUS.md`.
- `workstation.yml` — applies the `dev_env` role (interactive developer environment)
to the `control` group; built and applied to `ubongo` (see `STATUS.md`).
- `bootstrap.yml` — first-run setup for a host that may not have Python yet;

View file

@ -1,9 +1,9 @@
---
# site.yml — apply full standard state to all hosts
# Run via: make deploy PLAYBOOK=site
# NOTE: `base` is only partially built (its `firewall` concern; see STATUS.md) and the
# `docker_host` role does not exist yet, so this playbook applies base's firewall but is
# incomplete until `docker_host` is created.
# NOTE: `base` is only partially built (its `firewall` concern; see STATUS.md) and
# `docker_host` is scaffolded but has no tasks yet, so this playbook applies base's
# firewall but is otherwise incomplete until those roles gain content.
- name: Apply base configuration to all hosts
hosts: all

View file

@ -10,6 +10,6 @@ Each role must have: a `molecule/default/` scenario (Debian 13), a populated
Current state: `base` is **partially built** — its `firewall` concern (nftables) is
implemented and tested; the other concerns (SSH hardening, fail2ban, auditd, packages,
users) are not yet built. `docker_host` does **not exist yet**. `dev_env` (interactive
users) are not yet built. `docker_host` is **scaffolded but has no tasks yet**. `dev_env` (interactive
developer environment) is built and applied. See `STATUS.md` for the authoritative
breakdown.

View file

@ -0,0 +1,34 @@
# docker_host
Docker engine + Compose runtime applied to every host in the `docker_hosts` group.
Provides the container platform that the per-service roles (one service = one role,
ADR-004) deploy their Compose stacks onto.
> **Status: scaffolded, not yet implemented.** This role has no tasks yet — applying it
> is a no-op. It is wired into `playbooks/site.yml` so the full standard state is
> expressed end-to-end, and so `make lint` covers it. See `STATUS.md`.
## Planned scope
- Install Docker engine + the Compose plugin, version-pinned (ADR-011).
- Daemon hardening: `iptables: false` (the host `base` firewall owns nftables, ADR-020),
log driver, `live-restore`, user-namespace remapping where practical (ADR-002).
- Render container forward/NAT rules into `/etc/nftables.d/*.nft` — the include hook the
`base` role's ruleset exposes (see `roles/base/README.md`).
- Provide the runtime the service roles deploy their Compose files onto.
## Variables
None yet. Placeholders will use the `docker_host__*` namespace (CLAUDE.md convention).
## Example
```yaml
- hosts: docker_hosts
become: true
roles:
- role: docker_host
tags: [docker_host]
```
See ADR-004 (`docs/decisions/004-docker-model.md`) for the Docker & Compose model.

View file

@ -0,0 +1 @@
---

View file

@ -0,0 +1 @@
---

View file

@ -0,0 +1,11 @@
---
galaxy_info:
author: sjat
description: Docker engine + Compose runtime for boma docker_hosts (Debian 13).
license: MIT
min_ansible_version: "2.17"
platforms:
- name: Debian
versions:
- trixie
dependencies: []

View file

@ -0,0 +1,7 @@
---
- name: Converge
hosts: all
gather_facts: true
roles:
- role: docker_host

View file

@ -0,0 +1,31 @@
---
dependency:
name: galaxy
options:
requirements-file: ../../requirements.yml
driver:
name: docker
platforms:
- name: instance
# Project-owned image built from .docker/molecule-debian13/Dockerfile
# and hosted in the Forgejo container registry.
# Build/push with: make molecule-image / make molecule-image-push
image: forgejo.nyumbani.baobab.band/sjat/molecule-debian13:latest
pre_build_image: true
privileged: true # required for systemd
cgroupns_mode: host
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
command: /lib/systemd/systemd
provisioner:
name: ansible
inventory:
host_vars:
instance:
ansible_user: root
verifier:
name: ansible

View file

@ -0,0 +1,11 @@
---
- name: Verify
hosts: all
gather_facts: true
tasks:
- name: Add verification tasks here
ansible.builtin.assert:
that: true
msg: "Replace this with real assertions"
tags: [verify]

View file

@ -0,0 +1,13 @@
---
# docker_host — Docker engine + Compose runtime for hosts in the docker_hosts group.
#
# SCAFFOLDED, NOT YET IMPLEMENTED. This role is referenced by playbooks/site.yml so the
# full standard state is expressed end-to-end, but it has no tasks yet — applying it is a
# no-op. See STATUS.md ("Scaffolded but empty") and ADR-004 (Docker & Compose model).
#
# Planned scope (ADR-002/004/020):
# - install Docker engine + compose plugin (version-pinned, per ADR-011)
# - daemon hardening: iptables:false (host nftables owns the firewall, ADR-020),
# log-driver, live-restore, userns where practical
# - render container forward/NAT rules into /etc/nftables.d/*.nft (the base-role hook)
# - deploy per-service Compose stacks from the service roles (one service = one role)