Commit Graph
424 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 4.7 170b16c59d docs(design): rename matrix-deploy.md to variants.md, add inventory.md, sync cross-links
Two design docs under docs/contributing/design/:

- variants.md (renamed from matrix-deploy.md): explains the
  folder-per-round wrapper that consumes per-role meta/variants.yml
  and iterates variants at deploy time. Headings normalised to
  sentence-case; the "legacy unsuffixed inventory path" wording
  rewritten to drop the historical-context phrasing forbidden by
  documentation.md.
- inventory.md (new): explains how a single round's inventory is
  assembled out of meta/services.yml plus the variant payloads,
  which inclusion mechanisms exist (operator --apps, run_after walk,
  shared-service auto-include via strict `is True`), what lives in
  the inventory file vs. host_vars/<host>.yml, and when Jinja
  actually gets rendered (only at play runtime, via
  utils.cache.applications._render_with_templar). Worked example
  documents the WordPress + Discourse wiring as a reference case.

Cross-links updated wherever they pointed at matrix-deploy.md or
its concept name as a file:

- docs/agents/action/iteration/role.md
- docs/contributing/actions/deploy.md
- docs/contributing/artefact/files/plugins/lookup/applications.md
- docs/contributing/artefact/files/role/variants.md
- scripts/tests/deploy/local/deploy/reuse-kept-app.sh (path comment)

Plain-text occurrences of "matrix-deploy" as a concept term inside
Python comments, log messages, and test docstrings stay as-is; only
file-link references are migrated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 12:54:09 +02:00
kevinveenbirkenbachandClaude Opus 4.7 b34131ca1d feat(package-cache): add Sonatype Nexus 3 OSS pull-through cache (req-012)
Brings up the package-cache compose service alongside the existing
registry-cache, gated by the same `cache` profile. Bootstrap helper
(scripts/docker/cache/package.sh) auto-rotates the admin password from
the well-known default, accepts the Sonatype CE EULA (3.71+ blocks
artifact downloads with HTTP 403 until /v1/system/eula is POSTed —
disclaimer must be sent verbatim with its unicode quotes), enables
anonymous read, creates one default blobstore, and registers the MVP
proxy set: apt-debian, apt-ubuntu, pypi-proxy, npm-proxy, helm-bitnami,
raw-githubusercontent. The runner gets pip.conf / .npmrc /
sources.list snippets bind-mounted in (each defaulting to /dev/null
when the cache profile is inactive) so the deploy is byte-identical
with cache off.

Cache plumbing:
- scripts/meta/env/cache/{registry,package}.sh: per-cache env defaults,
  hostname-derived admin password seed (uses `hostname` command, not
  $HOSTNAME shell var, so the value is identical across interactive
  shells, non-interactive subshells, and Python subprocesses).
- scripts/docker/cache/{registry-ca,package}.sh: container-side
  bootstrap helpers, both moved under scripts/docker/cache/.
- scripts/system/cache/clean.sh + `make cache-clean`: wipes both
  /var/cache/infinito/core/cache/{registry,package} dirs and stops
  the cache containers.
- cli/deploy/development/compose.py: Profile gate exports the three
  new INFINITO_PACKAGE_CACHE_* client-snippet paths and runs the
  bootstrap helper after the package-cache reports healthy.

Compose service uses sonatype/nexus3:3.91.1-ubi pinned, runs as
root (UBI image refuses to chown a fresh bind-mount), and forces
NEXUS_SECURITY_RANDOMPASSWORD=false so the bootstrap can log in with
admin/admin123 before rotating; the random-password mode otherwise
auto-completes onboarding silently and leaves no readable secret on
disk for an automated bootstrap to pick up.

Tests: tests/unit/cli/deploy/development/test_compose.py grows two
new assertions (cache services listed in `up` args when local; build
tests pin CI=true to retain ci-only assertions).

Closes req-012.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 03:11:45 +02:00
kevinveenbirkenbachandClaude Opus 4.7 6867e2a699 refactor(registry-cache): drop redundant load-once guard
The body of registry_cache.sh is already idempotent: every assignment
uses `: "${VAR:=default}"`, the `df`-based size calculation is gated
on `[[ -z "${INFINITO_REGISTRY_CACHE_MAX_SIZE:-}" ]]`, and re-sourcing
re-evaluates the same values to the same result. The
INFINITO_ENV_REGISTRY_CACHE_LOADED guard was pattern-mimicry from
defaults.sh / runtime.sh / etc., where it serves a real purpose
(skipping non-trivial init blocks). Here it skipped a no-op and just
added noise to the env-var namespace.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 00:43:12 +02:00
kevinveenbirkenbachandClaude Opus 4.7 9af3055d80 feat(registry-cache): move env defaults into registry_cache.sh, mandate in compose.yml
The three INFINITO_REGISTRY_CACHE_* variables previously carried their
defaults in compose.yml as `${VAR:-...}`, which baked /tmp paths and
a static 32g cache size into the YAML. Move the defaults into a new
shell module that is sourced via `scripts/meta/env/all.sh` and tighten
compose.yml to require the variables strictly via `${VAR:?...}`.

Why
   * Adapt cache size to the host. Hard-coding 32g hurts hosts where
     `/tmp` is a small tmpfs (typical Linux: ~half of RAM); the cache
     would either be capped above the partition or starve other tmpfs
     users. Computing the default as half the free disk space at the
     cache path leaves a usable budget for the rest of the system on
     small hosts and grows on large ones.
   * Single source of values. With defaults in shell, the compose YAML
     no longer carries duplicated tunables; `compose.yml` just consumes
     what the env layer provides. Same pattern as the existing
     INFINITO_CONTAINER strict consumption.

Changes
* New scripts/meta/env/registry_cache.sh exports the three vars.
  CACHE_MAX_SIZE = `df --output=avail -B1G "$path" / 2`, walking up to
  the closest existing ancestor when the cache path itself does not
  exist yet (it is created on first start by `bind:
  create_host_path: true`). Minimum 1g, fallback 2g if `df` fails.
* scripts/meta/env/all.sh sources the new module so the Makefile-driven
  flow picks it up automatically.
* compose.yml: three `${VAR:-default}` -> `${VAR:?Source scripts/meta/env/registry_cache.sh ...}`
  so the absence of the env layer fails fast with a precise message.
