infinito-nexus (16.2.2-1) unstable; urgency=medium

  Fixed:

  - Moodle: a fresh install with MCP enabled mints its token on Moodle 5.3
  - Moodle: debug deployments keep the component cache; swarm pages load fast
  - Playwright: a stuck image pull times out and retries instead of hanging

  Version jumps (net since 16.2.1):

  - *svc-ai-ollama*: 0.40.6 to 0.40.7
  - *svc-runner*: BuildKit v0.34.0 to v0.34.1
  - *web-app-erpnext*: v16.51.1 to v16.51.2
  - *web-app-opentalk*: RabbitMQ 4.3.6 to 4.4.0
  - *web-app-wazuh*: 4.14.6 to 4.14.9
  - *web-app-xwiki*: AI LLM 0.10 to 0.11, OIDC authenticator 2.29.0 to 2.29.1

  Upgrade notes:

  - Device backup: the device needs a filesystem like ext4 or xfs, not FAT

  Contributors:

  - [Kevin Veen-Birkenbach](https://veen.world): Moodle, Playwright pull

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Wed, 21 Oct 2026 13:00:11 +0200

infinito-nexus (16.2.1-1) unstable; urgency=medium

  Fixed:

  - Cache: the package cache frontend starts again; 16.2.0 broke its nginx
  - Snipe-IT: SSO login works again on v8.8.1 behind oauth2-proxy
  - Snipe-IT: the SSO header is only trusted with the proxy's shared secret
  - Wazuh: seeded bcrypt salts no longer leak a fast password digest

  Version jumps (net since 16.2.0):

  - *web-app-socialhome*: 2026.10.17 to 2026.10.20

  Upgrade notes:

  - Snipe-IT: re-run *infinito administration inventory provision* once

  Contributors:

  - [Kevin Veen-Birkenbach](https://veen.world): cache, Snipe-IT SSO, bcrypt

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Tue, 20 Oct 2026 22:35:05 +0200

infinito-nexus (16.2.0-1) unstable; urgency=medium

  Added:

  - Compose: *make compose-network-recreate* rebuilds the stack network safely
  - CI: the updater keeps the GitHub-hosted runner labels current

  Fixed:

  - Backup: hard links, ACLs and xattrs survive every backup and recover hop
  - NFS client: clients on Linux 6.19+ no longer fail writes on ganesha < 9.11
  - AppArmor: the teardown keeps docker-default, so containers keep starting
  - Wazuh: hosts can be put into WireGuard meshes like every other role
  - CI: GitHub-hosted runners are pinned to ubuntu-24.04

  Version jumps (net since 16.1.0):

  - *svc-db-rabbitmq*: 4.3-management-alpine to 4.4-management-alpine
  - *web-app-n8n*: 2.44.4 to 2.45.0
  - *web-app-pixelfed*: 20261019 to 20261020
  - *web-app-snipe-it*: v8.8.0-alpine to v8.8.1-alpine
  - *web-app-taiga*: RabbitMQ 4.3-management-alpine to 4.4-management-alpine
  - *eslint* (dev): ^10.12.0 to ^10.13.0

  Upgrade notes:

  - Backup: a target without xattr support now fails instead of losing them
  - Dev: worktree hosts apply the IP pool with *make compose-network-recreate*

  Contributors:

  - [Kevin Veen-Birkenbach](https://veen.world): backups, NFS, AppArmor, CI

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Tue, 20 Oct 2026 21:11:50 +0200

infinito-nexus (16.1.0-1) unstable; urgency=medium

  Added:

  - Wazuh: new *web-app-wazuh* role runs Wazuh 4.14.6 as a single-node SIEM
  - Wazuh: Keycloak OIDC login, LDAP group roles and three RBAC tiers
  - Wazuh: the SIEM console is served on clearnet only, never as an onion

  Fixed:

  - NFS client: swarm backups stay on local disk with their extended attributes
  - NFS client: the backup user keeps access; a changed mount line remounts
  - Docker: RedHat installs iptables-nft, so Fedora no longer needs ip_tables
  - Compose: worktree stacks no longer take the primary stack's static IPs
  - Backup: the baudolo version comes from *pyproject.toml* alone
  - CI: runners with sudo-rs keep the environment the swarm matrix needs

  Version jumps (net since 16.0.1):

  - *pkgmgr*: v2.0.2 to v2.0.4
  - *web-app-gitea*: 28.1.0 to 28.2.0, MCP server 1.9.0 to 1.10.0
  - *web-app-n8n*: 2.44.3 to 2.44.4
  - *web-app-pixelfed*: 20261017 to 20261019
  - *web-app-semaphore*: v2.19.16 to v2.19.17
  - *web-app-stalwart*: v0.16.26 to v0.16.27
  - *web-app-stalwart*: webmail 1.7.4-apache to 1.7.5-apache
  - *ruff* (dev): 0.17.0 to 0.17.1
  - *@playwright/test* (dev): ^1.64.0 to ^1.64.1

  Upgrade notes:

  - Dev: run *make dotenv* once; compose now requires *INFINITO_IP_RANGE*
  - Dev: a compose network created earlier keeps no IP pool until recreated
  - NFS client: [older generations](roles/svc-storage-nfs-client/README.md) stay on the share; delete them by hand

  Contributors:

  - [Prageeth Panicker](https://github.com/pragepani): the Wazuh role
  - [Evangelos Tsakoudis](https://evangelostsak.com): the WireGuard CLI test
  - [Kevin Veen-Birkenbach](https://veen.world): NFS backups, compose IPs

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Mon, 19 Oct 2026 15:29:01 +0200

infinito-nexus (16.0.1-1) unstable; urgency=medium

  Fixed:

  - Changelog: every entry uses dash bullets, so the file lints clean again
  - Lint: Markdown lists must use dash bullets; MD004 now pins that style

  Upgrade notes:

  - 16.0.0 was never published; its two upgrade notes apply to this release
  - Moodle: [web-installed plugins](roles/web-app-moodle/README.md#updates) are not carried to 5.3
  - Zammad: Elasticsearch 9 rebuilds the search index on the first deploy

  Contributors:

  - [Kevin Veen-Birkenbach](https://veen.world): the Markdown list style fix

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Sun, 18 Oct 2026 23:04:22 +0200

infinito-nexus (16.0.0-1) unstable; urgency=medium

  Added:

  - WireGuard: new *svc-net-wireguard* role (beta) builds per-host mesh tunnels
  - Mesh: every role can put its hosts into WireGuard meshes via inventory
  - Mesh: full meshes without a hub, hub and spoke, controller as a spoke
  - Mesh CLI: writes the mesh *host_vars* of an inventory and rotates its keys
  - Swarm: nodes advertise and mount NFS over their primary mesh address
  - Inventory: credentials can be generated across hosts
  - Deploy: CLI end-to-end tests run after a deploy; roles declare them
  - Update: a version pin can declare its own upstream source, addons too
  - Plugins: memory parameters follow the container limit via one lookup
  - Stalwart: bans and failed logins appear in the container output
  - Playbooks: playbooks under *playbooks/* load the root *group_vars*

  Changed:

  - Moodle: runs 5.3; existing 4.5 sites are upgraded on deploy
  - OpenTalk: runs product version 26.2.1
  - Bluesky: PDS 0.4.66, so AppView proxy calls carry *lxm*
  - Nextcloud: held at 34 until *integration_openproject* supports 35
  - Nextcloud: BPM Files, News and Recognize are optional addons
  - Memory: PHP, Postgres and Snipe-IT limits follow the container size
  - Hugo: builds with the official Hugo image as the image's own user
  - BigBlueButton: the release set follows one version source

  Version jumps (net since 15.0.0):

  - *svc-ai-ollama*: 0.35.0 to 0.40.6
  - *svc-db-elasticsearch*: 9.5.3 to 9.5.5
  - *svc-db-qdrant*: v1.19.1 to v1.19.2
  - *svc-net-tor-smtp*: 3.14-slim to 3.15-slim
  - *svc-runner*: BuildKit v0.33.1 to v0.34.0
  - *sys-ctl-hlth-csp*: csp-checker 3.0.4 to 3.1.0
  - *test-e2e-playwright*: Playwright v1.63.0-noble to v1.64.1-noble
  - *web-app-baserow*: 2.3.4 to 2.4.1
  - *web-app-bigbluebutton*: dockerize v0.15.1 to v0.15.2
  - *web-app-bluesky*: PDS 0.4.0 to 0.4.66, AppView 1.134.0 to 1.135.0
  - *web-app-checkmk*: 2.4.0p32 to 2.4.0p37
  - *web-app-chess*: v1.0.1 to v1.2.2
  - *web-app-erpnext*: v16.37.0 to v16.51.1
  - *web-app-espocrm*: 10.0.9 to 10.0.10
  - *web-app-fediwall*: 1.31.6-alpine to 1.31.7-alpine
  - *web-app-flowise*: better-sqlite3 12.11.1 to 13.0.3
  - *web-app-funkwhale*: 2.0.11 to 2.0.12
  - *web-app-gitea*: 1.27.3 to 28.1.0, MCP server 1.6.0 to 1.9.0
  - *web-app-gitlab*: v19.4.1 to v19.5.0
  - *web-app-hermes*: v2026.9.7 to v2026.9.24
  - *web-app-hugo*: builder exts-0.148.0 to v0.168.0
  - *web-app-hugo*: hugoDocs source v0.148.0 to v0.165.0
  - *web-app-jenkins*: 2.568.3-lts to 2.580.1-lts
  - *web-app-jira*: 11.3.11 to 11.3.12
  - *web-app-keycloak*: 26.8.0 to 26.8.1
  - *web-app-lam*: 9.6 to 9.7
  - *web-app-littlejs*: 1.31.6-alpine to 1.31.7-alpine
  - *web-app-matrix*: Synapse v1.162.0 to v1.163.0
  - *web-app-matrix*: Element v1.12.30 to v1.12.32
  - *web-app-mattermost*: 11.11.0 to 12.0.0
  - *web-app-moodle*: 4.5.11 to 5.3.0, OIDC plugin v4.5.8 to v5.2.1
  - *web-app-n8n*: 1.100.1 to 2.44.3
  - *web-app-nextcloud*: context_agent 2.8.0 to 2.9.1
  - *web-app-nextcloud*: proxy 1.31.5-alpine to 1.31.7-alpine
  - *web-app-openbao*: 2.6.2 to 2.7.1
  - *web-app-opentalk*: frontend v2.9.0 to v2.9.2 (product 26.2.1)
  - *web-app-opentalk*: controller v0.34.1 to v0.34.5
  - *web-app-opentalk*: recorder v0.16.2 to v0.17.5
  - *web-app-opentalk*: LiveKit v1.13.7 to v1.13.10
  - *web-app-penpot*: 2.17.2 to 2.18.3
  - *web-app-pihole*: 2026.09.0 to 2026.10.0
  - *web-app-pixelfed*: 20260917 to 20261017
  - *web-app-pretix*: 2026.7.0 to 2026.8.0
  - *web-app-prometheus*: v3.14.0 to v3.15.0
  - *web-app-prometheus*: cAdvisor v0.52.1 to v0.55.1
  - *web-app-prometheus*: blackbox exporter v0.28.0 to v0.29.0
  - *web-app-semaphore*: v2.19.14 to v2.19.16
  - *web-app-socialhome*: 2026.10.2 to 2026.10.17
  - *web-app-stalwart*: v0.16.24 to v0.16.26, web UI v1.0.5 to v1.0.11
  - *web-app-suitecrm*: 8.9.1 to 8.10.2
  - *web-app-taiga*: gateway 1.31.6-alpine to 1.31.7-alpine
  - *web-app-wordpress*: 7.1.0 to 7.1.3, MCP adapter v0.5.0 to v0.7.1
  - *web-app-xwiki*: OIDC authenticator 2.19.6 to 2.29.0
  - *web-app-xwiki*: LDAP authenticator 9.15.7 to 9.16.6
  - *web-app-xwiki*: AI LLM 0.7 to 0.10
  - *web-app-zammad*: Elasticsearch 8.13.4 to 9.5.5
  - *web-svc-collabora*: 26.04.2.1.1 to 26.04.5.1.1
  - *web-svc-coturn*: gateway 1.31.6-alpine to 1.31.7-alpine
  - *web-svc-logout*: v1.4.1 to v1.4.2
  - *web-svc-onlyoffice*: 9.4.0 to 10.0.0
  - *backup-docker-to-local* (*baudolo*): 7.0.2 to 7.1.1
  - *community.general*: 13.4.0 to 13.5.0
  - *community.docker*: 5.3.0 to 5.4.0
  - *hetzner.hcloud*: 7.1.0 to 7.2.0
  - *ruff* (dev): 0.16.9 to 0.17.0
  - *@playwright/test* (dev): ^1.63.0 to ^1.64.0
  - *eslint* (dev): ^10.9.1 to ^10.12.0
  - *eslint-plugin-playwright* (dev): ^2.10.5 to ^2.12.1
  - *globals* (dev): ^17.12.0 to ^17.13.0
  - *stylelint* (dev): ^17.15.0 to ^17.16.0

  Fixed:

  - Zammad: init waits for search in swarm; disk watermarks fit the disk
  - Zammad: the deploy stops when Elasticsearch allocates no shard
  - Nextcloud: OnlyOffice server URL is written last and checked in time
  - Nextcloud: Talk server lists are written as typed arrays
  - Keycloak: concurrent *kcadm* jobs no longer lose the config lock
  - SuiteCRM: sessions live in Redis so replicas share them
  - SuiteCRM: external auth provisioning can create its user
  - n8n: SSO hook, MCP provisioning and API paging work under n8n 2
  - Stalwart: address bans expire; bots use app passwords under SSO
  - OpenProject: the migration probe no longer creates tables
  - Prometheus: probes are judged per target and reach apps on their vhosts
  - Collabora: the WOPI host follows Nextcloud's domain and TLS state
  - Odoo: the OAuth addon works on Odoo 20
  - Mobilizon: four migrations are patched for PostgreSQL 18
  - OpenBao: the LDAP auth method ships as an external plugin
  - Penpot: the frontend waits for its backend; its probe needs no curl
  - Pixelfed: the OIDC token exchange uses Tor on an onion issuer
  - Matrix: Synapse metrics are published in the ansible flavor
  - Matomo: the CSP allows the inline handler of the core top bar
  - Moodle: the code tree is replaced only after a complete copy
  - XWiki: an add-on off its pinned version is upgraded
  - Bridgy Fed: builds on Python 3.14 until cbor2 supports 3.15
  - Tor: the onion apex no longer redirects to a clearnet homepage
  - Health: the CSP crawler follows redirects to this node
  - Health: the journal check reads long messages, skips container logs
  - Services: a stale unit result no longer fails a deploy
  - Cache: the applications carrier stays valid across nested app loads
  - Docker: the installed apt key is kept when its refresh is refused
  - Mail: a connection dropped before the greeting is retried
  - Admin: */usr/local/bin* stays on the administrator's sudo path
  - User: the login banner stays off non-interactive stdout
  - CI: a change under *tests/* no longer deploys every role
  - CI: workspace and runner images are pulled through the GHCR mirror

  Upgrade notes:

  - Moodle: [web-installed plugins](roles/web-app-moodle/README.md#updates) are not carried to 5.3
  - Zammad: Elasticsearch 9 rebuilds the search index on the first deploy

  Contributors:

  - [Evangelos Tsakoudis](https://evangelostsak.com): WireGuard role and meshes
  - [Kevin Veen-Birkenbach](https://veen.world): app fixes, version sources, CI
  - [Prageeth Panicker](https://github.com/pragepani): the journal check fix

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Sun, 18 Oct 2026 22:08:32 +0200

infinito-nexus (15.0.0-1) unstable; urgency=medium

  **Breaking Changes**

  * **Stalwart replaces Mailu as the default mail provider.** *MAIL_PROVIDER* now defaults to
    *web-app-stalwart*, and every application switches its mail settings on only while the
    configured provider is deployed. An inventory that keeps Mailu must set
    *MAIL_PROVIDER: web-app-mailu*, otherwise its applications stop sending mail. Mailu is now
    *deprecated*: it keeps working until it is removed, but new deployments must not adopt it,
    and CI no longer deploys or tests it, the mailbox import included. To move over, deploy
    Stalwart next to Mailu and switch on *services.stalwart.migration.import_mailu*: in compose
    mode the role imports every inventory account's mail from Mailu over IMAP, keeping
    folders, flags and dates, and re-runs skip what is already there. See
    [web-app-stalwart](roles/web-app-stalwart/README.md).

  * **PostgreSQL 18 on the official image.** PGDG archived the Debian 11 repository that the
    old *postgis/postgis:17-3.5* base installed from, so every build of the shared database
    image failed. *svc-db-postgres* and the per-application database now run the official
    multi-arch *postgres:18* image, and the data volume mounts at */var/lib/postgresql*.
    Existing PostgreSQL 17 clusters are not migrated. Dump every database before you deploy
    this release and restore it into the new cluster afterwards. PostGIS is installed only for
    a role that declares it.

  * **Major versions arrive with the image updates.** MariaDB moves from 12.3 to 13.0 and
    upgrades its system tables on start. Odoo moves from 19.0 to 20.0 and does not migrate an
    existing database across major versions on its own, so back up Odoo before you deploy.
    XWiki moves from 17.10 to 18.8, and the OpenLDAP base from Debian 12 to 13.

  **For Users**

  * **Stalwart mail server.** The new *web-app-stalwart* role runs SMTP, submission, IMAP,
    POP3, JMAP, ManageSieve, CalDAV, CardDAV and WebDAV in one server, with spam filtering,
    DKIM and DMARC signing and a web admin interface, plus Roundcube webmail and a ClamAV scan
    of incoming mail. Domains, accounts, TLS, Keycloak sign-in, DKIM keys and DNS records are
    provisioned through Stalwart's management API on every deploy, in compose and in swarm
    mode. Two silent failures are closed: a Keycloak that was still starting could leave
    Stalwart without single sign-on for the life of the process, and the server wrote no logs
    at all. Both are fixed. See [web-app-stalwart](roles/web-app-stalwart/README.md).

  * **Mail to and from onion addresses.** The new *svc-net-tor-smtp* role accepts mail for
    *.onion* recipients and delivers it over Tor. The applications reach an onion mail
    provider too: 27 roles now resolve the provider's onion host inside their containers and
    skip STARTTLS towards it, since no certificate can exist for an onion name and Tor already
    encrypts the link. GitLab's test suite checks that a password reset mail actually arrives.

  * **GitLab migrations no longer run out of locks.** GitLab's database migration failed on
    PostgreSQL 18 with "out of shared memory". The shared and the per-application PostgreSQL
    now allow 256 locks per transaction.

  * **Backups survive a file truncated mid-copy.** *baudolo* 7.0.2 lets the stopped checksum
    copy replace a live pre-copy that a writing container broke by truncating files under
    rsync, which used to abort the backup.

  **For Developers**

  * **The mail provider is a service, not a role name.** Stalwart declares *provides: email*
    and Mailu *covers: [email]*. The new *mail_provider* lookup returns the configured provider
    while it is deployed, otherwise the first deployed role offering *email*, and reads the
    cluster-wide groups so swarm workers agree with the manager. The port-collision lint
    accepts a collision only inside a registry-declared provider-alternative group, and a new
    lint fails every role that reads the *email* lookup without emitting
    *container_extra_hosts*.

  * **Deprecated roles are untested.** A role in the *deprecated* lifecycle stage is no longer
    deployed or tested by CI and cannot block a release, so Stalwart stopped pulling Mailu into
    its deploy round. The allowed lifecycle values now have one source, the stage list in
    [lifecycle.md](docs/contributing/design/role/services/lifecycle.md).

  * **Release pushes on main.** A push to *main* whose head commit starts with
    *Release version* cancels the running *main* pipeline, so the release starts at once. A
    release-tagged commit deploys every chunk instead of stopping at the first failed one, and
    still ends red when any chunk fails, which keeps a broken release from being published. A
    new integration test requires every release tag from v13.0.0 on to sit on that subject,
    and the test job now checks out the full history with tags.

  * **Cancelling CI runs.** Pull request runs fail fast: the first failed row cancels its
    siblings in the deploy and workspace matrices, while push and manual runs keep every row.
    Identical deploy rows from different runs queue behind each other again, keyed on every
    matrix axis. A cancel now also stops jobs that used to run after a failure:
    *!cancelled()* replaces *always()*, enforced by lint, and only *done* and
    *report-main-failures* still report a cancelled run. CodeRabbit reviews draft pull
    requests too.

  * **Onion port probe runs everywhere.** A role with more than one onion-forwarded port wrote
    an unquoted port list into *test.env*, so sourcing it left the list empty and the probe
    passed without checking anything. The list is quoted now, and a unit test sources the
    rendered file in compose and swarm mode.

  * **Agent sandbox.** *git commit --amend* is denied, the host's credential files stay
    unreadable inside the sandbox, sandboxed commands may reach local services on 127.0.0.1
    to 127.0.0.12 and localhost, and third-party plugins are no longer switched on for every
    contributor.

  * **Ansible collections from Git first.** The automated retune made Git the preferred
    source for Ansible collections, with Galaxy as the fallback, after Galaxy's median install
    time reached three times Git's.

  * Image and dependency version jumps (net since 14.2.0):
    * *svc-db-postgres*: postgis/postgis 17-3.5 to postgres 18
    * *svc-db-mariadb*: 12.3 to 13.0
    * *svc-db-redis*: 8.10.1-alpine to 8.10.2-alpine
    * *svc-db-openldap* (Debian base): 12.15-slim to 13.7-slim
    * *svc-ai-ollama*: 0.34.2 to 0.35.0
    * *svc-runner* (BuildKit): v0.33.0 to v0.33.1
    * *svc-net-tor-smtp*: newly on python 3.14-slim
    * *web-app-stalwart*: newly pinned to Stalwart v0.16.24, Roundcube 1.7.4-apache, ClamAV
      1.5.4 and postgres 18
    * *web-app-dashboard* (port-ui): 2.1.3 to 2.2.2
    * *web-app-erpnext*: v16.35.0 to v16.37.0
    * *web-app-espocrm*: 10.0.8 to 10.0.9
    * *web-app-gitlab*: v19.3.2 to v19.4.1 (all six CNG component images)
    * *web-app-joomla*: 6.1.3-php8.3-apache to 6.1.4-php8.3-apache
    * *web-app-keycloak*: 26.7.4 to 26.8.0, oauth2-proxy v7.15.4 to v7.15.5
    * *web-app-magento*: OpenSearch 3.8.0 to 3.9.0
    * *web-app-matomo*: 5.13.0 to 5.14.0
    * *web-app-matrix*: Synapse v1.160.0 to v1.162.0, Element v1.12.28 to v1.12.30
    * *web-app-odoo*: 19.0 to 20.0
    * *web-app-peertube*: v8.3.0 to v8.3.1
    * *web-app-seaweedfs*: 4.46 to 4.48
    * *web-app-shopware*: OpenSearch 3.8.0 to 3.9.0
    * *web-app-snipe-it*: v8.7.2-alpine to v8.8.0-alpine
    * *web-app-socialhome*: 2026.9.18 to 2026.10.2
    * *web-app-xwiki*: 17.10.13 to 18.8.0
    * *web-app-bluesky* (view): 1.132.0 to 1.134.0
    * *web-app-discourse*: v2026.8.0 to v2026.9.0
    * *web-svc-logout*: universal-logout v1.3.1 to v1.4.1
    * *web-svc-xmpp*: 26.07 to 26.09
    * *pkgmgr*: v1.16.1 to v2.0.2
    * *backup-docker-to-local* (*baudolo*): 7.0.1 to 7.0.2
    * *ruff* (dev): 0.16.7 to 0.16.9
    * *brace-expansion* (dev): ^5.0.9 to ^5.0.12

  **Contributors**

  * [Alejandro Roman Ibanez](https://github.com/AlejandroRomanIbanez): Stalwart as the default
    mail provider in compose and swarm mode, the Tor SMTP gateway, onion mail delivery for
    every mail-sending role, the Mailu import and the provider resolution
  * [Kevin Veen-Birkenbach](https://veen.world): PostgreSQL 18, release-aware CI cancellation
    and chunk gating, the release tag test, the Mailu deprecation and the untested deprecated
    stage, the agent sandbox policy, review and version maintenance

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Sat, 03 Oct 2026 21:21:58 +0200

infinito-nexus (14.2.0-1) unstable; urgency=medium

  **For Users**

  * **Secrets management with OpenBao.** The new *web-app-openbao* role deploys OpenBao at
    *openbao.<domain>*. It stores application credentials, API keys and machine identities.
    People sign in through Keycloak, or through LDAP when OpenLDAP is deployed. Their
    *administrator*, *operator* or *reader* group decides what they can reach, and signing in
    without one of those groups grants only the default policy. Services and automation use
    their own AppRole identities, whose credentials change on every deploy, and no root token
    is kept after the first deploy. A recovery key kept in the token store lets a later deploy
    get back in if those credentials are lost. A static seal unseals the node on its own after
    every restart. An internal PKI can be switched on, and Prometheus alerts when the node is
    sealed or unreachable. The seal key lives only in the encrypted inventory, and a backup of
    the store cannot be read without it, so back up the inventory together with the volume.
    See [web-app-openbao](roles/web-app-openbao/README.md).

  * **LDAP import, export and Multi edit finish again in LAM.** The Content Security Policy
    blocked the inline script that checks on LAM's background jobs, so these tools stayed
    "in progress" forever. That script is allowed again.

  * **Swarm deploys no longer abort on a network that already exists.** Some networks are
    created before their application's stack, as Prometheus does for every application it
    scrapes. Those networks now carry the stack label, so the application's own deploy uses
    the existing network instead of failing with "network with name <entity> already exists".

  **For Developers**

  * **Alert rules live in the role that owns the metric.** A role ships its Prometheus rules
    in *templates/prometheus/alert_rules.yml.j2*. The new *alert_rule_roles* lookup includes
    them only for roles that Prometheus actually scrapes, so a rules file needs no activation
    check of its own. It must start at the *groups:* item level. OpenBao's seal alert is the
    first rule to use it.

  * **Video for hand-built Playwright contexts.** A context created with
    *browser.newContext()* ignored the project's video setting, so its artefact had a trace
    but no video. Spread *recordVideoOptions(testInfo)* from *personas/utils/env.js* into the
    context options to record one.

  * Image and dependency version jumps (net since 14.1.0):
    * *web-app-pgadmin*: 9.17 to 9.18

  **Contributors**

  * [Evangelos Tsakoudis](https://evangelostsak.com): the OpenBao role with its seal,
    credential rotation, recovery and RBAC end-to-end suite, role-local Prometheus alert
    rules, and the LAM and swarm network fixes that work uncovered
  * [Alejandro Roman Ibanez](https://github.com/AlejandroRomanIbanez): video recording for
    hand-built Playwright contexts
  * [Kevin Veen-Birkenbach](https://veen.world): review and version maintenance

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Tue, 22 Sep 2026 15:22:09 +0200

infinito-nexus (14.1.0-1) unstable; urgency=medium

  **[14.1.0] - 2026-09-21**

  **For Users**

  * **Local models reach the gateway again.** Upstream re-pushed the *svc-ai-lmstudio* CPU
    image with a server that listens on loopback only, so the role stayed green while litellm
    could not connect and the AI features of Mattermost, Matrix, xWiki, Moodle and Zammad
    failed their warm-up. The server binds the container network again and stays unpublished.
    The model preload now waits until the daemon has finished starting, so a slow host no
    longer ends up with a daemon that fails every later model load, and the readiness probe
    is capped per attempt so a silent peer cannot hang it. See
    [svc-ai-lmstudio](roles/svc-ai-lmstudio/README.md).

  **For Developers**

  * **CI deploys only the variants a change reaches.** An image bump in a provider no longer
    redeploys the variants of its consumers that switch that provider off. The diff-derived
    whitelist, used by the weekly update PRs, Dependabot and every other diff-driven run,
    narrows each affected role to the variants whose round closure still reaches a changed
    role, emitted as *role#0,2* selection tokens. A changed role and a role reached only
    through *run_after* keep every variant. A pinned token naming a role that discovery does
    not return now warns instead of aborting the run; role names are still checked against
    *roles/* before the run starts.

  * **One instruction file for every agent.** *AGENTS.md* now holds every agent rule, and
    *CLAUDE.md* and *GEMINI.md* are gone. The rules they carried bind every runtime anyway,
    and the PR templates, scope tables, requirement documents and docs that named the old
    files now name *AGENTS.md*.

  * Image and dependency version jumps (net since 14.0.0):
    * *svc-ai-ollama*: 0.34.0 to 0.34.2
    * *svc-net-tor* (Debian base): 13.6-slim to 13.7-slim
    * *web-app-baserow*: 2.3.3 to 2.3.4
    * *web-app-erpnext*: v16.34.2 to v16.35.0
    * *web-app-funkwhale* (front, api): 2.0.10 to 2.0.11
    * *web-app-jitsi* (web, prosody, jicofo, jvb): stable-9646 to stable-11031
    * *web-app-keycloak*: 26.7.3 to 26.7.4
    * *web-app-opentalk* (LiveKit): v1.13.6 to v1.13.7
    * *web-app-opentalk* (RabbitMQ): 4.3.5 to 4.3.6
    * *web-app-peertube*: v8.2.4 to v8.3.0
    * *web-app-pihole*: 2026.07.2 to 2026.09.0
    * *web-app-prometheus* (Alertmanager): v0.34.0 to v0.34.1
    * Ansible collection *hetzner.hcloud*: 7.0.1 to 7.1.0
    * *ruff* (dev): 0.16.6 to 0.16.7

  **Contributors**

  * [Kevin Veen-Birkenbach](https://veen.world): LM Studio reachability and startup,
    variant-narrowed CI selection, the single agent instruction file, and version maintenance

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Mon, 21 Sep 2026 23:12:35 +0200

infinito-nexus (14.0.0-1) unstable; urgency=medium

  **For Users**

  * **AI assistance inside the applications you already run.** A gateway role,
    *svc-ai-litellm*, fronts OpenAI, Anthropic and OpenRouter from declared keys, with
    *svc-ai-ollama* and the new *svc-ai-lmstudio* serving local models from one shared
    catalogue, so inference can stay inside the deployment. Sixteen applications use it for
    their own built-in AI features instead of calling a vendor directly: WordPress AI Engine,
    MediaWiki AIEditingAssistant, Moodle's AI subsystem, Zammad Smart Assist, the xWiki LLM
    extension, Discourse AI, the Matrix ChatGPT bridge, Nextcloud, Mattermost Agents, n8n,
    Open WebUI and Flowise. The gateway's admin UI signs in through Keycloak. See
    [svc-ai-litellm](roles/svc-ai-litellm/README.md) and
    [svc-ai-lmstudio](roles/svc-ai-lmstudio/README.md).

  * **Your tools reachable from your assistants.** About twenty applications now speak MCP.
    Gitea, GitLab, Mattermost, Nextcloud, n8n, Baserow, Jenkins, Home Assistant, Moodle,
    Snipe-IT and WordPress serve their own endpoints; ten more get one through a locked-down
    sidecar. Open WebUI, Flowise and the agents discover what the deployment offers and
    register it automatically. Access is bounded rather than assumed: blocked paths are
    declared, every served path is gated, tools a provider calls mutating are withheld,
    reader and writer roles are separate, and each provider authenticates callers as its own
    service account. See [svc-ai-mcp-adapter](roles/svc-ai-mcp-adapter/README.md).

  * **Agents as employees, with a kernel between them and the host.** *web-app-hermes* and
    *web-app-openclaw* deploy agents behind the single sign-on gate, wired to the gateway's
    models and to the MCP servers discovery hands them, each proving on deploy that it can
    actually reach them. *svc-virt-kata* runs that tier under a kernel-isolating container
    runtime, and *svc-ai-robot* embodies an agent on a dedicated device. Every sandboxed
    agent acts as its own platform account rather than borrowing yours. See
    [web-app-hermes](roles/web-app-hermes/README.md),
    [web-app-openclaw](roles/web-app-openclaw/README.md) and
    [svc-virt-kata](roles/svc-virt-kata/README.md).

  * **Home automation joins the platform.** *web-app-homeassistant* deploys the automation
    hub, exposes it as an MCP server so agents can drive it, persists the token it mints, and
    is reachable over tor. See [web-app-homeassistant](roles/web-app-homeassistant/README.md).

  * **Changing a password changes it everywhere.** A user declares how its password must be
    generated, the inventory pins one for every declared user, a new generator produces
    API-key shapes, the deploy rotates credentials between its two passes, and a reset command
    exists. Applications that persist an administrator password at install now realign it when
    the declared one changes: Nextcloud, Moodle, EspoCRM, Jellyfin, Listmonk, Discourse,
    Checkmk, xWiki, n8n, Matrix and the dedicated MariaDB and Postgres engines. Checkmk keeps
    a 401 fatal so a wrong password cannot lock the account out.

  * **Onions, firewall and name resolution.** *svc-net-firewall* owns the packet filter as one
    nftables table per role. Tor carries mail to a .onion in both deploy modes, rejects forged
    sources on the guarded ports, and binds its dnsmasq listeners statically so a failed
    re-enumeration can no longer drop every one of them. The deployment declares its own
    address space once, the egress range moved out of the Docker pool, and the container
    resolver follows the live bridge instead of a guessed address. Every host-bound port
    requires an explicit onion decision. See
    [svc-net-firewall](roles/svc-net-firewall/README.md).

  * **Application fixes you will notice.** Nextcloud installs an OnlyOffice release upstream
    actually published, keeps social login working over tor, and makes its OIDC account an
    administrator. Element shows invites again through the new room list. Moodle survives the
    container roll during installation. Mattermost keeps its configuration in Postgres instead
    of on a shared volume, and its plugin tree is per node. GitLab creates the default
    organization a headless install never gets. Jellyfin waits for health before driving the
    setup wizard. Keycloak registers redirect URIs only for the apps a play deploys, keeps the
    allowed-origins claim out of the access token, and no longer caches LDAP federation past a
    fresh grant. The platform's own health mail no longer eats the sender quota.

  * **Operating a deployment.** New make targets reach a single deployed application:
    *compose-app-exec*, *compose-app-logs* and *compose-app-restart*. Diagnostics collect the
    logs an application writes to a file, including from busybox containers. Health probes use
    a fast start cadence with a window that outlasts a real boot, so a slow starter is no
    longer reaped. The CSP health check accepts a status code a vhost declares instead of
    dropping the vhost, and gives onion vhosts a navigation budget Tor can meet.

  **For Developers**

  * **MCP is declared, not wired.** A role states an *mcp* block in *meta/services.yml*, and
    the vocabulary, discovery and rendering layer derives endpoints, clients, credentials and
    network edges from it. A client-provider pairing is declared once, on the provider;
    *sys-svc-mcp-reconcile* converges clients from the complete provider set; a refused pairing
    is expressed as off rather than gated at the call site; and *svc-ai-mcp-adapter* is a
    reusable per-provider sidecar rather than a standalone application. The contract is proven
    on deploy, including refusal on *initialize* and the tool inventory itself. The design,
    its delegation rules and a machine-checkable audit live under
    [docs/contributing/design/role/services/mcp.md](docs/contributing/design/role/services/mcp.md).

  * **Registry and variant changes.** A variant can dictate the configuration of the providers
    it pulls in, and an MCP round decides for itself what it carries, which is the breaking
    change in this release. Fifteen moving pins gained names the registry can order, every MCP
    provider is registered as a shared service, *meta/schema.yml* finished its rename to
    *meta/secrets.yml*, role scripts are sorted into per-language folders, and *meta/addons* is
    now an accepted declaration site for pins and plugins.

  * **Lints that fail closed.** Five properties that used to pass silently now fail. The PHP
    and Ruby that roles ship are syntax-checked, and so are shell scripts that ship as Jinja
    templates. *sys-service* owns every systemd unit a role installs. Healthcheck timings are
    judged against what the base image was measured to hold, a variable-free healthcheck
    belongs in *meta/services.yml*, an unenforceable NFS state layout fails at author time, a
    sandboxed role cannot be pinned to the manager, credentials are kept out of volumes
    declared non-secret, *no_log* is required on every task carrying one and forbidden in the
    test roles, and a JavaScript default for an unset environment variable is rejected.

  * **Pins that cannot age unnoticed.** Image pins are classified and pull references built
    from the class, a moving tag declares itself with the marker it already carries, and a
    role's images move as one release or not at all. The updater reads addon declarations,
    orders vendor patch counters and five-component versions, reads a release line written
    before the number as a family, and now also watches pinned pip requirements and Ansible
    collections. Seven roles moved off the Debian codename base onto slim, and one Ubuntu
    mirror list with failover, a short timeout and a health test serves both the package cache
    and the image builds. *js-yaml* was lifted to 4.3.2 for GHSA-2883-xcg3-v3hh.

  * **CI selects, resumes and proves.** A fourth deploy chunk stops a short priority chunk
    stranding budget, a manual run can set the chunk size the formula would derive, and a
    retrigger can override the chunk gate. Retriggering works by role and by what never got a
    verdict, resumes behind the green window rather than the deployed one, retires suites the
    source run already passed, and cancels the run before retriggering its failed roles. Each
    role's README instructions are replayed on its smallest deploy row, in the mode its
    *services.yml* declares. Distro images build concurrently on one runner, the matrix job
    folds into the image job, and the deploy chunks no longer wait on the DNS test.

  * **Swarm, measured rather than assumed.** The deploy waits for its own update instead of
    guessing after it, refuses to call a stack converged while it is still updating, survives a
    task that failed and came back, reports what converged rather than only what did not, and
    says why the convergence probe refused. Placement is constrained to a role's declared
    platform arch, redis and memcached sidecars get a deploy block, and a run-once service no
    longer strands the stack deploy waiting for a replica it never has. The rescue dump
    captures the contents of the state volumes. In the test lab, workers are labelled
    kata-capable only when the app is sandboxed, NFS detaches before the node containers die,
    and the sandbox tier gets a second node.

  * **Faster gates.** Per-host setup runs once per host instead of once per application, a
    role's volumes are answered from its own file instead of a whole-tree build, the Keycloak
    RBAC root group resolves once rather than once per app, the Playwright fixture copy the
    tree copy already made is dropped, logout helpers wait for a control instead of sleeping,
    and the OIDC login helper stops waiting out probes that cannot match. Onion timeouts scale
    by tor flavor instead of a flat multiplier, and every Playwright timeout has one source.

  * **Documentation and agent instructions.** Six new requirement documents cover the LLM
    gateway and model backends, agent employees, Home Assistant, the robot role, the MCP proxy
    expansion and native AI gateway integration. The MCP design docs are nested under their own
    folder with an audit the lints can check. The role README template asks the reader for a
    domain the deployment can resolve, enforces consistent Credits attribution, and drops the em
    dash. Documentation generators are gathered under *cli/build/docs*, and a new CLI edits role
    bonds in a matrix instead of hunting through files.

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Sat, 19 Sep 2026 16:35:48 +0200

infinito-nexus (13.0.0-1) unstable; urgency=medium

  * **Tor as a first-class deployment axis.** The new *svc-net-tor* role routes onion
    names through Tor via DNS instead of per-app proxies, offers a chutney flavor for
    private-net test deployments, and ties its dnsmasq listener to ownership rather than
    the deploy mode. Applications follow: server-side OIDC discovery and token exchange
    reach plaintext onion issuers through the SOCKS proxy (WordPress, Joomla, PeerTube,
    Mobilizon, EspoCRM, Nextcloud), session cookies drop the *Secure* flag where an onion
    cannot carry it (Taiga, Pixelfed, Joomla), and mail authenticates on the submission
    port the onion relay actually serves. The deploy matrix gained a tor axis with an
    onion marker plus enforced and exclusive modes, and lint now requires the onion in
    every variant. See the [svc-net-tor](roles/svc-net-tor/README.md) README.

  * **Single sign-on across protocols and domains.** Keycloak imports a SAML client for
    every app that asks for one; SuiteCRM authenticates against it via SAML instead of a
    proxy, and Shopware provisions its admin through AdminOpenAuth with the front and
    back channel split. Sessions are shared across app domains, the issuer is pinned to
    the canonical OIDC URL, each consumer's redirect scheme resolves on its own side, and
    the logout sweep runs where the visitor is looking. Directory users are read through
    one lookup so seeding cannot diverge.

  * **One CI chain instead of four workflows.** The deploy workflows collapse into a
    single chunk chain gated by one validation job, with every variant getting its own
    row and rotating distro, filesystem and tor axes. Selection tokens pin deploy axes,
    retriggers reproduce the exact selection of the source run in every mode, and the
    regular line resumes where the source stopped. Fork code no longer runs with the base
    repository token, workflow tokens are scoped to the jobs that need them, superseded
    runs are force-cancelled, and a mid-run budget SIGKILL is counted as skipped rather
    than green. Apt call sites are bounded so a stalled mirror cannot burn a job budget.

  * **Restore verified against real dumps.** The *recover* CLI replays a generation's
    database and cluster dumps, reads the generation's own layout instead of guessing it,
    and places each dump by the engine the backup observed rather than by name alone. The
    *svc-bkp-volume-2-local* drill now empties every volume it claims to test, fails on a
    database that was never dumped, and stops the backup timers before it goes
    destructive. Baudolo is pinned per host, its contract read from baudolo itself.

  * **A probe for every container.** Health checks are composed from prefixes and are
    mandatory: every declared image either carries a probe or states why it cannot have
    one, enforced from both sides. Probes hit the port the container actually binds, no
    longer derive an HTTP Host from a kernel hostname, and are defined once instead of per
    proxy. Rescue diagnostics pin the dnsmasq listener loss to its moment, sample what the
    resolver answers rather than just its socket, probe the daemon.json upstreams, and
    record the whole boot instead of the last 10 000 lines.

  * **Single points of truth, enforced by lint.** The new *sys-lint* role pins the
    GitHub-release lint tools. Every mount target has one spot in *meta/volumes.yml* and
    container paths are derived from it, *default.env* is the enforced source of the
    primary domain, *container_backup* is rejected where no volume exists, and role
    credentials live at *meta/secrets.yml*. Linters no longer read gitignored build
    output, comments are checked in every file rather than only changed ones, and every
    shipped script requires a mirrored unit test.

  * **Tests in every language the project ships.** The quality gate parses each shipped
    language and runs its unit suite; the PHP and Ruby interpreters are baked into the
    image, and the JavaScript, PHP and Ruby suites are reported in the CI summary
    alongside Python. Playwright stages the persona flows once per run and mounts them
    read-only, and the workspace environment suite splits into base, compose and swarm
    tracks.

  * **Swarm and proxy operation.** Convergence gates read one budget sized for a real
    stack, print every task row when a stack fails to converge, and give up on a terminal
    task; the deployment mode a stack was deployed with is resolved instead of assumed.
    OpenResty reloads a vhost, lua or redirect config instead of redeploying the task.
    GitLab ships its config trees as swarm configs rather than host binds, and role
    templates expand through a lookup instead of inline Jinja.

  * **New and reshaped roles.** *sys-svc-nss* is split out of *sys-hostname*,
    *web-svc-mirror* serves frontend assets as a privacy proxy, PeerTube becomes
    clearnet-only, and roles that could not run were deleted rather than repaired. CLI
    tools install via pip instead of pkgmgr.

  * **Image and dependency version jumps (net since 12.0.0):**

    *Applications*
    * *web-app-erpnext*: v16.26.2 to v16.32.3
    * *web-app-gitlab*: v19.1.2 to v19.3.0 (all six CNG component images)
    * *web-app-semaphore*: v2.18.25 to v2.19.8
    * *web-app-nextcloud*: PHP 33-fpm-alpine to 34-fpm-alpine, nginx 1.31.2 to 1.31.3
    * *web-app-shopware*: 6.7.8.2 to 6.7.13.0, OpenSearch 3.7.0 to 3.8.0
    * *web-app-matomo*: 5.11.2 to 5.13.0
    * *web-app-matrix*: Synapse v1.156.0 to v1.159.0, Element v1.12.23 to v1.12.26
    * *web-app-mattermost*: 11.9.0 to 11.10.1
    * *web-app-keycloak*: 26.7.0 to 26.7.2, oauth2-proxy v7.15.3 to v7.15.4
    * *web-app-gitea*: 1.26.4 to 1.27.2
    * *web-app-funkwhale*: 2.0.6 to 2.0.9 (api and front)
    * *web-app-penpot*: 2.16.2 to 2.17.1 (frontend, backend, exporter)
    * *web-app-espocrm*: 10.0.2 to 10.0.6
    * *web-app-opencloud*: 7.2.1 to 7.2.4
    * *web-app-seaweedfs*: 4.39 to 4.44
    * *web-app-snipe-it*: v8.6.3-alpine to v8.7.2-alpine
    * *web-app-wordpress*: 7.0.1 to 7.1.0
    * *web-app-peertube*: v8.2.2 to v8.2.4
    * *web-app-joomla*: 6.1.2-php8.3-apache to 6.1.3-php8.3-apache
    * *web-app-jira*: 11.3.8 to 11.3.10
    * *web-app-confluence*: 10.2.14 to 10.2.15
    * *web-app-baserow*: 2.3.1 to 2.3.3
    * *web-app-flowise*: 3.1.3 to 3.1.4
    * *web-app-mailu*: 1.5.3 to 1.5.4
    * *web-app-pgadmin*: 9.16 to 9.17
    * *web-app-prometheus*: Prometheus v3.13.1 to v3.14.0, Alertmanager v0.33.1 to
      v0.34.0, node-exporter v1.12.0 to v1.12.1
    * *web-app-opentalk*: LiveKit v1.13.3 to v1.13.5, RabbitMQ 4.3.2 to 4.3.4; the
      frontend (v2.9.0), controller (v0.34.1) and recorder (v0.16.2) stay pinned until
      the set is released as a bundle and *recorder.toml* is migrated
    * *web-app-magento*: OpenSearch 3.7.0 to 3.8.0; PHP held at 8.4-fpm and MariaDB at
      11.4, both deliberately fenced against the image bot
    * *web-app-socialhome*: newly pinned to 2026.6.16
    * *web-app-bluesky* and *web-app-bridgy-fed*: upstream git refs pinned to commits

    *Services and infrastructure*
    * *svc-ai-ollama*: 0.31.2 to 0.32.15
    * *svc-db-elasticsearch*: 9.4.3 to 9.5.2
    * *svc-db-qdrant*: v1.18.2 to v1.19.0
    * *svc-runner*: Ubuntu 24.04 to 26.04
    * *sys-ctl-hlth-csp*: 2.1.2 to 2.2.1
    * *web-svc-coturn*: 4.14.0 to 4.17.2
    * *web-svc-logout*: *latest* to universal-logout v1.3.1
    * *web-svc-xmpp*: 26.04 to 26.07
    * *test-e2e-playwright*: v1.61.1-noble to v1.62.1-noble
    * *svc-net-tor*: newly on debian trixie-slim
    * *svc-bkp-volume-2-local*: newly on alpine 3.24

    *Toolchain and dependencies*
    * *backup-docker-to-local* (*baudolo*): pinned to 7.0.1, via 6.0.0 and 7.0.0
    * *ruff*: 0.16.0 to 0.16.4
    * *@playwright/test*: ^1.61.1 to ^1.62.1
    * *eslint*: ^10.6.0 to ^10.9.0, *globals*: ^17.7.0 to ^17.9.0
    * *stylelint*: ^16.0.0 added
    * *brace-expansion*: overridden to ^5.0.9, past three DoS advisories
    * *keycloak-js*: 24.0.5 to 26.2.4, loaded as an ES module
    * *bootstrap*: 5.3.3 to 5.3.8, *@fortawesome/fontawesome-free*: 6.5.1 to 7.3.1
    * *roles/sys-lint* pins the GitHub-release tools: actionlint v1.7.12,
      hadolint v2.15.1, shfmt v3.13.1

  **Renamed**

  * Role credential declarations move from *meta/credentials.yml* to *meta/secrets.yml*,
    addressed at *secrets.credentials* like every other meta file.
  * The NSS half of *sys-hostname* becomes its own role, *sys-svc-nss*.

  **Contributors**

  * [Kevin Veen-Birkenbach](https://veen.world): Tor deployment axis, SAML and
    cross-domain SSO, CI chunk chain, generation-level recovery, health-check and
    lint enforcement, multi-language test suites

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Wed, 26 Aug 2026 09:39:19 +0200

infinito-nexus (12.0.0-1) unstable; urgency=medium

  * **Docker Swarm as a second deployment mode.** Roles can now be deployed across
    multiple hosts instead of being limited to a single Compose stack. New
    *svc-swarm-node* initializes the cluster, while *svc-swarm-manager* marks the Raft
    managers in the inventory. Shared state is provided through
    *svc-storage-nfs-server* and *svc-storage-nfs-client*. *svc-registry-docker*
    distributes locally built images, and *svc-registry-cache* provides a Docker Hub
    pull-through cache so additional nodes do not multiply external pulls and
    rate-limit usage. Deployment, dependency resolution, networking, health checks and
    role templates now understand both Compose and Swarm semantics. See the
    [svc-swarm-node](roles/svc-swarm-node/README.md) and
    [svc-storage-nfs-server](roles/svc-storage-nfs-server/README.md) READMEs.

  * **Central engines and object storage.** A new *engine* lookup extends the existing
    shared-database pattern to non-RDBMS services. Consumers can use centrally
    provisioned Redis, Memcached, RabbitMQ, Elasticsearch, Typesense, Qdrant and Unbound
    instances with isolated credentials, or retain an embedded sidecar.
    *sys-svc-objstore* and the new *objstore* lookup provide the same abstraction for
    SeaweedFS and MinIO, with lazy provider inclusion and object-storage integration
    across fourteen application roles.

  * **Backup and disaster recovery.** *svc-bkp-container-2-local* is superseded by
    *svc-bkp-volume-2-local*, making backup policy explicit per volume. The role prefers
    filesystem snapshots when available and can prepare btrfs hosts for snapshot-based
    backups itself. New *svc-bkp-nfs-2-local* protects the shared NFS export,
    *svc-bkp-secrets-2-local* captures secret material, and
    *svc-bkp-local-2-device* synchronizes backups to mounted external media. A unified
    *recover* CLI drives restores, while Compose recovery checks and a full Swarm
    disaster-recovery drill continuously verify the path. Rsync no longer retains
    superseded files as *~* copies.

  * **Deployment and packaging foundations.** A host deployment mode joins Compose and
    Swarm in the deployment tooling. The former *sys-package*, *sys-aur* and
    *sys-aur-install* roles were replaced by a distribution-independent package
    registry. Volume metadata now uses one canonical schema, single-file mounts become
    Swarm configs, and service placement, replica counts, dependencies, addresses and
    health probes resolve through deployment-aware lookups.

  * **Isolated development worktrees.** Parallel branches can use independent stacks
    while sharing one cache layer, preventing containers and network state from
    colliding. New *swarm-app-exec*, *swarm-diagnostic*, zombie-cluster and cross-mode
    roundtrip targets improve live inspection and Compose/Swarm parity testing.

  * **CI and diagnostics.** Deployment runs are planned separately per mode and divided
    into starred priority and regular lines, with branch-new roles scheduled first.
    Matrix entries serialize across branches, large lines serialize their deployment
    modes, and failed roles can be retriggered on the original distribution set;
    *--strict* limits this to hard failures. Cancelled runs can have their failed jobs
    rerun, and role failures on *main* open or update dedicated issues. Deployment stages
    are sourced from *categories.yml*.

    Rescue collection now uses one indexed diagnostic pipeline covering journal and
    kernel windows, resolver and network state, PostgreSQL activity, container-kill
    attribution, storage devices and NFS-Ganesha thread state. The Swarm recovery drill
    and README deployment guide run across every supported distribution. CI also varies
    Docker's data-root filesystem so ext4, btrfs and ZFS-specific paths receive real
    coverage.

  * **Testing and documentation tooling.** New *test-e2e-cli* exercises the command-line
    interface end to end. Lint and test jobs publish per-target and per-test summaries,
    failed target logs are replayed after the summary, and the complexity matrix shows
    whether a role already exists on *origin/main*. Generated role READMEs now provide
    consistent status tables, quick-start instructions and dependency diagrams.

  * **New lint contracts.** Port variables must resolve through lookups, scheduler
    services must declare *replicas: 1*, and volume-owning stack roles must declare
    their backup policy. *role_path* is forbidden in shell and command bodies.
    Deployment-mode defaults, container-inspection resolvers, asynchronous-task
    rationales and health-check declarations are validated centrally. Hadolint
    directives and documented *nocheck* exceptions are recognized instead of producing
    false positives.

  * **Image delivery and frontend dependencies.** Image references now resolve from
    *meta/services.yml* as their single source of truth, replacing unpinned *latest*
    tags. *web-app-bigbluebutton* pins its component images separately. *web-svc-cdn*
    builds and serves mirrored frontend packages through an internal one-shot service,
    with the package-cache CA trusted by build containers.

  * **Application improvements.** Penpot gained hardened OIDC, LDAP and native-login
    variants, SeaweedFS-backed asset tests and Swarm-ready health checks. Pi-hole
    received its v6-compatible login and password flow, Keycloak RBAC integration and
    expanded persona coverage. Further deployment, replication and startup fixes landed
    across Akaunting, BigBlueButton, ERPNext, Gitea, Keycloak, Magento, Mailu, Mastodon,
    Matomo, MediaWiki, Nextcloud, OpenProject, Pixelfed, Semaphore, Taiga, WordPress,
    XWiki and the internal CDN.

  * **Lifecycle promotions:** *web-app-checkmk*, *web-app-jellyfin*, *web-app-n8n*,
    *svc-registry-docker* and *svc-registry-cache* advance to *beta*.

  * **Image and dependency version jumps (net since 11.6.0):**
    * *web-app-gitlab*: omnibus *gitlab-ee* 19.1.1 to CNG component images v19.1.2
    * *web-app-mattermost*: 11.8.3 to 11.9.0
    * *web-app-opentalk*: v2.8.1 to v2.9.0
    * *web-app-prometheus*: v3.13.0 to v3.13.1
    * *web-app-joomla*: 6.1.1-php8.3-apache to 6.1.2-php8.3-apache
    * *web-app-baserow*: 2.3.0 to 2.3.1
    * *web-app-magento*: PHP 8.5-fpm to 8.4-fpm
    * *backup-docker-to-local*: 3.1.1 to 3.1.3
    * *baudolo*: pinned to 3.4.1

  **Removed**

  * *sys-package*, *sys-aur* and *sys-aur-install*, superseded by the
    distribution-independent package registry.
  * *svc-bkp-container-2-local*, superseded by the volume-oriented backup roles.

  **Contributors**

  * [Kevin Veen-Birkenbach](https://veen.world): Swarm and NFS deployment mode,
    central engines and object storage, backup and recovery rework, CI pipeline,
    linting and tooling

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Sat, 08 Aug 2026 08:37:14 +0200

infinito-nexus (11.6.0-1) unstable; urgency=medium

  * New *web-app-n8n* role: n8n workflow automation (Community Edition). SSO is wired through a trusted-header edge hook that is copied and mounted only when SSO is enabled (native SSO is enterprise-only in CE), LDAP sign-in is supported, and per-scenario Playwright coverage exercises the CE edge-SSO model. Ships at lifecycle *alpha*; subnet and port collisions with the new *web-app-semaphore* role were resolved on the way in. See the [web-app-n8n README](roles/web-app-n8n/README.md).

  * CI fixes: resolved four CI failures across the *web-app-n8n*, *web-app-pihole* and *web-app-mastodon* roles.

  * Routine maintenance: dependabot bumps of *docker/login-action* 4.3.0 to 4.4.0 and *eslint-plugin-playwright* 2.10.4 to 2.10.5; removed a stray *.claude/settings.local.json*.

  * Image and dependency version jumps (net since 11.5.0):
    * *web-app-baserow*: 2.2.2 to 2.3.0
    * *web-app-semaphore*: v2.18.16 to v2.18.21
    * *web-app-pihole*: 2026.06.0 to 2026.07.2
    * *web-app-seaweedfs*: 4.37 to 4.38
    * *web-app-opencloud*: 7.2.0 to 7.2.1
    * *web-app-mattermost*: 11.8.2 to 11.8.3
    * *web-app-decidim*: 0.31.5 to 0.31.6
    * *web-app-prometheus* (Alertmanager): v0.33.0 to v0.33.1
    * *web-app-bluesky* (git ref): 1.126.0 to 1.127.0

  **Contributors**

  * [Prageeth Panicker](https://github.com/pragepani): web-app-n8n role (workflow automation, edge-header SSO, LDAP, Playwright)
  * [Kevin Veen-Birkenbach](https://veen.world): n8n review, CI fixes and version maintenance

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Wed, 08 Jul 2026 02:41:53 +0200

infinito-nexus (11.5.0-1) unstable; urgency=medium

  * Three new web-app roles, each with SSO/LDAP wiring and per-scenario Playwright coverage:
    * *web-app-semaphore* ([requirement 028](docs/requirements/028-web-app-semaphore.md)): Semaphore UI with OIDC SSO + LDAP. The break-glass admin is sourced from the users model, OIDC admin seeding is idempotent, and the OIDC callback strips a trailing base-URL slash. Shipped at lifecycle *beta*.
    * *web-app-checkmk* ([requirement 029](docs/requirements/029-web-app-checkmk.md)): Checkmk behind oauth2-proxy SSO with LDAP; the SSO variant injects a reachable logout control.
    * *web-app-jellyfin* ([requirement 030](docs/requirements/030-web-app-jellyfin.md)): Jellyfin media server; LDAP and OIDC auth are modelled as *meta/addons* with per-addon integration hooks and specs.

  * Users-model rework: the per-user *reserved* flag is replaced by an *accounts* list (*host*/*mailbox*/*identity*) with role-derived defaults, so inventory-only users keep their LDAP/Keycloak accounts. A new *forward* field makes Mailu create *username@domain* forward aliases via a new create-only alias task. Mailbox creation and password rotation now gate on *mailbox* in *accounts*, the Mailu RBAC role *mail-bot* is renamed *bot*, and *svc-db-openldap* always provisions the identity subset.

  * Fixes: *web-app-odoo* restarts after auth provisioning so workers drop stale config; *web-app-roulette-wheel* builds with *npm ci* against the app lockfile; *web-app-discourse* allows *data:* script sources in the CSP.

  * New lint: git branch names are validated against the naming convention. Agent docs gained a hard pre-redeploy verification gate in the role iteration loop.

  * Routine maintenance: dependabot bumps of *actions/checkout* 6 to 7, *docker/login-action* 4.1.0 to 4.3.0 and *docker/setup-buildx-action* 4.0.0 to 4.2.0; *skills-lock.json* refresh.

  * Image and dependency version jumps (net since 11.4.0):
    * *web-app-mediawiki*: 1.45 to 1.46
    * *web-app-erpnext*: v16.25.0 to v16.26.2
    * *web-app-peertube*: v8.2.1 to v8.2.2
    * *web-app-prometheus*: v3.12.0 to v3.13.0
    * *web-app-opentalk* (LiveKit): v1.13.2 to v1.13.3
    * *web-app-semaphore*: pinned v2.18.16 (new role)

  **Contributors**

  * [Kevin Veen-Birkenbach](https://veen.world): Semaphore, Checkmk and Jellyfin roles, users-model rework, fixes and version maintenance

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Sat, 04 Jul 2026 20:30:40 +0200

infinito-nexus (11.4.0-1) unstable; urgency=medium

  * Self-hosted CI runners: new *svc-runner* role plus *cli/deploy/runner* command spin up N containerised GitHub Actions runners on any server, adding to the 20 GitHub-hosted ones. Each runner is isolated (own subnet, ports, inventory, Docker volume, DNS) and auto-re-registers. Runs on Debian, Ubuntu and Arch, supports Docker-in-Docker, and ships a full in-runner E2E deploy test. See the *svc-runner* README.

  * Fork PRs can now scope the CI matrix: a 🧩 *Subset* label reads a *roles:* block from the PR body and narrows the deploy whitelist. Gives forks the maintainer-only manual dispatch they could not trigger before. Without the label nothing changes. See [the fork-PR pipeline](docs/contributing/artefact/git/pipeline.md).

  * GitHub-hosted runner fixes: free disk before deploys and drop a dead 404 URL from the external tests.

  * *make local* reinstall now works correctly when all services are disabled.

  * Image and dependency version jumps (net since 11.3.0):
    * *web-app-funkwhale*: 2.0.4 to 2.0.6
    * *web-app-mobilizon*: 5.2.3 to 5.2.4
    * *web-app-bluesky* (git ref): 1.125.0 to 1.126.0

  **Contributors**

  * [Alejandro Roman Ibanez](https://github.com/AlejandroRomanIbanez): self-hosted containerised CI runners
  * [Kevin Veen-Birkenbach](https://veen.world): Subset-label CI scoping, runner disk fixes, version maintenance

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Wed, 01 Jul 2026 03:47:55 +0200

infinito-nexus (11.3.0-1) unstable; urgency=medium

  * Security: resolve all 80 open CodeQL/code-scanning alerts on the branch and add guards so they cannot recur. The security-sensitive fixes break an unsafe import cycle in the *cli/administration/deploy/development* package by extracting the env/arg helpers into a leaf *env.py* (load-time DAG *compose → common → deps → env*); harden the *web-app-bluesky* login-broker (*server.js*) with a prototype-pollution guard on cookie parsing, CR/LF + control-char log sanitisation, HTML-escaped exception text and a ReDoS-bounded handle regex; harden the *web-app-baserow* SSO open-redirect *next* handling; require TLS ≥ 1.2 in the network diagnose probes; validate *actionlint* installer tar members against path traversal; rewrite the *decidim*/*apt-purge* lint regexes to avoid catastrophic backtracking; and anchor six Playwright URL regexes while pinning *crs-k/stale-branches* to a commit SHA. Recurrence guards: an *import-linter* contract (wired into *pyproject* and *tests/lint*) now fails CI on a new dev-deploy cycle instead of relying on CodeQL, a new lint requires every bandit *S###* *noqa* to carry a justification, and *BLE001* (blind-except) is added to ruff as a ratchet (tests/roles exempt, current production offenders grandfathered). Intentional false positives (Django settings patches, the Odoo `__manifest__`, side-effect and cross-module imports) are left in place.

  * CI flakiness and fork-PR fixes: the Odoo *sale_management* Playwright spec adds *.first()* so the success case no longer trips a strict-mode violation when both the menu brand and the breadcrumb render; the Nextcloud GitLab-OAuth provisioning now resolves/creates the default Organization defensively with *retries/until* instead of hard-raising during the async seed window; the Jenkins deploy-lockdown service stop and the Moodle shared *compose up* handler each gain *retries/until* to ride out transient systemd "Transaction is destructive" contention and a not-yet-created external network. The fork image mirror gets *secrets: inherit* restored (follow-up to the 11.2.0 credential hardening) so untrusted fork PRs authenticate to Docker Hub again rather than mirroring anonymously and hitting rate limits — the build keeps its hardening since it pulls its parent from GHCR. The stale-branches cleanup is adjusted for *crs-k/stale-branches@v9.0.1*, which rejects *days-before-stale ≥ days-before-delete*: stale is now 350 days and delete 360 (10-day warning before deletion at 360 days of inactivity). See [the fork-PR pipeline](docs/contributing/artefact/git/pipeline.md) and [the workflow catalog](docs/contributing/tools/github/actions/workflows.md).

  * Routine maintenance: *.gitignore* now excludes local Claude agent artefacts (*launch.json*, *routines*, *workflows*), and dependabot bumps of *actions/stale* 9 → 10 and *eslint* 10.5.0 → 10.6.0.

  * Image and dependency version jumps (net since 11.2.0):
    * *web-app-seaweedfs*: 4.36 to 4.37
    * *pkgmgr* (package-manager git ref): v1.15.2 to v1.16.0

  **Contributors**

  * [Kevin Veen-Birkenbach](https://veen.world): code-scanning alert resolution and recurrence guards, CI/fork-PR fixes and version maintenance

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Mon, 29 Jun 2026 20:21:44 +0200

infinito-nexus (11.2.0-1) unstable; urgency=medium

  * Per-role update PRs: the Docker-image-version and repository-ref updaters now open one pull request per affected role instead of a single combined PR, and they recompute the diff against the latest *main* at run time — the update jobs check out *main* rather than the triggering commit, so a queued or stale run no longer proposes already-merged changes or bundles unrelated roles. Each role gets its own branch (*update/<type>-<role>-<date>-<fingerprint>*) and a role-scoped duplicate check, while the *skills-lock.json* updater deliberately stays a single PR. See [the update workflow catalog](docs/contributing/tools/github/actions/workflows.md).

  * Fork-PR CI hardening (follow-up to the 11.1.0 trusted-fork flow): the privileged *pull_request_target* image producer and mirror now set *allow-unsafe-pr-checkout* so *actions/checkout* stops refusing the merged fork ref it had begun rejecting. Untrusted fork builds run without organization secrets — they authenticate to GHCR with the per-job *GITHUB_TOKEN* only and mirror Docker Hub anonymously — while the maintainer-trusted path keeps full credentials and builds the raw PR head. The trust switch moved to the *🛡️ Trusted* label. See [the fork-PR pipeline](docs/contributing/artefact/git/pipeline.md) and [the GHCR authentication reference](docs/contributing/tools/ghcr/authentication.md).

  * Buildx setup resilience: the CI image build pre-pulls and pins the *moby/buildkit* image with a retry loop before *docker/setup-buildx-action*, so a transient *registry-1.docker.io* timeout during the buildkit bootstrap no longer aborts the build — buildx falls back to the locally cached image once it is present.

  * Cleanup-workflow fixes (follow-up to the 11.1.0 cleanup automation): the *crs-k/stale-branches* action is pinned to *v9.0.1* (the previous *@v8* was unresolvable and failed the job) and its non-existent *protected-branches* input is replaced with the action's real *branches-filter-regex*; the GHCR CI-image cleanup script now reports what it scans, keeps and deletes with a per-package summary and a *DRY_RUN* mode, so a no-op run is no longer indistinguishable from a broken one.

  * Routine maintenance: YAML normalization of the *web-app-pihole* role's *meta/services.yml* (document start, list indentation) and an autoformat pass.

  * Image and dependency version jumps (net since 11.1.0):
    * *web-app-matomo*: 5.3.2 to 5.11.2
    * *web-app-mattermost*: 11.8.1 to 11.8.2
    * *web-app-opentalk* (LiveKit): v1.13.1 to v1.13.2

  **Contributors**

  * [Kevin Veen-Birkenbach](https://veen.world): per-role update PRs, fork-PR CI hardening, buildx retry, cleanup-workflow fixes and version maintenance

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Sun, 28 Jun 2026 20:38:36 +0200

infinito-nexus (11.1.0-1) unstable; urgency=medium

  * Trusted fork PRs: a maintainer-applied *trusted-pr* label runs a fork PR as deliberately released code — the orchestrator skips the fork-prerequisite wait and the privileged *pull_request_target* build switches its source from the base merge ref to the raw PR head, while the built image keeps the merge-SHA tag so every downstream consumer is unchanged. The label is the only switch (applying a label is collaborator-only on GitHub, so fork authors cannot grant it), build-orchestration scripts always come from the base repository, and both unlabeled fork PRs and same-repository PRs are untouched. See [the fork-PR pipeline](docs/contributing/artefact/git/pipeline.md) and [the contributor fork workflow](docs/contributing/artefact/git/pull-request.md).

  * Fork main-branch sync: the push entry workflow now synchronizes the current repository's *main* from a configured source repository (default *infinito-nexus/core*) before CI scope and deploy discovery run, so a fork branch push sees only its own changes against an up-to-date base. The source is overridable or disablable through the *CI_SYNC_MAIN_SOURCE_REPOSITORY* repository variable, and the sync force-overwrites *main*, so a synced repository must not be used as a working branch. See [the Actions configuration reference](docs/contributing/tools/github/actions/configuration.md).

  * Package-changelog generator hardening: the Debian changelog and the Fedora spec *%changelog* are mirrored from CHANGELOG.md without tripping their native parsers — every change line is indented for Debian, every body line gains a dash continuation for RPM, and the archived-releases notice is folded into the oldest kept entry instead of trailing after the final entry. Previously the un-indented bodies made *dpkg-buildpackage* abort the Debian package build with an empty maintainer. The *Changelog* title is also preserved when the active window is trimmed. See [the release procedure](docs/contributing/actions/release.md).

  * New packaging lint: a *lint-packages* check, wired into *make lint* and its own CI workflow, validates the generated *debian/changelog* with *dpkg-parsechangelog*, the Fedora spec with *rpmspec* and the Arch *PKGBUILD* with a *bash -n* syntax pass, failing on any parser warning rather than only on a non-zero exit. A complementary *test-lint* check asserts that both package changelogs list every released version and that their inline window matches CHANGELOG.md. See [the workflow catalog](docs/contributing/tools/github/actions/workflows.md).

  * Repository cleanup automation: a scheduled cleanup workflow plus a GHCR helper prune stale CI images so the container registry does not accumulate per-commit build artefacts.

  * Routine maintenance: YAML normalization of the *web-app-pihole* role variables (document start, single-space keys).

  **Contributors**

  * [Kevin Veen-Birkenbach](https://veen.world): trusted-fork-PR flow, fork main sync, package-changelog generator and packaging lint, cleanup automation

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Sat, 27 Jun 2026 00:00:00 +0000

infinito-nexus (11.0.0-1) unstable; urgency=medium

  * Unified addon syntax (requirement 026): every role-level plugin, browser/GNOME extension, Matrix bridge and cross-role integration was migrated to a single declarative *meta/addons/* contract, replacing the per-role ad-hoc plugin definitions. 86 addon files now describe addons for *desk-chromium*, *desk-firefox*, *desk-gnome*, *web-app-discourse*, *web-app-friendica*, *web-app-joomla*, *web-app-mediawiki*, *web-app-pretix*, *web-app-wordpress*, *web-app-xwiki*, *web-app-nextcloud*, *web-app-odoo* and *web-app-matrix*. Each service-coupled addon gets a *tasks/addons* integration hook and its own Playwright spec (87 per-addon specs added), addon flags are gated on actual partner deployment through a new *addon_env_flags* lookup, and a lint requires a hook plus a spec for every coupled addon. The cross-role integration matrix is regenerated from a dedicated data module (*integration_matrix_data.py*).
  * New centralized API single-point-of-truth: an *api* lookup plugin plus a global API dict so API tokens and endpoints (Cloudflare, the Nextcloud proprietary OAuth integrations) resolve from one place instead of scattered role lookups.
  * Real Nextcloud integration coupling: end-to-end tests now assert genuine two-way OAuth/API coupling for *gitlab*, *mattermost*, *openproject*, *zammad*, *peertube*, *discourse*, *github*, *google*, *jira*, *mastodon*, *matrix*, *moodle* and *suitecrm*, rather than the mere presence of a connect button. All integration OAuth credentials are stored Nextcloud-encrypted via ICrypto (sensitive app values), OpenProject and Zammad secrets survive a re-deploy, and OpenWebUI/Ollama is wired in as a real *integration_openai* chat backend. Bridges with no Nextcloud-33 plugin (Moodle, SuiteCRM) are disabled.
  * Nextcloud deploy-variant rebalancing: Discourse and OnlyOffice are each isolated into their own loosely-coupled partner variant, the matrix is repacked to five variants overriding only the dynamic flags, LDAP is enabled wherever SSO is on, and the database is shared outside variant 1. The GitLab 19 OAuth app now carries an organization, and *fileslibreofficeedit* is gated on OnlyOffice absence.
  * Storage- and runtime-aware CI variant matrix: variant bundles are split per runner by a storage-aware job splitter driven by an explicit variant CSV, behind a unified variant selector (*variant_select.py*). The default per-bundle storage cap is centralized at 350GB, per-variant memory budgets are guarded against the host budget, shared database providers are enforced by a shared-false-once lint, and the full variant matrix always runs (the earlier runtime time-cut was removed).
  * Container resource governance: self-run and shared-provider services declare explicit compose mem/pids limits, container limits were right-sized across roles, and Node-heap services respect a floor via a new *node_max_old_space_size* lookup (Nextcloud whiteboard raised to 1g). A reworked *ressources* CLI emits per-variant and all-role resource summaries, the footprint tool gains a *min_storage* column, and shared services carry a *bond* integration-importance factor.
  * Matrix bridges modernized: Signal, Telegram, WhatsApp and Slack bridges migrate to bridgev2 config, the dead mautrix-facebook/instagram bridges are replaced by mautrix-meta, bridge specs are gated by the deployed addon, Synapse loads the bridge registrations from a mounted directory, and the role now tracks *matrix-docker-ansible-deploy* from the spantaleev upstream.
  * CA-trust and OIDC-over-self-signed-TLS hardening: the internal CA is installed into the OS bundle for PHP libcurl OIDC (*web-app-pixelfed*, *web-app-joomla*, plus a WordPress must-use plugin), the CA-trust wrapper is kept on probe timeout instead of degrading to env-only, and the CA injection is bounded so it cannot hang the handler. HSTS is now emitted on every response including non-2xx, *ansible-vault encrypt_string* reads plaintext from stdin so leading-dash values encrypt, and Mastodon allows inline *script-src-elem* for its server-rendered SPA.
  * Image and dependency version jumps (net since 10.1.1):
    * *web-app-erpnext*: v16.23.1 to v16.25.0
    * *web-app-gitlab*: 19.1.0-ee.0 to 19.1.1-ee.0
    * *web-app-opencloud*: 4.0.7 to 7.2.0
    * *web-app-seaweedfs*: 4.35 to 4.36
    * *web-app-matomo*: unpinned *latest* to pinned 5.3.2 (matches the bootstrap installer)
    * *web-app-nextcloud* proxy (nginx): unpinned *alpine* to pinned 1.31.2-alpine
    * *web-app-nextcloud* whiteboard: unpinned *latest* to pinned v1.5.9
    * *svc-ai-ollama*: switched to the official *ollama* image so inference works
    * *web-app-pretix* and *web-app-xwiki*: their inline plugin/extension version pins (2.3.1; 2.19.6, 9.15.7, 1.0) were removed as those addons moved into *meta/addons*
    * Dev/CI dependencies: *globals* 15.15.0 to 17.7.0, *@playwright/test* 1.61.0 to 1.61.1, *actions/cache* 5 to 6
    * Intermediate bot bumps were corrected back down: Mattermost 11.8.2 to 11.8.1 and Nextcloud 34 to 33 (kept at 33 because of plugin incompatibilities, same pin as 10.0.0)

  **Contributors**

  * [Kevin Veen-Birkenbach](https://veen.world): unified addon syntax, API single-point-of-truth, Nextcloud integrations and variant rebalancing, CI variant bundling, resource governance, Matrix bridge migration and version maintenance

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Sat, 27 Jun 2026 00:00:00 +0000

infinito-nexus (10.1.1-1) unstable; urgency=medium

  * Disabled the *email* service in the *web-app-pihole* role by pinning *services.email* to a static *enabled: false* / *shared: false* with the matching *dynamic-flag* and *email* nocheck suppressions, removing the now-static email overrides from *meta/variants.yml*, and adding *javascript* true/false variant pins to keep the dynamic-flag matrix-coverage guard green.

  **Contributors**

  * [Kevin Veen-Birkenbach](https://veen.world): Pi-hole email service disablement

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Wed, 24 Jun 2026 00:00:00 +0000

infinito-nexus (10.1.0-1) unstable; urgency=medium

  * New Pi-hole role (*web-app-pihole*) that ships the upstream Pi-hole DNS sinkhole and network-wide ad blocker, with its admin web interface gated behind *oauth2-proxy* and Keycloak RBAC and a native admin-password login (the *FTLCONF* web API password) available as a fallback variant. The upstream resolver and timezone are configurable, the role waits on the Pi-hole HTTP endpoint during deploy, and it is exercised end-to-end with Playwright covering the OAuth2, native-login and guest-access scenarios. The role ships at lifecycle beta.
  * Routine maintenance: Docker image version bumps, a dependabot update of *globals* from 15.15.0 to 17.7.0, and minor *services.yml* self-provider flag corrections for ERPNext, GitLab, SeaweedFS and the Playwright end-to-end role.

  **Contributors**

  * [Prageeth Panicker](https://github.com/pragepani): Pi-hole role and its Playwright test suite
  * [Kevin Veen-Birkenbach](https://veen.world): Pi-hole role hardening and review

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Tue, 23 Jun 2026 00:00:00 +0000

infinito-nexus (10.0.1-1) unstable; urgency=medium

  * Mark the *web-app-erpnext* role's own *erpnext* provider service with the *playwright-service-flag* self-provider suppression in *meta/services.yml*, fixing the Playwright service-flag integration guard that 10.0.0 tripped by exposing ERPNext as a consumable shared service.

  **Contributors**

  * [Kevin Veen-Birkenbach](https://veen.world): ERPNext self-provider test fix

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Fri, 19 Jun 2026 00:00:00 +0000

infinito-nexus (10.0.0-1) unstable; urgency=medium

  * New engine-agnostic object-store service (*web-app-seaweedfs*, backed by either *SeaweedFS* or *MinIO*) that exposes a shared S3 endpoint, now consumed across 14 web-app roles for media and asset storage. Nextcloud, Decidim, Shopware, PeerTube, Pixelfed, Taiga, Fider, Akaunting, Mobilizon and Matrix route their uploads to the central bucket, each exercised end-to-end with a SeaweedFS consumer check that requires a fresh object key per upload.
  * Trusted-header single-sign-on bridges (wave 1) for *web-app-openproject*, *web-app-snipe-it*, *web-app-postmarks* and *web-app-yourls*, with *web-app-baserow* and *web-app-bookwyrm* migrating to the header bridge and dropping their LDAP configuration. The shared proxy (*sys-svc-proxy*) now strips forgeable identity headers on every location to close trusted-header injection.
  * ERPNext is exposed as a consumable shared service and renders the Keycloak SSO button on */login* under gunicorn *--preload*. Nextcloud is provisioned on the SeaweedFS S3 object store with a dedicated consumer end-to-end test and has its OIDC plugins gated as mandatory; the upgrade to version 34 is deliberately not carried out because of plugin incompatibilities, so it stays pinned at 33.
  * Extensive deploy and end-to-end hardening, much of it reproduced in Docker-in-Docker: native username and password login fallbacks for the no-SSO admin persona across Akaunting, Snipe-IT, YOURLS and OpenProject; OpenProject linked to the LDAP auth source for header SSO to fix a trusted-header 401, with all rails-runner Ruby extracted to dedicated files under *files/ruby/*; Shopware retries asset and theme builds against a restarting SeaweedFS; Mailu retries user creation and waits for antispam readiness before DKIM keygen; YOURLS creates its database schema on deploy; and OpenProject boots db:migrate under Ruby 4.0.
  * Routine maintenance: Docker image and git-reference bumps, skills-lock refreshes, and dependabot updates (actions/checkout 6 to 7, @playwright/test 1.60.0 to 1.61.0, eslint 10.4.1 to 10.5.0).

  **Contributors**

  * [Kevin Veen-Birkenbach](https://veen.world): object-store service, trusted-header SSO bridges, role fixes and review

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Fri, 19 Jun 2026 00:00:00 +0000

infinito-nexus (9.3.0-1) unstable; urgency=medium

  * New Penpot role (web-app-penpot, requirement 235) that ships the upstream Penpot design platform comprising frontend, backend, exporter and Redis at image version 2.5.4, wired into the central Keycloak via OIDC and into the central OpenLDAP. Native local-password login is available as a toggle that is disabled automatically under OIDC, with the native-login and registration flags derived from the SSO flag. A custom JVM truststore imports the Infinito self-signed CA so OIDC over TLS succeeds, and the role is exercised end-to-end with Playwright covering OIDC login, logout and project creation. The local subnet was moved to 192.168.105.192/28 to avoid a collision with the ERPNext range.
  * The Matrix role gains an ansible flavor (requirement 025) built on a unified compose template, with full Docker-in-Docker isolation, host-network mode cascaded to the addons and Jitsi, a central Postgres backend sharing the MATRIX_POSTGRES variables, and a central coturn aligned to the shared coturn variables for the MASH role. A dedicated MDAD runner image (python 3.13-slim with docker 28.0.4 CLI, migration validated against v2026.05.18.0) mounts the Infinito self-signed CA into both the runner and Synapse, and bootstrap now recovers cleanly from a stale marker.
  * Smaller role fixes: ERPNext bypasses the v16 setup wizard per app and clears the Frappe cache after applying social-login, LDAP and email configuration; Friendica strips a trailing slash from the base URL and makes the admin-follow Playwright test idempotent; Funkwhale starts its API with gunicorn and binds the port.
  * Routine maintenance: a lint that enforces a parameterised FROM in role Dockerfiles, a re-sync of the Claude settings allow-list with upstream, git references bumped to the latest semver tags (Bluesky 1.122.0 to 1.123.0, Bookwyrm v0.8.6 to v0.8.7), and Docker image version bumps across ERPNext v15.45.0 to v16.22.0, Friendica 2026.01 to 2026.05, Funkwhale 2.0.2 to 2.0.4, GitLab 19.0.1-ee.0 to 19.0.2-ee.0, Mattermost 11.7.2 to 11.8.0, OpenTalk v1.12.0 to v1.13.1, Prometheus v0.32.1 to v0.32.2, Shopware 3.6.0 to 3.7.0, MariaDB 12.2 to 12.3 and CoreDNS 1.14.3 to 1.14.4.

  **Contributors**

  * [Evangelos Tsakoudis](https://evangelostsak.com): Penpot role and its end-to-end test suite
  * [Kevin Veen-Birkenbach](https://veen.world): Matrix ansible flavor, role fixes and review

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Thu, 11 Jun 2026 00:00:00 +0000

infinito-nexus (9.2.0-1) unstable; urgency=medium

  * New ERPNext role that ships the upstream Frappe Framework v15 stack as a web-app-erpnext role — backend, frontend, websocket, scheduler and two queue workers — wired into the central Keycloak via a built-in Keycloak Social Login Key and into the central OpenLDAP via the LDAP Settings doctype. The three-variant matrix (SSO plus LDAP, no auth, LDAP only) is exercised end-to-end and a repo-wide lint caps Ansible task names at 120 characters.
  * New Jitsi Meet role that adds an oauth2-proxy gated meeting surface plus matching LDAP-variant Playwright coverage. The spec layout is reorganised into a shared module and per-scenario test files so future scenarios can be added without touching the monolith.
  * Outbound mail and Nix install resilience: the dev-nix role guards its set_fact against getent returning None on hosts without an nixbld group, and the host-stack mail health checks keep behaving cleanly on minimal images.
  * Routine maintenance: Docker image version bumps including Friendica 2026.01, eslint 10.4.0 to 10.4.1, and a dockerignore re-sync after the recent claude hooks addition.

  **Contributors**

  * Release maintained by Kevin Veen-Birkenbach, <https://veen.world>.

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Wed, 03 Jun 2026 00:00:00 +0000

infinito-nexus (9.1.0-1) unstable; urgency=medium

  * More robust Nix installer on Debian, Ubuntu, Fedora and CentOS systems: stale nixbld users from earlier installation attempts are cleaned up before the multi-user installer runs, and a long-standing crash on hosts where the nixbld group is absent has been resolved.
  * Outbound mail health checks no longer fail silently on minimal container images: the heartbeat-email script copes with images that ship without the hostname binary, and the SSL trust file path is detected automatically across Red Hat, Debian, Ubuntu, Alpine and NixOS layouts.
  * The Zammad helpdesk role completes its single-sign-on schema unification, with the variant matrix and Playwright test suite brought in line with the new shape.
  * Internal infrastructure cleanups: re-synchronised .dockerignore and a shifted weekly CI schedule.

  **Contributors**

  * Release maintained by [Kevin Veen-Birkenbach](https://veen.world).

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Mon, 01 Jun 2026 00:00:00 +0000

infinito-nexus (9.0.2-1) unstable; urgency=medium

  * Restores the Debian dev-image build by introducing the INFINITO_VENV_DIR SPOT and calling infinito via its absolute venv path in scripts/docker/entry.sh, so the post-`make install` version check no longer trips on the PATH that bash -lc clobbers via /etc/profile; also scopes the auto-update PR dedup fingerprint to the files a run actually committed (instead of the whole commit tree) so unrelated drift on main no longer forces a fresh PR on every daily cron, nests web-app-bluesky's nocheck markers under the acl block they describe, and rolls up dependabot bumps (actions/cache 4→5, actions/setup-node 4→6) plus routine Docker image / git ref refreshes.

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Fri, 29 May 2026 00:00:00 +0000

infinito-nexus (9.0.1-1) unstable; urgency=medium

  * CI pipeline pass: PR flow now gates the env-matrix via a new detect-affected-roles resolver (skipped on role-only diffs); push CI skipped when an open PR exists (with 20s race retry for bot branches); pip cache + python 3.12 across all 16 workflows, plus npm and ansible-galaxy caches where missing. Update bot now deletes its own branches when closing superseded PRs.

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Thu, 28 May 2026 00:00:00 +0000

infinito-nexus (9.0.0-1) unstable; urgency=medium

  * Collapses the parallel **oauth2** and **oidc** top-level blocks in every role *meta/services.yml* into a single *services.sso* block with a **flavor** discriminator (*oidc* | *oauth2* | *saml*, default *oidc*). Flavor-specific keys live under *sso.<flavor>* (*sso.oauth2.{origin,acl,allowed_groups}*, *sso.oidc.plugin*).

  **Removed**

  * The dedicated oauth2-proxy provider role (its directory under *roles/*) is deleted; the 5 sidecar templates and the per-consumer render task fold into **web-app-keycloak** (*templates/sso_proxy/*, *tasks/sso_proxy.yml*). **web-app-keycloak** now declares **provides: sso**.
  * The two mutual-exclusion guards (*tests/integration/iam/oauth2_oidc/test_mutual_exclusive.py*, *test_acl_mutual_exclusive.py*) — obsolete under the single-block schema.

  **Added**

  * *utils/roles/applications/services/sso.py* — central resolver for **is_enabled** / **is_proxy_gated** / **is_oidc_native** plus the oauth2 sub-values (*oauth2_origin_{host,port}*, *oauth2_acl*, *oauth2_allowed_groups*).
  * Three lookup plugins on top of the resolver: **sso** (single-app predicate access), **sso_proxy_consumers** (enumerates oauth2-flavored consumers), **sso_oidc_plugin** (renamed from **oidc_flavor**, Nextcloud selector).
  * *tests/lint/repository/no_legacy_sso_paths/* static guard against the legacy strings re-entering the source tree.

  **Renamed**

  * *services.<entity>.ports.local.oauth2* → *ports.local.sso*
  * *PORT_BANDS.local.oauth2* → *PORT_BANDS.local.sso*
  * **OAUTH2_PROXY_\*** env / Jinja vars → **SSO_PROXY_\***
  * *oauth2_proxy_cookie_secret* → *sso_proxy_cookie_secret*
  * **OAUTH2_SERVICE_ENABLED** + **OIDC_SERVICE_ENABLED** → single **SSO_SERVICE_ENABLED**
  * Playwright gating helpers (*isServiceEnabled*, *skipUnlessServiceEnabled*, *requireService*, *safeIsEnabled*, *safeSkipUnlessEnabled*) move from service *oidc* / *oauth2* to *sso*
  * *test_oauth2_contract.py* → *test_sso_contract.py*

  **Changed**

  * **sys-stk-backend**, **sys-svc-compose**, **sys-svc-proxy** and *plugins/filter/compose_volumes.py* now gate on **lookup(sso, …, is_proxy_gated)** instead of hand-combining **enabled** + **flavor**.
  * Dual-block resolution: **web-app-bookwyrm** pinned to **flavor: oauth2**; **web-app-gitea** and **web-app-friendica** keep **flavor: oauth2** with the existing *oauth2.acl.blacklist* (*/user/login*) and *oauth2.acl.whitelist* (federation + discovery endpoints) — operative oauth2-proxy gating preserved.

  **Migration**

  Every consumer of the legacy oauth2 / oidc service sub-trees, the **OAUTH2_PROXY_\*** env / Jinja vars, **OAUTH2_SERVICE_ENABLED**, **OIDC_SERVICE_ENABLED**, *oauth2_proxy_cookie_secret*, *oauth2_proxy_application_id*, *ports.local.oauth2*, the dedicated oauth2-proxy provider role, or the legacy *oidc* / *oauth2* Playwright gating helper service names must migrate to the unified **sso** shape. Full plan and decisions are archived under req 21.

  **Validated**

  End-to-end against *web-app-{keycloak,nextcloud,bookwyrm,gitea,friendica,dashboard}* in matrix variants 0 and 1; v2 also passes Playwright for the four target roles (modulo a pre-existing **KC_HOSTNAME**-drift workaround, separate concern).

  **Contributors**

  * [Kevin Veen-Birkenbach](https://www.veen.world/)

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Thu, 28 May 2026 00:00:00 +0000

infinito-nexus (8.0.5-1) unstable; urgency=medium

  * Adds --version / -V flag to the infinito CLI, drops a dead Compose-CLI reference link, and fixes the dev-deploy router so per-invocation routing knobs (apps, mode, purge, bundles, disable, full_cycle, variant) no longer leak into the persistent .env and the make-alias / env-var surface is unified 1:1.

  For older releases, see https://docs.infinito.nexus/

  Earlier releases:
    8.0.4 (2026-05-28)
    8.0.3 (2026-05-28)
    8.0.2 (2026-05-28)
    8.0.1 (2026-05-28)
    8.0.0 (2026-05-27)
    7.0.0 (2026-05-08)
    6.0.0 (2026-04-25)
    5.2.0 (2026-03-21)
    5.1.0 (2026-02-28)
    5.0.0 (2026-02-25)
    4.1.0 (2026-02-17)
    4.0.3 (2026-02-16)
    4.0.2 (2026-02-15)
    4.0.1 (2026-02-15)
    4.0.0 (2026-02-13)
    3.0.0 (2026-02-11)
    2.1.9 (2026-02-10)
    2.1.8 (2026-02-09)
    2.1.7 (2026-02-09)
    2.1.6 (2026-02-09)
    2.1.5 (2026-02-09)
    2.1.4 (2026-02-08)
    2.1.3 (2026-02-08)
    2.1.2 (2026-02-08)
    2.1.1 (2026-02-08)
    2.1.0 (2026-02-08)
    2.0.0 (2026-02-08)
    1.0.0 (2026-02-03)
    0.12.0 (2026-01-25)
    0.11.0 (2026-01-10)
    0.10.0 (2026-01-08)
    0.9.0 (2026-01-07)
    0.8.0 (2026-01-06)
    0.7.2 (2026-01-06)
    0.7.1 (2026-01-06)
    0.7.0 (2026-01-05)
    0.6.0 (2025-12-31)
    0.5.0 (2025-12-30)
    0.4.0 (2025-12-29)
    0.3.5 (2025-12-21)
    0.3.4 (2025-12-21)
    0.3.3 (2025-12-21)
    0.3.2 (2025-12-19)
    0.3.1 (2025-12-18)
    0.3.0 (2025-12-17)
    0.2.1 (2025-12-10)
    0.2.0 (2025-12-10)
    0.1.1 (2025-12-10)
    0.1.0 (2025-12-09)

 -- Kevin Veen-Birkenbach <kevin@veen.world>  Thu, 28 May 2026 00:00:00 +0000
