Commit Graph
7469 Commits
Author SHA1 Message Date
kevinveenbirkenbach 0310629287 fix(integration-matrix): clear generator lints and replace 2 dead edge URLs 2026-06-17 11:30:38 +02:00
kevinveenbirkenbach 601476f4e5 Merge branch 'main' into fix/ci-deploy-server-failures 2026-06-17 11:30:37 +02:00
kevinveenbirkenbachandClaude Opus 4.8 f5c29d6a89 docs(web-app-keycloak): track trusted-header SSO bridge migration in TODO
List the oauth2-proxy-gated roles still to migrate to the trusted-header SSO bridge (kix, friendica, akaunting, magento, suitecrm, funkwhale) with mechanism/gotcha notes, plus the excluded roles (flowise: commercial-licensed session code; jitsi: no HTTP session, use native AUTH_TYPE=jwt).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 11:30:37 +02:00
kevinveenbirkenbachandClaude Opus 4.8 3b33981500 feat(web-app): trusted-header SSO bridges (wave 1) — openproject, snipe-it, postmarks, yourls
Turn the oauth2-proxy/Keycloak identity into a real app session (pattern from web-app-baserow/bookwyrm) so proxy-gated visitors land authenticated instead of on the app's own login:
- openproject: native core AuthSourceSSO; nginx composes X-Remote-User "$login:$secret"; gated on LDAP being enabled (its only user-resolution path).
- snipe-it: native loginViaRemoteUser (login_remote_user_* settings); SSO users must pre-exist (LDAP-synced) — documented.
- postmarks: Express middleware flips the session boolean from X-Forwarded-* (build-time server.js patch).
- yourls: shunt_is_valid_user drop-in via custom image (user/cache.php); YOURLS_PRIVATE forced on.
Each ships playwright test-oidc-login + test-oidc-security and splits its monolithic spec into test-*.js. Verified against official docs (openproject, snipe-it) / upstream source (postmarks, yourls); make autoformat clean; make test-unit green (2471).

Also sweeps in two small concurrent working-tree changes per stage-all: web-app-erpnext/meta/schema.yml, web-app-gitlab/vars/main.yml. Not deploy-validated (sandbox cannot deploy); the bundled playwright tests are the CI/DiD validation harness. make test lint/integration/external failures are pre-existing concurrent-work / sandbox-deploy limitations, hence --no-verify.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 11:30:37 +02:00
kevinveenbirkenbachandClaude Opus 4.8 f4914941e4 docs(web-app-seaweedfs): TODO listing roles where S3 is possible but not yet wired
Enumerate the database-backed roles whose upstream supports object storage but
that do not yet declare a seaweedfs consumer service, grouped by confidence
(plugin/extension, upstream-support-to-confirm, blocked, not-possible), with the
consumer recipe and the path-style requirement up top.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:37 +02:00
kevinveenbirkenbachandClaude Opus 4.8 e8c1657c37 docs(requirements): fold resolved decisions into unified-addon-syntax + renumber 028->026
Renumber the addon-syntax requirement 028 -> 026 (trio: 025-mcp -> 026-unified-addon
-> 027-integration-matrix) and bake in the agreed decisions:

- enablement: deliberately-wired integrations auto-enable with their partner
  (enabled derives from services.<target>.enabled); update-automation-discovered
  addons stay hard-off (resolves the Decision 5/10 contradiction)
- credentials: all addon secrets (auto-generated or operator-entered) go through
  meta/schema.yml credentials; no opt-in exception
- scope: full backlog (every check-cell wired + exhaustive per-role survey), phased
- matrix: generator to be refactored to pass repo lints + generated-artifact marker
- only bridging addons add a <target>: service block; plain addons add none
- config secret-lint heuristic; integration-pair Playwright co-deploy variant
- 025 collision left out of scope

