Commit Graph
21 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 5 b5fcd535de fix(ca): extend the container trust store instead of replacing it
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>
2026-09-09 12:32:44 +02:00
kevinveenbirkenbachandClaude Opus 5 5137465c6f style: delete every comment the comments-valid policy rejects
3529 invalid comments in 528 files, grandfathered in while the linter
only saw the working-tree diff. Removal ran through the linter's own
collectors, so detection and deletion cannot drift: a comment whose
opener line is blank before it and whose closer line is blank after it
loses all its lines, anything else loses only the comment region, and a
line left holding a bare delimiter goes with it.

Five files keep their comments behind a nocheck marker instead. Four are
upstream configs whose inline docs are upstream's own (mautrix, systemd
journald, nextcloud-oidc-login, MediaWiki). In littlejs the
commented-out entries ARE the deactivation mechanism for an example
tile, so deleting them would delete configuration.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 03:22:02 +02:00
kevinveenbirkenbachandClaude Fable 5 d88f272f9f Merge branch 'feature/swarm-nfs' into feature/svc-net-tor
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 15:31:08 +02:00
kevinveenbirkenbachandClaude Opus 4.8 f483ee9c9b fix(tls): trust the self-signed CA from PHP in wrapper-less containers
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>
2026-07-14 16:15:27 +02:00
kevinveenbirkenbachandClaude Fable 5 e2f8d4c304 refactor(container): drive exec timeout and CA paths from group_vars SPOTs
Two wrapper behaviours move onto single sources of truth:

Exec timeout: SYS_TIMEOUT_CONTAINER_EXEC (07_schedule.yml, default 7d so a
hard kill never beats a production migration; the dev/CI inventory
tightens it to 60min) reaches every task host via the play environment as
CONTAINER_EXEC_TIMEOUT. The wrapper prefixes only the exec subcommand with
coreutils timeout --kill-after=15. Ansible task timeouts are SIGALRM-based
and slept through the last wedged docker socket for 4.5 hours; the OS
level kill still fires there and returns 124, which until/failed_when and
the rescue diagnostics react to. Unset or 0 keeps todays behaviour.

CA container paths: the bind-mount targets for the injected root CA and
wrapper script lived as string literals in container.py, compose_ca.py
and the filter plugin. They now live once in the CA_TRUST group_vars SPOT
(inject_cert_container, inject_wrapper_container) and reach container.py
as env vars (play environment plus CA_TRUST_SYSTEMD_ENV) and compose_ca.py
as CLI arguments built by the compose_ca_inject_cmd lookup. A caller that
provides the host CA env but not the container paths is half-migrated and
fails loudly instead of silently dropping CA injection; a host without any
CA env keeps the soft fallback. All tests read the paths from the SPOT,
and a regression test asserts the runtime scripts carry no literal copy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 10:58:28 +02:00
kevinveenbirkenbachandClaude Opus 4.8 266a37d965 feat(svc-net-tor): onion mail/SSH ports + TLS-off compat (CA CN, HSTS, msmtp)
- svc-net-tor: expose SSH (22) + Mailu SMTP/submission/IMAP (25/465/587/143/993)
  as HiddenServicePorts on the node onion (TOR_ONION_SSH_ENABLED, 19_tor.yml).
- email lookup: relay plaintext (tls off, port 25) for .onion mail domains —
  no public TLS cert for a .onion; onion transport is already authenticated.
- CA root CN: drop the " Root CA" suffix when a 62-char v3 onion + suffix would
  overflow the X.509 commonName 64-char cap (02_tls.yml).
- Playwright onion compat: HSTS is HTTPS-only (RFC 6797), absent on the http
  onion vhost. New shared helper expectHstsWhenTls() gates the assertion on the
  reached scheme; wired into web-svc-cdn spec.
- rerun-spec.sh: route the inner-loop browser through the Tor SOCKS proxy when
  the staged env targets a .onion, mirroring the deploy-time runner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 16:39:40 +02:00
