Commit Graph
195 Commits
Author SHA1 Message Date
kevinveenbirkenbach 470d78da01 Merge branch 'main' into feature/designer 2026-10-17 12:08:36 +02:00
kevinveenbirkenbachandClaude Opus 5.5 159d7e6d01 test(web-app-openproject): accept a single probe before the gate deadline
`deadline` is whole seconds from `date +%s`. A run that starts just
before a second boundary reaches the deadline after the first probe and
one `sleep 1`, so the gate correctly gives up after one probe while the
test demanded two. With a stubbed clock (1000, 1000, 1002) the gate
probes once and prints `deadline of 2s reached`. The deadline message
already proves the pending migration held the entrypoint back; require
at least one probe.

Reported by CodeRabbit on #979.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-17 11:16:43 +02:00
kevinveenbirkenbachandClaude Opus 5.5 c66b79ce82 fix(web-app-openproject): probe migrations without creating tables
Swarm Nextcloud#3 on main 748189e890 (job 117182318941) wedged the
OpenProject schema: the convergence check gave up after 74 attempts
while the seeder kept exiting 1, first with
`relation "ar_internal_metadata" already exists` from its structure
load, then with `relation "announcements" already exists` on every
`db:migrate`.

The seeder (`docker/prod/seeder`) loads `db/structure.sql` through
psql, statement by statement, when `\dt` finds no relation. In swarm,
`depends_on` is ignored, so web and worker start alongside it and poll
`await_migrations.sh`. That gate called
`rails db:abort_if_pending_migrations`, which runs
`migration_context.open`, i.e. `Migrator.new`, whose constructor
executes `schema_migration.create_table` and
`internal_metadata.create_table` (Rails 8.0.4,
activerecord/lib/active_record/migration.rb:1430-1431). The seeder
saw an empty database at 05:33:20; by 05:33:38 the gate had created
`ar_internal_metadata`, the structure load aborted at line 255 with
everything alphabetically before it already committed, and every
later attempt took the migrate path into tables that already existed.

Probe with `rails db:migrate:status` instead. It aborts while
`schema_migrations` is missing and otherwise only reads the recorded
versions (database_tasks.rb:303, migration.rb:1318); the gate treats
any `down` row as pending.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-17 10:17:39 +02:00
kevinveenbirkenbach bb4646cfc0 Merge branch 'main' into feature/designer 2026-10-16 22:23:40 +02:00
kevinveenbirkenbachandClaude Opus 5.5 3855b75c5f fix(sys-ctl-hlth-journalctl): read long and repeated MESSAGE fields
Without `--all`, `journalctl --output=json` encodes a field over 4096
bytes as `null`, so a long host error matched nothing and the check
passed. A repeated `MESSAGE` field arrives as a JSON array of its
values, and `bytes()` on that array raised `TypeError`, which aborted
the whole check.

Pass `--all`, and tell a byte array (all integers) from an array of
field values, decoding each value on its own.

Reproduced against the host journal: a 5000-byte error line read back
as `"MESSAGE":null` and produced no match; an entry with two `MESSAGE`
fields, sent over the native journal socket, read back as
`["error first","error second"]` and crashed `host_errors` with
`TypeError`. With the fix both are reported.

Also clear the two test-lint findings of this PR:
`test_comments_valid` rejects the narrative comment above the
`journalctl` Popen, so it goes; the stderr-to-file choice stays, because
an unread stderr pipe that fills up would block `journalctl` while
stdout is still being read. `test_no_uncached_file_reads` flags the
read of the stub's argument file, which lives in the test's tempdir and
is written per run, so the path-keyed `utils.cache.files.read_text`
could serve a stale body; the read opts out with
`# nocheck: cache-read tempdir fixture`, as the other stub-driven
script tests do.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 22:11:09 +02:00
Prageeth Panicker dc3d30acaa fix(sys-ctl-hlth-journalctl): skip container logs in the host journal check
Problem:
Every compose service logs to journald through
sys-svc-container/templates/base.yml.j2. The daily journal health
check grepped the whole journal for "error", so routine application
errors from containers triggered the host alert (email/Telegram) and
buried real host problems.

