Add tests/lint/repository/test_task_file_numbering.py: numbered task
files use exactly two leading digits, a numbered *_core.yml must be
00_core.yml, per directory the numbers are unique/consecutive and start
at 01 (00 with a core file), numbered includes appear in ascending
order, and shared helpers live in tasks/utils/ instead of the numbered
chain.
Apply it across every role: rename NN_core.yml to 00_core.yml with a
consecutive down-shift of the siblings, move letter-suffix children into
a subdirectory named after their parent task, relocate helpers included
from more than one place into tasks/utils/, and update every reference
(include_tasks, path_absolute, .ansible-lint-ignore, requirement docs).
Legacy narration comments in the touched files are deleted; the upstream
xwiki.properties reference block opts out via nocheck. The unused
MAILU_IP6_PUBLIC var (only ever named in now-removed comments) is dropped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a nightly timer (02:00, after the secrets snapshot) next to the
existing plug trigger. ConditionPathIsMountPoint on the unit makes an
unmounted tick a silent condition-skip instead of a failure, so the
schedule only ever acts when the device is currently plugged in.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
svc-bkp-volume-2-local now declares secrets_backup (enabled+shared true):
host secrets are captured unconditionally wherever container backups run.
svc-bkp-remote-2-local declares local-2-device gated on group membership
(operator flag, no variants) so the pull host syncs to the encrypted
device when the role group is assigned; local-2-device registers as a
shared provider and gains the 01_core run-once schema. local-2-device
recover.py learns --device-target: snapshots live under <mount>/<target>,
so recovering without the configured target subpath found nothing.
secrets-2-local memory shrinks to 128m/256m (a oneshot rsync) to keep
variant stacks under the host memory budget.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add tests/lint/repository/test_name_emoji_and_length.py: every Ansible task
name and GitHub Actions workflow/job/step name must start with an emoji (first
char not 0-9/A-Za-z) and be <= 80 chars.
- emit emoji names at the source: cli/build/include generator now writes
"🔧 setup <app>" / "🚿 flush handlers after <app>" so generated
tasks/groups/*-roles.yml satisfy the convention too
- convert existing task/step/job/workflow names to emoji-prefixed, choosing a
task-fitting icon per action (📦 install, ✅ ensure, 🔍 probe, 🥾 bootstrap,
🚀 deploy, 🧩 service-module load, ⬇️ checkout, 📂 include-routines, ...)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The unit only Wants= the USB .mount (no Requires/ConditionPathIsMountPoint), so under MODE_DEBUG (CI) sys-service's default state=restarted ExecStarted the oneshot at deploy time. With no USB device present files/script.py exits 1 and the sys-service diagnostics task hard-failed the deploy. Suppress the flush, as btrfs-balancer and svc-bkp-container-2-local already do; the unit install and the .mount plug-trigger still run for production.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the empty-config compose deploy failure (assert
BACKUP_TO_USB_MOUNT != "") with a working test path:
- meta/variants.yml: pin non-empty mount/target/source so the CI matrix
(which applies variants) deploys the plug-triggered backup service. No
device appears in CI, so the .mount unit only waits and the oneshot
backup never fires.
- tasks/main.yml: fix the assert typo BACKUP_TO_USB_target -> ..._TARGET.
The lowercase name was undefined, so the assert would still error even
with the variant config present.
- vars/main.yml: derive entity_name via get_entity_name and build the
config lookup path from it instead of hardcoding 'local-2-device'.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
sys-ctl-cln-certs is gated at runtime by
`lookup('tls', application_id, 'mode') == 'letsencrypt'` inside
mtn-cert-renew's include chain, which the role-id/category-match
required_by mechanism cannot express. Mark the role with
`# nocheck: required-by-coverage` and the reason inline.
svc-bkp-local-2-device picks up sys-ctl-cln-faild-bkps as an explicit
dependency so failed snapshots on the external device are torn down
the same way they are for the container-2-local path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Consolidates the backup-role topology around four directional roles
under the existing svc.bkp category:
sys-ctl-bkp-docker-2-loc → svc-bkp-container-2-local
svc-bkp-loc-2-usb → svc-bkp-local-2-device
svc-bkp-rmt-2-loc → svc-bkp-remote-2-local
sys-bkp-provider → deleted (purely a bundler with no own logic;
the two pieces it pulled in — user-backup
and sys-timer-cln-bkps — are now declared
directly by callers that need them)
Every renamed role moves into svc.bkp (already invokable per
roles/categories.yml), gets an `application_id` in vars/main.yml, and
declares the per-app primary-entity service flags + resource limits the
invokable-role lint requires.
Companion changes:
- svc-bkp-container-2-local: meta/variants.yml declares three matrix
rounds (gitea+keycloak, postgres+mariadb, openldap) that pin the
companion topology via service-key flags the CombinedResolver
translates into provider-role includes.
- sys-svc-compose: container-2-local is no longer in the docker-mgmt
include loop; it joins via a separate `'svc-bkp-container-2-local'
in group_names` gate so the 3 backup roles are deployable
independently (each addresses its own failure mode).
- The three `04_seed-database-to-backup.yml` includes (web-app-matrix,
web-app-bigbluebutton, sys-svc-rdbms) are gated by the same
group_names check so consumer apps only seed when the container
backup is actually in scope.
- web-app-gitea picks up `enabled: true, shared: true, provides:
gitea` on its primary entity so the service registry can pull it in
via a `services.gitea.enabled+shared` flag (needed for the
container-backup variant 0).
- group_vars constants and the pyproject ruff per-file-ignore + the
.ansible-lint-ignore paths follow the role-id rename.
- The remote-2-local script picks up the snake_case rename
(`pull-specific-host.py` → `pull_specific_host.py`) and the
`os.makedirs` → `Path.mkdir` conversion so the role no longer needs
the PTH/N999 ruff escape hatch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>