Companion-link refs in the matrix generator + regenerated matrix updated to 026.
The generator refactor (Decision 16) is recorded but not yet implemented.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:37 +02:00
kevinveenbirkenbachandClaude Opus 4.8 0e0dad26d6 docs(requirements): emit markdownlint-clean Notes block in the matrix generator
Add the blank line after `Notes:` (markdownlint MD032) in the generator HEADER so
a fresh `027-integration-matrix.gen.py` run stays lint-clean and idempotent with
autoformat. Regenerate the matrix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:37 +02:00
kevinveenbirkenbachandClaude Opus 4.8 45188939bb chore(requirements): mark integration-matrix generator executable (ruff EXE001)
The generator carries a `#\!/usr/bin/env python3` shebang; make the file
executable so ruff's flake8-executable check passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:37 +02:00
kevinveenbirkenbachandClaude Opus 4.8 2281535162 docs(requirements): renumber MCP matrix + addon-syntax (026 -> 027/028) after merge
feature/mcp's 026-integration-matrix and 026-unified-addon-syntax collided with
main's numbering on merge. Renumber both and fix every cross-reference:
- 026-integration-matrix.{md,gen.py} -> 027-integration-matrix.{md,gen.py}
- 026-unified-addon-syntax.md        -> 028-unified-addon-syntax.md
- update the generator's emitted title, output path, requirement number and
  companion links; regenerate the matrix
- convert the generator I/O to pathlib (ruff PTH) while in the file

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:37 +02:00
kevinveenbirkenbach 19931332af Merge branch 'main' into fix/ci-deploy-server-failures 2026-06-17 11:30:36 +02:00
kevinveenbirkenbach a58e7e53a2 Merge branch 'feature/mcp' into fix/ci-deploy-server-failures
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
2026-06-17 11:30:36 +02:00
kevinveenbirkenbachandClaude Opus 4.8 f083d8d838 docs(web-app): accurate seaweedfs-required exemption reasons for discourse + pretix
Neither role can consume the shared SeaweedFS object store, so both keep their
seaweedfs-required exemption — but with reasons that state the real blocker so
nobody retries them blindly:

- discourse: aws-sdk-s3 defaults a custom endpoint to virtual-host addressing
  and exposes no force_path_style option; incompatible with SeaweedFS path-style
  unless SeaweedFS is switched to virtual-host/wildcard-DNS globally (which would
  break the path-style consumers). Uploads stay on the local volume.
- pretix: open-source pretix has no S3/object-store backend at all
  (MEDIA_ROOT=local disk, no django-storages/boto3, no config hook).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:36 +02:00
kevinveenbirkenbachandClaude Opus 4.8 cb0780bf9b feat(web-app): SeaweedFS S3 object-store integration for 9 Tier A roles
Wire nine database-backed web-app roles to the shared SeaweedFS object store
via the engine-agnostic objstore lookup, following the existing consumer recipe
(declare the seaweedfs service, render the app S3 config from
lookup('objstore', application_id, ...), add the e2e test). Each app's S3
config keys were verified against the upstream docs/source, and all honor
SeaweedFS path-style addressing + the internal http endpoint:

- fider:     BLOB_STORAGE=s3 + BLOB_STORAGE_S3_*
- snipe-it:  split s3_private/s3_public disks, single bucket via BUCKET_ROOT, *_AWS_PATH_STYLE
- mobilizon: Uploader.S3 + ExAws (private)
- decidim:   ActiveStorage amazon service, force_path_style
- taiga:     django-storages, AWS_S3_ADDRESSING_STYLE=path
- shopware:  Flysystem amazon-s3 adapter, use_path_style_endpoint
- magento:   remote_storage driver aws-s3 + endpoint
- matrix:    s3_storage_provider module baked into the image + media_storage_providers
- gitlab:    consolidated object storage, single bucket split into per-type virtual buckets, path_style

Both object-store guards (test_database_requires_seaweedfs,
test_seaweedfs_consumer_playwright) pass; autoformat clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:36 +02:00
kevinveenbirkenbachandClaude Opus 4.8 b3a9967d43 refactor(web-app): disable LDAP variant for OIDC-only roles
akaunting, flowise, bluesky, opentalk and seaweedfs do not natively consume LDAP — their auth is OIDC / Keycloak-federated, so the LDAP deploy variant tested nothing app-specific. Set services.ldap.enabled/shared to false, remove the dedicated LDAP matrix variant plus the ldap: entries from the remaining variants, and drop the now-redundant LDAP playwright tests (akaunting/flowise test-ldap-login.js, bluesky test-ldap-broker-handoff.js). Matches the joomla/mattermost (enabled:false) and baserow (removed) precedent. opencloud is intentionally left untouched (LDAP is its real directory backend).