Change:
- Read the last day of the journal as JSON (MESSAGE, CONTAINER_ID,
  SYSLOG_IDENTIFIER, _SYSTEMD_UNIT) and skip every entry that carries
  a CONTAINER_ID field. Docker daemon and system errors are still
  reported.
- Report each match as one readable "source: message" line instead
  of raw journal output.
- Stream the journalctl output line by line instead of buffering a
  whole day of logs in memory.
- Exit 2 with journalctl's stderr if journalctl itself fails, instead
  of silently reporting "no errors".
- Rewrite the check in Python (files/python/script.py, deployed by
  sys-service like the other sys-ctl-hlth roles) and decode non-UTF-8
  messages.
- Fix the success message, which claimed "All docker containers are
  healthy".

Impact:
Container log errors no longer raise this host alert on any
installation. Container health stays covered by sys-ctl-hlth-container;
application log analysis belongs in a log platform such as Wazuh.
system_service_suppress_flush is unchanged until a deploy confirms
that no host errors remain.

Tests:
Unit tests run the script against a fake journalctl (8 cases):
container error ignored, daemon error reported, identifier fallback,
non-UTF-8 message, journalctl failure, many container lines streamed
past, clean journal passes. The core cases fail against the previous
script.
2026-10-16 14:41:05 +00:00
kevinveenbirkenbachandClaude Opus 5.5 130789d9b9 fix(litellm): give the admin UI the corporate logo through LiteLLM's own ui_theme_config
The web-app-litellm stylesheet swapped the navbar image with content: url(), so LiteLLM's own logo setting stayed empty and its UI theme page showed nothing. svc-ai-litellm now renders litellm_settings.ui_theme_config.logo_url from LITELLM_UI_LOGO_URL, the design lockup of web-app-litellm, only when that role is deployed with design and a logo; the stylesheet override is removed. The design spec asserts the served setting, the navbar src, the absence of a CSS swap, the loaded wide lockup and the value on the UI theme page; the config render test covers both the logo and the no-logo case.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 10:45:04 +02:00
kevinveenbirkenbachandClaude Opus 5.5 1c35cdef91 style: apply the autoformat output main drifted from
`make quality-high` on main (fea6f31d9f) rewrote two files in its
autoformat step:

- roles/sys-service/handlers/refresh_systemctl_service.yml: the
  handler's `when` was wrapped by hand; the formatter wraps it at line
  width. Both forms parse to the same YAML.
- tests/unit/python/roles/web-app-nextcloud/test_onlyoffice_check_deadline.py:
  two over-long assertIn calls that ruff format splits.

A second autoformat run leaves the tree unchanged, so main is stable
under the formatter again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 01:35:21 +02:00
kevinveenbirkenbachandClaude Opus 5.5 5359b99ed4 style(tests): reflow the onlyoffice deadline assertions the way autoformat writes them
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 23:33:58 +02:00
kevinveenbirkenbach a575b86469 Merge branch 'main' into feature/designer 2026-10-15 22:46:09 +02:00
kevinveenbirkenbach ec2a4b96f0 Merge pull request #930 from infinito-nexus/fix/web-app-nextcloud/onlyoffice-settings-race
fix(web-app-nextcloud): write onlyoffice's server URL last and recheck it
2026-10-15 22:36:45 +02:00
kevinveenbirkenbach f126b24693 Merge branch 'main' into feature/designer 2026-10-15 16:23:42 +02:00
kevinveenbirkenbach 06a74e7658 Merge pull request #869 from infinito-nexus/fix/web-app-stalwart/relay-rule/793
fix(web-app-stalwart): authenticate bots with an app password under SSO
2026-10-15 15:42:19 +02:00
kevinveenbirkenbach fb125e163d Merge branch 'main' into feature/designer 2026-10-15 11:32:54 +02:00
kevinveenbirkenbachandClaude Opus 5.5 cce4373abd fix(web-app-nextcloud): bound the onlyoffice check by a wall-clock deadline
`retries: 30` with `delay: 10` only bounded the wait while each
`occ onlyoffice:documentserver --check` failed fast. onlyoffice v10.2.1
gives the health check and the version command 60 s each and the
conversion 120 s, so a document server that accepts connections but
does not answer held the deploy for up to two hours before it failed.