* Stale block comment above the registry-cache service (CI-only,
  /tmp-defaults, etc.) collapsed to a one-line pointer to the README,
  per documentation.md "default to no comments / explain only the
  non-obvious why".
* docs/contributing/environment/variables.md and
  docs/contributing/artefact/files/compose.yml.md describe the new
  defaults source and the half-free-disk computation.

Verified
   `bash -c 'source scripts/meta/env/all.sh && docker compose --profile ci --profile cache config'`
   resolves the three vars (CACHE_MAX_SIZE: 3g on a host with 7G free
   in /tmp). Without sourcing, compose fails with the exact
   "Source scripts/meta/env/registry_cache.sh ..." message.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 00:38:58 +02:00
kevinveenbirkenbachandClaude Opus 4.7 1dd8d3f5c0 feat(compose): add pull-through registry-cache + reorg compose/ subdirs
Introduces a `registry-cache` service (rpardini/docker-registry-proxy) in
the `ci` profile that MITM-caches every outbound docker registry pull
made by `infinito`'s inner dockerd, drastically cutting bandwidth on
re-deploys, parallel CI shards, and local fresh-purges.

Wiring (all bind-mounted via compose.yml, no Dockerfile or entry.sh
staging, so edits hot-reload on next container start):
- CA bundle at /opt/registry-cache-ca (read-only),
- systemd drop-in at /etc/systemd/system/docker.service.d/registry-cache-proxy.conf,
- registry-cache-ca.sh at /usr/local/bin/registry-cache-ca.sh.

The drop-in registers registry-cache-ca.sh as ExecStartPre. Compose
gates `infinito` on `registry-cache` `service_healthy` so the CA is on
disk before dockerd boots; the install script supports both Debian
(`update-ca-certificates`) and Arch/Fedora (`update-ca-trust extract`)
trust stores, detected by command availability.

Also reorganizes compose/ into per-service subdirectories
(compose/coredns/, compose/registry-cache/) with README.md per directory
following the documentation guidelines, adds scripts/docker/README.md,
and nests the registry-cache host paths under /tmp/infinito/core/
(mirror/ for blobs, ca/ for the MITM CA bundle).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 18:14:11 +02:00
kevinveenbirkenbachandClaude Opus 4.7 ed04dad5d5 refactor(meta): land req-008/009/010 role-meta layout/networks/ports/run_after migration
Single atomic landing for three back-to-back role-meta refactor requirements
(see docs/requirements/008-role-meta-layout.md, 009-per-role-networks-and-ports.md,
010-role-meta-runafter-lifecycle-migration.md).

