Commit Graph
685 Commits
Author SHA1 Message Date
kevinveenbirkenbachandClaude Opus 5.5 268b2a46f9 feat(web-app-wordpress): set admin_email, blogname and the FreedomTranslate options through the options map
WORDPRESS_OPTIONS now merges the role's own contributions (admin_email from the platform administrator, blogname from the design title while the design service is on, the freedomtranslate-wp config.options while that addon is on) under services.wordpress.options, so the inventory still wins and wordpress_options_checked guards the merged map. design.php no longer writes blogname, the addon task no longer writes its options, and WORDPRESS_OPTIONS_OWNED_ELSEWHERE drops the three. Every deploy now converges admin_email, and blogname while the design is on; a write WP-CLI reports as unchanged no longer counts as a change, and the FreedomTranslate readback runs after the options write.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 12:53:11 +01:00
kevinveenbirkenbachandClaude Opus 5.5 c3e2114e91 Merge branch 'fix/web-app-wordpress/permalink-options' into feature/store-i18n
Conflicts resolved: the inventory validator keeps store-i18n's custom-role
declaration skip and gains the open-subtree check; the suppression catalogue
keeps both new rows; WORDPRESS_PERMALINK_STRUCTURE gives way to the options
map. WORDPRESS_OPTIONS_OWNED_ELSEWHERE also lists the store-i18n writers the
merge brought along: the design logos (WORDPRESS_DESIGN_LOGOS keys) and the
freedomtranslate options; two custom-role validator tests pass open_paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 11:35:18 +01:00
kevinveenbirkenbachandClaude Opus 5.5 46e0bd1367 feat(web-app-wordpress): manage WordPress options from services.yml and pin the old permalink structure
Deploys since 197d74416d rewrote the blog's permalink structure to
/%postname%/, so links of the form /blog/YYYY/MM/DD/<slug>/ answer 404
(docs/security/user.md:24 fails the URL check).

services.wordpress.options maps WordPress option names to values; the role
reads each option, writes only the ones that drift and flushes the rewrite
rules after a write. permalink_structure is pinned to
/blog/%year%/%monthnum%/%day%/%postname%/, the value WordPress itself sets
for a subdirectory network. A guard refuses options the role writes
elsewhere, secret-named keys and values WordPress would not read back
unchanged.

The inventory validator accepts any child key under a services.yml key that
carries '# nocheck: inventory-open-subtree <reason>'; services.wordpress.options
carries it, everything else stays strict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 11:30:31 +01:00
kevinveenbirkenbachandClaude Opus 5.5 c5a69ee951 test(web-app-discourse): build the vars path from ROLE_FILE_VARS_MAIN
test_file_spot flagged the hand-written vars/main.yml path in the chat
integration test (gate pass 16).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 11:20:58 +01:00
kevinveenbirkenbach 3d7c9da23e Merge branch 'fix/front-inj-design/css-per-network' into feature/store-i18n 2026-11-04 10:53:49 +01:00
kevinveenbirkenbachandClaude Opus 5.5 01e27bfddb fix(sys-front-inj-design): store and link the role stylesheet per network
The design inject renders a role's style.css once per vhost but copied every
render to one shared CDN path, so the last render won. xwiki's stylesheet
embeds its own origin; on a multi-network node the onion render landed last
and every clearnet page loaded onion URLs, failing network-leak.spec.js
whenever the pass picked clearnet.

The first network keeps style.css, every further network gets
<label>-style.css (onion-style.css), keyed by the same rule the tls lookup
uses for the render (serving_network), and each vhost links its own file.
Single-network roles keep the exact old path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 10:46:58 +01:00
kevinveenbirkenbachandClaude Opus 5.5 09e855ae1b fix(web-app-discourse): enable the chat integration and the bridged partners' providers
The chat-integration plugin ships with core and defaults to
chat_integration_enabled: false, while the addon task was empty, so the
spec's first assertion could never pass and topic notifications never left
Discourse for Matrix or Mattermost.