The retries now also stop once a deadline of
`lookup('timeout', 300)` seconds, set right before the check, has
passed. A per-attempt `timeout:` would not bound the total: ansible-core
2.21 leaves the `until` loop on the first timed-out attempt, so only a
limit below onlyoffice's 60 s would end a hanging server's loop.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 04:04:33 +02:00
kevinveenbirkenbach 2b43cd666f Merge remote-tracking branch 'origin/main' into feature/designer 2026-10-15 02:39:36 +02:00
kevinveenbirkenbach 1967a218d0 Merge remote-tracking branch 'origin/main' into dependabot/pip/ruff-0.17.0 2026-10-15 01:24:32 +02:00
kevinveenbirkenbachandClaude Opus 5.5 47565781ea style(python): import datetime as dt for ruff 0.17
ruff 0.17.0 adds `datetime` -> `dt` to the default aliases of
flake8-import-conventions, and the project selects ICN, so lint-python
failed with ICN001 in seven files.

The files now import `datetime as dt`. ruff's unsafe fix also renamed
two local names in utils/github/playwright_summary.py into the alias:
the `_to_iso_z` parameter and the parsed value in
`_parse_iso_timestamp`. Both are now called `moment`; with the bare fix,
nine tests of test_playwright_summary.py failed with UnboundLocalError
and AttributeError.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 01:24:31 +02:00
kevinveenbirkenbach e8ffe2536e Merge remote-tracking branch 'origin/main' into HEAD 2026-10-15 01:07:59 +02:00
kevinveenbirkenbach 7344565df1 Merge remote-tracking branch 'origin/main' into fix/web-app-nextcloud/onlyoffice-settings-race 2026-10-15 01:07:41 +02:00
kevinveenbirkenbach 365ea9e764 Merge pull request #911 from infinito-nexus/fix/web-app-prometheus/blackbox-verify/851
fix(web-app-prometheus): verify publicly trusted probe targets while the private CA is missing
2026-10-15 00:17:47 +02:00
kevinveenbirkenbachandClaude Opus 5.5 bfaa834f29 fix(web-app-nextcloud): write onlyoffice's server URL last and recheck it
The role writes onlyoffice's settings with one occ call per key,
DocumentServerUrl first and jwt_secret fourth. Nextcloud loads app
config once per process, so a cron.php that starts inside that batch
runs onlyoffice's EditorsCheck job, registered when the app was
enabled, with the document server URL but without the secret. The
document server answers "Invalid token", EditorsCheck stores it in
settings_error, and onlyoffice stops loading its Files integration;
EditorsCheck itself skips every later run while that error is set.

