From 6ce188a270873c4fd737b6dddc5e501f217e32e0 Mon Sep 17 00:00:00 2001 From: Serina Mcfall Date: Sat, 15 Aug 2026 11:56:09 +1200 Subject: [PATCH] docs(launchpad): record ADR-0013 -- config-management tool, Ubuntu baseline, runtime shape Records the decision for #24: Ansible as the configuration-management tool, Ubuntu 24.04 LTS (noble) as the supported starting state, and containers via the existing deploy/compose/ bundle as the runtime shape. Ansible matches the PRD's own expectation, is agentless (no resident daemon on the 1 vCPU/1.9Gi VPS), and has real idempotency satisfying Ruling 11's convergence requirement. Containers avoid the upstream-divergence maintenance trap AGENTS.md section 3 already exists to prevent. Folds in the archived deploy/archived/ansible/ attempt as supporting evidence rather than precedent to resume: it already measured these same three answers on a VM matching the VPS spec (563 MB peak, Compose 2.40.3 from Ubuntu's own archive), and its later archival was for a narrow, orthogonal reason -- wrong upstream image selection, already being fixed separately under #144/ADR-0005 -- not a defect in the tool, OS, or runtime-shape choice itself. Includes a contingency for the accepted risk that Ansible may be unfamiliar to the cohort against the milestone deadline: the tool choice does not change if that risk materializes, since shell + systemd was already rejected for failing Ruling 11. The archived role sketches and measured facts serve as reference material instead. Decided directly in conversation with @serina-mcfall on 2026-08-15, following the recommendation and contingency plan both posted as comments on #24. Signed-off-by: Serina Mcfall --- ...anagement-ubuntu-baseline-runtime-shape.md | 128 ++++++++++++++++++ .../scripts/test-adr-0013-frontmatter.sh | 61 +++++++++ 2 files changed, 189 insertions(+) create mode 100644 launchpad/decisions/ADR-0013-config-management-ubuntu-baseline-runtime-shape.md create mode 100755 launchpad/scripts/test-adr-0013-frontmatter.sh diff --git a/launchpad/decisions/ADR-0013-config-management-ubuntu-baseline-runtime-shape.md b/launchpad/decisions/ADR-0013-config-management-ubuntu-baseline-runtime-shape.md new file mode 100644 index 00000000000..dc452bb53ac --- /dev/null +++ b/launchpad/decisions/ADR-0013-config-management-ubuntu-baseline-runtime-shape.md @@ -0,0 +1,128 @@ +--- +status: Proposed +date: 2026-08-15 +issue: launchpad-26/buzz#24 +decided_in: launchpad-26/buzz#24 +supersedes: none +--- + +# ADR-0013 — Configuration-management tool, Ubuntu baseline and service runtime shape + +## Decision + +**Ansible** as the configuration-management tool, **Ubuntu 24.04 LTS (noble)** as the +supported starting state, and **containers via the existing `deploy/compose/` bundle** +as the runtime shape for Buzz and its dependencies. + +Ansible is the PRD's own named expectation, is agentless (no persistent daemon on a +1 vCPU / 1.9 GiB host), has genuinely idempotent modules (satisfying Ruling 11's +convergence requirement, which shell scripts plus systemd cannot without effectively +reimplementing a configuration-management system), and is readable by a teaching cohort +that has not used it before. Host-managed services are rejected: replacing +`deploy/compose/` with them discards an upstream-maintained artifact and creates the +divergence-at-every-sync maintenance trap `launchpad/AGENTS.md` section 3 already exists +to avoid. + +## Context + +#5 requires the cohort server to be reproducible from version-controlled automation, and +Ruling 3 names the core problem as reproducibly transforming a supplied bare Ubuntu host +into the intended application and security state. Three of that PRD's open questions — +configuration-management tool, Ubuntu LTS baseline, and container-vs-host-managed +runtime shape — are bundled into one ADR because they are not independent: the runtime +shape constrains what the automation manages, and the supported baseline constrains +both. + +This was also at risk of being decided de facto rather than by choice. `#22` deploys the +relay using the repository's existing `deploy/compose/` bundle, which is containerised; +had it shipped before this ADR was settled, the container question would have been +answered by precedent rather than by a recorded decision. As of this ADR, #22 has not +shipped. + +**A prior attempt exists and informs this decision without being resumed.** +`launchpad/deploy/archived/ansible/README.md` records that Ansible, noble, and +containers via `deploy/compose/` were already built toward as "ADR #24's expected +option," pending this ratification. That attempt measured real numbers on a VM matching +the VPS's spec: peak memory 563 MB against 1.9 GiB with swap never touched, all-healthy +in 18 seconds, and Docker installed from Ubuntu 24.04's own archive package +(`docker.io`/`docker-compose-v2`/`containerd`) measured at Compose **2.40.3** — well +clear of the 2.24.4 floor `compose.caddy.yml`'s `!reset` tag needs. Keeping Docker on +Ubuntu's own trusted repository also means its security patches ride Ubuntu's +`-security` stream rather than needing a separate third-party GPG key and its own +unattended-upgrades allow-list entry on a host that is about to be hardened. + +The entire `launchpad/deploy/` tree is nonetheless marked as a **failed deployment +method** and is not to be used to build or deploy Buzz. Reading +`launchpad/deploy/VPS-DEPLOYMENT-AUDIT.md`, the actual failure was narrow and orthogonal +to this ADR's three questions: the deployment defaulted to Block's own upstream image +(`ghcr.io/block/buzz:main`) instead of `launchpad-26/buzz`'s own, mixing +Launchpad-specific work with upstream image-selection behaviour. That problem is being +fixed separately (#144, tracked under ADR-0005's deployment boundary), not by this +decision. The three answers this ADR ratifies are the same ones the archived attempt +assumed, now backed by measurement rather than assumption — no code from the archive is +reused or resurrected; a fresh implementation still builds its roles against the +already-fixed image-selection path. + +## Consequences + +**Good.** Settling this unblocks nearly every implementation task under #5, all of which +are currently worded tool-agnostically and cannot be written concretely until it is +answered. It also gives #44 (host firewall) and #45 (AppArmor) — both blocked on this +ADR — a concrete confinement target: container runtime permissions, capabilities, and +networks, rather than operating-system users alone. + +**Bad, stated honestly.** This is on the critical path, and the tasks under #5 cannot +start in earnest until it lands. Choosing Ansible also adds a tool the cohort may not +already know, against a milestone deadline. Choosing to keep `deploy/compose/` means the +security posture of the container runtime becomes part of the hardening surface, which +Ruling 9 must then address through container and service permissions rather than +operating-system users and AppArmor applied directly to Buzz's own processes. + +**Contingency — Ansible unfamiliarity against the milestone deadline.** + +*Trigger:* #5's Ansible-authoring tasks are visibly behind schedule as the milestone +approaches, or the cohort reports being genuinely blocked by unfamiliarity with Ansible. + +*The fix:* the tool choice does not change. Shell scripts plus systemd were already +rejected earlier in this same decision for failing Ruling 11's convergence requirement, +and switching under deadline pressure would forfeit real idempotency for a false +schedule win. The fix is investing in reference material and pairing, not reopening the +tool decision. + +*The safety net in the meantime:* `launchpad/deploy/archived/ansible/` is unusable for +execution — it is the archived, failed attempt — but is retained as a documented +reference for role structure and shape. `docker` and `compose-bundle` roles were already +sketched there, and the measured facts (Docker-from-archive ships Compose 2.40.3, the +three-file compose invocation, env-var ordering constraints between mounts and +`BUZZ_ADMIN_WEB_DIR`) can be consulted without resurrecting its code. + +## Security implications + +The runtime shape determines what confinement even means. Host-managed services would +be confined with operating-system users and AppArmor directly; containerised services +are confined with runtime permissions, capabilities, and networks instead. Rulings 4, 5, +and 9 have different implementations under each, so this decision is what lets the +confinement tasks under #5 state what they are actually confining. The choice of tool +also determines how secrets reach the host: the archived attempt's pattern of +generating `.env` secrets on the target when absent, rather than templating them from +control-node variables or committing them, satisfies #22's requirement that secrets be +generated on the host and appear in no tracked file, without pre-empting #25's separate +secret-storage decision. A private VPS hostname must never be committed to the +inventory — it belongs in a gitignored `inventory/hosts.local.yml`, per +`launchpad/AGENTS.md`'s public-repository rule. + +## Provenance + +Decided directly in conversation with the repository owner (@serina-mcfall) on +2026-08-15, following the recommendation posted as a comment on #24 (2026-08-15) and the +contingency plan posted as a follow-up comment on #24 (2026-08-15) — the same pattern +used for [ADR-0008](./ADR-0008-security-audit-privilege.md), +[ADR-0009](./ADR-0009-upstream-intel-phase-1-scope.md), +[ADR-0011](./ADR-0011-external-security-smoke-test-floor.md), and +[ADR-0012](./ADR-0012-inference-provider-boundary.md). `issue` and `decided_in` both +point to #24 because the decision and its filing issue are the same place. + +Not verified independently in this document: whether the cohort has existing Ansible +experience (the original issue's own caveat, still open); the archived measurements were +read from `launchpad/deploy/archived/temp-handoff.md` and +`launchpad/deploy/archived/ansible/README.md`, not re-measured in this session. diff --git a/launchpad/scripts/test-adr-0013-frontmatter.sh b/launchpad/scripts/test-adr-0013-frontmatter.sh new file mode 100755 index 00000000000..26ea4c21663 --- /dev/null +++ b/launchpad/scripts/test-adr-0013-frontmatter.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# Verifies ADR-0013's frontmatter and numbering, following the same checks +# used for ADR-0008/0009/0010/0011/0012. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DECISIONS_DIR="${SCRIPT_DIR}/../decisions" +FILE="${DECISIONS_DIR}/ADR-0013-config-management-ubuntu-baseline-runtime-shape.md" + +PASS=0 +FAIL=0 + +check() { + local label=$1 ok=$2 + if [ "${ok}" = "0" ]; then + echo "PASS: ${label}" + PASS=$((PASS + 1)) + else + echo "FAIL: ${label}" + FAIL=$((FAIL + 1)) + fi +} + +status=1 +[ -f "${FILE}" ] && status=0 +check "ADR-0013 exists at the expected path" "${status}" + +status=1 +[ ! -f "${DECISIONS_DIR}/ADR-0024-config-management-ubuntu-baseline-runtime-shape.md" ] && status=0 +check "not filed under the issue number (0024)" "${status}" + +status=1 +python3 -c " +import yaml +text = open('${FILE}').read() +front = text.split('---')[1] +data = yaml.safe_load(front) +assert data['status'] == 'Proposed', data['status'] +assert data['issue'] == 'launchpad-26/buzz#24', data['issue'] +assert data['decided_in'] == 'launchpad-26/buzz#24', data['decided_in'] +assert data['supersedes'] == 'none', data['supersedes'] +" && status=0 +check "frontmatter parses and status is Proposed" "${status}" + +status=1 +grep -q "^# ADR-0013 —" "${FILE}" && status=0 +check "the H1 heading number matches the filename" "${status}" + +status=1 +dupes=$(ls "${DECISIONS_DIR}" | grep -oE '^ADR-[0-9]{4}' | sort | uniq -d) +[ -z "${dupes}" ] && status=0 +check "no duplicate ADR numbers on this branch (found: ${dupes:-none})" "${status}" + +status=1 +grep -q "Trigger:\* #5's Ansible-authoring tasks" "${FILE}" && status=0 +check "the Ansible-unfamiliarity contingency trigger is stated" "${status}" + +echo "" +echo "===================================================" +echo "${PASS} passed, ${FAIL} failed" +[ "${FAIL}" -eq 0 ]