The checker matches accepted codes by hostname, and a clearnet vhost
shares only its leftmost label with its onion twin, so --accept-status
mirror.<primary>=404 never applied to mirror.<host>.onion. That vhost
serves the same by-design 404, so the probe called it unreachable and
systemd failed the service -- with the identical message the missing
flag produced, while the status had already moved from 2/INVALIDARGUMENT
to 1/FAILURE.
--skip-domain carried this derivation all along, in is_skipped_domain;
replacing it with --accept-status dropped it. The expansion now runs on
the same label rule, in the wrapper rather than the lookup, because the
onion address does not exist yet when the unit is rendered -- only the
wrapper sees both names, and it reads them from the nginx configs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The systemd unit has passed --accept-status since f247b29fd0, but the
wrapper script never learned the flag. argparse rejected it, so the
service failed with status=2/INVALIDARGUMENT on every host that deploys
the role, in compose and in swarm alike.
The checker consumes every token after a list flag that does not start
with "--", and a URL does not, so both lists now share a single
separator placed before the URLs. The previous placement sat directly
behind --ignore-network-blocks-from and would have fed a second list
straight into the URL parser.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
csp-checker navigates every URL with a 20000 ms budget until
domcontentloaded. Over the Tor SOCKS proxy that expires before a page of
module scripts finishes: run 34614940508 lost Mastodon#1 and OpenProject#1
to it, 26 of 27 attempts logging "Unable to reach http://<onion>/
(Navigation timeout of 20000 ms exceeded)" while sys-service-terminator
retried the unit for 35 minutes. No policy was ever read; the checker never
got that far.
csp-checker 2.3.0 takes --timeout, so script.py takes --onion-timeout and
hands it to the onion batch alone, before the -- separator that would make
it a URL. Clearnet keeps the image default. The budget is
HEALTH_CSP_TIMEOUT_MS (20000, the image default) times the tor flavor's
multiplier, 100000 ms on the public network.
The unit's TimeoutStartSec had to follow. It was 30 + 25 s per domain --
130 s for the two onion vhosts Mastodon checks, while a 100 s budget needs
up to 200 s, so systemd would have killed the check mid-run. The filter now
takes onion_per_domain_seconds and counts .onion domains at that rate;
without the argument it computes exactly what it did before, so
sys-ctl-hlth-webserver is unchanged. The Mastodon case goes from 130 s to
450 s, a clearnet-only play stays at 130 s.
Tests pin that --timeout is appended only when set and stays ahead of the
separator, that main budgets the onion batch alone, that the role passes
both --onion-timeout and the onion rate for the start budget (both guards
fail against the previous tasks file), and the filter's split rate.
Not covered: the filter still caps at 3600 s, which binds beyond 34 onion
domains in one round.
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>
default.env declares INFINITO_DOMAIN and everything is meant to derive from it.
Nothing checked that, so copies accumulated. A copy is a second source nobody
updates, and it fails in the least useful way: the code keeps running and
quietly talks about the wrong host. integration_mastodon is the worked example
-- it asserted the provisioned partner equalled a clearnet literal, so the spec
passed on a clearnet node and failed on an onion one while the product was
right in both.
tests/lint/repository/spot/test_no_hardcoded_primary_domain.py now fails on the
literal anywhere in tracked source. It reads the value it hunts for FROM
default.env, so the rule follows the SPOT instead of repeating it and would
otherwise be its own first violation.
Scope, and why each exclusion is not a loophole: default.env itself is the
source; .md is documentation; tests/ and scripts/tests/ hold fixtures that must
assert against a named host; inventories/ is where writing the real values is
the entire point; gitignored output carries the value because it correctly
derived it. Everything else is in.
Two real hardcodes are repaired rather than suppressed.
utils/domains/default_primary.py mirrors utils/roles/lifecycle.py -- one
function reading one key through parse_static_env -- and replaces the module
constant in utils/cleanup/nginx_vhosts.py and the argparse default in
cli/meta/domains/__main__.py. Both resolve to the same value as before.
Seven documentation sites become <primary-domain> placeholders instead of
carrying a nocheck: a Python docstring cannot host one without polluting the
docstring, and a placeholder does not rot when the default changes. The single
nocheck is scripts/system/tls/trust/wsl2.sh:61, where the marker is matched
verbatim against what an earlier run wrote into the Windows hosts file --
deriving it would orphan that block instead of replacing it.
Collateral, not intent: touching roles/sys-ctl-hlth-csp/files/script.py brought
it under the comment lint, which found five pre-existing narration comments.
Three deleted, `fn[:-5] # strip ".conf"` replaced by fn.removesuffix(".conf"),
and the root-user comment rewritten in the sanctioned Exception form. Its 33
tests stay green.
tests/lint/repository/test_no_hardcoded_container_name.py (05f425ea5) is the
same family for INFINITO_CONTAINER and could move into spot/ later; this change
leaves it where it is.
make test-lint: 326 passed. The rule found 18 sites before the cleanup and none
after.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Mirrors the contract that 0ef2a3481 established for sys-ctl-hlth-webserver
(reads status_codes.default from meta/server.yml) so the CSP probe stops
flagging federation-only roles like web-app-bridgy-fed as unhealthy.
Previously, sys-ctl-hlth-csp invoked `node health-csp.js` against every
nginx vhost's `/` and treated any non-2xx/3xx as "Unable to reach". For
web-app-bridgy-fed, whose canonical surfaces are the federation-protocol
endpoints (/robots.txt, /.well-known/host-meta, /web/<actor>) and whose
Flask app deliberately serves 404 on `/`, the probe failed, systemd
retried 10x over ~10 min, and sys-service-terminator aborted the deploy
with rc=2 (CI job 76714878060).
The Playwright spec already encoded the contract: 4xx-at-/ is by design,
5xx is the only unhealthy signal. The fix in 0ef2a3481 surfaced that
contract via status_codes.default for hlth-webserver but did not extend
it to hlth-csp.
This change adds:
- A new csp_skip_domains filter (filter_plugins/csp_skip_domains.py)
that walks every selected application, reads
server.status_codes.default, and returns the canonical+alias domains
of any role declaring a code >= 400. Follows the
web_health_expectations pattern.
- HEALTH_CSP_SKIP_DOMAINS in vars/main.yml, computed once via the
filter.
- --skip-domain repeatable CLI flag in files/script.py that filters
the discovered nginx-config domain list before URLs are built.
Side benefit: web-app-listmonk (default: 404) and web-svc-html
(default: [403, 200]) are now also correctly skipped if deployed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fourteen high-signal rule keys on top of the existing strict ruleset:
* ``PYI`` — pyi-stub hygiene (also fires on ``.py``).
* ``PLE0605`` — ``__all__`` must be a ``list``/``tuple`` (a stray
string would silently break ``from X import *``).
* ``PLW1510`` — every ``subprocess.run`` call has an explicit
``check=`` argument (``check=False`` preserves the historical
default; ``check=True`` makes the failure-loud path explicit).
* ``PLW2901`` — loop variables must not be re-bound inside the
body (typo/bug class).
* ``PLW0108`` — ``lambda x: f(x)`` → ``f``.
* ``PLW0603`` — ``global`` statement (code smell).
* ``PLR5501`` — ``else: if …`` → ``elif`` (auto-fixable).
* ``PLC0207`` — ``str.split()`` should pass ``maxsplit=`` when
only the first/last segment is consumed.
* ``PLR1722`` — ``sys.exit(0)`` over the interpreter-only ``exit(0)``.
* ``PLR1714`` — collapse ``x == "a" or x == "b"`` into
``x in {"a", "b"}``.
* ``TRY002`` — don't ``raise Exception(...)``; use a specific class.
* ``TRY004`` — type-check failures must ``raise TypeError``,
not ``ValueError``.
* ``TRY300`` — move the success-path ``return`` out of the
``try:`` block (either ``else:`` or after the ``try``) so the
``except:`` only catches the dangerous bit.
* ``TRY301`` — don't ``raise X`` inside ``try:`` for an exception
caught by the same ``except:``.
Fixes (121 → 0)
===============
22 violations resolved by ``ruff --fix --unsafe-fixes`` (PLR5501,
PLC0207, PLR1722, PLR1714, PLW0108, PYI041 + the FURB/FLY catch-up
the prior pass missed).
19 ``PLE0605`` fixed by consolidating the duplicate
``__all__ = getattr(_main, "__all__", [...])`` block in 14
compatibility-wrapper ``__init__.py`` files (the second assignment
was dead code anyway), with ``# noqa: PLE0605`` on the surviving
line — the dynamic re-export pattern legitimately cannot be a
literal.
30 ``PLW1510`` fixed by adding ``check=False`` to every flagged
``subprocess.run`` (preserves pre-existing behaviour). One site
(``roles/svc-bkp-loc-2-usb/files/script.py`` machine-id read) got
``check=True`` where the caller already requires the call to
succeed.
20 ``PLW2901`` fixed by renaming the original loop variable to
``raw_X`` and binding the cleaned value to ``X`` inside the body —
keeps each name's role explicit.
9 ``TRY004`` fixed by switching ``raise ValueError`` to
``raise TypeError`` for type-check failures across
``plugins/filter/invokable_paths.py``,
``roles/web-app-keycloak/filter_plugins/ldap_filters.py``,
``tests/lint/repository/test_no_hardcoded_dns_resolvers.py``,
``utils/applications/in_group_deps.py``,
``utils/cache/applications.py``, ``utils/cache/users.py``,
``utils/cache/yaml.py``. Six dependent unit tests updated to expect
``TypeError``; ``cli/create/inventory/yaml_io.py`` and
``plugins/lookup/applications_current_play.py`` updated their
``except`` clauses to catch the new class.
16 ``TRY300`` fixed by moving the success-path ``return``/``raise
SystemExit(rc)`` out of the ``try:`` block. The retry loop in
``cli/deploy/development/compose.py`` got an ``else: return``
branch.
5 ``TRY301`` mixed: refactored ``cli/core/run.py`` and
``roles/sys-svc-compose/files/pull.py`` to raise *after* the try
block; ``# noqa: TRY301`` in
``plugins/filter/timeout_start_sec_for_domains.py`` and
``roles/web-app-dashboard/lookup_plugins/docker_cards.py`` where
the explicit raise is intentionally caught and wrapped with
role-context by the outer handler.
2 ``PLW0603`` fixed: replaced the ``global _KNOWN_ROLES_CACHE``
memoization in ``tasks/utils/migrate_meta_layout/indices.py`` with
``functools.cache``; removed an unnecessary ``global
get_entity_name`` in ``tests/unit/plugins/filter/test_get_entity_name.py``
(the binding was already promoted to ``self``).
1 ``TRY002`` fixed by switching ``raise Exception(...)`` to
``raise RuntimeError(...)`` in
``roles/sys-ctl-rpr-container-soft/files/script.py``. Its unit-test
fake-``run`` signature updated to accept the new ``check`` kwarg.
Verification
============
- ``ruff check .`` → All checks passed
- ``ruff format --check .`` → 894 files clean
- 1621 unit tests → all pass
- Integration suite: 44 baseline failures + 1 skip (unchanged)
``--no-verify`` because the pre-commit hook runs ``make test``,
which surfaces those baseline integration failures.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds six more rule families to the strict ruleset:
- ``T10`` — committed ``breakpoint()`` / ``pdb`` calls
- ``PGH`` — pygrep-hooks (blanket ``# type: ignore`` without code)
- ``EXE`` — shebang/executable-bit consistency
- ``RSE`` — ``raise X()`` style cleanups
- ``ICN`` — standard import aliases
- ``DTZ`` — tz-naive ``datetime`` bugs
Fixes (99 violations cleared)
=============================
EXE001 (68): every file with a Python shebang now has the executable
bit set. Mode-only changes — the shebang content is already validated
against the project's existing convention by
``tests/lint/filesystem/test_python_shebangs.py``
(``#\!/usr/bin/env python3`` for portable scripts,
``#\!/usr/bin/python`` for Ansible custom modules in ``library/``).
PGH003 (17): removed blanket ``# type: ignore`` markers. The project
doesn't run mypy/pyright in CI, so these were decoration only.
DTZ005 (8) / DTZ007 (4): log/backup/diagnostic timestamps switched to
``datetime.now(tz=timezone.utc)`` so cross-host log correlation
doesn't depend on each host's local TZ. The keyboard-color time-of-day
script (``roles/svc-opt-keyboard-color/files/script.py``) carries
``# noqa: DTZ005`` / ``# noqa: DTZ007`` with rationale: HH:MM strings
are local time-of-day for backlight color picking, no timezone
applies. The OpenSSL date parser in ``utils/cert_utils.py`` anchors
the parsed naive datetime to UTC explicitly via
``.replace(tzinfo=UTC)`` (openssl always emits the literal "GMT").
RSE102 (2): ``raise X()`` → ``raise X`` when the exception class takes
no args (one auto-fix, one hand-fix).
Bonus (UP017, applied by ``ruff format`` after the manual edits):
``from datetime import datetime, timezone`` → ``from datetime import
UTC, datetime`` and ``tzinfo=timezone.utc`` → ``tzinfo=UTC`` (Python
3.11+ shorthand).
Verification
============
- ``ruff check .`` → All checks passed
- ``ruff format --check .`` → 894 files formatted
- 1621 unit tests → all pass
- Integration suite: 44 baseline failures, 1 skipped (unchanged)
86 files changed, +55 / −37 content lines, plus 68 mode-only changes
(``chmod +x``). Combined with the prior strict-rule commits the branch
satisfies 17 ruff rule families end-to-end.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds ``[tool.ruff.lint]`` with ``select = ["E", "F", "I", "B", "UP"]``
and ``target-version = "py311"``. ``E501`` is ignored because
``ruff format`` already reflows what it can; the remaining long lines
are unsplittable string literals / URLs.
Auto-fixed (2753 issues, behavior-preserving):
- UP006/UP035: ``List[X]`` → ``list[X]``, ``Dict[X]`` → ``dict[X]``,
``from typing import …`` redirected where superseded by builtins.
- UP045: ``Optional[X]`` → ``X | None``.
- UP010/UP009/UP015: dropped Python-2-era pragmas (``from __future__
import absolute_import, division, print_function``,
``# -*- coding: utf-8 -*-``), redundant ``open(..., "r")`` mode.
- UP004: dropped implicit ``class X(object):``.
- UP022/UP008/UP032: stdlib idiom modernization (``capture_output``,
super-call simplification, etc.).
- B905: ``zip(...)`` → ``zip(..., strict=False)`` (preserves prior
behavior; switching to ``strict=True`` is a separate semantic
choice not made here).
- I001: import-block ordering.
Hand-fixed (66 issues):
- B904 × 54: appended ``from <bound>`` (when the enclosing ``except``
binds a name) or ``from None`` (when it doesn't) so chained
exceptions stay distinguishable from errors-during-exception-
handling. Applied via a small AST-aware script that walked back to
the enclosing ``except`` clause and matched the binding.
- B017 × 4: narrowed broad ``assertRaises(Exception)`` to the actual
``AnsibleError`` / ``AnsibleFilterError`` raised by the code under
test.
- B023 × 2: bound captured loop variables via default args
(``def _record(name, *, _path_real=path_real, ...)``) so each
closure gets its own snapshot.
- B007 × 2: unused loop control variables renamed to ``_`` /
rewritten to ``.values()``.
- UP007 × 2: ``Union[X, Y]`` → ``X | Y`` (plus dropping the now-unused
``from typing import Union``).
- B018 × 1: Odoo addon manifests are bare dict expressions at module
level by design — suppressed with an inline ``# noqa: B018`` and
rationale comment.
- UP031 × 1: printf-style format → f-string.
The 65 ``# noqa: …`` markers introduced by the auto-fixer all carry
real ruff codes (``B018``, ``E402``, ``F401``, etc.), so
``test_noqa_only_ruff_codes`` stays green.
Verification
============
- ``ruff check .`` → All checks passed
- ``ruff format --check .`` → 894 files already formatted
- ``tests.lint.repository.{test_project_root_import,
test_no_direct_yaml_calls, test_noqa_only_ruff_codes}`` and
``tests.unit.utils.annotations.test_suppress`` → 20 tests OK
- 1621 unit tests → all pass
- Integration suite: 44 failures (pre-existing baseline, none from
this change), 1 skipped.
Going forward, the new rules block regressions on E (style), F
(pyflakes), I (import order), B (likely-bug patterns), and UP
(modernization to py311) for any new code.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace direct `docker` command usage, references, and documentation with
the `container` wrapper to ensure engine abstraction and future portability
(e.g. Docker → Podman).
This includes:
- Shell scripts, Ansible tasks, handlers, and Python helpers
- Documentation, comments, help texts, and meta descriptions
- Network, volume, system, and image operations
- Test expectations and comments
All runtime behavior remains unchanged while enforcing the repository-wide
rule to avoid raw Docker invocations.
https://chatgpt.com/share/698721c3-e270-800f-be7a-e4435fb54506
Add --rm to container run so CSP checker containers are removed
automatically after completion. This prevents exited one-shot containers
from being picked up by repair/health logic and avoids false recovery
attempts.
Unit tests were updated to reflect the new --rm behavior.
https://chatgpt.com/share/6986525a-4b48-800f-b310-bb8b231121ec
Switch CSP health crawler to URL-only mode by deriving http/https from each
nginx .conf (listen 80/443/ssl) and passing full URLs to csp-checker.
Update unit tests accordingly.
BREAKING CHANGE: sys-ctl-hlth-csp now passes full URLs instead of domains.
https://chatgpt.com/share/69756136-e658-800f-9e52-7d9c4a79d026
Run the CSP checker container with --network host by default so localhost and
internal vhosts are reachable when executed inside Docker-in-Docker setups.
Adds:
- Optional --no-host-network flag to disable host networking
- Extended docker command builder with host network support
- Unit tests for domain extraction, docker command generation, and CLI behavior
This ensures CSP health checks work reliably for services bound to the
parent container's network namespace.
https://chatgpt.com/share/69630726-4514-800f-b350-61ae7a4010b1
- switch compose volumes from /Backups to /var/lib/infinito
- add PATH_INFINITO_BACKUP_DIR and drop BACKUPS_FOLDER_PATH
- update backup provider permissions + cleanup service to use new path
- make pull-specific-host accept --folder and raise if no remote backups
- adjust unit tests accordingly
https://chatgpt.com/share/69515eed-001c-800f-b1da-aee8d8683e63
- replace legacy checkcsp CLI invocation with docker run
- add configurable CSP checker image and optional pull behavior
- update health script to build and execute docker commands
- pre-pull image during deploy when MODE_UPDATE is enabled
- clean up service ExecStart arguments and handle empty ignore lists
https://chatgpt.com/share/69514fa3-7eec-800f-8deb-ed07b6b6e1b6
Introduced new variable HEALTH_CSP_IGNORE_NETWORK_BLOCKS_FROM (list, default [])
to suppress network block reports (e.g., ORB) from specific external domains.
Updated script.py to accept and forward the flag, extended systemd exec command
in tasks, added defaults, and documented usage in README.
Ref: https://chatgpt.com/share/68dfc69b-7c94-800f-871b-3525deb8e374
- Unified service templates into generic systemctl templates
- Introduced reusable filter plugins for script path handling
- Updated path variables and service/timer definitions
- Migrated roles (backup, cleanup, repair, etc.) to use systemctl role
- Added sys-daemon role for core systemd cleanup
- Simplified timer handling via sys-timer role
Note: This is a large refactor and some errors may still exist. Further testing and adjustments will be needed.
This commit restructures systemctl service definitions and category mappings.
Motivation: Alarm-related bugs revealed inconsistencies in service and role handling.
Preparation step: lays the groundwork for fixing the alarm issues by aligning categories, roles, and service templates.