The addon now turns on chat_integration_enabled plus the provider of every
partner the round deploys, idempotently, reads the settings back and fails
when one does not stick. The spec asks the plugin's real admin route
(/admin/plugins/discourse-chat-integration/providers.json; the old path is a
404 on v2026.10.0) and requires an enabled partner provider, since the
catalogue now lists disabled providers too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 10:44:19 +01:00
kevinveenbirkenbachandClaude Opus 5.5 7ac795c0cb test(web-app-gitlab): build the runner-stdin environment with select_autoescape
ruff S701 flagged the jinja2 Environment without autoescape in the single-boot rails test; select_autoescape(default_for_string=False) keeps the rendered Ruby stdin unescaped, the repository's pattern.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 21:56:06 +01:00
kevinveenbirkenbachandClaude Opus 5.5 81ef94e111 style(dev-python): fold the env filter onto one line after the rename
DEV_PYTHON_REQUIRES is shorter than the old name, so ruff format joins the
dict comprehension in test_install.py onto one line, and the CI format
check would report the drift.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 21:47:46 +01:00
kevinveenbirkenbach a70802eb1b Merge branch 'perf/web-app-gitlab/one-rails-boot' into feature/store-i18n 2026-11-03 21:43:54 +01:00
kevinveenbirkenbachandClaude Opus 5.5 a6d15a5ed4 test(web-app-moodle): mark the tempdir walk of the auth_oidc sync test
test_no_raw_project_walk flagged tree.rglob in files_in; the walk scans the test's own temporary directory, the exception the lint documents, so it carries the project-walk nocheck instead of the cached project helpers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 21:43:54 +01:00
kevinveenbirkenbachandClaude Opus 5.5 6e2166d825 perf(web-app-gitlab): converge root, platform admin and appearance in one Rails boot
Three separate 'rails runner -' calls each paid a full Rails boot (47 s, 42 s and 39 s on the echoserver production deploy). platform_admin.yml now runs one runner whose stdin is a small step driver (run_steps.rb) followed by the unchanged root-password, platform-admin and appearance scripts, each wrapped in its own InfinitoSteps.run block: every step keeps its own [CHANGED]/[FAIL] verdict prefixed with its name, exit(0) ends only that step, and the run fails naming every failed step. Rails 7.2.3.1 evals runner stdin at TOPLEVEL_BINDING inside an executor that passes SystemExit through; puts writes to the swapped $stdout while the logger writes to files. The design render and slurp move to tasks/utils/design.yml ahead of it, keeping the numbered includes ascending, and the file layout leaves feature/iam-profile-rbac's hunks on platform_admin.yml and 00_core.yml mergeable without conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 21:33:37 +01:00
kevinveenbirkenbachandClaude Opus 5.5 f7923b9f94 test(web-app-moodle): build the sync-body environment with select_autoescape
ruff S701 flagged the jinja2 Environment without autoescape in the auth_oidc sync test; select_autoescape(default_for_string=False) keeps the from_string shell body unescaped, as other tests do, and autoformat wrapped one long filecmp call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 21:31:24 +01:00
kevinveenbirkenbach 089ca505b6 Merge branch 'fix/web-app-moodle/oidc-plugin-missing' into feature/store-i18n 2026-11-03 21:16:59 +01:00
kevinveenbirkenbachandClaude Opus 5.5 9985fb6d08 style(dev-python): apply ruff format to the install test after the DEV_PYTHON_REQUIRES rename
The shorter variable name from 0bdb14cc1d (#1358) lets the dict comprehension fit on one line; make autoformat in gate pass 6 collapsed it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 21:16:47 +01:00
kevinveenbirkenbachandClaude Opus 5.5 279a449566 fix(web-app-moodle): sync the auth_oidc plugin from the image into the code volume
The entrypoint refills the shared code volume only when the release changes, so a volume bootstrapped at 5.3.0 by an image without auth_oidc never received the plugin from a later same-release image; galaxyserver's production deploy failed at the presence check although the image ships it (probe: /opt/moodle-source/public/auth/oidc exists). 04_oidc.yml now asserts the image ships the plugin, naming the image when it does not, and syncs it into the volume as the runtime user through a staging directory, only when diff -rq finds a difference, before the existing presence check. The OIDC upgrade.php run no longer hides a failed plugin install behind failed_when: false, matching the MCP and objectfs plugin tasks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 20:42:17 +01:00
kevinveenbirkenbach 42315cb065 Merge commit '0bdb14cc1d' (fix/dev-python/python-requires-env-name, #1358) into feature/store-i18n 2026-11-03 20:25:21 +01:00
kevinveenbirkenbach 9642ff13e4 Merge remote-tracking branch 'origin/main' into feature/store-i18n 2026-11-03 20:22:44 +01:00
kevinveenbirkenbachandClaude Opus 5.5 0bdb14cc1d fix(dev-python): name the requires-python hand-over outside the .env key space
#1347 passed the supported Python range to the remote install.sh as
INFINITO_PYTHON_REQUIRES. Every INFINITO_* name is a key of the generated
.env, so test_every_referenced_infinito_key_is_registered fails on main
for this one, which no registry entry can carry: its value is derived
from pyproject.toml's requires-python, the single source, and a default
in default.env would be a second copy of the range. The task environment
and the script now call it DEV_PYTHON_REQUIRES; behaviour is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 19:55:27 +01:00
kevinveenbirkenbachandClaude Opus 5.5 7ee231201d perf(web-app-stalwart): batch independent management JMAP reads into one request each
Every jmap_call.yml call pays about 1.5 s of fixed cost (container exec, curl, include, parse and assert tasks) before Stalwart does any work; the echoserver production deploy spent 98 s in 33 calls. Independent reads now share one request: Security, AllowedIp and Http in security.yml, Domain and MailingList in 15_aliases.yml, Application and OAuthClient in 02_webadmin.yml, Directory and Role in 06_oidc.yml. The rendered method calls and arguments are identical to the old requests, no JMAP call runs between a batched read and the facts it feeds, and the User-role assert still follows the directory writes. Writes stay in their own requests, because JMAP keeps processing later calls after a refused set. A refused read is now named by its object instead of c0. Projected saving: about 19 s per echoserver deploy, about 10 s on galaxyserver.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 19:54:58 +01:00
kevinveenbirkenbachandClaude Opus 5.5 eb36a54c44 perf(sys-svc-compose-ca): stop probing images on the swarm no-wrapper path
render_override gathered the metadata of every image (docker image inspect, a pull when missing, a docker run probe) before it checked wrap, and the swarm handler's --no-wrapper path threw the result away, because image_meta is read only behind 'if not wrap: continue'. The gather now runs only when wrap is true, so the swarm override is unchanged byte for byte. It pulled again the externals that registry_sync had just removed (WordPress#0 run 41303726270: 275 s over 64 calls).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:14:01 +01:00
kevinveenbirkenbachandClaude Opus 5.5 81e2be2204 fix(dev-python): hand requires-python to the remote script with a valid backreference
76adaeb9e8 wrote the regex_search group reference as '\1' inside the folded
task scalar. Jinja unescapes string literals, so the filter received \x01,
rejected it as an unknown argument and failed the dev-python task on every
remote host. The literal is now '\\1', and the unit test renders the task's
environment expression the way Ansible does instead of only checking that
the variable is named.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 15:55:22 +01:00
kevinveenbirkenbachandClaude Opus 5.5 76adaeb9e8 fix(dev-python): take the supported Python range from requires-python
bf9c5be173 capped the bootstrap at 3.14 with constants inside install.sh, a
second copy of the range pyproject.toml already declares. requires-python is
now the single source (>=3.11,<3.15): install.sh derives its floor and cap
from it, in the repository or, on a remote host where the script runs alone,
from INFINITO_PYTHON_REQUIRES that the dev-python role reads from the same
file; any other form fails loudly. The Dockerfile copies pyproject.toml
before the bootstrap, and pip now refuses 3.15 for the package as well.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 15:42:34 +01:00
kevinveenbirkenbachandClaude Opus 5.5 bf9c5be173 fix(dev-python): cap the bootstrap Python at the newest minor ansible-core supports
Fedora 45 defaults to Python 3.15. ansible-core 2.21, which the unpinned
`ansible` extra still resolves to (ansible 14.5.0 pins ~=2.21.5), calls
dataclasses._is_type, which 3.15 removed, so the Workspace (fedora:latest)
job died before the first playbook (run 41491308729).

install.sh now picks and installs the highest Python between
PYTHON_MIN_MINOR and PYTHON_MAX_MINOR (3.11 to 3.14) on apt, rpm and
pick_python_bin, and fails loudly when only newer interpreters exist. On
Fedora 45 it installs python3.14. Raise PYTHON_MAX_MINOR once ansible ships
ansible-core 2.22.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 15:06:41 +01:00
kevinveenbirkenbachandClaude Opus 5.5 e66c635fb6 perf(sys-svc-container): roll a single-replica swarm service without a stagger delay
swarmkit sleeps update_config.delay after the last slot too and then monitors for delay + 1s, so a one-replica service paid about 21 s per rollout for nothing to stagger (database stacks converged in 32 s instead of 8 s in WordPress#0's update pass of run 41303726270, about 400 s per row). A service whose replicas line is 'replicas: 1' now rolls with delay 0s; several replicas keep service_update_delay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 12:45:15 +01:00
kevinveenbirkenbachandClaude Opus 5.5 d6151aa559 fix(web-app-bigbluebutton): publish every container port once, on the proxy's upstream host
BBB's upstream compose template publishes nginx's 48081/48082/48087 on 127.0.0.1 and on 10.7.7.1. On Arch, Docker's nftables backend with the userland proxy off writes one identical 'MASQ TO OWN PORT' rule per binding and the second fails with 'rule exists' (moby 60109b06fb, PR #54031, first released in 29.10.0), so compose up failed in Moodle#3 of run 41303726270. compose_mods keeps only bindings on services.bigbluebutton.upstream_host or without a host address; nothing in the platform uses the loopback ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 12:34:22 +01:00
kevinveenbirkenbachandClaude Opus 5.5 486d85c29f perf(web-app-docs): claim a build only for a free slot and skip current translations
The builder handed every queued marker straight to its worker pool, whose own FIFO let a background latest build run ahead of the deployed:de build a visitor waited on (40-81 min in docs#1 of run 41303726270). A marker is now claimed only while a slot is free. build_language dequeues a translation that is already current instead of rebuilding it for 2-3 h on a duplicate request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 12:31:03 +01:00
kevinveenbirkenbachandClaude Opus 5.5 4f20a57a7e fix(web-app-docs): look at the shared volume again before answering 'Building'
On the swarm NFS volume a docs replica keeps a failed lookup until the parent's cached attributes expire, so a site another replica just renamed into place stayed invisible to it and it served the 202 'Building' page (lang en) after a sibling had answered 200 (docs#1 of run 41303726270). recheck_servable() lists the parent to force close-to-open revalidation and is called only on the path that would answer 202; ordinary page requests pay nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 12:27:05 +01:00
kevinveenbirkenbachandClaude Opus 5.5 cfc8cbb8fe fix(web-app-wazuh): give the manager room for the vulnerability feed and name failing API calls
At 2g the vulnerability-detection feed update thrashed the manager cgroup until the kernel OOM-killed wazuh-modulesd; Wazuh restarts no daemon, so the API answered every call with HTTP 500 (error 1017) and the API administrator grant failed in the update pass of run 41303726270 (Wazuh#1, swarm). The manager gets mem_limit 4g, and api_administrators.py raises '<METHOD> <path> answered HTTP <code>: <body>' instead of a bare 500 so a dead daemon shows up by name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 12:23:53 +01:00
kevinveenbirkenbachandClaude Opus 5.5 23c3da94cd Merge the ERPNext standard price lists into staging/alphaserver
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 17:28:07 +01:00
kevinveenbirkenbachandClaude Opus 5.5 a394d4bf6b fix(svc-net-wireguard): dial every endpoint at the IPv4 address it resolves to
The mesh is IPv4 only (60-byte header, IPv4 pools), yet wg.conf handed
wg the endpoint name, and wg dials the first getaddrinfo answer, an AAAA
on a dual-stack name. A host with broken IPv6 then never handshook
(alphaserver, 2026-11-01). Resolve every dialled endpoint with getent
ahostsv4 on the dialling host, render that literal, measure the path MTU
against it, and fail before rendering when a name has no IPv4 address.
Also set the tunnel MTU on a live interface, since wg syncconf drops it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:53:01 +01:00
kevinveenbirkenbachandClaude Opus 5.5 ac4a8f45e2 fix(web-app-erpnext): seed the standard price lists the skipped setup wizard creates
The deploy bypasses ERPNext's setup wizard and with it install_defaults, so a site without the store's GUI fixtures had no Standard Selling or Standard Buying price list and every Item Price insert failed with "Could not find Price List: Standard Selling". company.py now creates both after the Company, insert-if-missing, with the wizard's selling and buying flags and the Company's own currency; Price List.on_update fills the Selling and Buying Settings defaults.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:45:53 +01:00
kevinveenbirkenbachandClaude Opus 5.5 0aa5d1386b fix(web-app-erpnext): seed the tree roots the skipped setup wizard creates
The deploy bypasses ERPNext's setup wizard and with it install_fixtures, so a site without the store's GUI fixtures had no root Item Group and every consumer creating one failed with "Could not find Parent Item Group: All Item Groups". company.py now seeds All Item Groups, All Customer Groups and All Supplier Groups as root groups, insert-if-missing like All Territories.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 13:51:07 +01:00
kevinveenbirkenbach 8e3aac5cae Merge branch 'fix/web-app-erpnext/api-bot-extra-roles' into feature/store-i18n 2026-11-01 12:34:41 +01:00
kevinveenbirkenbachandClaude Opus 5.5 4afa1183c1 feat(web-app-erpnext): grant consumer-declared roles to the API bot
Consumers that build on the ERPNext site from their own repositories (web-app-cst-store, web-app-cst-security-check) now declare the roles they need on erpnext-api-bot in services.erpnext.api_bot.extra_roles instead of granting them themselves. Every deploy grants them beside System Manager and the Server Scripts role, creating an empty Role first when the site does not hold it yet, so this role stays the only writer of the bot's roles and keys. Port of the api-bot part of 067 from feature/iam-profile-rbac (3e5acbd104).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 12:32:42 +01:00
kevinveenbirkenbachandClaude Opus 5.5 4f03e7f9f8 fix(web-app-stalwart): create accounts without credentials while SSO is on
Under SSO the OIDC directory is the authentication directory, and Stalwart refuses any password credential for its accounts ("Cannot set credentials for accounts in an external directory"). Every create carried one, so an account added after SSO was switched on never came into existence: the echoserver deploy failed on the keycloakbootstrap bot right after the 0.16 to 1.x migration, and a new person would have been skipped silently. Under SSO the create now carries no credentials and the password update is skipped; existing accounts come back as primaryKeyViolation with their id, as without SSO. Any remaining refusal fails the deploy. Closes the accounts entry of the role's TODO.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 12:11:03 +01:00
kevinveenbirkenbach ca722a00b7 Merge branch 'fix/web-app-dashboard/build-once' into feature/store-i18n 2026-11-01 10:05:32 +01:00
kevinveenbirkenbachandClaude Opus 5.5 c1d1d377c6 fix(web-app-dashboard): build the dashboard image once for all network instances
Every network instance (dashboard, dashboard-onion) carried the full build block, so compose built the same dashboard_custom tag twice in parallel. In CI run 41261531965 (odoo#0) dockerd 29.9.2 died with SIGSEGV inside BuildKit during that double build and stopped the stack. Now only the first instance builds; its siblings run the same tag with pull_policy never in compose, the pattern bookwyrm, espocrm and openproject use. Swarm builds, labels and pushes the image once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 09:58:58 +01:00
kevinveenbirkenbach 7e3c290d51 Merge branch 'feature/stalwart/forward-platform-admin-mail' into feature/store-i18n 2026-11-01 09:42:13 +01:00
kevinveenbirkenbachandClaude Opus 5.5 629fe2375d style(tests): apply ruff format to test_migrate_v016
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 09:42:06 +01:00
kevinveenbirkenbach d2443e3451 Merge branch 'fix/web-app-stalwart/migrate-verify' into feature/store-i18n
# Conflicts:
#	roles/web-app-stalwart/files/shell/migrate_v016.sh
2026-11-01 09:35:07 +01:00
kevinveenbirkenbachandClaude Opus 5.5 33b635bca7 test: satisfy the role-file SPOT and cached-read lints in two new tests
test_openproject_redirect_uris builds the vars path from ROLE_FILE_VARS_MAIN, and test_store_migration_job reads its tempdir stub log under a cache-read nocheck, because the log is rewritten between reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 09:33:20 +01:00
kevinveenbirkenbachandClaude Opus 5.5 bd233bdb96 fix(web-app-stalwart): migrate 0.16 stores with stalwart-migrate 1.0.4 and verify every account
stalwart-migrate 1.0.0 failed the final verify on echoserver (6 rows) and galaxyserver (2 rows), all x:BlockedIp entries that expire when the tool starts 1.x, so a final run could never pass; 1.0.0 to 1.0.3 also skip accounts without a change counter. The pin moves to 1.0.4, final runs with --verify-full instead of a sample of 16 accounts, and a failed final restarts 0.16 and prints the error and verify rows of the new report phase, since final-report.txt is only written by a passing run. The README describes how to read the rows and when to start over.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 09:26:59 +01:00
kevinveenbirkenbach fb284f797c Merge branch 'perf/ci/nextcloud7-budget' into feature/store-i18n 2026-11-01 09:20:19 +01:00
kevinveenbirkenbach d6530ca3fe Merge commit 'e277edf6e7' into feature/store-i18n 2026-11-01 09:20:09 +01:00
kevinveenbirkenbachandClaude Opus 5.5 b386119642 perf(email): resolve only the requested key in lookup('email')
Every lookup('email') resolved all 15 settings. On swarm workers and the nfs-server of Tor rows that took 22-26 s per call, even for tasks that only read external or timeout and then skip (CI run 41261531965, Nextcloud#7: ~600 s over both passes, the job ran out of its budget). The lookup takes key= now and stops after that entry; the four call sites and mail_recipient that need only external or timeout use it. Without key= the result is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 09:19:28 +01:00
kevinveenbirkenbachandClaude Opus 5.5 e277edf6e7 fix(web-app-keycloak): close self-service email takeover
Users could change their Keycloak email without verification, and every deploy stamped any address an inventory user held as verified. An address no Keycloak account holds (for example erpnext-api-bot@) could thus be claimed and reach apps that link SSO logins by email. The user profile now lets only admins edit the email, and email_verified.py stamps only an address that equals the inventory address.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 08:57:17 +01:00
kevinveenbirkenbachandClaude Opus 5.5 925809c377 Merge branch 'fix/web-app-nextcloud/openproject-oauth-roundtrip' into feature/store-i18n
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 08:43:54 +01:00
kevinveenbirkenbachandClaude Opus 5.5 c3f482f23f fix(web-app-nextcloud): register the OpenProject redirect URI of every Nextcloud network
CI run 41261531965 Nextcloud#3 (multi network) tested Nextcloud on its onion address. integration_openproject builds redirect_uri from the request host, so it sent http://next.cloud.<onion>.onion/.../oauth-redirect, but ensure_nextcloud_storage.rb registered only the URIs built from the canonical NEXTCLOUD_URL; Doorkeeper answered every /oauth/authorize with 400 invalid_redirect_uri (proxy log: 6x 400, OpenProject web log status=400 user=6, trace page 'The requested redirect uri ... doesn't match client redirect URI').

NEXTCLOUD_NETWORK_BASE_URLS comes from lookup('network_urls', NEXTCLOUD_URL), like the Zammad and GitLab base URLs, and reaches the provisioning input as nextcloud_base_urls; the script registers every base URL with the apps/ and index.php/apps/ paths, canonical first, and the base URLs feed the converge digest so an install switched to multi network re-provisions. Clearnet-only and tor-only rows register the same string as before. Zammad, GitLab and Mattermost integrations still register only the canonical URI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 08:43:41 +01:00
kevinveenbirkenbachandClaude Opus 5.5 5c2bf23063 fix(web-app-stalwart): run the 0.16 store migration as a polled async job under a lock
galaxyserver deploy -6 ran the migration as one plain command for 28:30 and then lost its SSH channel (ServerAlive 15x3 ends it after 45 s of silence): the deploy ended UNREACHABLE while the script kept running on the host unobserved, possibly through final, which stops Stalwart and swaps the databases.

The task now runs migrate.sh as an async job polled every STALWART_MIGRATE_ASYNC_POLL (60 s); each poll is a short new SSH call and a failed poll is retried, so a dropped connection no longer ends the deploy. The budget STALWART_MIGRATE_ASYNC_TIME is lookup('timeout', 21600) (6 h, scaled by TIMEOUT_FACTOR); past it Ansible kills the job and the task fails loudly. Every phase but status takes a non-blocking flock on migrate-v016/migrate.lock and stops while another run holds it or a stalwart-migrate container of an earlier run still runs, so two migrations never touch the same store and dump. A kill inside final leaves Stalwart stopped and the next run refused while the orphan container lives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 08:39:36 +01:00