req-008 — role meta layout
  * Move every role's metadata under meta/<topic>.yml with file-root
    convention (no wrapping key matching the basename):
      - schema/main.yml + config/main.yml.credentials → meta/schema.yml
      - users/main.yml                                → meta/users.yml
      - config/main.yml.server                        → meta/server.yml
      - config/main.yml.rbac                          → meta/rbac.yml
      - config/main.yml.compose.services + every other top-level key
        of config/main.yml inlined under <entity>.<key>
                                                      → meta/services.yml
      - config/main.yml.compose.volumes               → meta/volumes.yml
  * Drop the legacy schema/, users/, config/ directories from every role.
  * meta/schema.yml: support nested credential keys, optional `default:`
    field (Jinja literal, not rendered/validated/regenerated), implicit
    `algorithm: plain`.
  * Materialised paths drop the `compose.` prefix:
      compose.services.<e>.<…> → services.<e>.<…>
      compose.volumes.<k>      → volumes.<k>
      <former-top-level>.<…>   → services.<entity_name>.<former-top-level>.<…>
  * Update every Python consumer (utils/cache, utils/manager, utils/applications,
    plugins/lookup, plugins/filter, cli/*) plus every Jinja template, vars/*.yml
    and task file to the new paths.
  * No legacy fallback. Repository-wide grep for `compose.services.` /
    `compose.volumes.` / `schema/main.yml` / `users/main.yml` / `config/main.yml`
    is empty outside docs/requirements/ and tasks/utils/migrate_meta_layout.py.
  * Add tests/lint/repository/test_role_meta_layout.py guarding against the
    old paths returning, and an explicit lint guard that meta/variants.yml
    files do not carry a `compose:` wrapper.

req-009 — per-role networks/ports
  * Move group_vars/all/08_networks.yml.defaults_networks.local.<role>.{subnet,
    dns_resolver} into roles/<role>/meta/server.yml.networks.local.{subnet,
    dns_resolver}.
  * Delete group_vars/all/09_ports.yml entirely; ports now live on the
    service entity that exposes them at
    roles/<role>/meta/services.yml.<entity>.ports.{inter,local,public}.
  * Rename + int-cast every former top-level `services.<entity>.port`
    field to `services.<entity>.ports.inter` (single int).
  * Always-category-keyed maps for ports.local / ports.public; relay
    spans use {start, end} integers.
  * Add PORT_BANDS map to group_vars/all/08_networks.yml — single source
    of truth for the per-<scope>.<category> band ranges, consumed by both
    the new CLI helpers and the lint test.
  * New CLI helpers (cli meta ports suggest, cli meta networks suggest)
    walk the live role tree gap-first then increment-fallback, with
    deterministic output and non-zero exit on capacity overflow.
  * cli create role consumes both helpers when scaffolding a new role.
  * Lint extensions: bare-int port shape rejection, host-bound port
    collision check (single ints + relay span integers, inter excluded),
    band-membership check with documented allow-list for the legacy
    web-app-bigbluebutton.bigbluebutton.local.http: 48087 entry.

req-010 — run_after / lifecycle migration
  * Move galaxy_info.run_after and galaxy_info.lifecycle out of
    meta/main.yml into meta/services.yml.<primary_entity>.{run_after,
    lifecycle}; <primary_entity> is the value returned by
    get_entity_name(role_name).
  * For multi-entity roles whose primary entity has no compose service
    (web-app-bluesky → bluesky, web-app-matrix → matrix, desk/dev/drv/
    gen roles), create a dedicated metadata-only top-level entry under
    <primary_entity>.
  * Introduce utils/roles/meta_lookup.py with get_role_run_after /
    get_role_lifecycle that resolve <primary_entity> once and read from
    meta/services.yml; degrade gracefully when meta/services.yml is
    absent OR the field is absent; raise MetaServicesShapeError when the
    file exists but is malformed.
  * Update every consumer (utils/service_registry, utils/invokable,
    cli/meta/applications/resolution/run_after/__main__, cli/meta/roles/
    lifecycle_filter/__main__, cli/meta/applications/resolution/combined,
    cli/build/{tree,graph,role_include}, cli/deploy/development/{common,
    init,deps,deploy}, plugins/filter/canonical_domains_map, etc.) to
    use the helper.
  * Lint guards: no run_after/lifecycle in meta/main.yml; lifecycle
    values restricted to {planned,pre-alpha,alpha,beta,stable,deprecated};
    run_after/lifecycle never on a non-primary entity; empty run_after
    lists rejected.

Also folded into this change set
  * services_disabler now writes `services.<svc>.{enabled,shared}` into
    host_vars instead of the old `compose.services.<svc>.{...}` path; the
    discover/find pair walks meta/services.yml and only treats entries
    carrying enabled/shared/provides as provider declarations (so
    metadata-only entries from req-010 do not create spurious provider
    rows).
  * service_registry skips `run_after` deps whose target was filtered
    out via SERVICES_DISABLED instead of failing the whole load.
  * cli/mirror/resolver and cli/create/inventory/mirror_overrides emit/
    consume the new applications.<role>.services.<svc>.image shape
    (mirror.md updated to match).
  * utils/domains/list.build_applications_from_roles now reads
    meta/server.yml directly (it had been silently returning {} since
    config/main.yml stopped existing).
  * utils/roles/resource_validation.filter_roles_by_min_storage reads
    meta/services.yml.<entity>.min_storage instead of the legacy
    compose.services path.
  * web-app-bluesky pre-clones the social-app build context BEFORE
    sys-svc-compose so the build context is on disk by the time
    sys-stk-front-proxy/tasks/main.yml flushes the docker build handler.
  * 4 meta/variants.yml files (svc-ai-ollama, web-app-phpmyadmin,
    web-app-wordpress, web-app-keycloak) lose their `compose: services:`
    wrapper so deep-merge over meta/services.yml lands on the new
    file-root shape.

Validation
  * Unit tests:        1571 passed.
  * Lint tests:        50 passed.
  * Integration tests: 161 passed (1 skipped).
  * Full-cycle fresh-purged-apps deploy succeeded for the 8-app validation
    set: web-app-{nextcloud,wordpress,yourls,bigbluebutton,bluesky,gitea,
    discourse,mattermost}.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 09:58:26 +02:00
kevinveenbirkenbachandClaude Opus 4.7 3f2021b07d refactor: collapse INFINITO_DISTRO/INFINITO_CONTAINER to a single SPOT and align all consumers
Follow-up to d002796a5. The formula `infinito_nexus_${INFINITO_DISTRO}` is
now defined in exactly one place — `scripts/meta/env/defaults.sh:18`,
exported outside the load-once guard so it tracks INFINITO_DISTRO across
matrix iterations (caller re-sources defaults.sh after distro changes).
Every other site reads INFINITO_CONTAINER strictly from env; nothing
re-derives the formula, no lazy/colon-dash fallbacks, no per-call defaults.

Production code aligned:

- cli/deploy/development/common.py: add resolve_container() (strict env
  reader, raises SystemExit on unset). make_compose() calls it as an
  early-fail guard so a missing defaults.sh source surfaces here instead
  of as a confusing compose.yml interpolation error later.
- cli/deploy/development/deploy.py: stop re-deriving from compose.distro;
  use resolve_container().
- compose.yml: container_name reads ${INFINITO_CONTAINER:?...} strictly,
  so callers that bypass defaults.sh fail fast with a clear hint.
- scripts/meta/resolve/image/local.sh: drop ${INFINITO_DISTRO:-debian}
  fallback; require INFINITO_DISTRO via :? error.
- scripts/tests/deploy/ci/dedicated.sh: drop local _container fallback.
- scripts/tests/deploy/local/deploy/fresh-kept-app.sh: drop redundant
  ${INFINITO_DISTRO:-debian} reassignment after sourcing env/all.sh
  (defaults.sh already defaults the value).
- scripts/tests/deploy/local/deploy/fresh-purged-app.sh: drop
  target_container fallback; INFINITO_CONTAINER is already in env.
- scripts/tests/deploy/local/exec/container.sh: drop the lazy
  ${INFINITO_CONTAINER:=infinito_nexus_...} default; defaults.sh source
  in env/all.sh already guarantees the value.
- scripts/tests/dns.sh: source defaults.sh so callers only need to set
  INFINITO_DISTRO (the implicit dependency was previously a footgun).
- scripts/system/tls/trust/{wsl2,linux}.sh: same — source defaults.sh,
  drop the ${INFINITO_CONTAINER:-infinito_nexus_debian} fallback in
  linux.sh.
- .github/workflows/test-dns.yml: drop redundant
  INFINITO_CONTAINER: infinito_nexus_${{ matrix.distro }} from per-step
  env blocks (3 sites). The env now flows from INFINITO_DISTRO via the
  defaults.sh sourced inside the scripts.
- tests/unit/cli/deploy/development/test_matrix_deploy.py: replace the
  module-level os.environ.setdefault('INFINITO_CONTAINER', 'infinito_nexus_arch')
  shim — which itself encoded the formula — with a module-level patch of
  resolve_container that returns an opaque test-fixture string. Drop the
  now-unused distro="arch" field from the _args() Namespace fixture
  (deploy.handler stopped consuming it after the --distro arg removal).

Documentation aligned:

- docs/contributing/environment/variables.md: rewrite the INFINITO_CONTAINER
  row to spell out the SPOT contract — set automatically by defaults.sh,
  never re-derived elsewhere, callers re-source defaults.sh after
  changing INFINITO_DISTRO mid-process.
- docs/contributing/artefact/files/compose.yml.md: replace the
  INFINITO_DISTRO row (which described the now-removed
  `infinito_nexus_${INFINITO_DISTRO:-debian}` interpolation) with an
  INFINITO_CONTAINER row noting the strict ${INFINITO_CONTAINER:?...} read.

Verified:
  - Formula derivation: 1 production hit (the SPOT in defaults.sh:18).
  - INFINITO_CONTAINER fallback patterns (:-/:=): 0.
  - --distro CLI arg refs: 0 (one comment explaining the removal).
  - make test green: 1553 unit + 168 integration tests, no skipped suites.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 12:21:35 +02:00
kevinveenbirkenbachandClaude Opus 4.7 3e31630d58 fix(ci/deploy)+refactor(cli/deploy/development): single-SPOT INFINITO_DISTRO/INFINITO_CONTAINER
Two related improvements anchored on the same principle: the env var
INFINITO_DISTRO is the single source of truth for the development distro,
and INFINITO_CONTAINER is purely derived from it.

1. Fix the matrix-deploy purge failure (CI run 24942172018, job 73038526513,
   web-app-wordpress @ fedora):
   scripts/meta/env/defaults.sh eager-exported INFINITO_CONTAINER once
   inside its load-once guard; ci/all.sh's iteration loop re-exported
   INFINITO_DISTRO per round but the cached INFINITO_CONTAINER stayed at
   the initial-default infinito_nexus_debian, so _purge_app_entities()
   between rounds tried to docker exec into a container from the wrong
   distro and failed with "No such container".

   Move the INFINITO_CONTAINER derivation OUTSIDE the load-once guard so
   every re-source picks up a CONTAINER name in lock-step with the current
   INFINITO_DISTRO. ci/all.sh now re-sources defaults.sh after each
   export INFINITO_DISTRO=\$distro to trigger that re-derivation. defaults.sh
   stays the single spot that owns the formula.

2. Retire the --distro CLI arg from cli.deploy.development {up,down,stop,
   restart,init,deploy,exec,logs,build}. The arg duplicated INFINITO_DISTRO
   (its default already read from the env), creating a hidden second source
   of truth that drifted from the env across matrix iterations. Replace it
   with cli.deploy.development.common.resolve_distro() which reads
   INFINITO_DISTRO strictly (raises SystemExit on unset/invalid), and
   simplify make_compose() to take no args. All bash callers
   (scripts/tests/deploy/{ci,local}/**, Makefile restart target) drop the
   redundant --distro \"\${INFINITO_DISTRO}\" pass-through.

Verified: make test passes (1553 unit + 168 integration). The mechanism of
the WP fix is verified by a synthetic distro-cycling test of defaults.sh
re-derivation; the original CI multi-distro repro can only be confirmed
end-to-end by the next CI push (local single-distro deploys do not exercise
the stale-env path).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 12:21:34 +02:00
kevinveenbirkenbachandClaude Opus 4.7 0639fb2fa7 fix(deploy): unblock matrix-deploy of web-apps on dev runtime
Four independent bugs were each blocking the autonomous
fresh-purged-apps iteration of web-app-{openwebui,wordpress,nextcloud,
phpmyadmin,yourls}; bundling them so the pipeline keeps running.

- cli/core/discovery.py: stop walking argv prefixes at the first flag
  or path-shaped arg and ignore OSError so the matrix-init's
  `infinito create inventory <abs-path> --vars '<huge JSON>'` no longer
  hits ENAMETOOLONG inside `is_dir()`. New regression tests pin both.
- utils/cache/data.py: when this module moved to utils/cache/data.py,
  PROJECT_ROOT silently slid one level deep, pointing ROLES_DIR at a
  non-existent `<repo>/utils/roles`. Implicit lookups returned empty
  defaults so `lookup('users','contact')` failed at deploy time as if
  the user weren't defined. Use parents[2] and pin the invariant in a
  new regression test.
- roles/web-app-keycloak/vars/main.yml: KEYCLOAK_PER_APP_LDAP_ROLES_MAPPER
  inherited `subComponents`/`id` from the realm-import dictionary's base
  `ldap-roles` mapper. kcadm rejects those keys on `create components`
  ("Unrecognized field 'subComponents'"), with stderr swallowed by
  `2>/dev/null` in upsert_app_mapper.sh. Strip both keys exactly like
  update/05_ldap.yml already does for the shared mapper.
- scripts/tests/deploy/local/deploy/fresh-purged-app.sh: pass
  `RUNTIME=dev` through `--vars`, otherwise the host process running
  init falls back to `detect_runtime() == "host"` and the Playwright
  E2E gate (`RUNTIME in [dev, act, github]`) never fires.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 17:17:14 +02:00
kevinveenbirkenbach f5e81ee56b refactor(modes): retire MODE_CI in favour of a direct RUNTIME check
The Playwright E2E gate previously fired through `MODE_CI`, a boolean
that OR-ed three env-var markers (`GITHUB_ACTIONS`, `ACT`,
`INFINITO_MAKE_DEPLOY`) AND-ed with an `INFINITO_SKIP_E2E` opt-out.
That mechanism duplicated information `RUNTIME` already carries:

- `make deploy-*` already bakes `RUNTIME=dev` into the inventory at
  init time (`RUNTIME_VARS_JSON='{"RUNTIME":"dev"}'`).
- CI runners (act / GitHub Actions) detect `RUNTIME=act|github` from
  their canonical env markers via `cli.meta.runtime.detect_runtime`
  during init.
- A bare `ansible-playbook` from a production shell stays at the
  default `RUNTIME=host`.

Switching the gate to `RUNTIME in ['dev', 'act', 'github']` therefore
covers every case `MODE_CI` covered, AND folds the gate into the
single existing runtime SoT rather than maintaining a parallel one.

Changes:
- `tasks/stages/02_server.yml` E2E include now keys on `RUNTIME`.
- `group_vars/all/01_modes.yml` drops the `MODE_CI` block; a short
  note explains the retirement.
- `scripts/meta/env/ci.sh` deleted (it only existed to export
  `INFINITO_MAKE_DEPLOY=1`).
- The 5 `scripts/tests/deploy/local/deploy/*.sh` entry-points stop
  sourcing `ci.sh`.
- `cli/deploy/development/deploy.py` no longer forwards the four
  retired env markers into the container; the runtime decision is
  already serialised into the inventory the deploy stage consumes.
- `scripts/tests/deploy/local/deploy/reuse-kept-app.sh` drops the
  same four `-e` flags from its `docker exec`.
- `docs/requirements/006-playwright-service-gated-tests.md` gets a
  prepended "MODE_CI retired" update note; the historical sections
  remain for context but the dead `ci.sh` link target was reduced to
  inline code.

Behaviour: any runtime where E2E was fired before still fires it now,
and the previous opt-outs have a single replacement: pass
`RUNTIME=host` (or `--vars '{"RUNTIME":"host"}'`) to skip Playwright on
a deploy that would otherwise have run it.
2026-04-25 14:21:45 +02:00
kevinveenbirkenbachandClaude Opus 4.7 d97ed7a9e8 feat(deploy): per-role matrix-deploy variants with folder-per-round model
Introduces per-role `meta/variants.yml` for declaring multi-variant
deploys (e.g. WordPress Single-Site vs. Multisite) and reshapes the
development deploy CLI to materialise one inventory folder per variant
and iterate them with co-located sync+async passes.

Why:
- The previous single-inventory + Pass-1-then-Pass-2 split deployed each
  variant twice and ran the async pass against a host left in a
  different variant's state, masking real bugs.
- Inventory and runtime concerns were tangled: variant selection lived
  as a runtime extra-var and required cross-stage coordination.

How:
- `roles/<role>/meta/variants.yml` (file format docs at
  docs/contributing/artefact/files/role/variants.md): YAML list of
  deep-merge overrides on top of `config/main.yml`. Empty entry `{}` is
  variant 0 (= legacy payload). 6 roles get their dev overrides moved
  here from inventories/dev.yml; WordPress declares 2 variants.
- `utils/runtime_data.py` gains `_build_variants` / `get_variants` and
  drops the retired `_active_variants` runtime selector. Loader always
  returns variant 0 as the default; non-zero variants reach
  `get_merged_applications` as inventory-level `applications.<app>`
  overrides baked at init time.
- New `cli/deploy/development/inventory.py`: `DevInventorySpec`,
  `plan_dev_inventory_matrix` (pure planner shared between init and
  deploy), `build_dev_inventory_matrix` (materialises one folder per
  round at `<dir>-<R>`, or `<dir>` when single-variant). Single-folder
  paths preserve full backward compat.
- `cli/deploy/development/deploy.py`: matrix loop with per-round
  cleanup (only apps whose variant changed AND that are about to
  re-deploy), `--variant <idx>` (or `VARIANT=` env) to pin a single
  round, and `--full-cycle` (or `FULL_CYCLE=true`) which interleaves
  sync + async per round so the async re-deploy targets exactly the
  host state the matching sync deploy just produced.
- `cli/deploy/development/exec.py` gains repeatable `--env KEY=VAL` so
  bash callers can inject per-run context into in-container helpers
  without inlining heredocs.
- `scripts/meta/env/inventory.sh` exports `INVENTORY_VARS_FILE`
  (Python mirror in `cli.deploy.development.common.DEV_INVENTORY_VARS_FILE`;
  test_inventory_vars_file_spot.py locks them against drift).
- `make deploy-fresh-purged-apps` and `scripts/tests/deploy/ci/dedicated.sh`
  switch to a single init + single deploy `--full-cycle`. The
  `make deploy-reuse-*` bash scripts honour `VARIANT=` by suffixing
  `INVENTORY_DIR`/`INVENTORY_FILE`.
- The 5 `scripts/tests/deploy/local/deploy/*.sh` scripts now delegate
  their in-container heredocs to dedicated helpers in
  `scripts/tests/deploy/local/utils/`, removing the bash-in-bash
  quoting hell.

Tests:
- `tests/unit/utils/test_variants.py` (renamed from
  `test_application_variants.py`): loader edge cases, variant-zero
  default, retired-`_active_variants` guard.
- `tests/unit/cli/deploy/development/test_inventory.py` (new): planner,
  variant baking, builder, `filter_plan_to_variant`, `DevInventorySpec`.
- `tests/unit/cli/deploy/development/test_matrix_deploy.py` (new):
  per-round deploy filter, cleanup decisions, `--variant` pinning,
  `--full-cycle` per-round interleave.
- `tests/unit/cli/deploy/development/test_inventory_vars_file_spot.py`
  (new): drift guard for the bash <-> Python literal pair.
- `tests/unit/cli/deploy/development/test_exec.py` extended for `--env`.

Docs:
- `docs/contributing/artefact/files/role/variants.md` (new): file format.
- `docs/contributing/design/matrix-deploy.md` (new): runtime mechanism
  (folder layout, --variant, --full-cycle, cleanup logic).
- `docs/contributing/actions/deploy.md` (new): contributor dev-deploy
  flow (make targets, --variant pinning).
- `docs/administration/deploy.md` slimmed to admin-only (was mixed
  admin+dev), cross-links to the new contributor doc.
- `docs/agents/action/iteration/role.md` keeps only agent-iteration
  recipes; mechanism explanation moved out and linked.
- `docs/contributing/documentation.md` adds the "no self-SPOT
  declaration" rule; `applications.md` renamed its SoT header for
  consistency.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 13:47:48 +02:00
kevinveenbirkenbachandClaude Opus 4.7 6b5aeabc3f refactor(env): move INFINITO_MAKE_DEPLOY SPOT from Makefile into scripts/meta/env/ci.sh
Drop the `DEPLOY_CI_EXPORTS := INFINITO_MAKE_DEPLOY=1` declaration
from the Makefile and the per-recipe `$(DEPLOY_CI_EXPORTS)` prefix
from every `deploy-*` target. The marker now lives in a dedicated
on-demand env module `scripts/meta/env/ci.sh` and is sourced by every
deploy entry-point under `scripts/tests/deploy/local/deploy/*.sh`,
which keeps the export aligned with the rest of the project's env
layer (`scripts/meta/env/all.sh` and friends).

`ci.sh` is intentionally NOT composed into `all.sh`: the marker
flips `MODE_CI` to `true` and MUST surface only when a deploy
actually runs, not for unrelated `make test*` / `make build*`
recipes that share the same `BASH_ENV`.

`docs/requirements/006-playwright-service-gated-tests.md` and the
`scripts/meta/env/README.md` document the new SPOT and the
always-on / on-demand distinction.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 09:50:03 +02:00
kevinveenbirkenbach 43e18d1ee2 Merge branch 'main' into feature/rbac-hierarchy-and-service-gates 2026-04-25 09:38:27 +02:00
kevinveenbirkenbach 8563df9400 chore: adopt git-maintainer-tools as dev dependency for git ops
Drop the project-local copy of scripts/git/sign-push.sh in favour of
the git-maintainer-tools CLIs (git-setup-remotes, git-sign-push),
which now ship as a standalone PyPI/GitHub package so the same helpers
can be reused across every maintainer repo without vendoring shell
scripts.

The package is declared in pyproject.toml's `dev` extras and lands on
$PATH via `make install-python-dev` (which already runs
`pip install -e .[dev]`). Consumers invoke the CLIs directly; there
are no make wrappers because doing so would just duplicate help text
the tool itself already prints.

Fallout:
  * scripts/git/sign-push.sh deleted.
  * Makefile: sign-push target removed (no replacement target).
  * .claude/settings.json: deny rule for `make sign-push*` replaced
    with direct `git-setup-remotes*` / `git-sign-push*` denies so the
    sandbox blocks the real binaries, not just the make alias.
  * CLAUDE.md: operator instruction points at the bare `git-sign-push`
    CLI + `make install-python-dev` for setup.
  * docs/contributing/artefact/git/remotes.md: new tool-focused page.
    Describes the layout and the two CLIs; does not duplicate raw git
    commands already covered by git-maintainer-tools.
  * docs/contributing/artefact/git/README.md: trimmed per readme_md.md
    (docs READMEs must not act as a file index).
  * docs/contributing/tools/make.md: Git section now documents the
    external CLIs and the install entry point, not a wrapper target.
2026-04-25 09:38:26 +02:00
kevinveenbirkenbachandClaude Opus 4.7 d009a35116 feat(rbac): hierarchical /roles/<app>/<role> + service-gated Playwright + Discourse round-trip
Implements requirements 005, 006, and 007.

005: per-application Keycloak `group-ldap-mapper`s anchor each app's
role groups under `/roles/<application_id>/<role_name>` (and, for
tenant-aware apps, `/roles/<application_id>/<tenant>/<role_name>`).
The flat-CN LDAP layout keeps each CN globally unique so Keycloak's
group-tree resolver never collides; per-app mappers use the LDAP
`description` as the visible Keycloak group name to keep the path
free of redundant prefixes. Migration purges legacy pre-005 entries
and forces a Keycloak `logout-all` so stale tokens drop pre-005
group paths.

006: per-service `<SERVICE>_SERVICE_ENABLED` env flags and the
`service-gating.js` helper let role specs `skipUnlessServiceEnabled`
cleanly when a shared service is missing or `SERVICES_DISABLED` is
set; `MODE_CI` markers (`INFINITO_MAKE_DEPLOY`, `GITHUB_ACTIONS`,
`ACT`) propagate through the deploy CLI into the test stage.

007: WordPress->Discourse round-trip Playwright scenario gated on the
`web-app-discourse` deployment.

WordPress Playwright runs 5/5 green on the single-site flow; the
multisite + Discourse scenarios are correctly skipped when their
service prerequisites are absent. The `rbac_group_path` lookup is the
single sanctioned producer for OIDC group paths and is wired into
every consumer (yourls, lam, minio, openwebui, pgadmin, phpldapadmin,
phpmyadmin, prometheus, xwiki).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 08:51:01 +02:00
kevinveenbirkenbach d5e9ad072b docs: relocate guides/ to administration/
Move installation.md and rename administration.md to deploy.md under
docs/administration/, delete docs/guides/, and add a scope-only
README.md per the docs-README rules. Update README.md, SUPPORT.md,
and scripts/tests/environment/README.md cross-links.

Refresh roles/web-app-decidim/README.md to match the current OIDC
patch set (decidim-core omniauth.rb + omniauth_helper.rb only, no
secrets.yml on Rails 7.2, ENV-prefixed credential names).
2026-04-21 21:04:03 +02:00
kevinveenbirkenbachandClaude Opus 4.7 8ce9179706 fix(lookups,web-app-peertube): finish users/applications migration and harden OIDC plugin install
- Resolve merge conflict in web-app-peertube/vars/main.yml; migrate
  remaining `applications |` and `users.*` references to
  `lookup('applications')` / `lookup('users', ...)` in web-app-odoo and
  web-app-decidim (compose, env, playwright, setup tasks).
- web-app-peertube: split OIDC tasks into oidc/{enable,disable}.yml,
  add install_mem_limit filter + PEERTUBE_MEM_LIMIT /
  PEERTUBE_OIDC_PLUGIN_INSTALL_MEM_LIMIT vars to raise the cgroup ceiling
  during the one-off `npm run plugin:install` exec (prevents OOM-SIGKILL
  that was surfacing as "Container is restarting"); add
  upsert-oidc-plugin.sql.j2 and diagnostics.sh.
- CI: test-deploy-{local,server,universal,workstation}.yml tweaks plus
  scripts/github/{dump_runner_state,enlarge_swap}.sh for constrained
  runners.
- cli/meta/applications/ressources: new CLI entry with unit tests.
- Misc config/docs updates across dashboard, mailu, mattermost, asset,
  cdn, file roles and contributing docs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 10:14:21 +02:00
kevinveenbirkenbach cc5d4e6d54 Merge pull request #182 from kevinveenbirkenbach/fix/web-app-peertube/oom/162
Fix peertube OOM during auth-openid-connect plugin install (#162)
2026-04-21 09:05:31 +02:00
kevinveenbirkenbach 17b6d755fd Merge branch 'main' into fix/users-applications.yml
Resolve conflicts by migrating lookup plugins to utils.applications.config
(get_app_conf -> get) while preserving branch's runtime applications/users
lookups. Notable changes:

- plugins/lookup/{config,nginx,service}.py, roles/web-app-dashboard/
  lookup_plugins/docker_cards.py: switch to utils.applications.config.get,
  keep utils.runtime_lookup_data.get_merged_applications/_users.
- roles/web-app-matrix/docs/Installation.md: use lookup('config', ...)
  syntax with markdown link formatting.
- scripts/tests/e2e/rerun-spec.sh: take main's single-source image tag
  derivation from roles/test-e2e-playwright/defaults/main.yml.
- tests/integration/test_get_app_conf_paths.py -> test_config_lookup_paths.py:
  migrate scan pattern to lookup('config', ...) and skip docs/ directory.
- tests/unit/plugins/lookup/test_config_lookup.py: union of new imports.
- tests/unit/plugins/lookup/test_cert.py: reset runtime caches in
  setUp/tearDown to avoid id()-based fingerprint cross-test pollution.
- Add __init__.py in 7 test directories left behind after prior cleanup.
- Delete dead duplicate group_vars/all/{04_users,05_applications}.yml
  (untracked on disk; content long migrated to roles/*/{config,users}/main.yml).