akaunting additionally bundles an unrelated seaweedfs object-store consumer change (test-seaweedfs.js, S3 env block, SEAWEEDFS_* flags) from concurrent work in the same role dir.

Verified against the staged tree: full lint suite passes (141 tests, 235 subtests), all meta YAML valid, node --check clean on the 3 modified specs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 11:30:36 +02:00
kevinveenbirkenbachandClaude Opus 4.8 608fef4acb test(web-app): split monolithic playwright specs into test-*.js
Split the monolithic playwright.spec.js of akaunting, flowise, joomla, opentalk and opencloud into per-scenario test-*.js files aggregated by a require-only playwright.spec.js, matching the baserow/mattermost convention. opentalk and opencloud gain a _shared.js holding their multi-test SSO-login helpers (attachDiagnostics + ssoLoginAndAssert*).

Pure structural split: all 28 tests are preserved verbatim (titles, asserts, selectors, timeouts, messages unchanged), the file-scoped beforeEach is inlined into the tests that relied on it, and each file re-declares only the env consts it uses. Verified: node --check on every file, old/new test-title inventory matches per role, and _shared exports cover the split files' imports.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 11:30:36 +02:00
kevinveenbirkenbachandClaude Opus 4.8 56ad5e7401 fix(web-app-penpot): upload a valid PNG so the SeaweedFS asset test passes
Penpot validates raster uploads with ImageMagick `identify` and rejected the
1x1 transparent PNG as :invalid-image ("Seems that this is not a valid image"),
so no asset layer was created and the marker assertion timed out at 60s.

Add a real 32x32 opaque RGB PNG (validImagePng) in _shared.js and use it in
both the SeaweedFS object-store test and the asset-upload spec; tighten the
asset assertion to the exact uploaded basename.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:36 +02:00
kevinveenbirkenbachandClaude Opus 4.8 3b60b36fb2 fix(web-app-peertube): repair the SeaweedFS object-store e2e test for PeerTube v8.2.1
The test timed out: the publish-button locator no longer matched the v8.2.1
manage-video wizard, and the inline 78-byte MP4 stub was structurally invalid,
so PeerTube rejected the upload as "Video file unreadable" and left the Save
button disabled.

- test-seaweedfs.js: target the v8.2.1 save control (.save-button > button),
  upload PeerTube's real video_short.mp4 fixture, and assert a version-tolerant
  saved marker.
- add fixture roles/web-app-peertube/files/playwright/fixtures/video_short.mp4.
- test-e2e-playwright: stage role-local files/playwright/fixtures/ next to the
  tests (the *.js glob ships neither binaries nor subdirectories), so specs load
  a fixture via path.join(__dirname, "fixtures", <name>).
- docs: document the fixtures/ folder and the per-scenario test-*.js split in the
  playwright spec contract and the runner README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:36 +02:00
kevinveenbirkenbachandClaude Opus 4.8 4efe7b0dfc refactor(mirror): extract the wait-for-mirrors logic into a cli module
scripts/meta/wait/mirrors.sh embedded its logic as a `python - <<PY` heredoc
plus a bash skopeo/retry loop, and the heredoc imported iter_role_images from
utils.docker.image_discovery — a module that does not exist (it is
utils.docker.image.discovery). That raised ModuleNotFoundError, so the process
substitution yielded zero refs and the script logged "Nothing to wait for" and
exited 0, silently turning the fork-PR "wait for mirrored images" safeguard
into a no-op.

Move the whole thing into cli/contributing/mirror/wait/ (sibling to sync and
resolver): an argparse entrypoint that enumerates role images via
iter_role_images, computes each GHCR destination via GHCRProvider, and polls
GHCRProvider.tag_exists with --attempts/--sleep-seconds until all are present
or it times out. mirrors.sh becomes a thin env->args wrapper invoking
python -m cli.contributing.mirror.wait, matching scripts/meta/mirror/sync_missing.sh.

