76adaeb9e8 wrote the regex_search group reference as '\1' inside the folded
task scalar. Jinja unescapes string literals, so the filter received \x01,
rejected it as an unknown argument and failed the dev-python task on every
remote host. The literal is now '\\1', and the unit test renders the task's
environment expression the way Ansible does instead of only checking that
the variable is named.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bf9c5be173 capped the bootstrap at 3.14 with constants inside install.sh, a
second copy of the range pyproject.toml already declares. requires-python is
now the single source (>=3.11,<3.15): install.sh derives its floor and cap
from it, in the repository or, on a remote host where the script runs alone,
from INFINITO_PYTHON_REQUIRES that the dev-python role reads from the same
file; any other form fails loudly. The Dockerfile copies pyproject.toml
before the bootstrap, and pip now refuses 3.15 for the package as well.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fedora 45 defaults to Python 3.15. ansible-core 2.21, which the unpinned
`ansible` extra still resolves to (ansible 14.5.0 pins ~=2.21.5), calls
dataclasses._is_type, which 3.15 removed, so the Workspace (fedora:latest)
job died before the first playbook (run 41491308729).
install.sh now picks and installs the highest Python between
PYTHON_MIN_MINOR and PYTHON_MAX_MINOR (3.11 to 3.14) on apt, rpm and
pick_python_bin, and fails loudly when only newer interpreters exist. On
Fedora 45 it installs python3.14. Raise PYTHON_MAX_MINOR once ansible ships
ansible-core 2.22.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The small GitHub runner shape (AWS, 72G root) ran out of disk in swarm and compose rows (#1329: WordPress#2 of run 41261531965, Nextcloud#6 and #7 of run 41262286962) while an empty instance-store disk nvme1n1 sat unused. mount_spare_disk.sh formats and mounts it at /mnt, but only on a GitHub-hosted runner outside act, and only a whole unpartitioned disk without any signature, mount or swap use of at least 75G; every decline leaves a notice. relocate_docker_dataroot.sh then bind-mounts /var/lib/containerd from the spare disk and moves the Docker data-root to /mnt/docker-host (not /mnt/docker, which is the nested daemon's volume that dedicated.sh wipes). It refuses before stopping anything when images, volumes, build cache, foreign containerd namespaces or shims exist or daemon.json is unreadable, and rolls back the bind mount and daemon.json on any later failure or cancel. Swap goes on / once containerd lives on /mnt, dump_state.sh logs disk size, model and free space per storage root, and the swarm watchdog samples / and /var/lib/containerd every 10 s.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TestDotenvSmoke ran `python -m cli.meta.env` with cwd=PROJECT_ROOT and no
--output, so every make test rewrote the real .env with defaults: as root
inside the container, with a fresh INFINITO_CACHE_PACKAGE_ADMIN_PASSWORD
and possibly another INFINITO_DOMAIN.
The test now writes to --output <tempdir>/.env and asserts that the
repository .env keeps its inode and mtime. A marker line in a worktree .env
vanished on every run of the old test and survives the new one; without
--output the new assertion fails with "The smoke run replaced the
repository .env".
Fixes#1188
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The access-log map read $request_uri, which keeps percent-encoding and
repeated slashes. A request such as /%70assword/reset/<token>,
/password%2Freset/<token> or //password//reset/<token> missed the
token-route expression, fell through to the plain-path branch and was
logged with the token in plain text, while the backend still decoded it
to its reset handler.
The map now reads $uri: decoded, slash-merged (merge_slashes is not
overridden anywhere) and without the query string, so the existing
expressions match every spelling of a token route. Trade-off: after an
internal redirect the log shows the internal URI; with the generic
vhost's error_page (ws_generic.conf.j2) a 5xx line now carries
/500.html instead of the client's path, with the status unchanged.
Nothing in the repository parses the proxy access log.
The unit test resolves the map against the variable it reads, modelling
$uri as nginx builds it, and adds the three encoded spellings above.
Negative control: with the map back on $request_uri, exactly those three
cases fail (3 failed, 8 passed).
Reported by CodeRabbit on #1322.
Part of #1192
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The front proxy logged the full request line: production had no
access_log, so nginx fell back to `combined` ($request, Referer,
$remote_user), and the debug format logged $request and the Referer.
Query-string tokens (?access_token=, ?key=, ?token=) and token routes
such as /password/reset/{token} and /i/confirm-email/{user}/{random}
ended up in plain text in the container log.
nginx.conf.j2 now declares a map from $request_uri to
$infinito_access_log_path:
- the path is written without the query string;
- the segments after a known token route (password/reset,
password-reset, reset-password, confirm-email,
lostpassword/reset/form, email-login, activate-account; matched
case-insensitively at any depth) are written as [redacted], so the
request, its status and timings stay visible without the token.
Both modes write an explicit access_log to /dev/stdout:
- production uses the new `redacted` format, laid out like `combined`
(remote address, time, "method path protocol", status, bytes,
user agent) with Referer and basic-auth user written as "-", so
combined-style parsers keep working;
- debug keeps its fields (host, timings, upstream time, lengths,
X-Forwarded-For, scheme, server name) but logs
"method path protocol" instead of $request and drops the Referer.
The path comes from $request_uri instead of $uri so internal rewrites
(error_page, try_files) do not replace the path the client requested.
Nothing in the repository parses the proxy access log (Wazuh, the
health checks and the Prometheus log_by_lua metrics do not read it),
so no consumer needs to change. The error log still carries
`request:` and `referrer:` on error-level entries; that is outside
this change.
A unit test renders nginx.conf.j2 in both modes and asserts that every
access_log uses a format declared there, that no format references
$request, $request_uri, $args, $query_string, $is_args, $arg_*,
$http_referer or $remote_user, and, by resolving the map as nginx does,
that query strings are dropped, token routes are redacted and
lookalike paths stay unchanged.
Verified live with web-svc-html in production mode (deploy failed=0, so
openresty accepted the config). A probe request to
/password/reset/<probe>?token=<probe> was logged as
"GET /password/reset/[redacted] HTTP/1.1" 404 62 "-" "curl/8.14.1",
and /plain/page as "GET /plain/page HTTP/1.1". Negative control: the
same request against the previous config logged the probe path token
and the query token in plain text. The debug format with the fix is
covered by the unit test only.
Closes#1192
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shared proxy's nginx.conf derived worker_connections from the
openresty container's pids_limit. pids_limit caps processes and threads,
worker_connections caps simultaneous sockets per worker (client and
upstream), so with pids_limit 256 and one worker the whole host's proxy
held about 256 sockets, roughly 128 proxied requests in flight across
every vhost. Raising pids_limit to buy connections would loosen the
fork-bomb guard.
svc-prx-openresty now declares services.openresty.worker_connections
(4096 per worker, the common nginx production range, enough for several
chunk-heavy apps such as OpenWebUI at ~150-170 requests per page load).
WEBSERVER_PROXY_WORKER_CONNECTIONS reads it through lookup('config'),
without a default, so a missing key fails loudly. pids_limit stays 256.
WEBSERVER_PROXY_WORKER_RLIMIT_NOFILE is derived as 2x worker_connections
(8192), one descriptor for each client and upstream socket of a proxied
request, and nginx.conf renders it as worker_rlimit_nofile. The openresty
container pins its nofile ulimit (soft and hard) to the same value:
workers call setrlimit as root without CAP_SYS_RESOURCE, so they can only
lower the hard limit, and the inherited hard limit depends on the host
daemon (Docker >= 25 defaults to 1024:524288, DiD nests whatever the
outer container has). An explicit ulimit makes the rlimit succeed on
every host instead of logging a setrlimit alert.
The unit test pins worker_connections 4096, worker_rlimit_nofile 8192 and
the container ulimit, and fails when worker_connections follows
pids_limit again.
Live proof (compose deploy of web-svc-html, both plays failed=0): the
deployed nginx.conf renders worker_rlimit_nofile 8192 and
worker_connections 4096; openresty's master, worker and cache manager
run with "Max open files 8192 8192"; the openresty log carries no
setrlimit or "worker_connections are not enough" line.
Closes#1300
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_sso.py, test_sso_oidc_plugin.py and test_sso_proxy_consumers.py
evict utils, utils.cache, utils.cache.applications and the roles
packages from sys.modules to import their plugin against fresh modules,
and never put the originals back. Ansible's plugin loader keeps the
applications lookup it loaded earlier in sys.modules, so that lookup
stayed bound to the evicted utils.cache.applications, whose
_MERGED_APPLICATIONS_CACHE utils.cache._reset_cache_for_tests() no
longer reaches.
On main this turned test_webserver_worker_limits red whenever one xdist
worker ran a test that loads the applications lookup through Ansible,
then one of these sso tests, then the worker-limits test: the stale
cache served the cpus 0.5 payload of test_a_sub_cpu_proxy_still_runs_one_worker,
so the later methods rendered worker_processes 1 instead of 4 (main run
41139196554, Workspace centos and fedora, second make test on gw1).
_load_module now runs inside mock.patch.dict(sys.modules): the plugin
keeps the fresh modules it was built against, and sys.modules returns to
its prior state afterwards. This is the change store-i18n carries since
ec0fde6561, ported unchanged so both lines stay identical.
Evidence:
- Before: pytest -n 0 test_tor_ports.py test_sso_oidc_plugin.py
test_webserver_worker_limits.py -> "3 failed, 33 passed", the CI
failure set ('1' != '4' twice, worker_processes 1 vs 4).
- After: test_tor_ports.py, the four sso test files and
test_webserver_worker_limits.py -> "68 passed".
(cherry picked from commit ec0fde6561)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The bare ghcr.io/shopware/docker-base:8.3 tag is frozen. Its digest
sha256:14826eb9 is the same as 8.3.25, built 2025-09-05, and carries
PHP_VERSION=8.3.25. Upstream has published only flavoured tags since
8.3.26. 8.3.36-caddy (sha256:b1fdca6e, built 2026-10-30, also the
current 8.3-caddy) carries PHP_VERSION=8.3.36. It has the same
supervisord entrypoint, www-data user and exposed ports 8000/9000 as
the bare tag. That clears the 8.3.36 security floor that hlth-runtime
reports for shopware-web, -worker and -scheduler.
The flavoured tag needs two follow-ups:
- SHOPWARE_COMPOSER_PLATFORM_PHP drops the "-<flavor>" suffix before it
pads major.minor. Without that, composer gets platform.php
"8.3.36-caddy". It now renders 8.3.36, and 8.3 still renders 8.3.0.
- shopware/docker 6ae1717 ("feat: remove default env vars") removed
SHOPWARE_ES_ENABLED=0, SHOPWARE_ES_INDEXING_ENABLED=0 and
SHOPWARE_ES_INDEX_PREFIX= from the image. Shopware 6.7.13.0 reads
them through %env()% with no container default, so env.j2 now sets
the values the old image shipped. SHOPWARE_HTTP_DEFAULT_TTL keeps its
7200 default from core services.xml. Nothing in Shopware reads
DATABASE_PORT, and the role uses DATABASE_URL.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>