2026-04-21 02:34:06 +02:00
kevinveenbirkenbachandClaude Opus 4.7 fe18ae43cf Fix enlarge_swap.sh starving deploys of disk space (#162)
Swap size was free(/)-25, which took ~99 GB on the 145 GB root fs.
Remaining ~25 GB then ran out mid-deploy during Playwright image
pulls (ENOSPC). Replace with a fixed 16 GB cap and use mountpoint
to detect a real /mnt partition instead of [ -d /mnt ], which
matched the empty placeholder dir on single-partition runners.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 16:54:03 +02:00
kevinveenbirkenbach 5e02e084dc Merge branch 'main' into feature/e2e-friendica-joomla-playwright 2026-04-20 16:29:56 +02:00
kevinveenbirkenbachandClaude Opus 4.7 033b045cf0 Add nested-container diagnostics to runner-state dump
Surfaces df, failed units, and hlth-disc-space status/journal from
inside the infinito container so failure-time dumps reflect the same
filesystem view that the disk-space health check evaluates, not just
the host.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 12:44:37 +02:00
kevinveenbirkenbachandClaude Opus 4.7 6fac8996ff Add runner-state diagnostic helper for CI deploy debugging
Extracts disk/memory/docker/swap dump into a reusable helper,
calls it pre/post swap resize, and runs it on deploy failure
across all four deploy workflows. Also adds SKIP_SWAP=1
short-circuit for A/B probing swap-related regressions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 12:37:54 +02:00
kevinveenbirkenbachandClaude Opus 4.7 0cfe0b4d51 Normalize whitespace in enlarge_swap.sh
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 03:23:37 +02:00
kevinveenbirkenbachandClaude Opus 4.7 8cdfcad9ef Enlarge CI runner swap dynamically across deploy workflows
Introduce scripts/github/enlarge_swap.sh that consumes all remaining
space on / minus a 25 GB buffer (Docker layers, checkout, runtime
state), falling back to /mnt when / is too small. Replaces the
default ~4 GB runner swap so transient memory spikes (e.g. peertube
plugin install, issue #162) don't trip the host OOM-killer on the
16 GB GitHub-hosted runner.

Wire the script into all four deploy test workflows (local, universal,
server, workstation) and flip free-disk-space to the aggressive config
(tool-cache, large-packages, docker-images, swap-storage all true):
the deploy actually runs inside the infinito container, so host-side
toolchains, language runtimes, and the default Docker layer cache are
dead weight. Update the workflow SPOT doc accordingly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 03:18:45 +02:00
kevinveenbirkenbach b5c4b8a3d9 Merge branch 'main' into fix/users-applications.yml 2026-04-19 22:21:16 +02:00
kevinveenbirkenbach 19de80b506 fix(sign-push): sign via rebase -S instead of exec amend
The --exec 'git commit --amend --allow-empty --no-edit -S' pattern
multiplied identical-tree empty commits during a rebase-merges run
(observed: 3 upstream empties became 16 locally). git rebase -S signs
every rewritten commit natively, without the amend loop, while still
preserving merges via --rebase-merges.
2026-04-19 22:16:44 +02:00
kevinveenbirkenbach eb5e87ee28 fix(sign-push): allow amending empty commits during re-sign
The re-sign rebase failed on empty commits pulled in from upstream
because git refused the --amend. Adding --allow-empty keeps those
commits in place while still GPG-signing them.
2026-04-19 22:04:24 +02:00
kevinveenbirkenbach c3da3ce013 fix(scripts): derive Playwright image tag from defaults/main.yml
The old derivation read @playwright/test out of
roles/test-e2e-playwright/files/package.json and assembled the tag
as `v{version}-{distro}`. That package.json no longer exists — the
version+distro tag now lives verbatim in defaults/main.yml under
images.playwright.version, so read it from the single source of
truth instead of rebuilding a broken tag from a missing file. The
TEST_E2E_PLAYWRIGHT_IMAGE_DISTRO fallback is no longer referenced
once the distro suffix is baked into the upstream version string.
2026-04-19 19:12:54 +02:00
kevinveenbirkenbach 2d91eb3368 Merge branch 'main' into fix/users-applications.yml 2026-04-19 15:37:37 +02:00
kevinveenbirkenbachandClaude Opus 4.7 5e75a4b0ad feat(git): add make sign-push and deny direct push in sandbox
Route pushes through scripts/git/sign-push.sh so the operator's GPG key
stays outside the sandbox. Script signs only @{u}..HEAD (or merge-base
with origin/main|master on first push), skips re-signing when all
commits already carry a good signature, and uses --force-with-lease on
rewritten history. settings.json denies git push*, git send-pack*, and
make sign-push* so the agent cannot trigger a push path directly.
CLAUDE.md instructs the agent to hand off to the operator instead.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 12:15:44 +02:00
kevinveenbirkenbach ccb3e8a96c Merge branch 'main' of github.com:infinito-nexus/core into fix/users-applications.yml 2026-04-19 03:29:31 +02:00
kevinveenbirkenbachandClaude Opus 4.7 9ffd118264 fix(skills): mirror .agents/skills to .claude/skills instead of symlink
The previous symlink approach left .claude/skills as a real empty dir in
some setups, which collides with the agent sandbox (denyWithinAllow on
.claude/skills expects a file path, not a directory) and breaks every
Bash invocation. Copy the installed skills tree into .claude/skills so
both locations are real, independent copies — no symlink involved.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 18:32:20 +02:00
kevinveenbirkenbach a8df31d898 Merge branch 'main' into fix/free-disk-space/138 2026-04-18 18:14:07 +02:00
kevinveenbirkenbachandClaude Opus 4.7 433a4138dd feat(ci): recognize chore branch prefix
Treat chore/** branches like feature/fix/etc. for CI: trigger the
push pipeline on chore/** pushes and accept chore as a valid PR
branch prefix in full scope. This unblocks the chore/update-* PRs
that update.yml creates automatically, which previously failed
branch-prefix validation and never triggered the push pipeline.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 17:11:28 +02:00
kevinveenbirkenbachandClaude Opus 4.7 4f2dd95fe9 feat(ci): add CI_RUN_ON_MAIN gate, reorg actions docs
Add repository variable CI_RUN_ON_MAIN that gates whether pushes to
main trigger CI. When unset (default), pushes to main skip CI;
set to 'true' to enable. Pushes to other branches are unaffected.

Reorganize GitHub Actions docs into a dedicated tools/github/actions/
directory and consolidate every CI repository variable in a single
SPOT (configuration.md). Move actions.md to actions/workflows.md and
branch/configuration.md to actions/configuration.md, then update
every cross-reference. Trim README files to scope-only per
docs/contributing/artefact/files/docs/readme_md.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 16:31:52 +02:00
kevinveenbirkenbachandClaude Opus 4.7 6364b8949f fix(install/arch): init pacman keyring before pacman -Syu
Move `pacman-key --init` + `--populate` ahead of the first `pacman -Syu`
call in `build_and_install_arch`. The previous guard
`pacman-key --list-sigs` always returned 0 on a fresh Manjaro/Arch image,
so the keyring init never ran and the upgrade of `archlinux-keyring`
failed with `==> ERROR: There is no secret key available to sign with.`,
breaking the manjarolinux/base test-environment matrix entry.

Refs: https://github.com/infinito-nexus/core/issues/167

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 12:28:35 +02:00
kevinveenbirkenbachandClaude Opus 4.7 fa10c3c9bc test(e2e): add playwright specs for keycloak, matomo, openwebui, discourse + extend dashboard
Closes Verification checkboxes in requirement 003. Each spec exercises
the `lookup('applications')` + `lookup('users')` pathway end-to-end:
login via lookup('users'), OIDC round-trip from dashboard (where the
app supports it), canonical-domain DOM assertion, and a full CSP
assertion covering every directive emitted by csp_filters.build_csp_header
(15 directives), meta-parity, enforced policy (not Report-Only), and
zero securitypolicyviolation events.

The dashboard spec is extended in place with the CSP + canonical-domain
assertions while keeping its existing login/logout coverage.

rerun-spec.sh is pointed at the Jinja template + the staged copy of
package.json (the repo has templates/package.json.j2, not files/package.json),
so the inner spec-only iteration loop works without re-deploying.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 08:53:47 +02:00
kevinveenbirkenbach 59173f7274 Merge branch 'main' into fix/users-applications.yml 2026-04-17 00:02:48 +02:00
kevinveenbirkenbach 4ee8441e33 Merge branch 'main' into fix/free-disk-space/138 2026-04-16 19:48:05 +02:00
Environment TestandClaude Opus 4.6 e2dbbceb6a chore(sandbox): add cross-distro installer for bwrap/socat via make agent-install
Adds scripts/install/sandbox.sh, a distro-aware installer for the OS-level
dependencies the Claude Code sandbox backend needs (bubblewrap, socat),
and exposes it through a new `make agent-install` target. Documents the
target in docs/contributing/tools/make.md, scripts/install/README.md, and
the Claude sandbox guide so contributors on a fresh host can bring up a
working sandbox without hand-installing distro packages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 18:06:57 +02:00
Environment Test dfa8cd99bb Merge branch 'main' into fix/free-disk-space/138 2026-04-16 14:00:27 +02:00
kevinveenbirkenbach 34ddc4d2d2 Merge branch 'main' into fix/users-applications.yml
# Conflicts:
#	group_vars/all/00_general.yml
#	roles/web-app-dashboard/templates/playwright.env.j2
#	roles/web-app-nextcloud/config/main.yml
#	roles/web-app-nextcloud/templates/playwright.env.j2
2026-04-16 11:54:20 +02:00
kevinveenbirkenbach efc651a057 Merge branch 'main' of https://github.com/kevinveenbirkenbach/infinito-nexus-core 2026-04-16 11:31:31 +02:00
kevinveenbirkenbachandClaude Opus 4.6 f04bae680e chore(skills): symlink .claude/skills -> .agents/skills
Claude Code discovers project skills under .claude/skills/, but
`skills experimental_install` writes to .agents/skills/. Add a
relative symlink so installed skills (caveman, compress, ...) are
picked up without extra copy step.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 11:30:41 +02:00
kevinveenbirkenbachandClaude Opus 4.6 83d88a28fc chore(skills): symlink .claude/skills -> .agents/skills
Claude Code discovers project skills under .claude/skills/, but
`skills experimental_install` writes to .agents/skills/. Add a
relative symlink so installed skills (caveman, compress, ...) are
picked up without extra copy step.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 11:29:55 +02:00
kevinveenbirkenbach 262e1b43fc Merge branch 'main' into fix/users-applications.yml
# Conflicts:
#	docs/contributing/design/services.md
#	plugins/lookup/applications_current_play.py
#	plugins/lookup/service.py
#	roles/sys-stk-backend/lookup_plugins/service_should_load.py
#	roles/sys-stk-backend/tasks/_load_service.yml
#	roles/sys-stk-backend/vars/main.yml
#	roles/sys-svc-compose/templates/base.yml.j2
#	roles/sys-svc-mail/tasks/01_core.yml
#	roles/sys-utils-service-loader/tasks/load_frontend_service.yml
#	tests/unit/plugins/lookup/test_applications_current_play.py
#	tests/unit/plugins/lookup/test_service.py
#	tests/unit/roles/sys-stk-backend/lookup_plugins/test_service_should_load.py
2026-04-15 23:59:54 +02:00
kevinveenbirkenbachandClaude Opus 4.6 b70a783423 refactor(lookups): migrate to runtime applications/users lookups
Replaces inline `applications`/`users` merging with dedicated lookup
plugins and runtime hydration. Introduces `add_csp_hash` filter,
integrates sort into `find_dock_val_by_bkp_entr`, splits the Keycloak
one-shot realm import into `05a_realm_import_run.yml`, and fixes the
Mastodon administrator task YAML. Removes obsolete `csp_hashes` filter,
`sys-token-store` Jinja filter, and empty CLI setup modules.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 23:44:19 +02:00
Environment TestandClaude Opus 4.6 a07ef379ed chore(scripts): drop unused CA trust env pass-through from Playwright rerunner
The rendered .env already contains the deploy-time CA trust values and the
spec uses ignoreHTTPSErrors, so duplicating the Ansible task's -e wiring
never adds anything in the inner-loop case.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 20:31:03 +02:00
Environment TestandClaude Opus 4.6 53efeb7eb7 docs(agents): add Playwright Spec Loop with rerunner script
Adds the documented inner-loop workflow for iterating role-local
Playwright specs against a live stack without a full redeploy, plus
the helper script that reuses the existing staging dir, rendered .env,
and container image defaults so no new TEST_E2E_PLAYWRIGHT_* override
mechanism is introduced.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 20:22:26 +02:00