Seen in run 38455782184 (Nextcloud#4, swarm): cron.php started at
06:50:00 inside the batch that ran from 06:49:59 to 06:50:04, logged
"Invalid token" at 06:50:22, and both Playwright passes found .docx
files offered for download only.

DocumentServerUrl is now written last, so a process that cached the
config before the batch ended sees no URL and EditorsCheck returns
early. After the batch, `occ onlyoffice:documentserver --check` sets
settings_error from a fresh check: a redeploy clears an error latched
earlier, and a document server that stays unreachable fails the
deploy. A unit test keeps the URL last.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 13:34:52 +02:00
kevinveenbirkenbach a3c8f31d07 Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	roles/web-app-stalwart/README.md
2026-10-14 13:34:01 +02:00
kevinveenbirkenbachandClaude Opus 5.5 15bba26ef3 fix(web-app-prometheus): verify publicly trusted probe targets while the private CA is missing
The readiness module http_ready rendered insecure_skip_verify from
TLS_MODE and lookup('ca_injected', application_id) (#851). The lookup is
true only when Prometheus itself has a domain, TLS and the self-signed
mode, so in a self-signed deployment where Prometheus runs in another
mode the exporter had no CA file and skipped verification. All targets
share the module, so a target with a publicly trusted certificate was
probed without verification too, and anyone on the probe's path could
answer for a failed application.

http_ready now always verifies. A second module, http_ready_private_ca,
skips verification, and only the targets that need it select it: those
in the self-signed mode while the CA is not mounted into Prometheus,
through the __param_module label, which overrides the job's module for
that target. The default self-signed deployment mounts the CA and keeps
every target on the verifying module.

Verified on a local self-signed Tor round, where the CA is mounted:
the rendered blackbox.yml holds http_ready with insecure_skip_verify:
false and http_ready_private_ca with true, and no target of the scrape
configuration carries the module label. All targets of that round are
http onion names, so the https path itself ran only in the unit tests:
tests/unit/python/roles/web-app-prometheus pass, among them a publicly
trusted target that keeps the verifying module while the CA is missing,
a self-signed target that switches to the private-CA module, and the
verifying module rendering insecure_skip_verify: false without the CA.
Against the templates of main the three new cases fail.

Closes #851

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 21:31:58 +02:00
kevinveenbirkenbachandClaude Opus 5.5 ba8f4b5f3a fix(sys-svc-container): keep the installed Docker apt key when its refresh is refused
A local deploy stopped four times in two days in "Ensure Docker apt key
and sources are consistent" (#849). apt_repo.sh downloads the Docker GPG
key on every run, and when download.docker.com refused the connection,
curl's four attempts were over within about six seconds and the script
returned 1, although the key from an earlier run was installed and is
all the following apt operations need.

install_key now fails only when no key is installed. With a key in
place, a refused download prints a warning and keeps it. A downloaded
key that differs still replaces the installed one, and the 0644 mode is
enforced in every case. The keyring path became an argument:
ensure-key-and-sanitize passes the keyring it already hands to
sanitize_sources, so the role's call still resolves to
/etc/apt/keyrings/docker.asc, and a test can point the script at a
temporary keyring.

Verified: tests/unit/python/roles/sys-svc-container/files/test_apt_repo.py
stubs curl and sudo. A refused download keeps the installed key and ends
with changed=0, a refused download without a key exits 1, and a new key
replaces the installed one. A copy of the script whose keep branch always
errors, the old behaviour, fails the first test. Two local deploys ran
the new script with rc 0 and an empty stderr and left the installed key
untouched. shellcheck is clean.

Not verified live: the refused download itself, which needs
download.docker.com to refuse a connection during a deploy. A host
without a key still needs the download and still fails when it is
refused.

Closes #849

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 21:31:47 +02:00
kevinveenbirkenbach 8c82e5262e Merge branch 'main' into feature/designer
# Conflicts:
#	roles/web-app-suitecrm/files/session.yaml
#	roles/web-app-suitecrm/meta/volumes.yml
2026-10-13 15:27:59 +02:00
kevinveenbirkenbach 29fee68f3c Merge branch 'main' into HEAD 2026-10-13 15:10:08 +02:00
kevinveenbirkenbachandClaude Opus 5.5 0709de6fa1 feat(web-app-pgadmin): bring the corporate design to pgAdmin and converge its administrator
templates/style.css.j2 maps page, panes, object tree, tabs and primary
actions onto the design tokens and makes the menu bar the brand frame;
templates/design.js.j2 follows pgAdmin's native theme switch. The logo
and login-logo slots meta/services.yml declares sit on frame chrome, and
with design on templates/env.j2 sets PGADMIN_CONFIG_APP_NAME to the
configured title.

pgAdmin keeps its internal user in its own database, and
PGADMIN_DEFAULT_PASSWORD only seeds the first start. tasks/main.yml now
flushes the compose handlers and, on the stack host outside server
mode, runs tasks/administrator.yml with files/python/administrator.py:
it creates the internal administrator if missing, otherwise realigns
password, active and locked state, failed login attempts and the
Administrator role. The identity is the platform administrator as on
feature/store-i18n: PGADMIN_ADMIN_EMAIL and PGADMIN_ADMIN_PASSWORD in
vars/main.yml, meta/users.yml declares the user, and meta/secrets.yml,
whose only credential the role no longer reads, is gone.
tests/unit/python/roles/web-app-pgadmin/files/test_administrator.py
covers the script. meta/services.yml names the container pgadmin for
PGADMIN_CONTAINER_ADDRESS.

files/playwright/test-design.js checks tokens, that the administrator
signs in with that password and holds admin rights, panes, tree, tabs,
the menu bar frame and its focus indicator, primary actions, the theme
switch, logo, favicon and title, and captures the gallery against the
role's own database server; templates/playwright.env.j2 hands the
platform administrator, the design URLs and the database to it.
README.md links the gallery; TODO.md records the Gravatar request and
the memory pressure at the 256m limit.

The design was verified green on the compose stack (10 passed); the
switch to the platform administrator is checked statically only, the
first live run is the verification on the merged state.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 13:27:27 +02:00
kevinveenbirkenbachandClaude Opus 5.5 9a797a805a feat(web-app-openwebui): style Open WebUI through its custom.css and converge a native administrator
Open WebUI loads /static/custom.css on every page, so tasks/00_core.yml
renders templates/custom.css.j2 when design is on and meta/volumes.yml
mounts it read-only there; it maps the sign-in page, the chat shell,
Markdown, tables and code onto the design tokens and leaves the OLED
dark theme to the user. templates/design.js.j2 mirrors the native theme
setting into data-design-theme. meta/services.yml keeps logo and title
replacement off, so Open WebUI keeps its own branding.

With sso and ldap both off, tasks/utils/admin.yml runs files/python/
administrator.py, which creates the native administrator or resets its
password and promotes it, so the native sign-in has an account; the
identity is lookup('users', 'platform_administrator') as on
feature/store-i18n, declared in meta/users.yml next to the user the
role still reads elsewhere. tests/unit/python/roles/web-app-openwebui/
files/test_administrator.py covers the script with fake values.

files/playwright/test-design.js checks tokens, sign-in page, chat shell,
a stored answer, the native theme setting, the provisioned
administrator, the in-house custom.css, logo and title, and captures
the gallery; files/playwright/_shared.js waits for editable password
fields. README.md links the gallery; TODO.md records the open
self-registration with sso and ldap off and the bot password and
bearer tokens in container exec argv.

The design was verified green on the compose stack (10 passed); the
switch to the platform administrator is checked statically only, the
first live run is the verification on the merged state.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 13:25:44 +02:00
kevinveenbirkenbachandClaude Opus 5.5 5efd82b9c4 feat(web-app-listmonk): carry the design in Listmonk's appearance settings
Listmonk serves its admin and public pages with the custom CSS and JS it
keeps in the settings table, so the role writes the design there:
tasks/03_design.yml, included after the settings reaper in
tasks/02_manager_ops.yml, pipes templates/design/appearance.sql.j2 into
psql. With design on it writes the marked admin stylesheet, admin script
and public stylesheet, but only over an empty value or one that starts
with the role's own marker, so custom CSS an operator wrote stays; with
design off it empties only its own values. The same SQL sets
app.logo_url, app.favicon_url and app.site_name from the design lookup
and restores the former values when design is off; those keys move from
LISTMONK_SETTINGS to LISTMONK_DESIGN_BRANDING in vars/main.yml.

templates/design/admin.css.j2 maps the colors of the Bulma and Buefy
admin onto the design tokens, templates/design/public.css.j2 does the
same for the subscription pages, and templates/design/admin.js.j2 keeps
favicon and title on the configured ones while the single-page app
rewrites its head. meta/services.yml declares the 182x34 logo slot of
the navbar; the emptied files/style.css goes.

files/playwright/test-design.js checks tokens, public pages, the
administration, navigation and tags, the code editors, logo, favicon and
title, that the role writes only its marked settings, and captures the
gallery; the native form takes ADMIN_NATIVE_PASSWORD. The unit test
renders the SQL for design on, design off and design off without a
fallback. README.md links the gallery; TODO.md records the settings task
that always reports changed, the secrets it renders into the shell argv,
and the logo URLs taken unguarded when the logo replacement is off.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 02:01:02 +02:00
kevinveenbirkenbachandClaude Opus 5.5 f363c9d075 feat(web-app-funkwhale): theme the instance and converge its administrator
Funkwhale stores its theme in global preferences, so
files/python/design_preferences.py sets the instance name, primary,
accent and background color, ui__custom_css from
templates/custom.css.j2 and the logo and banner as own uploads. It only
writes while ui__custom_css is empty or starts with the role's marker;
custom CSS an administrator wrote reports FOREIGN and stays untouched,
and design off takes back only the role's own values. tasks/main.yml
flushes the compose handlers and includes tasks/01_manager.yml, and
templates/compose.yml.j2 names the api container so container exec can
reach it.

tasks/01_manager.yml probes the administrator with
files/python/admin_state.py (MISSING, DRIFT or OK for active, staff,
superuser and password), creates it with fw users create --superuser
or realigns password and rights with fw users set, the password always
through the task environment. The account is platform_administrator:
meta/users.yml and the ADMIN_* lines of templates/playwright.env.j2 are
identical to feature/store-i18n (3c72c2002a), so the host
administrator's sudo password never becomes a Funkwhale password and
the later merge keeps these hunks clean. store-i18n provisions no
Funkwhale administrator at all.

templates/design.js.j2 follows Funkwhale's own theme switch, and
files/playwright/test-design.js checks the tokens in both modes, the
instance theming with palette, custom CSS and pod name, the start page
hero and sign-in card, the signed-in interface, a theme picked in
Funkwhale, logo, favicon and title, the platform administrator's sign-in
with administration rights and a visitor's refused sign-up, and captures
the gallery. Unit tests cover
both scripts. TODO.md records the Typesense key in the sidecar's argv
and the icons the front fetches from api.iconify.design.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 23:51:09 +02:00
kevinveenbirkenbachandClaude Opus 5.5 d6970cfc58 feat(web-app-bookwyrm): ship the corporate design as a BookWyrm theme
BookWyrm styles its pages from compiled Bulma themes, so the design
arrives as one of them. files/scss/infinito.scss maps the theme onto the
design tokens; the Dockerfile copies it into css/themes/ and
tasks/00_core.yml stages it in the build context and triggers the build.
The old files/style.css overrides are dropped, the theme replaces them.

tasks/01_site.yml reads the generated logos and runs
files/python/site_design.py through manage.py shell, with every value
handed over in the task environment. The script provisions the
administrator as superuser with the admin and moderator groups and
realigns its password, sets the site name and the navigation logo, touch
icon and favicon, registers the corporate theme and makes it the default
only on a fresh site, and closes the setup wizard. With design switched
off it removes only its own theme and logos. It prints BOOKWYRM_RESULT,
which changed_when and failed_when read.

templates/design.js.j2 mirrors the light or dark theme a user picks in
BookWyrm into data-design-theme, meta/csp.yml admits scripts, styles and
fonts from the object store that serves the static files, and
meta/services.yml declares a 100x100 navigation logo slot.
files/playwright/test-design.js checks the tokens in both modes, the
theme's page, primary action, selection and text, the divider, the
closed setup wizard, an upstream theme a user picks, title and logo, and
captures the gallery.

TODO.md records the registration keys BookWyrm 0.9.3 never reads, the
stale reason that blocks the administrator persona, the SeaweedFS test
that only signs in through Keycloak, and the missing static files of the
variant without an object store.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 22:30:15 +02:00
kevinveenbirkenbachandClaude Opus 5.5 9364abfe26 feat(web-app-baserow): carry the corporate design and sign the administrator in by e-mail
templates/style.css.j2 maps the colors of Baserow's compiled stylesheet
onto the design tokens, with the vendor pseudo-element lists split so no
browser drops a rule. templates/design.js.j2 sets the favicon, and
meta/services.yml declares a 160x32 logo and a 32x32 symbol slot.
files/playwright/test-design.js checks the tokens in both modes, the
sign-in page, the signed-in interface, the API documentation, logo,
title and the collapsed sidebar symbol, and captures the gallery.

Baserow's sign-in form asks for the e-mail address and authenticates it
against the username. files/python/bootstrap_admin.py therefore stores
the superuser under its normalized e-mail address. It moves a superuser
kept under the username, or found by its first name after the address
changed, onto the current address, gives an adopted account staff and
superuser rights, realigns the password through check_password,
completes the profile and closes the first-administrator signup page.
It prints BOOTSTRAP_STATE, which the task's changed_when reads.

Ported from feature/store-i18n as identical text, so the later merge
keeps these hunks clean: the platform_administrator account in
roles/user-administrator/meta/users.yml as store-i18n has it now
(username platformadmin, administrator role), roles/web-app-baserow/
meta/users.yml and the BASEROW_BOOTSTRAP_ADMIN_* switch (3c72c2002a and
its follow-ups), and the argv form of the migration, bootstrap and MCP
tasks, which hand only the secrets through -e NAME plus environment
(177255329f). The bootstrap task keeps its own changed_when, because
store-i18n's 'created' never matches this script's output; the
password realign of 23e642a3f3 is part of the script.
templates/playwright.env.j2 reads ADMIN_* from platform_administrator,
the README persona note names the e-mail identifier, and TODO.md is
gone with the argv port that resolves it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 22:22:49 +02:00
kevinveenbirkenbachandClaude Opus 5.5 ad99ded588 fix(web-app-n8n): stop paging when n8n hands back a page it already sent
Both provisioners follow nextCursor for workflows and step skip for API
keys until a short page arrives. A cursor n8n repeats, or a key listing
that ignores skip, would keep that loop running forever and hang the
deploy instead of failing it.

workflows_named now remembers every cursor it followed and exits with
"FAILED listing workflows: nextCursor repeats" when one comes back.
api_keys_labelled exits when a non-empty page brings no key id it has not
already seen. Normal paging is unchanged.

Four regression tests drive both scripts against a fake API that repeats
its cursor or ignores skip; without the guards exactly those four fail.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 10:06:29 +02:00
kevinveenbirkenbach 0abc52302e Merge remote-tracking branch 'origin/main' into feature/designer 2026-10-08 23:20:55 +02:00
kevinveenbirkenbachandClaude Opus 5.5 75f024e71a fix(web-app-n8n): page through API keys and workflows when provisioning
n8n 2 returns ten API keys and a hundred workflows per page by default. A
stale managed key behind the first page survived, and a managed workflow
behind it was created a second time. Both scripts now follow every page,
narrowed by label or name, and keep the exact match in the script.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 22:13:28 +02:00
kevinveenbirkenbach 9dbda607e0 Merge remote-tracking branch 'origin/main' into update/version-sources-web-app-n8n-20261008-279346d 2026-10-08 19:41:07 +02:00
kevinveenbirkenbachandClaude Opus 5.5 e4078c05fd fix(web-app-n8n): provision the MCP surfaces against the n8n 2 API
n8n 2 pages GET /rest/api-keys as {data: {items, counts, totals, owners}},
ten keys at a time, and matches `label` as a substring, so walking the body
raised "'str' object has no attribute 'get'". It also enforces API key
scopes, so activating the MCP server workflow needs `workflow:activate`.
Both scripts now read every list and create response through one
normalisation that unwraps the page, look the managed key up by label, and
name an unexpected shape by its keys only. The normalisation is taken over
from a392e2dd49 on feature/store-i18n.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 19:41:06 +02:00
kevinveenbirkenbach 6e9b4e23dc Merge remote-tracking branch 'origin/main' into fix/web-app-stalwart/relay-rule/793 2026-10-08 19:27:27 +02:00
kevinveenbirkenbachandClaude Opus 5.5 08ef66d8de fix(web-app-stalwart): authenticate bots with an app password under SSO
Under SSO the role tried to let applications relay without SMTP AUTH by
widening the relay rule to the private networks. The rule compared the
peer address to the text of a CIDR, which never matches, so an
application could reach local mailboxes but got "550 5.1.2 Relay not
allowed" for every external recipient. Repairing the comparison would
have opened a relay, because published ports, Tor and the swarm mesh all
hand outside clients an address inside those networks.

Stalwart checks app passwords before it asks the external directory, so
a bot can authenticate while the directory is OIDC. With SSO enabled the
role now creates one app password per bot, acting for that account as
the recovery admin, and stores it in the token store the SMTP password
is already read from. A bot gets a new one when its entry is deleted in
Stalwart or its token is removed.

The relay rule is back to authenticated sessions only. The lookup loses
its SSO relay branch and the role its submission_via_relay flag, so an
SSO deployment submits on 465 with AUTH like every other one.

The role's CLI test now fails when an unauthenticated session is given
an external recipient, or when the bot credential is refused one.

Closes #793

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 08:57:22 +02:00
kevinveenbirkenbach b3563f4ea3 Merge remote-tracking branch 'origin/main' into fix/web-app-prometheus/blackbox-targets/792 2026-10-08 08:57:13 +02:00
kevinveenbirkenbach fb8dd810c6 Merge remote-tracking branch 'origin/main' into feature/designer 2026-10-07 16:28:26 +02:00
kevinveenbirkenbach e78056db08 Merge pull request #816 from infinito-nexus/update/version-sources-web-app-wordpress-20261005-950916f
update(versions): update pins with a declared source (web-app-wordpress)
2026-10-07 13:35:57 +02:00
kevinveenbirkenbach 9fc68a5477 Merge remote-tracking branch 'origin/main' into update/version-sources-web-app-moodle-20261005-4f455e2 2026-10-07 13:21:56 +02:00
kevinveenbirkenbachandClaude Opus 5.5 0c748f74ed fix(web-app-moodle): swap the code tree only after a complete copy
The entrypoint removed the old tree before it copied the new one. A
copy that failed left the volume without a working Moodle, and every
restart removed again instead of recovering. It now copies into a
staging directory inside the volume and swaps the trees once the copy
is complete, so a failed copy leaves the old tree in place.

An image that ships an older release than the volume records no longer
replaces it and exits with a message instead. Two instances on
different releases would otherwise overwrite each other's tree.

upgrade.php reports "No upgrade needed" on stderr, so the task counted
every run on a current site as a change. It reads stderr now.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 10:45:29 +02:00
kevinveenbirkenbachandClaude Opus 5.5 d0ec38017a fix(test-e2e-cli): name the negotiated MCP protocol version on requests
MCP Adapter 0.7.0 for WordPress refuses a request that follows
initialize without the MCP-Protocol-Version header: 'Invalid Request:
MCP-Protocol-Version header is required for a 2025-06-18 session'. The
contract probe never sent it, so the CLI test of web-app-wordpress
failed at tools/list.

The probe now keeps the version the server answers initialize with and
sends it on every later request of the streamable HTTP transport, which
the specification asks of a client since 2025-06-18.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 02:58:03 +02:00
kevinveenbirkenbachandClaude Opus 5.5 d1eee3e26f fix(web-app-moodle): run Moodle 5.3 and upgrade existing sites to it
The pin moved from 4.5.11 to 5.3.0, and the role fitted neither a new
install nor an existing one.

Moodle 5 serves from public/ and routes every other path through
r.php. nginx now uses public/ as its root, hands unknown paths to the
router and passes PATH_INFO for scripts with slash arguments, and
config.php declares the router as configured. The paths the role checks
and writes (version.php, auth, webservice, the OIDC plugin) moved below
public/, the URL of the OIDC plugin stays auth/oidc. The source archive
no longer contains vendor/, so the image installs the Composer
dependencies it declares. The OIDC plugin is pinned to v5.2.1, the
newest release.

An existing site kept its old code: the entrypoint filled the code
volume once and never again. It now records the release it copied and
replaces the tree when the image carries another one, and a new task
runs admin/cli/upgrade.php. Plugins that were installed through the web
interface are not carried over, the README says how to ship them.

A site installed with 5.3 forces login by default, which 4.5 did not,
so its start page answers 303 to the login page and the webserver
health check refused it. The role declares 200 and 303 as the codes
that check accepts: 200 is what an upgraded site keeps answering.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 02:10:13 +02:00
kevinveenbirkenbach dba49ab0a0 Merge remote-tracking branch 'origin/main' into feature/designer 2026-10-07 00:33:16 +02:00
kevinveenbirkenbach 0f403630d7 Merge remote-tracking branch 'origin/main' into fix/code-scanning-alerts/807 2026-10-06 21:10:40 +02:00
kevinveenbirkenbach bb0fff43a4 Merge remote-tracking branch 'origin/main' into fix/web-app-prometheus/blackbox-targets/792 2026-10-06 21:10:33 +02:00