A handler name was doing two jobs at once: the line an operator reads in the
play output, and the identifier a `notify:` binds to. The two pull in opposite
directions -- the log wants "restart the SSH service, with diagnostics", the
binding wants a token that survives quoting, YAML flow style and a shell
round-trip -- and a name that changes for readability silently changes which
handler runs.
Every handler that a notify reached by name now carries a `listen:` with that
exact wording, so the binding is explicit before anything is renamed. The
topics themselves become identifiers: `compose up` reads `compose-up`,
`Restart NFS ganesha` reads `restart-nfs-ganesha`, 22 of them across both
sides of every notify. Where the topic is assembled at runtime, both branches
moved together: the conditional in sys-service carries two literals, and the
LDAP renderer interpolates `import-{{ folder }}-ldif-files`, which is what
makes those handlers reachable at all.
Freed from the binding, the names take the emoji prefix every other task line
already has.
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 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>
Adds a mode-aware `container_address` lookup plugin and a
`resolve-container-id` helper script that resolve swarm task IDs at
exec time (compose mode returns bare names unchanged). Migrates ~25
roles + ~28 task files from `command:` to `shell:` so the lookup's
`$(...)` subshell evaluates correctly, and extends `database` lookup
with an `address` key for cross-role DB exec.
Three new lint tests enforce the contract: every container exec
target goes through the resolver, lives behind an IS_STACK_HOST gate
(walks the include graph one hop deep), and uses `shell:` rather than
`command:` when the target is a Jinja reference. Existing nocheck
suppressions on bare-name DB exec sites (nextcloud redis, listmonk,
moodle OIDC, mailu) are replaced with the lookup-based form.
Fixes the systemd Environment= injection in sys-ctl-cln-disc-space
(it never expanded $(...)) by passing STACK / SERVICE_KEY parts and
resolving inside the script. Extracts shopware admin and mediawiki
extension install bodies into dedicated .sh files invoked via env.
Rewrites password-bearing shell tasks to either `| quote` directly or
the `-e VAR=... | quote` + `sh -c '... ${VAR} ...'` env-bridge pattern.
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>
The Multisite variant deploy (variant=1 in meta/variants.yml) failed at
06_multisite.yml's `wp config set` loop, then the next iter failed even
earlier at 02_install.yml's `wp core install`. Two distinct shapes of
the same root cause — Ansible's `command:` module pipes the rendered
argv string through shlex, which strips the outer single-quotes from
`'blog.infinito.example'` before exec — meaning wp-cli received a
bareword and, because the loop used `--raw`, wrote it verbatim:
define( 'DOMAIN_CURRENT_SITE', blog.infinito.example );
define( 'PATH_CURRENT_SITE', / );
…which PHP can't parse. From that moment on, every subsequent `wp` CLI
invocation died at parse-time before it could do anything. The state
also persists across reuse-kept iterations because it lives inside the
WordPress docker volume — `wp config delete` couldn't clean it up
either, since it loads wp-config through PHP itself.
Fix:
- Split the Multisite constant-write loop in 06_multisite.yml into two:
one with `--raw` for true PHP literals (booleans, ints — MULTISITE,
SUBDOMAIN_INSTALL, SITE_ID_CURRENT_SITE, BLOG_ID_CURRENT_SITE) and a
second one WITHOUT `--raw` for string constants (DOMAIN_CURRENT_SITE,
PATH_CURRENT_SITE). Without `--raw`, wp-cli detects the type and
quotes the value itself — which is the right SPOT for that decision.
- Add a defensive `sed -i` at the very top of 01_patch_config.yml that
removes any pre-existing
`define( 'DOMAIN_CURRENT_SITE'|'PATH_CURRENT_SITE', ... );` line
before any `wp` CLI call runs. Pure textual substitution survives an
unparseable wp-config.php, so this self-heals carryover state from
pre-fix runs. The canonical, properly quoted versions are then
rewritten by 06_multisite.yml's string-write loop on the same play.
Empirical proof: iter 5 (`make deploy-reuse-kept-apps APPS=
web-app-wordpress VARIANT=1`) finished `ok=1630 changed=158 failed=0`,
wp-config.php now contains all seven Multisite constants in valid PHP,
and the Playwright suite reports 3 passed / 4 correctly skipped (the
Multisite sentinel passes; the Single-Site RBAC scenarios skip via
`test.skip(multisiteEnabled)` as intended).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- add sed_escape Jinja filter for sed replacement safety
- escapes \, &, and the active sed delimiter
- designed explicitly for sed s/// replacement context
- patch all sed-based config updates across roles
- espocrm, friendica, gitea, matomo, moodle, wordpress
- ensure every Jinja value in sed replacements uses sed_escape
- use correct delimiter-specific escaping (/, |, #)
- add robust integration test to enforce sed_escape usage
- scans all roles/*/tasks/**/*.yml
- detects sed substitutions with Jinja in replacement part
- ignores delimiters inside {{ ... }} blocks
- fails CI if sed_escape is missing
- harden repository against sed injection, delimiter breakage,
and credential corruption in config patching tasks
https://chatgpt.com/share/6974fb45-1ee8-800f-80bd-c71122e4ac02
- Replaced all lowercase wordpress_* variables with uppercase WORDPRESS_* equivalents
- Ensured consistency across tasks, templates, and vars
- Improves readability and aligns with naming conventions
Conversation: https://chatgpt.com/share/68af29b5-8e7c-800f-bd12-48cc5956311c