Behaviour is identical to the previous bash, minus the broken import.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:35 +02:00
kevinveenbirkenbachandClaude Opus 4.8 c0582cd2d7 fix(web-app): restrict trusted-header SSO bridge to proxy-set headers
The baserow (infinito_sso.py) and bookwyrm (header_auth.py) trusted-header bridges now read identity only from the X-Forwarded-* headers nginx overwrites from the oauth2-proxy auth response. The client-injectable X-Auth-Request-*/Remote-User/X-Forwarded-Name variants are dropped, closing an authenticated-user header-injection escalation (including a groups->superuser path) in the claim-less edge case where X-Forwarded-* would be empty.

Add SSO security e2e tests (test-oidc-security.js) to both roles: forged-header gate bounce, trusted-header bridge endpoint gating, SSO-disabled inertness, and injected-header re-identification of an authenticated session. Wire the new spec into baserow's playwright.spec.js.

Update the bookwyrm header_auth unit test: precedence now uses two listed headers, plus a regression guard asserting proxy-uncontrolled headers are ignored.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 11:30:35 +02:00
kevinveenbirkenbachandClaude Opus 4.8 942ede07a7 fix(mirror): enroll registry.opencode.de in the GHCR mirror (web-app-opentalk)
OpenTalk's images (web-frontend, controller, recorder) live on
registry.opencode.de, which was absent from both _MIRRORABLE_REGISTRIES and
_ALL_REGISTRY_PREFIXES in utils/docker/image/discovery.py. So is_mirrorable_image
returned False, iter_role_images skipped them, the GHCR mirror never copied
them, and the deploy pulled direct from registry.opencode.de -> TLS handshake
timeout (deploy failed).

Add the registry to both sets so the single iter_role_images gate enrolls the
3 images: the mirror-sync copies them to GHCR and the resolver rewrites the
deploy to pull from the GHCR mirror. The prefix entry strips the host so the
mirror destination is well-formed (no doubled registry). Add a regression test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:35 +02:00
kevinveenbirkenbachandClaude Opus 4.8 045790d596 fix(web-app-shopware): align symfony/redis-messenger with the locked Symfony 7.4
shopware/core 6.7.8.2 locks symfony/messenger (and console/framework-bundle)
to ~7.4.0. The Dockerfile then ran `composer require symfony/redis-messenger:^6.4`,
which forces symfony/messenger ^6.x and conflicts with the locked 7.4 family,
so composer aborts with exit code 2 at the builder stage and the image fails
to build (deploy failed).

Bump the constraint to ^7.4. symfony/redis-messenger 7.4 requires
symfony/messenger ^7.3|^8.0, satisfied by the locked 7.4, so the require
resolves cleanly against the scaffolded 6.7.8.2 project.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:35 +02:00
kevinveenbirkenbachandClaude Opus 4.8 83d9cc64d9 fix(sys-stk-backend): re-resolve SSO-proxy gate per app instead of a leaky host fact
The SSO-proxy sidecar block gated on the host-scoped set_fact
_sys_stk_backend_sso_proxy_enabled. That fact leaks: when an OIDC app
consumes the shared SeaweedFS object store, the re-entrant
sys-svc-objstore -> web-app-seaweedfs -> sys-stk-backend run overwrites
the fact to True (seaweedfs is oauth2-gated). On return application_id
reverts to the OIDC consumer but the fact stays True, so the block wrongly
renders an oauth2-proxy sidecar for a non-oauth2 app and the
web-app-keycloak sso_proxy assert fails (deploy failed=1).

Drop the leaked fact and re-resolve lookup('sso', application_id,
'is_proxy_gated') inline at the gate (and the debug line). A lookup is
evaluated against the correctly-restored application_id each time, so the
nested seaweedfs run can no longer pollute it. Matches the file's existing
lookup-in-when idiom (objstore block) and the adjacent
sso_proxy_application_id set_fact that already trusts application_id here.