kevinveenbirkenbachandClaude Opus 4.8 af6f2bdcdf feat(filter): ca_trust_paths as the SPOT for the root-CA cert locations
ca_cert_host derives the host cert path from SOFTWARE_DOMAIN; ca_cert_container carries the fixed in-container bind-mount target. CA_ROOT.cert_host and the test fixtures consume the filter; the standalone runtime scripts (compose_ca.py, container.py) cannot import it and are pinned to the SPOT by the unit test instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:18:20 +02:00
kevinveenbirkenbachandClaude Opus 4.7 b627e383a1 fix(keycloak-swarm): manager pin + LOCAL_CID + chdir + sso-proxy DNS/CA
Make web-app-keycloak deployable in swarm without the manager-only
assumptions of the original compose-only shape.

Manager-side delegate scaffolding:
* tasks/02_manager_ops.yml: switch the `include_tasks` of
  02a_manager_ops_on_host.yml to the dict form so the `apply:
  delegate_to: KEYCLOAK_HOST_NODE` block actually attaches (free
  form raised `conflicting action statements: include_tasks, apply`
  in ansible 2.19).

Swarm bind sources that exist only on the manager:
* tasks/05b_realm_import_swarm.yml: pin the one-shot
  `<stack>_<service>_import` service to `node.role == manager` so
  the realm-import bind mount `/opt/compose/keycloak/volumes/import`
  resolves.
* templates/sso_proxy/container.yml.j2: same manager pin for the
  sso-proxy sidecar (its oauth2-proxy cfg bind source also lives
  only on the manager). Also add `extra_hosts` for the keycloak
  auth domain pointing at `host-gateway` so OIDC discovery reaches
  the host's openresty, and mount the self-signed CA when TLS_MODE
  is `self_signed`.
* templates/sso_proxy/oauth2-proxy-keycloak.cfg.j2: add
  `provider_ca_files` so oauth2-proxy validates the self-signed
  cert instead of failing OIDC discovery with TLS EOF.

CA mount path SPOT:
* group_vars/all/02_tls.yml: add `CA_TRUST.cert_container` for the
  in-container CA file path so the literal `/etc/ssl/certs/...` is
  defined once.
* roles/web-app-matrix/vars/main.yml: consume the new SPOT.

Container ID resolution in delegated tasks:
* vars/main.yml: add `KEYCLOAK_TARGET_CONTAINER` (= LOCAL_CID with
  fallback to KEYCLOAK_CONTAINER_ADDRESS) and recompose
  `KEYCLOAK_EXEC_CONTAINER` from it, replacing the inline default.
* tasks/update/05c_per_app_mappers.yml and
  tasks/update/ldap/per_app_mapper_one.yml: `KC_CONTAINER` env now
  resolves via the new SPOT, so the per-app-mapper helper scripts
  hit the actual swarm container ID on the delegated host node
  (compose mode keeps using the bare container_name via the
  fallback).

Delegated script paths:
* tasks/update/08_users_email_verified.yml and
  tasks/update/08_users_realm.yml: drop
  `chdir: "{{ role_path }}/files"` from `ansible.builtin.script`.
  role_path is a controller-side absolute path; on a delegated
  DinD swarm node that path doesn't exist and the script fails to
  start. Both scripts only consume env vars.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-16 13:34:06 +02:00
