Commit Graph
489 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 5.5 30084f986c fix(web-app-keycloak): keep concurrent kcadm jobs from losing the config lock
In the async pass the role runs kcadm jobs side by side. Every kcadm call in
the Keycloak container shares one config file. kcadm rewrites that file when
it refreshes the access token, about every 55 seconds, takes the file lock
with tryLock() and gives up at once when another kcadm process holds it. One
of two overlapping declared-client jobs then failed with "Failed to get lock
on /opt/keycloak/.keycloak/kcadm.config", and the deploy with it.

keycloak_kcadm_update now repeats a call that lost the lock, up to ten times
one second apart. The call fails while it refreshes its token, before it
sends its own request, so repeating it is safe. Every other error still ends
the call at once.

That covers the module only, so the async jobs are reduced to the ones that
go through it:

- The default-scope assignment was an async shell task. It ran beside the
  shell tasks that follow it, and neither side repeats a lost lock. It runs
  in line now.
- The two mail-settings jobs were async without a register, so nothing
  reaped them and a failure was never seen. They run in line now.

The client job and the declared-client jobs stay async. A test fails when an
async kcadm task does not use the module or is not registered for the reap.

A config file of its own per call was the other option. It does not work by
itself, because the admin session lives only in the shared file.

Closes #805

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 09:17:56 +02:00
kevinveenbirkenbach 123f85f1c0 Merge remote-tracking branch 'origin/main' into fix/web-app-penpot/frontend-start-guard/806 2026-10-06 09:17:52 +02:00
kevinveenbirkenbach 207553eeb2 Merge remote-tracking branch 'origin/main' into fix/web-app-penpot/frontend-start-guard/806 2026-10-06 09:17:51 +02:00
kevinveenbirkenbachandClaude Opus 5.5 5494fbad48 fix(web-app-penpot): make the frontend wait for its backend by absolute name
nginx resolves its upstreams once, at start, so the frontend starts through
a guard that waits until `backend` and `exporter` resolve. A container
resolves with a search domain, and the zone of a deployment answers every
name with the address of the ingress. A service that Docker's DNS did not
know yet therefore resolved anyway, as `backend.<domain>`. The guard let
nginx start at once and nginx pinned the ingress as its backend. Every API
call then failed with 502 and the SPA showed "Bad Gateway" instead of the
login form, until the container was restarted.

The guard now asks for the absolute names, `backend.` and `exporter.`. The
search domain is not applied to those, so the lookup fails until the
services themselves resolve.

Closes #806

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 09:17:51 +02:00
kevinveenbirkenbachandClaude Opus 5.5 e142d07f97 fix(web-app-xwiki): upgrade an add-on that is not on its pinned version
The extension installer returned as soon as an add-on was installed, in
whatever version. A new pin in meta/addons/ therefore reached a fresh wiki
only, and an existing wiki kept the version it was first deployed with.

The installer now skips an add-on only when the installed version equals
the pin. Otherwise it calls install(id, version, namespace), which the
Extension Manager resolves as an upgrade, and it reports an error when the
add-on is not on the pin afterwards.

