The package-cache frontend terminates TLS for package traffic and signs
with its own CA: probed from inside the test container, pypi.org arrives
as subject CN=pypi.org issued by CN=Infinito Package-Cache Frontend CA.
package-frontend-ca.sh installs that CA into the OS store, which is why
apt, pip, curl and urllib work — but requests reads certifi, which has
never heard of it, so test-external failed its reachability precondition
with CERTIFICATE_VERIFY_FAILED. Upgrading certifi does not help; the
issuer is not a public root.
load.sh is where the runner puts the venv on PATH, so it is also where the
interpreter carrying the pip certifi is chosen; pointing REQUESTS_CA_BUNDLE
and SSL_CERT_FILE at the store the stack deliberately fills keeps one trust
decision instead of two. A caller-set value still wins.
The bundle location was about to become a fourth copy: dev-nix and the
network diagnose CLI each carried their own candidate list, and the two had
already drifted apart in their fourth entry. All of them now read
group_vars CA_BUNDLE_CANDIDATES — Ansible directly, the shell through a
generated env key, python through read_group_paths.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every script a role ships now lives under `files/<language>/`, and the unit
suite is split the same way: `tests/unit/python/` beside new sibling trees for
the other languages. `tests/lint/filesystem/test_role_files_layout.py` keeps it
that way; directories whose shape is dictated from outside carry a `.nocheck`
naming the constraint (Docker build contexts, WordPress mu-plugins, the Joomla
extension, Odoo addons, Playwright suites, the `files/test/test.sh` harness
that `test-e2e-cli` resolves generically).
This cannot be applied in halves. Moving a file and repointing what names it
are the same change: 14 references resolve only at deploy time, so the tree is
green either before or after, never between.
Those 14 were found by static resolution over every reference class, not by the
test suite -- a full `make quality-high` was green while all of them were
broken:
* 7 `src:` values built from a role variable holding a bare filename
(svc-bkp-remote-2-local, decidim, espocrm, friendica, joomla, pixelfed x2).
The variable doubles as the destination name inside the container, so the
language segment belongs at the call site, not in the variable.
* `sys-service/tasks/04_base.yml` resolved every consumer's systemd script
through `first_found(..., errors='strict')` over `files/script.{sh,py}`.
18 roles ship exactly that and have no template fallback: the play would
have died on each.
* `sys-front-inj-javascript` fell back to `<role>/files/javascript.js`; the
interface now reads `files/javascript/javascript.js`.
* `sys-front-inj-logout/templates/head_sub.j2` built the cache-busting mtime
path through `path_absolute(... 'files', ...)`.
* 4 `ansible.builtin.script:` invocations naming a bare command
(pull.py, prune.sh, set_postgres_superuser_password.sh, ganesha-serve.sh).
* `dev-python/files/shell/install.sh` pointed at a sibling role's script.
Depth-dependent paths inside the moved trees were bumped accordingly: 59
`parents[N]` computations, 12 relative climbs, and the per-role recover CLI,
whose path `cli/administration/recover/recoverers.py` still assembled from the
pre-move location.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Replaces the raw package modules at 76 call sites with package_install, moves
each role's distribution mapping into its own meta/packages.yml, and turns the
inventory PACKAGES variable from source-keyed literal names into a list of
declared package ids. The lint that forbids the raw modules is enabled here,
because the migration burns it down from 76 offenders to zero.
Removes sys-aur, sys-aur-install and sys-package. The AUR build user, the
passwordless pacman rule and the inventory-driven install are part of the plan
the action plugin executes, so the roles no longer carry them.
Declaring the mappings surfaced breakage the raw modules had hidden: on Debian
the `which` binary comes from debianutils, jrnl is in no Debian or Ubuntu
suite, chromium-browser is absent from Debian stable, JDK 11 is unobtainable on
RedHat, wireguard had no RedHat branch at all, qbittorrent was built from the
AUR although Arch ships it in extra, and desk-docker installed a Docker variant
that conflicts with the one sys-svc-container installs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
Add a 'timeout' lookup plugin that returns round(base * TIMEOUT_FACTOR)
(group_vars default 1, so value-identical to the old literals). One knob
in group_vars stretches every wait in the stack for a slow uplink:
TIMEOUT_FACTOR: 2 doubles them all.
- plugins/lookup/timeout.py + unit tests (identity, factor scaling,
string coercion, error paths).
- group_vars/all/17_resource.yml: TIMEOUT_FACTOR: 1.
- Convert all 34 literal 'timeout:' task keywords / module args across 25
task+handler files to "{{ lookup('timeout', <base>) | int }}".
- tests/lint/repository/yaml/test_timeout_uses_lookup.py enforces the
lookup in every task/handler timeout (opt-out: # nocheck: timeout-lookup).
- swarm.yml pre-deploy build handler: add the missing timeout (same
network-build hang vector as compose.yml's Build compose).
- Drop 13 pre-existing narration comments in the touched files (comment
lint enforces whole-file on any edited file).
Co-Authored-By: Claude Opus 4.8 (1M context) <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>
`make autoformat` (mbake) rewrites multi-whitespace gaps in YAML
inline comments to a single space. Pure formatting touch on the
`# nocheck: stack-host-template` marker, no functional change.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds plugins/action/conf_template.py, a drop-in `template:` replacement
that returns `skipped: true` whenever IS_STACK_HOST evaluates to false.
Routes the rendered Jinja through ansible.module_utils.parsing.convert_bool
so both Python bool and string "True"/"False" templar outputs resolve
correctly. Inherits every standard template module argument.
Migrates seven nginx-tree consumers that previously aborted on workers
with "Destination directory does not exist": sys-front-tls,
svc-db-openldap, sys-svc-webserver-core (nginx.conf + default_server.conf),
web-app-matrix (synapse vhost), web-app-peertube (per-domain vhost),
web-opt-rdr-domains (redirect vhost), and web-app-prometheus.
Tags twelve legitimate bare `template:` callers with
`# nocheck: conf-template`: per-host system configs (locale, nix, systemd
manager defaults, journald, msmtprc) and per-app compose-dir configs
(magento x2, mailu x2, moodle, odoo, taiga) that intentionally bypass the
project's nginx tree.
Adds tests/lint/ansible/modules/test_conf_template_migration.py as the
drift guard: any future `template:` task whose `src:` ends in `.conf.j2`
must adopt conf_template or carry the nocheck marker. Adds seven unit
tests for the plugin covering skip semantics, dispatch, mode defaults,
mode overrides, full-arg passthrough, and the string-bool templar path.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Cherry-pick of bdc5945bc from feature/web-app-zammad, retargeted at
roles/dev-nix/tasks/02a_nix_install_only.yml (the include_tasks
companion file that 201670975 introduced on this branch).
Same root cause + fix as on the zammad branch:
TASK [dev-nix : Set build group override for Nix installer when nixbld exists]
Error while resolving value for 'dev_nix_installer_build_group_id':
object of type 'NoneType' has no attribute 1
`getent` with `fail_key: false` inserts the key with a `None` value
when the entry is missing, so the membership test `'nixbld' in
ansible_facts.getent_group` evaluates True but `getent_group.nixbld[1]`
crashes on None. The guard now explicitly checks the value with
`is not none` for both `nixbld` (group) and `nixbld1` (passwd).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CI run 26643336674 (feature/web-app-zammad) failed at:
TASK [dev-nix : Set build group override for Nix installer when nixbld exists]
Error while resolving value for 'dev_nix_installer_build_group_id':
object of type 'NoneType' has no attribute 1
Root cause: `getent` with `fail_key: false` does NOT drop the key when
the entry is missing — it inserts the key with a `None` value. The
existing `when:` guard `'nixbld' in ansible_facts.getent_group`
therefore evaluates True (the membership test only checks the key, not
the value), and `getent_group.nixbld[1]` then crashes when the value
is None.
Fix: replace the membership check with an explicit `is not none`
assertion for both `nixbld` (group entry) and `nixbld1` (passwd entry).
A short comment above each `when:` block names the trap so the next
reader does not re-introduce the membership-only check.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two pre-existing lint failures on main (introduced in 45eea2892) blocked
`make test` from going green:
- test_excessive_repeated_when_in_tasks_files flagged the four
`when: dev_nix_binary_path_before | length == 0` repeats across
Purge / getent group / getent passwd / Run-installer tasks.
- test_blocks_with_when_and_sections_have_max_three_tasks rejects any
`block:` carrying a block-level `when:` and more than 3 tasks per
section, because the block-level `when:` does NOT skip parse-time
resolution of inner tasks.
Final shape: the six install tasks live in
`02a_nix_install_only.yml` and are pulled in via a single
`include_tasks` guarded by the original `when:`. When the binary is
already present, Ansible never opens the included file at all.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adding the purge task pushed dev-nix/tasks/02_non_arch_installer.yml
to 4 tasks gated by `when: dev_nix_binary_path_before | length == 0`,
tripping tests.integration.roles.when.test_duplication.
A `block:` would satisfy that lint but exceeds the project convention
of max 3 entries per block. Move the six install-branch tasks (purge,
nixbld group+uid probes, override set_facts, installer run) into a
dedicated tasks/03_install_official.yml and call it once via
include_tasks with the original guard `when:` on the include.
Behavior unchanged; inner tasks no longer re-evaluate the binary-path
condition each call.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The Nix 2.32.4 installer allocates build users ascending from
NIX_FIRST_BUILD_UID. The pkgmgr-centos:stable base image ships
nixbld1..nixbld32 with descending UIDs (RHEL `useradd -r` default:
nixbld1=997, nixbld2=996, ...) and no nix binary, so the role
detects no install, runs the installer, which then aborts on
"nixbld2 already exists, but with the UID '996'".
Add purge_stale_nixbld.sh, called via ansible.builtin.script
before the installer. Three guards must all hold to purge:
1. No nix binary in candidate paths.
2. nixbld2 UID < nixbld1 UID (broken descending layout).
3. /nix/store does not exist (no live store to orphan).
Idempotent. Distro-agnostic at the script level: Debian/Ubuntu stay
[UNCHANGED] because guard 2 fails (nixbld users absent); Arch never
hits this code path. Production hosts with /nix/store present but
nixbld users get [FAIL] (rc=1) instead of silent destruction.
Fixes the 31/32 test-deploy-server / test-deploy-universal failures
observed in run 26634712613.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* scripts/lint/autoformat.sh: rename AUTOFORMAT_PARALLEL to PARALLEL,
declare it at file start with default `true`; still env-overridable.
Reword the worker comment so the literal `# shellcheck` token no
longer appears at start-of-line and trips SC1072/SC1073.
* scripts/lint/galaxy.sh: drop the GALAXY_FATAL / GALAXY_NAMESPACE /
GALAXY_PARALLEL env vars (over-engineered for a single repo). Failure
is always fatal. NAMESPACE is an internal constant. Add the same
PARALLEL env var (default `true`, env-overridable); falsy demotes the
xargs fan-out to one worker.
* docs/contributing/environment/variables.md: collapse the four
AUTOFORMAT_*/GALAXY_* rows into one `PARALLEL` row covering both
consumers.
* docs/contributing/tools/make.md: refer to `PARALLEL=0` everywhere;
add the lint-galaxy parallelism note.
Plus working-tree drift across roles/, group_vars/, inventories/, and
tasks/ that accumulated from the make-test setup phase regenerating
generated artefacts during recent test runs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inline-script-size lint regex previously matched only bare keys
(shell:/command:/script:/raw:/run:). Extended to also catch FQCN
forms (ansible.builtin.*, ansible.legacy.*, ansible.windows.win_shell)
which are interchangeable in Ansible and must be linted identically.
Four pre-existing violations exposed and fixed by extracting the
shell bodies to files/ and calling them via ansible.builtin.script:
- sys-cleanup/main.yml (21 logical lines)
-> files/cleanup_caches.sh
- svc-db-openldap/06_migrate_005.yml (30 logical lines)
-> files/migrate_005_purge_stale.sh (4 args, no_log preserved)
- dev-nix/02_non_arch_installer.yml (24 logical lines)
-> files/aggressive_prune_nix.sh (1 arg)
- web-app-espocrm/01_patch_config.yml (13 logical lines)
-> files/set_siteurl.sh (URL via env, fixes quote injection
risk in original Jinja-into-PHP-single-quoted body)
Removed cargo-culted args.executable: /bin/bash from the four
script: tasks; the script: module ignores it (shebang controls
interpreter).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Introduce a new sys-cleanup role to perform cross-distro cleanup (apt, pacman,
dnf, yum, language caches, temp dirs and journald) for CI/container environments.
Add aggressive Docker prune handler (system + buildx + builder + log truncate)
to prevent GitHub Actions images from growing uncontrollably.
Integrate Nix garbage collection and store optimisation for CI runs.
Cleanup is guarded by MODE_CLEANUP and DOCKER_IN_CONTAINER to ensure it never
runs on real production hosts.
https://chatgpt.com/share/6967ad99-8e50-800f-b4e9-4cd8e45c29bc
- Restrict pacman-based Nix install to Arch Linux x86_64 only
- Use official Nix installer as fallback on ARM and other platforms
- Prevent sudo usage on localhost when fetching installer checksum
- Align README with actual online, verified installer behavior
https://chatgpt.com/share/695e483c-1f68-800f-9f94-87d5295b871d
Migrate most roles from community.general.pacman to the generic package module for better portability. Keep pacman where it is required (system upgrades and Arch-specific tasks). Split VirtualBox setup into an Arch-only task file and add a warning when skipped on non-Arch systems. Update integration test examples accordingly.
https://chatgpt.com/share/6947ce9f-3228-800f-8365-2253b5ed9451
This commit introduces the new 'dev-nix' Ansible role, which installs the
Nix package manager in a secure and reproducible way using a locally stored,
SHA256-verified installer script.
Key features:
- Local pinned installer (no network download during execution)
- SHA256 checksum validation to prevent execution of modified installers
- Multi-user (daemon) installation mode
- Optional shell integration via /etc/profile.d
- Fully idempotent, distro-agnostic design
This role matches the structure and conventions of existing dev-* roles
in the Infinito.Nexus ecosystem.
https://chatgpt.com/share/69387c73-bf3c-800f-abcd-c5e7d3717059