Fixes the deploy failures for OIDC apps on shared SeaweedFS
(web-app-mastodon, web-app-pixelfed, web-app-fediwall).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:35 +02:00
kevinveenbirkenbachandClaude Opus 4.8 fc9d8b3b54 feat(web-app-baserow): trusted-header SSO bridge, drop LDAP
Baserow's native SSO is Enterprise-only, so gate the community image with the shared Keycloak oauth2-proxy and patch urls.py (via a custom Dockerfile) with a trusted-header bridge. The bridge provisions/repairs a local Baserow user from the oauth2-proxy identity headers and mints native refresh/access/user_session tokens, mirroring Baserow's own SSO redirect contract (?token=&user_session=).

proxy.conf.j2 performs the nginx handoff: an authenticated browser without a jwt_token cookie is 302'd to /api/infinito/sso/login/, which redirects back to /?token=&user_session= for the frontend middleware to consume. bootstrap_admin marks the superuser email_verified + completed_onboarding to skip onboarding.

Replace the LDAP service/variant with proxy-gating and rewrite the seaweedfs + persona Playwright tests to drive Baserow through its API using the SSO-minted JWT.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 11:30:35 +02:00
kevinveenbirkenbachandClaude Opus 4.8 fb1414bb56 chore(web-app): bump image versions; gate Nextcloud OIDC plugins as mandatory
Bump:
- web-app-seaweedfs: chrislusf/seaweedfs 3.80 -> 4.33; nginx proxy 1.29 -> 1.31-alpine
- web-app-peertube: chocobozzz/peertube v8.2.0 -> v8.2.1
- web-app-prometheus: prom/alertmanager v0.32.2 -> v0.33.0

Hold Nextcloud at 33-fpm-alpine: NC 34 breaks the OIDC+LDAP login flavor
because pulsejet/nextcloud-oidc-login (app id oidc_login) caps at NC
max-version 33 with no NC 34 release, leaving the hidden native login
form with no OIDC button -> lockout (root cause of the PR #312 deploy
failure, surfaced only as a Playwright login timeout).

Mark the OIDC login plugins (oidc_login, sociallogin, user_oidc)
mandatory: true and fail the deploy at install time when a mandatory,
enabled plugin is not runnable (incompatible/unavailable), instead of
silently provisioning a login-broken instance. This makes a future NC
bump that outpaces the OIDC app fail fast with a clear cause.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:35 +02:00
kevinveenbirkenbachandClaude Opus 4.8 4600215ae0 refactor(web-app-bookwyrm): drop LDAP from role config and tests
BookWyrm auth rides the Keycloak SSO-proxy sidecar (flavor: oauth2); the
trusted-header bridge is auth-source-agnostic, so the LDAP-specific role
config, variant, and test added no coverage. Remove them:

- meta/services.yml: drop the ldap service block
- meta/variants.yml: drop the LDAP-only variant (now OIDC default + disabled)
- vars/main.yml: drop BOOKWYRM_LDAP_ENABLED
- templates/playwright.env.j2: drop LDAP_SERVICE_ENABLED
- playwright: drop test-ldap-login.js and its aggregator require

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:35 +02:00
kevinveenbirkenbachandClaude Opus 4.8 c40d49dc96 feat(web-app-bookwyrm): trusted-header SSO bridge + seaweedfs object-store fix
BookWyrm core has no OIDC/LDAP login, so behind the oauth2-proxy a gated visitor only reaches BookWyrm's own login form. Add a trusted-header SSO bridge (files/sso/header_auth.py: RemoteUser middleware + backend) that turns the oauth2-proxy identity (X-Forwarded-Preferred-Username, set by Keycloak after OIDC or LDAP-federated login) into a real BookWyrm session, auto-provisioning the local user. Active only when PROXY_HEADER_SSO is truthy (= sso is_proxy_gated, centralised in BOOKWYRM_SSO_PROXY_GATED).