The check that follows the installer compared presence only. It takes the
pin as well, so a deploy fails when an add-on is installed in another
version.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 09:17:15 +02:00
Prageeth Panicker bf37d5e7b3 fix(web-app-wazuh): address PR review feedback
Tor: use the shared tor block (bond 0.5, enabled when svc-net-tor is in the node's groups, tor pinned on in both variants) so a node serves one network, onion on a Tor node and clearnet otherwise.

SPOT and sizing: WAZUH_INDEXER_URL replaces four hand-built copies of the indexer URL, and the indexer heap is half of mem_limit instead of a hardcoded 1g (unchanged for the current 2g limit).

Fixes: bcrypt_hash rejects secrets over 72 bytes; the Keycloak group-removal helper uses the same resolver as the add helper and checks every response (the realm is no longer URL-encoded, matching the add helper), and a failed cleanup fails an otherwise passing test; the swarm certs generator removes a stale container before each attempt; vm.max_map_count is set before the early handler flush; securityadmin failed_when now needs rc 0 and the success marker.

The regenerated role README also picks up generator drift (mailu to stalwart).
2026-10-06 05:15:19 +00:00
kevinveenbirkenbachandClaude Opus 5.5 e6fbb4b3f4 chore: drop two leftovers of the Wazuh role's first commit
That commit added test_bcrypt_hash.py with an Exception comment and left a
trailing blank line in group_vars/all/11_oidc.yml. Both go.

What the comment said still holds: the test hashes with the raw bcrypt package
because Ansible's password_hash('bcrypt') routes through passlib, whose bcrypt
self-test can misreport a password well under 72 bytes as too long when
passlib and the installed bcrypt disagree on the version.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 05:15:17 +00:00
Prageeth Panicker 87c8199012 feat(web-app-wazuh): add Wazuh SIEM role with OIDC/LDAP RBAC
Add single-node Wazuh stack (manager, indexer, dashboard) as a new
Ansible role, integrated with the platform's Keycloak realm via native
OIDC and secondary LDAP authorization, with three-tier RBAC.

- Pins wazuh/wazuh-{manager,indexer,dashboard}:4.14.6 and
  wazuh-certs-generator:0.0.4 (one-shot cert bootstrap job)
- No external database: wazuh.indexer (OpenSearch) is self-contained
- Auth: native OIDC against Keycloak, working end-to-end after two
  infra fixes (JVM cacerts trust for the indexer's outbound calls to
  Keycloak, front-proxy vhost wiring)
- LDAP wired as a secondary, authentication-independent authorization
  backend (group-based backend_roles enrichment only)
- RBAC: three roles (administrator, security-analyst, readonly-auditor)
  via the platform's standard meta/rbac.yml + rbac_group_path pipeline
- Documents a genuine upstream Wazuh bug: wz-home as the default
  landing route throws an uncaught client exception for any
  non-all_access role; default route moved to OpenSearch Dashboards'
  own /app/home

Addressed two full rounds of review feedback (Kevin, Alejandro):
WEB_PROTOCOL usage, service-account naming, comment cleanup, shared
Keycloak-admin-helper extraction, a real TOCTOU race in RBAC test
setup, a Mermaid auth-flow diagram, meta/services.yml placement for
the certs-generator, CA-trust-path SPOT usage, CSP tightening
(including a genuine worker-src: blob: requirement found via live
testing), and a loud (non-silent) dev-bucket fallback warning.

Rebased onto main after a 3-month gap; brought the role in line with
~30 platform-convention changes introduced in that window (swarm
compose-template rules, Tor/onion contract, Playwright timeout/goto
wrappers, file layout, task numbering, comment policy, and more).

Live-deploy verification (9 iterations) found and fixed 6 further
real bugs invisible to lint/unit tests alone:
- credentials.* / meta/schema.yml -> secrets.credentials.* /
  meta/secrets.yml (another platform-drift casualty)
- notify: "compose up" -> "compose-up" (day-one handler-name typo)
- local.subnet collision with web-app-mailu's /24
- missing primary: false alongside tor.exclusive: false, which let
  the onion domain shadow the clearnet vhost entirely
- a second front-proxy include for the onion domain, later reverted
  (see below)
- shared sys-stk-front-proxy/sys-front-tls TLS-lookup keying bug
  (app-id-keyed vs. domain-keyed), which silently applied TLS to
  Wazuh's onion vhost

The last two are related: enabling genuine dual-stack (clearnet +
onion) reachability for tor.exclusive: false surfaced a real bug in
two shared platform roles. That fix was verified safe against all 103
apps currently in the repo, but is deliberately NOT included here -
shared-role changes deserve their own PR and review, not to ride in
on a single app's feature branch. Wazuh's second front-proxy include
was reverted accordingly; the resulting onion-reachability gap is
documented in the README as a known, platform-scoped limitation.

make test: 5508 passed, 3 failed (all three trace to untracked local
scratch files - Dockerfile.bak, scripts/lab-*.sh - not present in any
CI checkout).
2026-10-06 05:15:00 +00:00
kevinveenbirkenbach be800e8741 Merge pull request #803 from infinito-nexus/fix/sys-ctl-hlth-msmtp/empty-reply-is-transient/790
fix(mail): retry a connection the mail server drops before its greeting
2026-10-06 02:28:06 +02:00
kevinveenbirkenbach 88418016ba Merge pull request #788 from kevinveenbirkenbach/fix/ci-fork-image-wait/784
fix(ci): keep the fork image wait alive and let it find its privileged run
2026-10-06 01:36:54 +02:00
kevinveenbirkenbachandClaude Opus 5.5 89b95d751c fix(update): stop bumping pins of eol roles
The update bot opened a version PR for web-app-jira (#821) although the role is end of life: none of the updaters looked at the lifecycle. utils.update.base.is_maintained now answers that question once, and the collectors for image versions, declared version sources, pip pins, repository refs and the addon check skip a role whose lifecycle is eol.

The lint that requires every semver pin to be watched shares that scope, otherwise it would report the pins of the eol roles as unwatched. Every other stage, deprecated included, stays in the update scope.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 01:09:10 +02:00
kevinveenbirkenbachandClaude Opus 5.5 517d40484e test(mail): select autoescape for the rendered alarm script
The unit test of the alarm script built its Jinja environment with
autoescape switched off, which code scanning reports as a possible XSS
sink and fails the PR on. The template is a shell script, so
select_autoescape() leaves it unescaped exactly as before, and the finding
no longer applies.

Refs #790

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 17:19:56 +02:00
kevinveenbirkenbachandClaude Opus 5.5 4c955d080f feat(update): fail a declared update source whose pin is not a semver
The version-source resolver orders upstream versions by semver. A pin that
is not one, such as `latest` or a branch name, has no depth, so no candidate
ever matches it and the declaration never moves anything. Nothing reported
that: the watched-pins lint only looks at values that already are a semver.

test_version_keys now fails on an `update:` block whose pinned key does not
hold a semver. A monitored addon the resolver does not read is held to the
same rule as soon as it pins a `version`, because the catalog check skips a
non-semver pin just as silently. An addon without a `version` keeps tracking
the app default and is left alone.

The Stalwart README no longer asks for `webui_version` to be bumped by hand
next to the image tag. Its `update:` block names the upstream tags, the
server itself follows `releases/latest` of the bundle, and the resolver
plans v1.0.5 to v1.0.11 for it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 12:48:22 +02:00
evangelostsak 4e44d7fb84 Merge remote-tracking branch 'upstream/main' into feature/svc-net-wireguard
No conflicts. Two files were touched by both sides and both kept each
side: the Makefile, where upstream retires the mcp-audit target and this
branch pins the throwaway cluster's vpn axis, and requirement 035, where
the role table keeps the single svc-net-wireguard row.

Incoming carries the fixes for two applications this branch's last run
failed on -- penpot probing its frontend without curl, and mobilizon's
postgres 18 migrations -- plus dependency bumps and a lint repair.

Gates on the merged tree: unit 6202, lint 523, integration 330,
ansible-lint clean.
2026-10-05 12:17:12 +02:00
kevinveenbirkenbachandClaude Opus 5.5 7c7b895ae9 fix(ci): match the privileged run of a fork PR by its number as well
The wait job picks the newest privileged run on the head commit of the
PR. Two fork PRs that share a head repository, branch and commit but
target different bases each start such a run, and the commit alone cannot
tell them apart. A failed run of the other PR would then end the wait of
this one while its images are still being built.

The run name of the workflow carries the PR number, so the match now
requires it next to the head commit. A run without that name matches
nothing, and the wait does what it does whenever no run is found: it
keeps polling for the images until they exist or the wait times out.

Raised by the CodeRabbit review of this PR.

Refs #784

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 12:07:14 +02:00
kevinveenbirkenbach 1d03c001ea Merge remote-tracking branch 'origin/main' into fix/sys-ctl-hlth-msmtp/empty-reply-is-transient/790 2026-10-05 11:45:00 +02:00
kevinveenbirkenbachandClaude Opus 5.5 b37cf2e9fe fix(mail): retry a connection the mail server drops before its greeting
Stalwart closes a connection that exceeds its per-IP connection budget
without a greeting. msmtp reports that as "the server sent an empty reply"
with exit 76, and the mail health check treated exit 76 as permanent. One
such drop, in a second in which Alertmanager flushed its notifications,
failed a deploy although the same path accepted a mail one second later.

The health check now retries exit 76 when msmtp names the empty reply,
within the ten attempts it already gives to exit 69, 75 and 124. Every
other protocol error still ends the check at once.

The alarm mail was sent once, so the alarm about a failed unit was lost to
the same drop. It now makes three attempts ten seconds apart for the
transient cases of the health check and still reports the last error.

The limit on the server is left alone. Exempting the node's own senders by
source address would also exempt Tor clients and, in swarm, every external
client. That question is tracked in #793.

Closes #790

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 11:44:58 +02:00
kevinveenbirkenbach 8e9fa797fe Merge remote-tracking branch 'origin/main' into fix/ci-fork-image-wait/784 2026-10-05 03:13:43 +02:00
kevinveenbirkenbach a306b225c3 Merge remote-tracking branch 'origin/main' into update/onlyoffice-nextcloud-connector 2026-10-05 03:13:35 +02:00
kevinveenbirkenbachandClaude Opus 5.5 69fceafb5d fix(ci): limit the fingerprint shortcut to branches of this repository
The duplicate check accepted any open PR whose head branch started with
the dedupe prefix and ended in the fingerprint of the fresh proposal. The
listing also returns PRs from forks, and the fingerprint can be derived
from public data, so a fork PR with such a branch name would have made the
bot skip its own update PR without carrying the change.

The shortcut now only applies to PRs whose head branch lives in this
repository, which only accounts with write access can create. A fork PR
is still compared by the content of its head, as before.

Raised by the CodeRabbit review of PR #781.

Refs #780

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 03:06:57 +02:00
kevinveenbirkenbachandClaude Opus 5.5 739c78bad1 feat(update): bump addon pins through their declared source
The version-source updater only read meta/services.yml. An addon pinned in
meta/addons/<id>.yml stayed where it was: the ONLYOFFICE connector for
Nextcloud sat on v10.1.2, which does not install on Nextcloud 35, until
someone bumped it by hand.

The updater now reads addon files too. An addon declares its source in the
`update:` block it already has, beside monitored, catalog and upstream_id.
A monitored addon of the github-releases catalog needs nothing more: its
upstream_id names the repository whose tags are its versions. The
update-version-sources job opens the PRs, one per role.

Every pinned addon is wired:

- nextcloud onlyoffice carries its own version pin now. Its archive URL
  stays a literal that carries the pin, because
  test_pinned_addon_archives_available reads it as written
- wordpress mcp-adapter, moodle auth_oidc and jellyfin sso-authentication
  follow the tags of their repository, moodle within its v4.5 series
- the three xwiki extensions read their Maven metadata
- pretix-oidc reads the PyPI simple index. The JSON API answers 404 behind
  the package cache the test stack routes pypi.org through
- joomla's plugin is built here and jellyfin's ldap-authentication is bound
  to the md5 of its archive; both are opted out with that reason

A bump rewrites the pin and the config.archive line together, and an
archive that does not carry the pin fails test_version_keys, so the two
cannot drift apart again. test_version_pins_are_watched covers addon pins
as well: a new one without a source or a reasoned opt-out fails the lint.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 02:50:48 +02:00
kevinveenbirkenbachandClaude Opus 5 81ab6e4ffc feat(update): let a version pin declare its own upstream source
The Docker updater watches image plus version, the repository updater
repository plus ref. A dozen pins fell outside both - an app_version, a
release, a tag in a registry neither speaks - so nothing ever told anyone
they had gone stale.

Such a pin now names its upstream next to itself in an `update:` block:
git_tags, registry_tags, npm, http_regex or a role-local script, with
`match` selecting a tag flavour and `strip` removing a prefix the pin does
not carry. utils/update/source.py resolves them beside the two existing
updaters, `python -m cli.contributing.update.source` bumps them, the
update-version-sources job opens one PR per role, and
tests/external/update/source warns while a bump is pending.

Two lints hold the rule: every semver pin is watched by one of the three
updaters or opted out with a reason, and a semver only ever sits behind
`version`, `<what>_version` or `ref` - which renames moodle's release to
app_version and simpleicons' local_version to local_tag, the latter being
the tag of a locally built image rather than an upstream version. A
declaration naming a key the entity does not carry, or a type the resolver
does not know, fails the lint instead of resolving nothing in silence.

Picked from feature/store-i18n (94d607fd0a) onto current main. Two things
differ from the original: svc-ai-lmstudio no longer carries an app_version
pin, so its declaration is dropped, and web-app-stalwart's webui_version,
added since, declares the git tags of the webui repository.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 02:50:48 +02:00
kevinveenbirkenbach f6228580be Merge remote-tracking branch 'origin/main' into fix/ci-fork-image-wait/784 2026-10-04 22:18:20 +02:00
kevinveenbirkenbachandClaude Opus 5.5 3ab0153856 fix(ci): find the privileged run of a fork PR by its head commit
The wait for fork CI images never found the privileged workflow run it
watches. It selected a run only when the run's pull_requests array held
the PR number, and for a run whose head repository is a fork the API
returns that array empty. The job only runs for fork PRs, so the lookup
matched in no case it exists for.

The early exit for a failed privileged run was therefore unreachable.
When that run failed, the job kept polling until its timeout of 1980
attempts and held a runner for about five and a half hours.

The lookup now asks the API for the runs of the PR head commit and
selects by head_sha. That also replaces a paginated listing of every run
of the workflow with one small request per attempt. Without a known head
commit the match by PR number stays.

Refs #784

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 20:55:54 +02:00
kevinveenbirkenbachandClaude Opus 5.5 53f71d85c8 fix(ci): keep waiting for fork images when a run lookup fails
The job that waits for the CI images of a fork PR ended on the first
failed GitHub API call. The lookup of the privileged workflow run is
assigned with run_json="$(find_matching_run)" under set -e, so a failing
gh ended the script, although the loop is built for 1980 attempts. PR #782
went red that way: GitHub answered one request with HTTP 504 in attempt
31, while the privileged run was still queued.

A failed lookup now counts as "no information in this attempt" and the
loop goes on waiting for the images, which are its success criterion.
Thirty failed lookups in a row end the job with a message that names the
cause, so a broken token does not hold a runner until the timeout.

The test runs the script against a gh that fails every lookup. The
previous script fails both cases at the first lookup.

Closes #784

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 20:55:54 +02:00
kevinveenbirkenbachandClaude Opus 5.5 9bdf33b89d fix(ci): treat an edited update branch as a duplicate of its own bump
The update bot recognised an open PR as a duplicate by hashing the blobs
of the files its commit changed, read at the PR head. A follow-up commit
that edits one of those files changes the hash, so the next run opened a
second PR with the identical bump: PR #778 next to PR #775 for
web-svc-collabora, after a fix had edited the bumped services.yml.

The branch name of a bot PR ends in the first seven characters of the
fingerprint of the change the bot proposed. An open PR whose branch ends
in the fingerprint of the fresh proposal now counts as a duplicate before
its head is compared. The head comparison stays as the second criterion.

The test runs pr.sh in a throwaway repository against a stubbed gh. It
fails on the previous script with exit 1, because that one goes on to
push.

Closes #780

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 14:36:57 +02:00
evangelostsak bb484ecb8f Merge remote-tracking branch 'upstream/main' into feature/svc-net-wireguard
One conflict, in the generated role table of README.md. Both sides changed
the same rows: upstream swapped WordPress's Mailu dependency for Stalwart,
while this branch replaced the three svc-net-wireguard-{core,plain,
firewalled} rows with the single svc-net-wireguard it deploys.

Resolved by regenerating rather than by hand, since make readme-index owns
that table and gates it with --check. The regenerated table carries both
sides and the check reports it up to date.

README.md was the only file both sides touched, and nothing in the tree
still names the three replaced roles.
2026-10-04 02:18:03 +02:00
evangelostsak 25c7fdd1a5 test(mesh): pin the switched host to a tty-free transport
The carriage returns a pty adds are invisible until a role parses its own
output, so the absence of the tty is asserted where the transport is
written rather than left to the next role that reads stdout.
2026-10-03 23:20:26 +02:00
kevinveenbirkenbachandClaude Opus 5.5 52fe3c9c2c style: apply make autoformat to the merged Stalwart and Tor files
Formatter output only: the parsed YAML and the Python AST of every touched file equal their previous versions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-03 19:57:30 +02:00
evangelostsak e456b1eee6 Merge remote-tracking branch 'origin/main' into feature/svc-net-wireguard
One conflict, in the MCP exclusion list of requirement 035. Both sides
edited the same line: main added svc-net-tor-smtp, this branch replaced the
three svc-net-wireguard-{core,plain,firewalled} entries with the single
svc-net-wireguard it deploys. Resolved to the union of both — 24 entries —
which is what the merged tree actually holds.

Everything else merged clean and was checked for overlap rather than
assumed: no reference anywhere still names the three replaced roles;
matrix.py kept main's PRIMARY_APPS alongside the reordered round, and the
bootstrap pass does not need it, since only one template reads it and
group_vars defaults it empty; and main's ansible.cfg addition sits in
[defaults], so the ssh_args the mesh tool derives its own connection
settings from are untouched.
2026-10-03 02:29:45 +02:00
kevinveenbirkenbach d90f1e8ed6 Merge pull request #756 from kevinveenbirkenbach/fix/svc-db-postgres/pgdg-bullseye-archived
fix(svc-db-postgres): build on postgres 18 since PGDG archived bullseye
2026-10-02 17:41:28 +02:00
evangelostsak 152588b511 perf(mesh): retune the multiplexer for the hosts the mesh switches
The pass that runs over the mesh reaches every host by SSH where the
pass before it used the container connection, and it pays that difference
once per task. Measured in a container: an exec through a warm multiplexer
costs 7ms, a fresh connection 85ms, and 165ms once sudo asks for the
password every task demands. The pass runs thousands of tasks.

Two settings threw the multiplexer away. A persist window of 20s closes the
master whenever a host waits longer than that for its next task, which in a
linear play is most of them; and the named ControlPath points at a directory
nothing creates, so a master that cannot open its socket reconnects per task
without reporting it.

Neither is retuned for the repository. The tool that moves a host onto the
mesh now writes that host's own ssh_args, derived from the repository's
value so every other option is kept and only the window and the socket
differ. A host the mesh never touches keeps exactly what it had.

Confirmed against the built command: a switched host gets ControlPersist=600s
and a ControlPath under the directory Ansible creates, while a host beside it
keeps the repository's own.
2026-10-02 16:35:29 +02:00
evangelostsak ff75343aba fix(ci): stop a held mesh axis deleting priority rows
The pin narrowed the cross product after the axis was resolved, so a row
pinned against an enforced or disabled run lost every combination and left
the matrix entirely -- while a regular row with the same pin was simply moved
onto the held side. A priority row now takes the same precedence a regular
row already took: the run's axis first, the pin second.
2026-10-02 00:54:20 +02:00
evangelostsak dc14e8a3a8 fix(mesh): keep every member on the address it already holds
The offset came from the spoke's position in a sorted list, so a host that
sorts earlier renumbered every member behind it. `wg syncconf` carries keys
and peers onto a live interface and never its address, so a renumbered member
keeps answering on the old address while the peers and the inventory have
moved to the new one -- and the address it gave up can be handed to a new
member while the old owner still holds it.

The planner now reuses the addresses already on disk and allocates only an
unseen member, from the lowest free offset. A value outside the subnet, a
duplicate, and the hub's own offset are refused, so a mirrored host_vars
cannot hand a spoke the hub's address. Rotation keeps addresses: it replaces
identities, not addresses.

The routing assertions re-planned the mesh and compared the result with
itself, so a writer that stored the mesh subnet in place of the routed range
would have passed; they now read the entry off disk, which is what the role
reads.
2026-10-02 00:54:20 +02:00
kevinveenbirkenbach 30417d2515 fix(svc-db-postgres): build on the official multi-arch postgres 18 image
postgis/postgis publishes amd64 only, so every postgres consumer failed
on arm64. The official postgres image serves both architectures and
already ships the contrib extensions bloom, pg_trgm and unaccent.

PostGIS moves into the postgres_libraries registry and is installed from
PGDG (postgresql-18-postgis-3) only for a consumer that declares it as
an extension, which is mobilizon alone. apt update reads every source
list again, because the PostGIS package depends on GDAL from Debian
main, which a PGDG-only update left without an install candidate.

Postgres 18 keeps its cluster in /var/lib/postgresql/18/docker and its
entrypoint treats a volume at the former /var/lib/postgresql/data as an
old database, so the central and the dedicated data volume both mount at
/var/lib/postgresql. Existing 17 clusters are not migrated: their data
directory trips the entrypoint's old-data check and the container
refuses to start.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit f78ebe46a4)
2026-10-01 22:19:09 +02:00
Alejandro Roman 73b2593e47 fix(swarm-stalwart): mapache once and make fixes regarding comments 2026-09-29 18:31:02 +04:00
evangelostsak 94f2401fab test(mesh): pin one key per host across a rotation
Asserts the hub's two mesh entries carry the same key and that its spoke holds
it. A first attempt matched only the first entry and passed against the bug.
2026-09-29 08:59:23 +02:00
Alejandro Roman a390fea9e9 fix(swarm-stalwart): fix CI issues, cmments mapache 2026-09-28 18:23:16 +04:00
evangelostsak 7634c1ed32 test(mesh): pin the controller's key across a rerun
Without the fix its public half changes on every write, which is what made
the hub re-render and re-key a tunnel nothing had asked to rotate.
2026-09-28 15:44:30 +02:00
evangelostsak ba4b8cd6f5 test(credentials): cover the kept subtree
The destination keeps its own block and still takes the rotated values around
it; a destination without that block takes the source's.
2026-09-28 11:46:01 +02:00
evangelostsak f91f9bccdf test(swarm-test): cover the transport switch
A node is pointed at its mesh address; the controller keeps the connection it
runs the play with. The step had no test, and this entry has now bitten twice.
2026-09-28 08:52:50 +02:00
evangelostsak 02c56f6434 test(swarm-test): pin the become password to the account
Asserts it equals users.administrator.password and that it survives into the
deploy-facing twin, which strips the users block it comes from.
2026-09-27 23:56:22 +02:00
evangelostsak b16a2fb511 revert(provision): restore the user password generator tests
Follows the generator back to its previous behaviour.
2026-09-27 23:55:21 +02:00
evangelostsak fbf17949d7 test(mesh): cover the endpoint argument
A stated address replaces the name peers dial, an unstated host keeps its
name, and a malformed pair aborts rather than being ignored.
2026-09-27 02:05:00 +02:00
evangelostsak 7a996cd0f5 test(provision): cover the declared password reference
Three cases: the value is pinned from host_vars, its vault node survives, and
a reference to something absent still falls back to a fresh password.
2026-09-27 01:30:59 +02:00
evangelostsak f4c336b5a1 test(provision): pin the declared-password behaviour
Covers both halves: the policy reaches the caller with no value, and a stated
password is carried through and draws no random pin.
2026-09-26 23:30:07 +02:00
evangelostsak a868045d69 test(mesh): cover the controller in the foreign-entry prune
Mirrors the hub's host_vars onto the controller the way the credential reset
does, then asserts the rerun leaves only the mesh the controller belongs to.
Without the fix the file keeps the hub's data entry, is_hub included.
2026-09-26 19:47:10 +02:00
Alejandro Roman b160d07008 fix(lint): annotate ClassVar in test_email and reorder pixelfed playwright hooks 2026-09-26 19:27:31 +04:00
evangelostsak 78409cb642 test(packages): pin the per-manager install arguments
Covers the three cases that matter: apt drops recommends, another manager keeps
its defaults, and a concrete module is left untouched.
2026-09-26 16:19:57 +02:00
Alejandro Roman fadf36e0a6 fix(swarm-stalwart): fix CI issues 2026-09-26 18:05:56 +04:00