Reflows the cat argument list in the CA trust-bundle task. The scalar is
folded, so the wrapped closing braces fold back to a space and the shell sees
the same string.
It landed unformatted because the cluster work ran targeted checks only -
ansible-lint on the touched task files, ruff, shellcheck, the unit and lint
suites - and never a full autoformat, which is a writer rather than a checker.
The first complete gate pass afterwards collected it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
SSL_CERT_FILE, CURL_CA_BUNDLE and REQUESTS_CA_BUNDLE replace the trust store;
only NODE_EXTRA_CA_CERTS extends it. compose_ca.py pointed all four at the
deployment own root CA, so every OpenSSL and rustls client in every container
was blind to every public certificate. Measured here, one variable, same URL:
unset curl https://registry.npmjs.org/ -> 200
a single unrelated CA -> 000 unable to get local issuer certificate
that CA + the system bundle -> 200
PeerTube was the one process that hit it: its plugin install fetches from
registry.npmjs.org at runtime through a client that honours SSL_CERT_FILE, and
it failed identically in compose and in swarm with
invalid peer certificate: UnknownIssuer
Everything else reaches its artefacts at image build time, or through Node,
whose NODE_EXTRA_CA_CERTS is additive and therefore kept working.
The replacement was never a posture. e7e8cf8c03 calls these variables an
"env-based CA trust fallback", f1ea036bd2 adds them "instead of falling back to
a plain docker run without CA trust", e79183f96a wires the extra CA in "so npm
traffic through the cache validates". Every commit framed them as adding trust.
sys-ca-selfsigned now builds ca-bundle.crt beside the root CA on every node,
from that node own files: the stack host mints the CA and workers fetch it, so
there is no cross-node dependency. The bundle is the root CA concatenated with
the first present CA_BUNDLE_CANDIDATES entry, the SPOT fc0881ff33 introduced.
The cat argument list is assembled in Jinja, so a host with no public bundle
emits the root CA alone, which is exactly the previous behaviour, rather than an
empty argument that would fail. bundle_host joins the existing stray-directory
sweep and the build runs after it.
compose_ca.py uses the bundle only when the file exists and is non-empty, so a
host that has not built one keeps the previous behaviour instead of binding a
missing path that Docker would materialise as a directory. The php ini, msmtprc
and the Matrix flavour may name the container path with no fallback because the
bundle is written at task 99 and the php ini at task 123: no host can carry the
ini without the bundle.
CA_TRUST_CERT and NODE_EXTRA_CA_CERTS stay on the single certificate. keytool,
occ security:certificates:import and update-ca-certificates each take one.
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>
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>
A missing bind-mount source is auto-created as a directory, so the trust helper / php.ini wrapper copy nests inside it and the entrypoint execs a directory. Stat the two SPOT sources and delete any that is a dir before the copy.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PHP's libcurl and stream layer ignore CURL_CA_BUNDLE/SSL_CERT_FILE, so
swarm containers - which get env-and-mount CA injection only, the
entrypoint wrapper being stack-deploy-incompatible - fail TLS against
self-signed in-cluster services from PHP code (verified live: curl CLI
reaches keycloak, PHP curl_exec returns 'unable to get local issuer
certificate'; nextcloud's /apps/oidc_login/oidc answered 500 on every
swarm variant). Ship a ca-trust-php.ini pointing openssl.cafile and
curl.cainfo at the mounted CA and bind it into the official-image PHP
conf.d path from the compose CA override in both deploy modes.
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>
The j2 extraction replaced the inline copy/content with ansible.builtin.template, which the stack-host-template lint flags as a bare template. The task is already stack-host-scoped (the parent include applies delegate_to STACK_HOST + run_once), so a leaf gate would be redundant; suppress with nocheck.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the inline copy/content block into templates/openssl-root-ca.cnf.j2 and switch the task to ansible.builtin.template; the rendered config is identical.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Workers resolve the app domain to the manager IP (openresty is mode:host +
manager-pinned), so sys-ctl-hlth-webserver can run on every node instead of
being gated to the stack host. The shared Root CA is minted once on the
stack host and fetched by each worker, so a worker's trust store matches the
manager's leaf signer and the healthcheck's TLS chain verifies.
- sys-ctl-hlth-webserver: drop the IS_STACK_HOST gate (reachability, not
data-locality, now that workers resolve the domain to the manager)
- sys-ca-selfsigned: mint the Root CA once on STACK_HOST (run_once +
delegate_to), extract the generation steps to 02_mint.yml; non-stack hosts
slurp the cert from STACK_HOST and write it to their host-local path
(cert only, never the key)
- svc-swarm-node: anchor the shared CA early on every swarm node so it
exists before any per-node consumer reaches it; bundle the swarm-only
tasks under one DEPLOYMENT_MODE block
- sys-front-tls-selfsigned: gate leaf signing to the stack host (workers
have no CA key and run no manager-pinned openresty)
- scripts/tests/deploy/swarm/02_start_swarm_nodes.sh: workers run dnsmasq
resolving the domain to MGR_IP; excluded from the 127.0.0.1 /etc/hosts
entries that would shadow it
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Picked up by the make-test setup phase regenerating generated artefacts
during recent test runs.
Co-Authored-By: Claude Opus 4.7 (1M context) <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>
Makefile lint integration:
* scripts/install/lint.sh: replace the prior checkmake binary install with
ensure_mbake (mbake is pip-installable, parses our Makefile correctly
whereas checkmake bailed on .ONESHELL: / .SHELLFLAGS: / ifneq).
* scripts/lint/makefile.sh: new runner — `mbake format --check Makefile`
reports formatting drift and missing .PHONY declarations.
* scripts/lint/autoformat.sh: add a two-pass `mbake format Makefile`
(mbake's formatter is not strictly idempotent on the first pass).
* Makefile: split the multi-line `.PHONY:` block into 18 single-line
`.PHONY:` declarations so mbake's parser can validate it; add the
previously-missing `install-python-dev` and `build-dependency` PHONY
targets; add `lint-makefile` aggregate-and-direct target.
* .pre-commit-config.yaml: new make-lint-makefile hook scoped to
`^Makefile$`.
* .github/workflows/lint-makefile.yml: new CI workflow (pip-install mbake
+ make lint-makefile).
* .github/workflows/ci-orchestrator.yml: wire lint-makefile into
lint-gate.needs.
Misc:
* .gitignore: add roles/importer_result.json (tooling artefact written by
galaxy-importer when it runs in roles/).
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>
- Persist CA_TRUST_* variables in /etc/environment for self-signed TLS
- Remove CA trust environment when switching to letsencrypt
- Enable container.py to auto-detect CA injection context via env
https://chatgpt.com/share/69864bf2-7830-800f-ae8a-dad17c4f6e13
Move `with-ca-trust.sh` to `sys-ca-selfsigned` as the single source of truth, add `CA_TRUST_NAME`/`trust_name`, and pass it through `compose_ca_inject.py` + `compose_ca_inject_cmd`. Introduce `module_utils/templating.py` for strict multi-pass rendering with fallback env lookup, update TLS self-signed regen logic, purge script, and extend unit tests accordingly.
https://chatgpt.com/share/697c876d-7140-800f-9981-762dfeeafdfe
Move TLS/ACME/CA vars out of 00_general into group_vars/all/02_tls.yml and renumber subsequent group_vars files. Add sys-ca-selfsigned to generate /etc/<software>/ca/root-ca.crt and make docker-compose-ca depend on it. Update setup script, templates, and tests to use the new applications/users file numbering.
https://chatgpt.com/share/697b8150-22ac-800f-98b1-e063a659b684