kevinveenbirkenbachandClaude Opus 4.7 9d7854bd2e fix(lint): wire new env-lookup guard into the existing lint contracts
Four follow-ups uncovered by \`make test\` after 3c9db6142:

* tests/lint/ansible/variables/test_role_and_group_vars_used:
  TLS_ENABLED is declared in group_vars/all/02_tls.yml but never
  read via \`{{ TLS_ENABLED }}\`; it is consumed by
  plugins/lookup/{tls,email,cert}.py, which the YAML/Jinja-scoped
  lint cannot see. Tag the declaration with the project's standard
  \`# nocheck: unused-var\` marker and document the consumer.

* tests/lint/filesystem/python/test_no_raw_project_walk: the new
  test_no_ansible_env_lookups.py used \`Path.rglob("*")\` for its
  walk, which is the exact shape the no-raw-walks lint forbids in
  test code. Switch to \`iter_project_files(extensions=_SCAN_EXTS)\`
  with a relative-path prefix filter; same coverage, shared cache.

* tests/unit/cli/administration/deploy/development/test_inventory_vars_file_spot:
  the new lint test legitimately names the dev inventory path
  (\`inventories/development/default.yml\`) in its failure message.
  Add it to the SPOT-drift-guard allowlist with a justification.

* tests/integration/ansible/jinja/test_variable_definitions: the
  debug task added in 0884eb4e2 prints \`ansible_env\` and \`vars\`,
  both Ansible magic variables. Add them to the test's reserved-name
  list so the static analyzer stops flagging them as undefined.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 17:57:14 +02:00
kevinveenbirkenbachandClaude Opus 4.7 79faaf21e7 refactor(ansible): move INFINITO_* env lookups out of group_vars/roles into dev inventory
\`lookup('env', 'INFINITO_*')\` calls in group_vars / roles couple
deployment-agnostic Ansible code to default.env, which only exists in
dev/CI. Production deploys never source .env, so the lookups silently
fall back to their group_vars defaults and the inventory's intent gets
lost on the way (see the IP4 host_vars-overlay incident fixed in
8980b00b1).

Migrated keys (group_vars/roles -> inventory):
- DOMAIN_PRIMARY            (00_general.yml)
- BUILDKIT_ENABLED          (00_general.yml)
- CA_CONTAINER_ENABLED      (02_tls.yml)
- defaults_networks.internet.{ip4,ip6,dns}  (08_networks.yml)
- TEST_E2E_PLAYWRIGHT_KEEP  (roles/test-e2e-playwright/defaults/main.yml)

group_vars and the role default now carry literal fallback values;
the dev/CI inventory at \`inventories/development/default.yml\` pulls
the live values from .env via straight \`lookup('env', 'INFINITO_*')\`
calls without any \`| default(...)\` filter, because load.sh guarantees
the keys are set whenever .env exists.

The convention is enforced by the new lint test
\`tests/lint/filesystem/env/test_no_ansible_env_lookups.py\`, which
scans group_vars/, tasks/, roles/, and playbook.yml for any
\`lookup('env', 'INFINITO_*')\` against a default.env key and fails
with a pointer to the dev inventory as the injection site.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 17:57:13 +02:00
kevinveenbirkenbachandClaude Opus 4.7 6b78a05abc refactor(ca): invert CA toggle to INFINITO_CONTAINER_CA_ENABLED
Renames the container-wrapper CA toggle from the negated INFINITO_CONTAINER_NO_CA
to the positive INFINITO_CONTAINER_CA_ENABLED, mirroring the BUILDKIT_ENABLED
pattern: SPOT in env/default.env (default 1), passthrough handler + .env baseline
test, compose.yml env block, the CA_CONTAINER_ENABLED ansible var in
group_vars/all/02_tls.yml, a playbook-level CA_CONTAINER_ENABLED task-env
projection, and a strict `== "1"` read in container.py. Also drops the
redundant INFINITO_ prefix from the DNS/DOMAIN/IP4 vars that the DinD-DNS
test-dns.sh handler injects, since those names are local to the script's
caller-controlled env block. Finally lets pull.py fall back to a plain
`compose build` when `compose build --pull` fails — BuildKit can then use the
local FROM image instead of requiring a registry round-trip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 14:55:10 +02:00
kevinveenbirkenbach 92cc9a49df test(lint): forbid | default(...) on resolvable group_vars/all SPOT paths
Adds tests/lint/ansible/test_no_jinja_default_on_spot_path.py: scans every
Jinja `{{ ... | default(...) }}` block under roles/, tasks/, group_vars/,
identifies the actual default target via a paren-aware left-walk from the
matched `|`, and fails when the target's dotted path resolves cleanly
against any group_vars/all/*.yml dict tree. Role vars/defaults are
intentionally out of scope (Ansible loads them only when the owning role
is in the play, so cross-role defaulting is a legitimate caller guard,
not SPOT decoupling). Per-line opt-out: `# nocheck: spot-default`.

