Review follow-up on the in-container probes of gitea, shopware and fider.
- fider: the tenant POST over /dev/tcp waited on `head` without a bound,
so a peer that accepts and stays silent blocked the task for good. It
now runs under `timeout` (TIMEOUT_FACTOR-scaled 30 s, the old uri
socket timeout) and reads the status line with the `read` builtin, so
the bound kills the whole probe. Only outcomes Fider never committed
are retried: a refused connect and a 5xx (Fider rolls the request
transaction back on a handler error). A timeout or a closed socket
after the request was sent fails the task instead of resending the
POST; the next run's tenant count decides again.
- fider: both bootstrap tasks get `no_log` behind MASK_CREDENTIALS_IN_LOGS,
as the body carries the administrator's name and email.
- shopware: each wget probe runs under `timeout`, as busybox wget waits
900 s per read by default.
- shopware, gitea: the first readiness wait derives its retries from
lookup('timeout', 300) again, the budget of the wait_for it replaced,
so TIMEOUT_FACTOR scales it.
Refs #1089
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In swarm, `docker stack deploy` drops the host IP of a short port mapping,
so a `127.0.0.1:<ports.local>` publish lands on the ingress routing mesh
on every node. The provisioning of these three roles only worked because
the mesh answered on the host:
- web-app-gitea waited for `DOCKER_REACH_HOST:<ports.local.http>` with
wait_for and polled `/api/v1/version` there with uri_retry.
- web-app-shopware waited for `DOCKER_REACH_HOST:<ports.local.http>`.
- web-app-fider bootstrapped its first tenant in swarm by POSTing to
`127.0.0.1:<ports.local.http>` on the hosting node.
As long as these calls depend on the mesh, container_ports cannot stop
publishing loopback ports in swarm. The probes now run inside the
service's own container through the resolved `<PREFIX>_LOCAL_CID` on
`<PREFIX>_HOST_NODE`, against the internal port:
- Gitea uses the curl it ships. The first wait accepts any HTTP answer,
as wait_for did. The post-migration wait still requires 200.
- Shopware runs the wget probe its compose healthcheck already uses.
- Fider ships no HTTP client, so bash writes the tenant POST to
/dev/tcp. The body comes in on stdin and the status line is checked
for 200/201.
Fider's compose path is unchanged. The compose render of all three roles
is unchanged. The host-side ingress guard from svc-swarm-node stays in
place until the remaining host-side consumers are moved.
Refs #1089
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
At -vvv Ansible prints each module argument and result, so a password in
a command line, a request body, a header or a fact lands in the deploy
log. Nothing enforced masking outside the MCP task files, and no check
looked at parents at all.
A new lint, test_credential_tasks_are_masked, finds tasks whose printed
parts (module arguments, environment, loop) reference an identifier ending
in password, secret, token, api_key or private_key, or
secrets.credentials. A task passes when it or an enclosing block sets
no_log, when every file that pulls it in does so through include_tasks
with apply.no_log or import_tasks with no_log (transitively), when the
secret only reaches a parameter the module masks itself, or when the
module masks its values. no_log on an include_tasks task itself does not
count, because it does not reach the included tasks. The rule can be
suppressed per task with `# nocheck: credential-masking`, now listed in
the suppression catalog, and the lint tests its parent and suppression
logic on synthetic cases.
It found 39 unmasked tasks in 19 roles, all real: join tokens, client
secrets, API tokens in Cloudflare headers, database passwords in
command lines, the ChatGPT bot login body and more. Each now sets
no_log from MASK_CREDENTIALS_IN_LOGS. The sys-dns-hetzner-rdns alias
hetzner_no_log, which only restated that switch, is gone.
Not covered yet: template and copy tasks whose rendered file carries a
secret print it as a diff under --diff.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The image seeds app.ini from the flat DB_* variables only while the file is
absent, so a persisted install kept whatever credentials it was first given and
the role patched the current ones back in with sed. environment-to-ini runs
unconditionally on every start and overwrites the [database] keys in place, so
declaring them as GITEA__database__* converges the config on its own.
The restart that makes the new values take effect already comes from rendering
env.j2, whose handler the backend stack flushes before the manager tasks run,
so the patch task loses its last reason to exist.
Drops 02_database.yml and renumbers the following task files to keep the
sequence gapless.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three roles reach their MCP work from a manager-ops file rather than
from the role entry point, and the rename missed them, leaving them
including task files that no longer exist.
Gitea ships no MCP server, so the role runs the project-owned gitea-mcp
package as a sidecar next to it and points it at the instance over the
container network.
Two things the image forces. Its command carries the binary path
explicitly because the image declares no ENTRYPOINT, so a bare flag list
would replace the binary instead of extending it. The port is stringified
because Ansible's native types make a bare lookup an int, and compose
rejects a non-string in a command array.
Keep the surface internal. The public vhost answers a bearerless probe
with an SSO redirect, so the guest spec pins maxRedirects to 0 and
asserts no MCP response is ever served; following the redirect would land
on a login page and pass for the wrong reason.
Calls run with administrator rights, so the tool policy is pinned
read-only and the sidecar starts with the read-only flag and a scope
list.
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>