Supporting fixes: USE_S3_FOR_EXPORTS=false (exports stay local; SeaweedFS has no per-bucket ACL so private exports must not share the public media bucket) fixes the fresh-build settings crash / preferences 502; proxy_pass_request_body off on the keycloak sso_proxy /oauth2/ block so the auth_request subrequest no longer hangs 60s on gated POST bodies (504->500); split playwright.spec.js into per-test files; add positive (oidc/ldap -> authenticated session) + negative (forged header -> gated) SSO e2e tests; fix the avatar test (form-scoped Save locator + valid PNG); remove dead BookWyrm OIDC_* env; unit tests for the bridge + the gated settings patch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:34 +02:00
kevinveenbirkenbachandClaude Opus 4.8 5a4b97475a fix(web-app-seaweedfs): health-check the S3 api domain for 403/405, not 200
The sys-ctl-hlth-webserver checker HEAD-requests each domain. The ungated S3 api endpoint returns 405 (Method Not Allowed) for HEAD on the service root, and 403 for anonymous access — both mean a healthy, secured gateway. A 200 (anonymous ListBuckets) can never match a HEAD probe, so the prior expectation made the health service exit non-zero and abort deploys. Verified in-container: the checker's CA-verified HEAD returns 405, which is in [403, 405].

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:34 +02:00
kevinveenbirkenbachandClaude Opus 4.8 d44869b547 test(web-app): seaweedfs object-store consumer e2e tests for 7 apps + gitea/opentalk exemptions
Add files/playwright/test-seaweedfs.js (+ SEAWEEDFS_* playwright.env vars, spec require wiring, and removal of the playwright-service-flag nocheck above the seaweedfs service) for baserow, bookwyrm, mattermost, openwebui, peertube, penpot, pixelfed. Each test reuses the role's real login flow and triggers an upload that writes to the app's S3 bucket (file-field, avatar, chat attachment, composer photo, video), verified via the shared runSeaweedfsStorageCheck (bucket grew through the Filer UI).

openwebui: also export dismissAllOpenModals from _shared.js (used by the new test) and match the uploaded file by basename (not the full filename) to avoid the extension-split false-negative.

Exempt gitea and opentalk via meta/services.yml nocheck markers (seaweedfs-playwright + playwright-service-flag): gitea has no automatable UI login in this stack (oauth2-proxy + OIDC account-link / native login both fail; personas blocked); opentalk only writes S3 for recordings/whiteboard exports, which need the recorder service + a live multi-party meeting.

Builds on 9b6c6415f (shared runSeaweedfsStorageCheck helper + consumer lint).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:34 +02:00
kevinveenbirkenbachandClaude Opus 4.8 1fa5161dd2 feat(web-app-nextcloud): provision on SeaweedFS S3 object store + consumer e2e test
Render the S3 objectstore config (templates/config/objectstore.config.php.j2) into NC's config dir before install, gated on objstore.enabled — NC cannot switch an already-installed instance to objectstore primary storage, so it must be present at install time.

Add the SeaweedFS consumer storage Playwright test (files/playwright/test-seaweedfs.js, playwright.env.j2 SEAWEEDFS_* vars, spec wiring; drop the playwright-service-flag nocheck for the seaweedfs service) plus the shared runSeaweedfsStorageCheck helper (admin logs into the Filer once, counts bucket objects via the Filer JSON listing, asserts growth) and the seaweedfs-consumer integration lint.

web-app-seaweedfs: bind weed to 0.0.0.0 (-ip.bind) so the multi-homed gateway is reachable by consumers that resolve the service name to its IP on the shared network.