Fixes the 21 violations the test surfaces:
- group_vars/all/00_general.yml: drop `| default('')` on CAPTCHA self-checks
- group_vars/all/02_tls.yml: drop `| default(...)` on DOMAIN_PRIMARY in CA subject
- roles/web-app-{listmonk,keycloak,espocrm}/meta/schema.yml: drop defaults on CAPTCHA.*
- roles/web-app-suitecrm/templates/env.j2: map empty SECURITY -> 'none' via `or`,
  not via the default filter (SPOT value `""` is always set)
- roles/web-app-opentalk/tasks/main.yml: drop default on MODE_ASSERT (always bool)
- roles/user-workstation/tasks/01_core.yml: drop default on MASK_CREDENTIALS_IN_LOGS
- roles/test-e2e-playwright/tasks/run_one.yml: drop defaults on TLS_MODE + CA_TRUST.*

Also corrects the dotted-path bugs in roles/web-app-odoo/vars/main.yml that
referenced non-existent LDAP keys (`LDAP.SERVER.REACH_DOMAIN`,
`LDAP.USERS.SERVICE_ACCOUNT.*`, `LDAP.ATTRIBUTES.ID`, `LDAP.SERVER.SSL`) to
the canonical paths actually defined in group_vars/all/12_ldap.yml.
2026-05-14 17:12:56 +02:00
kevinveenbirkenbachandClaude Opus 4.7 2a023ca6a1 chore(lint): autoformat strict-failure semantics + claude.sh rename
* scripts/lint/autoformat.sh:
  - rename run_sort_claude_settings worker to run_claude (matches the
    renamed scripts/lint/claude.sh).
  - drop the EXIT trap that wiped status_dir; cleanup happens explicitly
    after `wait` returns, so a parallel worker that interrupts the
    parent never races a vanished status dir.
  - each worker now captures its tool's exit code and writes one of
    OK / SKIP / FAIL. Tolerated `expected violations remain`-style exits
    (markdownlint-cli2 rc≤1, ansible-lint rc≤2) still count as OK; only
    real failures count as FAIL.
  - all workers run to completion regardless of individual tool failures
    (so the user always sees the full picture); after the summary, the
    script exits 1 if any worker reported FAIL.
* scripts/lint/sort_claude_settings.sh -> scripts/lint/claude.sh
  (history preserved via git mv); update test_claude_settings_sorted.py
  fix-hint accordingly.

Plus working-tree drift across roles/, group_vars/, inventories/,
requirements/, and .pre-commit-config.yaml 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>
2026-05-09 01:30:49 +02:00
kevinveenbirkenbachandClaude Opus 4.7 4350e2f993 chore(vars): prune dead vars and add nocheck markers (lint cleanup)
Resolve every remaining mismatch flagged by the new
test_role_and_group_vars_used lint:

- group_vars/all/02_tls.yml — mark TLS_SELFSIGNED_SCOPE as
  `nocheck: unused-var`; the self-signed cert generator consumes it
  outside the YAML/Jinja scope.
- group_vars/all/08_networks.yml — mark PORT_BANDS as
  `nocheck: unused-var`; the port-allocation plugin reads it via
  Python, not YAML.
- roles/sys-stk-front-proxy/vars/main.yml — delete the role-local
  one-liner whose only var (`front_proxy_domain_conf_dst`) was never
  consumed.
- roles/test-e2e-playwright/defaults/main.yml — drop
  TEST_E2E_PLAYWRIGHT_IMAGE_DISTRO; the image-derivation comment
  that referenced it is gone too.