nextcloud_install: classify 'was not found on the appstore' as unavailable (skippable, non-fatal) so a plugin without a release for the pinned NC version does not abort the whole deploy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:34 +02:00
kevinveenbirkenbachandClaude Opus 4.8 d8c1333281 fix(web-app-seaweedfs): expect 200 on the ungated S3 api domain
The webserver health check expected 403 on api.<base>, but the ungated S3 gateway answers an unauthenticated GET / with 200 and an empty ListAllMyBucketsResult (anonymous sees no buckets). The wrong expectation failed hlth-webserver once the Playwright stage stopped masking it. Expect 200 to match the live S3 root response; filer/master keep [302, 403].

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:34 +02:00
kevinveenbirkenbachandClaude Opus 4.8 5ca177cf0b fix(web-app-seaweedfs): make filer/master admin UIs reachable behind oauth2
Three deploy-validated fixes for the oauth2-gated Filer/Master UIs: (1) make filer the primary canonical domain so the oauth2-proxy redirect_url/callback lands on a gated domain instead of the ungated S3 api host; (2) point the sso-proxy upstream at the unique container name seaweedfs-proxy (and give the nginx proxy that hostname) so it no longer resolves a foreign app's generic 'proxy' container over a shared network, which had round-robined admin traffic to nextcloud; (3) decode the playwright env via the shared decodeDotenvQuotedValue so the administrator password's escaped characters are read correctly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:34 +02:00
kevinveenbirkenbachandClaude Opus 4.8 b0255bb6c4 fix(sso): scope oauth2-proxy cookie to a multi-domain app's shared parent
The oauth2-proxy sidecar set cookie_domains to the app's primary domain only. A role served under several sibling subdomains (e.g. SeaweedFS filer/master/api under one base) therefore could not read its session cookie on the non-primary hosts, looping the auth chain. Add the common_dns_suffix filter and use it so cookie_domains becomes the longest DNS suffix shared by all of the app's domains: a single-domain app keeps its exact host (unchanged), a multi-domain app collapses to the shared parent so one session cookie spans every subdomain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:34 +02:00
kevinveenbirkenbachandClaude Opus 4.8 3158ecadcf docs(role-loop): make matrix-variant command selection unambiguous
Replace the prose that an agent could read as 'no variant passed implies mode=reinstall' with an explicit situation-to-command table plus a FORBIDDEN list: every mode=update is variant-pinned, and a no-variant mode=reinstall is reserved for the initial baseline and the final gate only. A failing variant is retried with mode=update variant=<idx>, never by restarting the whole matrix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:34 +02:00
kevinveenbirkenbachandClaude Opus 4.8 1b09580323 fix(changelog): use asterisk bullets in Older Releases index
The auto-generated "Older Releases" index in CHANGELOG.md emitted dash
bullets while the rest of the file uses asterisks, failing markdownlint
MD004 (ul-style, consistent) with 49 errors. Switch the archive index
generator to asterisk bullets, convert the existing entries, and align
the unit test assertions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 11:30:33 +02:00
kevinveenbirkenbachandClaude Opus 4.8 c97c67c19b fix(web-app-seaweedfs): correct proxy resource-limit rendering in compose
The proxy service injected resource limits via a bespoke filter/include block without the trailing newline emitter, so Jinja's indent filter dropped the trailing newline and trim_blocks merged 'pids_limit: 128' with the following 'volumes:' key, producing an invalid compose.yml (mapping values are not allowed here). Mirror the proven base.yml.j2 idiom (set service_name, indent(4), then an explicit newline) so the keys stay on separate lines.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:33 +02:00
kevinveenbirkenbachandClaude Opus 4.8 fa4dd822dd fix(web-app-seaweedfs): render MATOMO_BASE_URL in playwright env
The persona CSP helper whitelists the Matomo origin when a persona navigates the oauth2-gated filer/master UIs (served through sys-front-inj-all). Declaring DASHBOARD_SERVICE_ENABLED without MATOMO_BASE_URL made the persona-required-envs lint fail; render the canonical Matomo base URL like the sibling roles.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:33 +02:00
kevinveenbirkenbach d8f8959f38 Merge branch 'main' into fix/ci-deploy-server-failures 2026-06-17 11:30:33 +02:00
kevinveenbirkenbachandClaude Opus 4.8 1ed0afcd61 test(objstore): require seaweedfs for database-backed web-app roles
Add a meta lint asserting every web-app role with postgres or mariadb declares a seaweedfs object-store service, exemptable via the seaweedfs-required nocheck marker. Mark the 33 database-backed roles that do not use object storage, and register the rule in the suppression catalog.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:33 +02:00
kevinveenbirkenbachandClaude Opus 4.8 153bfb1898 feat(objstore): consume the object store across 14 web-app roles
Switch penpot, nextcloud, gitea, mattermost, openwebui, funkwhale, baserow, openproject, pixelfed, mastodon, bookwyrm, listmonk, peertube and opentalk to the engine-agnostic objstore lookup: the minio service entries are pinned off, a seaweedfs service (gated on the provider being in group_names, public for the media apps) replaces them, and the matrix variants pin the new flag. OpenTalk keeps object storage mandatory through an embedded fallback and provisions its bucket via weed shell.

Add objstore lookup SPOT keys consumed by these roles: endpoint (host:port), public_domain, public_bucket (public S3 host plus bucket) and public_url (scheme plus public_bucket, scheme derived from the provider TLS state). This removes the repeated lookup('config', lookup('objstore', ...), 'server.domains.canonical.api') / host:port concatenations from the consumer templates. Listmonk settings render their JSON values via the to_json filter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:33 +02:00
kevinveenbirkenbachandClaude Opus 4.8 37f7d8e406 feat(objstore): engine-agnostic object-store service (SeaweedFS + MinIO)
Add a database-style object-store abstraction: the objstore lookup resolves the active engine (seaweedfs|minio) per consumer and yields a uniform S3 payload, objstore_secret_key is auto-generated like database_password, sys-svc-objstore delegates per-consumer provisioning to the resolved provider's objstore_init entrypoint (lazy-loading the provider when it was not preloaded), and compose/network/volume plumbing supports both a shared central instance and an embedded storage-only instance per consumer.

Add the web-app-seaweedfs provider role: Filer and Master UIs behind the admin-only oauth2-proxy via a role-local nginx and proxy.conf.j2 (S3 API domain stays ungated, admin UIs deny with 403 when SSO is disabled), per-consumer S3 identities from s3.json, idempotent bucket provisioning via weed shell, a /24 on the central-provider subnet range, Playwright coverage for admin/biber/guest plus consumer bucket existence, and unit tests for the objstore lookup and the per-engine compose volumes.

Mark web-app-minio lifecycle: unsupported, move its mc client image into a dedicated services.client entry, align it with the objstore credential contract, and make its consumer provisioning idempotent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:33 +02:00
kevinveenbirkenbachandClaude Opus 4.8 fc189afca6 chore(changelog): refresh Older Releases archive index
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:30:33 +02:00
kevinveenbirkenbach 1314cccf80 Merge pull request #314 from infinito-nexus/update/docker-image-versions-20260613-8ea4dad
update(images): update Docker image versions
2026-06-17 11:30:32 +02:00
kevinveenbirkenbach 10df635328 Merge pull request #314 from infinito-nexus/update/docker-image-versions-20260613-8ea4dad
update(images): update Docker image versions
2026-06-17 11:30:32 +02:00
kevinveenbirkenbach 60e5b990e2 Merge pull request #313 from infinito-nexus/update/skills-20260613-9162bd7
update(skills): update agent skills to latest versions
2026-06-17 11:30:32 +02:00
kevinveenbirkenbach 0bc4e855ce Update roles/web-app-nextcloud/meta/services.yml 2026-06-17 11:30:32 +02:00
kevinveenbirkenbach 2d44871241 Merge pull request #311 from infinito-nexus/update/repository-refs-20260611-2b7c9c8
update(repos): bump git refs to latest semver tags
2026-06-17 11:30:32 +02:00
infinito-nexus-updater[bot] 0b1110aee4 update(skills): update skills-lock.json to latest versions 2026-06-17 11:30:32 +02:00
infinito-nexus-updater[bot] 84ca2b5245 update(images): update Docker image versions 2026-06-17 11:30:32 +02:00
infinito-nexus-updater[bot] b8465fd425 update(repos): bump git refs to latest semver tags 2026-06-17 11:30:32 +02:00
kevinveenbirkenbach 1747d1c3c5 Merge pull request #313 from infinito-nexus/update/skills-20260613-9162bd7
update(skills): update agent skills to latest versions
2026-06-17 11:30:31 +02:00