- roles/web-app-opentalk/templates/playwright.env.j2 — switch
  OPENTALK_OIDC_ENABLED to render the inventory-resolved var
  directly instead of duplicating the lookup.
- roles/web-app-discourse/tasks/04_network.yml — trailing-newline
  fix (incidental autoformat).

After this commit the lint passes cleanly across the role tree and
the two `group_vars` markers stand as the single canonical place a
contributor finds when researching "why is X exempt".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 11:28:22 +02:00
Environment TestandClaude Sonnet 4.6 750c110f45 fix(docs): apply documentation rules, fix URLs, and remove orphaned references
- Apply documentation.md rules across staged files (emojis after headlines, RFC 2119 keywords, correct link text, Credits format)
- Remove expired ChatGPT share URLs from 39+ role READMEs and templates
- Remove orphaned text after link deletion: bare # @see, {# @see  #}, Discussion: #}, empty Chat Conversation sections, dangling "Conversation is here available.", "Refer to the conversation with ChatGPT ()", "See ChatGPT Conversation", "ChatGPT Session Transcript" references
- Remove empty ## Further Resources and ## Credits sections where no links remain
- Fix URL reachability test: treat 401/403/405 as ok, 429/500/503 as warn, Timeout as warn; add # nocheck: url suppression (inline and line-above)
- Add lint test for broken relative and repo-root-absolute markdown links
- Add # nocheck: url to AUR git clone URLs, CSP allowlist entries, and other non-browser URLs
- Restore docs.infinito.nexus/setup in documentation.md MUST NOT example
- Fix Mobilizon geospatial endpoints (Addok, Photon) reverted to original values with # nocheck: url
- Add integration test rule: MUST NOT write unit tests for integration test logic

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 00:38:42 +02:00
kevinveenbirkenbach 846444effa fix(tls): unify self-signed CA env for health services 2026-02-23 23:53:18 +01:00
kevinveenbirkenbach 92946ee008 refactor(core): introduce SOFTWARE_DOMAIN and unify homepage, tls, service, and oidc defaults
* add SOFTWARE_DOMAIN derived from SOFTWARE_NAME lowercased
* introduce DOMAIN_HOMEPAGE defaulting to dashboard domain if present, otherwise SOFTWARE_DOMAIN
* add automatic primary → homepage redirect mapping in constructor
* migrate TLS and CA paths from SOFTWARE_NAME|lower to SOFTWARE_DOMAIN
* update systemd unit suffix and timer/service discovery globs to SOFTWARE_DOMAIN
* switch OIDC default realm and client id to SOFTWARE_DOMAIN
* adjust SuiteCRM well-known software name to SOFTWARE_DOMAIN
* update unit tests to use SOFTWARE_DOMAIN for strict Jinja expansion

https://chatgpt.com/share/698db094-22bc-800f-8454-10d97f817659
2026-02-12 11:50:55 +01:00
kevinveenbirkenbach e1e1f3eb11 chore(vars): rename PATH_* to DIR_/BIN_/FILE_ conventions
https://chatgpt.com/share/6985cf49-3c74-800f-af97-f68b1969ab01
2026-02-06 12:23:46 +01:00
kevinveenbirkenbach 76d09dbc3f refactor: centralize docker wrappers, CA injection paths, and update tests
Unify compose/run wrappers under PATH_* variables, wire CA trust injection
consistently, extend with NSS/Chromium support, and adapt unit tests to the
new run-based execution model.

https://chatgpt.com/share/697e1f92-bc5c-800f-968d-ad43c50afaea
2026-01-31 17:06:09 +01:00
kevinveenbirkenbach 2678090934 feat(ca-trust): unify trust injection + strict templating
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
2026-01-30 11:26:46 +01:00
kevinveenbirkenbach b2fb72d112 feat(tls): split TLS settings to 02_tls and add self-signed root CA role
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
2026-01-29 16:48:03 +01:00