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>
Talk 25, the release for Nextcloud 35, reads stun_servers and
turn_servers through the typed app config and declares both as arrays.
Talk 24 read them as JSON strings. The role wrote them without a type,
so on Nextcloud 35 the Talk settings showed no STUN and no TURN server
although the deploy had set both.
A plugin configuration entry may now carry a type, which is passed to
occ config:app:set. The three Talk server lists use it. Entries without
a type are written as before.
Not yet run against a live Nextcloud 35.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Nextcloud 35 has no app store release of BPM Files and News yet, and the
role skips an addon it cannot install. Their tests ran anyway and failed
on the missing app. Recognize installs on 35, but its test trips over a
second element with the id it looks for.
An addon test runs only for an addon that is enabled and required. With
required set to false the three addons are still installed whenever a
compatible release exists, and their tests no longer run. metadata is
declared the same way already.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The version-source updater only read meta/services.yml. An addon pinned in
meta/addons/<id>.yml stayed where it was: the ONLYOFFICE connector for
Nextcloud sat on v10.1.2, which does not install on Nextcloud 35, until
someone bumped it by hand.
The updater now reads addon files too. An addon declares its source in the
`update:` block it already has, beside monitored, catalog and upstream_id.
A monitored addon of the github-releases catalog needs nothing more: its
upstream_id names the repository whose tags are its versions. The
update-version-sources job opens the PRs, one per role.
Every pinned addon is wired:
- nextcloud onlyoffice carries its own version pin now. Its archive URL
stays a literal that carries the pin, because
test_pinned_addon_archives_available reads it as written
- wordpress mcp-adapter, moodle auth_oidc and jellyfin sso-authentication
follow the tags of their repository, moodle within its v4.5 series
- the three xwiki extensions read their Maven metadata
- pretix-oidc reads the PyPI simple index. The JSON API answers 404 behind
the package cache the test stack routes pypi.org through
- joomla's plugin is built here and jellyfin's ldap-authentication is bound
to the md5 of its archive; both are opted out with that reason
A bump rewrites the pin and the config.archive line together, and an
archive that does not carry the pin fails test_version_keys, so the two
cannot drift apart again. test_version_pins_are_watched covers addon pins
as well: a new one without a source or a reasoned opt-out fails the lint.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Connector v10.1.2 declares Nextcloud 33 to 34 as its supported range, so
occ refuses to enable it on Nextcloud 35 and the ONLYOFFICE variant of the
Nextcloud 35 bump fails in the onlyoffice addon spec. v10.2.1 declares 33
to 35 and therefore serves the pinned Nextcloud 34 as well as the pending
bump.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ONLYOFFICE listed onlyoffice 10.2.0 in the Nextcloud appstore on 2026-09-18 at
10:16:31Z and never published the matching GitHub artifact; the tag itself does
not exist. `occ app:install` takes the newest compatible release, so it dies on
a 404 that no retry can heal. CI proved that twice independently: upstream run
35329488812 and fork run 35330645276 both fail Nextcloud#4 at that task after
six attempts, on the same commit, for the same reason.
The fault had three layers, each visible only once the one above it was gone.
Install. meta/addons/onlyoffice.yml pins `config.archive` to v10.1.2, whose
artifact answers 200 and which the appstore also lists for this server major.
01_install.yml stages that archive on the node and unpacks it into custom_apps
through a stdin pipe: the webroot is a named volume, so the node can only reach
the app directory through the container, and piping avoids both `container cp`
and a cleanup step that would have to run as a user which does not own the file.
Enable and configure needed no change at all. With the release already on disk
`occ app:install` reports "already installed", which nextcloud_install_status
has always mapped to runnable, so 02_enable.yml and 03_configure.yml proceed as
they always did. That is why this commit adds no branch to the plugin pipeline.
Update. `occ app:update --all` then dragged the pinned app back to the broken
10.2.0 and killed the deploy. occ offers no exclusion flag, but it does offer
`--showonly` and single app ids, so a pinned app is simply not updated. The old
`--all` call stays byte-identical and now carries a `when`: with nothing pinned,
thirteen of the fourteen variants take exactly the path they took before. This
layer cannot appear in CI at all, because it needs a second deploy with the app
already installed, and the install always died first.
The classifier test is inverted rather than deleted:
test_an_advertised_release_whose_artifact_is_gone_stays_fatal pins the decision
that a dead artifact fails loudly instead of degrading to a silent skip.
Verified by a local compose deploy of variant 4: ok=2508 changed=149 failed=0
and `✓ 36 tests/addons/onlyoffice.spec.js:12:1`, which drives the whole
JWT-authenticated round trip to the document server over its onion address, not
merely an enabled plugin. In the live container beforehand: `occ app:enable`
returned "onlyoffice 10.1.2 enabled", `occ app:install` returned "already
installed", and `occ app:update onlyoffice` reproduced the 404 that the pin now
avoids.
The loop expression was wrong before this commit and is fixed here: Jinja reads
'\1' as an octal escape, so the backreference produced chr(1) instead of an app
id. Evaluated against ansible-core's own filters over four shapes, the tail is
now cut instead of captured, which removes the escaping hazard entirely.
occ output is not localised: with default_language=de and a de_DE.UTF-8 locale
the strings are unchanged, so parsing them is as safe as the five markers the
role already relied on.
Residual: the per-app update branch has never run with a real item inside a
deploy. Its chain and `occ app:update <id>` are each verified separately.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
API is a plain mapping, so an inventory that configures three providers replaces the whole declaration from group_vars/all/18_api.yml instead of merging into it. inventories/development/default.yml declares openai, anthropic and openrouter, so github, google and jira did not exist in CI at all and lookup('api', 'github.client_id') raised.
A raising lookup inside a value the applications renderer is templating does not fail the render: _templar_render_best_effort returns the string untouched and, for a value carrying a non-env lookup(, refuses the fallback as well. The raw template text reached plugin_value.enabled | bool, which coerced it to False and emitted the deprecation warning that fails the deploy gate. Run 34893363518, job 104153194109 lost web-app-nextcloud to exactly that, for three integrations at once. The configvalue entries would have stored template text as the client id.
utils/api.py merges the scope's override over the declaration, so an override narrows values and never the provider set, and holds the enable rule: every credential a provider declares must carry a value. The api lookup reads through it; the new api_enabled lookup gates on it and returns a real boolean instead of a string a filter has to coerce.
The same class sat in group_vars/all/16_ai.yml and roles/svc-ai-litellm/meta/secrets.yml as bare API.<provider>.<key> dereferences; both now go through the lookups.
Two lints keep it shut. test_addon_enabled_renders_to_bool renders the whole applications map against a scope with API blanked, the most hostile override an inventory may legally write, and requires every addon gate to come out a boolean. test_api_accessed_via_lookup forbids a bare API dereference in Jinja under roles/, group_vars/ and inventories/.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ansible renders the values of a structure loaded through include_vars and leaves its keys as written, so the mapping key a564385068 wrote as a Jinja expression reached Nextcloud as its own source text. occ config:app:get sociallogin custom_providers on a live variant-1 instance returns "groupMapping": {"/{{ lookup('rbac_group_path', application_id=application_id, role='administrator') }}": "admin"} while every neighbouring value of the same provider is rendered, scope among them.
Keycloak emits /roles/web-app-nextcloud/administrator, so the mapping matched nothing and the account sociallogin provisions on first login stayed out of admin. occ group:list on that instance shows admin holding only the local administrator while keycloak-ac165525-e9e8-4230-9381-13b1acd83fc0 exists and belongs to no group, and every Playwright spec that needs an admin settings section answered 'Access forbidden'.
An independent reproduction outside this repo showed the same split on ansible-core 2.21.4 and 2.20.6: in a payload of the same shape the sibling value rendered and the key did not, so it is not a version artifact. Nextcloud 34.0.3 settles the other half: Manager.php hands an admin the unfiltered settings of a section and ai is a core section of the always-enabled settings app, so an admin cannot receive 403 there whatever is installed.
Writing the whole mapping as one expression makes jinja2_native, which this deployment has on, return a real mapping with the key computed. The lint takes the class: no meta payload may carry Jinja in a mapping key. It found exactly this one case in the whole meta tree, and it ignores a line that itself opens with the expression so the corrected form does not trip it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sociallogin creates a Nextcloud user as "<provider name>-<keycloak sub>",
and the provider name was the Keycloak domain. On an onion variant that
domain alone is 67 chars, so every OIDC callback died with
InvalidArgumentException "Username is too long" and HTTP 500 at
/apps/sociallogin/custom_oidc/auth.<onion> (run 34615516009,
Nextcloud#1, 204 occurrences). The whiteboard and admin OIDC specs failed
on the same login.
This is the second derived identifier an onion domain overflows; the
kernel hostname is the first, clamped to 63 bytes by container_hostname
since the pretix and bookwyrm fixes. The domain-or-entity rule now lives in
one lookup, bounded_name(application_id, max_length): the primary domain
when it fits, otherwise the entity name. container_hostname delegates to it
at 63 and keeps its eight callers and its behaviour.
The provider name is bounded_name('web-app-keycloak', 64 - 1 - 36), the uid
limit minus the separator and the UUID sub. A short clearnet domain stays
the name, so existing users keep their uids; an onion deploy gets
"keycloak", which, unlike a hash of the domain, survives a rotated onion
address.
Tests cover the lookup's fit, boundary and fallback cases and its argument
validation, container_hostname's delegation at 63, and the provider name's
room for the sub.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nextcloud#1 of run 33923190220 fails two admin surfaces:
addons/whiteboard.spec.js:28 waiting for the settings/admin/whiteboard shell
and addons/integration_openai.spec.js:32 waiting for #openai_prefs on
settings/admin/ai. Both time out with "element(s) not found" after two
retries.
The cause is not the apps. In the sociallogin flavor the Keycloak login
provisions a second Nextcloud account whose uid is <provider>-<keycloak sub>,
while the role only ever puts the local administrator account into the admin
group, so the browser session that the specs drive is an ordinary user and
every /settings/admin/* route renders nothing. sso_oidc_plugin.py returns
"oidc_login" if ldap_enabled else "sociallogin", so this only bites where LDAP
is off: variants on oidc_login with oidc_login_proxy_ldap land on the
LDAP-backed account that 07_admin_identities.yml adds to admin instead.
The provider asked for scope "openid" with an empty groupsClaim, so no group
information reached the plugin at all and no mapping could have worked. It now
requests the RBAC scope, reads the claim, and maps the application's
administrator group onto Nextcloud's built-in admin group.
Every link is in place already. RBAC.GROUP.CLAIM is "groups", the Keycloak
client scope carries the same name and sits in defaultClientScopes, and its
mapper sets full.path true, so the claim carries /roles/<app>/<role> and the
leading slash in the mapping key matches. rbac_group_path accepts
'administrator' without a declaration because it is the implicit admin role,
and build_realm_rbac_groups adds that group to every RBAC application, so the
group the key names is really provisioned. Upstream reads both keys:
ProviderService maps groups_claim to groupsClaim and group_mapping to
groupMapping, and applies the mapping by group id.
Adminship is necessary for those two specs but may not be sufficient: both
assertions also claim their app is installed and enabled.
plugins/lookup/tls.py:102 builds url.base as "{protocol}://{domain}/", pinned by test_tls.py:59. meta/addons/onlyoffice.yml appended another one, so DocumentServerInternalUrl was stored as https://host//.
The connector treats its two URLs differently: getDocumentServerUrl normalises with rtrim, getDocumentServerInternalUrl returns the stored string raw and concatenates the endpoint onto it. Every server-side call therefore went to //healthcheck and //converter. A swarm vhost forwards $request_uri verbatim, so the document server answered 404; a compose vhost uses proxy_pass with a URI part and nginx collapses the double slash. The 404 makes the connector cron write settings_error, and FilesListener then stops adding the onlyoffice script to the Files view, which is why iframe#onlyofficeFrame never appears and the addon spec times out. Observed in run 33828309745 job 100890535389: "GET //healthcheck HTTP/2.0" 404 in the openresty log, and the docx row offering Download as its default action.
The lint generalises it. This class has been fixed one site at a time at least five times before (efee415b21, 035c1efbf3, b0c7c9601e, a96bdbba38, 83636408c9), so the rule now holds repo-wide: a variable carrying an unstripped tls url.base may never be followed by a slash. Definitions that strip it again are excluded, which is what keeps KEYCLOAK_URL and OPENTALK_KEYCLOAK_BASE_URL out of the finding set; measured, no name is defined both ways.
Not fixed here: settings_error is sticky, so an instance already poisoned stays broken after a redeploy. The obvious remedy, gating the deploy on occ onlyoffice:documentserver --check, is a four-step round trip whose steps never succeeded in either artifact, so it needs its own design.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
834 commits from the Tor onion-service branch against 299 of ours, from merge
base 7b646bdece: 127 conflicted files, 3506 files changed.
The two sides are largely orthogonal. Theirs adds svc-net-tor and a `tor` service
across the role tree, plus two sweeping refactors: shipped scripts sorted by
language (files/ruby/, files/python/, files/shell/, scripts/lint/*.sh) and the
unit suite moved under tests/unit/python/. Ours is requirement 036, the native AI
gateway, plus the MCP role work. Most conflicts were both sides appending to the
same service map, resolved as unions.
Resolutions that were not unions:
Credentials moved namespace. Theirs renamed meta/schema.yml to meta/secrets.yml
and re-addressed credentials as secrets.credentials.<name>. Git carried our
litellm_api_key blocks through the rename, but every consumer we added still read
'credentials.<name>', which no longer resolves. All 45 call sites are migrated.
plugins/filter/container_volumes.py is deleted by theirs and relocated to
plugins/lookup/container_volumes.py, where it carries the same tmpfs defect our
f85996b43 had just fixed: a hard-coded {"size": ...} that silently drops a per
mount `mode`. The fix is re-applied in the relocated file, as that commit's merge
note anticipated.
compose.yml keeps only INFINITO_PLAYWRIGHT_REPORTS_BASE_DIR. Theirs also forwards
INFINITO_PLAYWRIGHT_STAGE_BASE_DIR; that line is deliberately dropped, per the
analysis recorded in 45804e9ad -- STAGE has a drift-proof role-vars fallback,
while forwarding it promotes a setdefault-pinnable .env value above that SPOT.
tasks/stages/01_constructor.yml takes their include, ./tasks/utils/setup/group.yml,
because their refactor removed tasks/groups/*-roles.yml, leaving our include
target nonexistent. Our task name is kept, since svc-virt is in the loop.
Makefile swarm-zombie is a union: their validate plus teardown, then our
lab_subnet cleanup, which their side dropped while leaving lab_subnet.sh as the
only orphaned reference in the file.
01_bootstrap.sh keeps our deterministic --ip pinning, which distinguishes an
already-attached node from a real failure, over their `|| true`.
Their language-sorted layout orphaned references our side still held: zammad's
two scripts/apply/*.rb, jenkins' java-with-ca-trust.sh, and the notify topics
their handlers renamed to hyphenated form. All corrected.
roles/sys-svc-compose/files/shell/swarm/stack_ready.sh gains our latched-update
block: paused, rollback_paused, rollback_started and rollback_completed exit 2
rather than polling a state that cannot resolve without another deploy.
config_from_run is unioned to (title, logged, jobs); the production caller in
trigger/__main__.py now passes jobs, without which our suite suppression would
have been inert.
READMEs are regenerated rather than hand-merged: 15 LiteLLM rows and 91 Tor rows
now derive from the merged metadata, which is itself evidence the meta merge is
coherent.
Verified: 5161 unit tests pass. The single failure, test_query.py TestSortSpec,
also fails on fork/feature/svc-net-tor at b51d2f9919, measured in a baseline
worktree -- so this merge introduces no new unit-test failure. Two modules are
excluded from that run because `baudolo`, a dependency their branch introduces,
is not installed on this host.
Not verified: no deploy has run against the merged tree, and make quality has not
been run end to end here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3529 invalid comments in 528 files, grandfathered in while the linter
only saw the working-tree diff. Removal ran through the linter's own
collectors, so detection and deletion cannot drift: a comment whose
opener line is blank before it and whose closer line is blank after it
loses all its lines, anything else loses only the comment region, and a
line left holding a bare delimiter goes with it.
Five files keep their comments behind a nocheck marker instead. Four are
upstream configs whose inline docs are upstream's own (mautrix, systemd
journald, nextcloud-oidc-login, MediaWiki). In littlejs the
commented-out entries ARE the deactivation mechanism for an example
tile, so deleting them would delete configuration.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
meta/schema.yml became meta/secrets.yml, but its content still landed at
applications.<app>.credentials — the one meta file whose payload did not sit
under a root named after the file. Every other topic obeys that rule:
meta/volumes.yml goes to volumes, meta/rbac.yml to rbac. Credentials now go to
secrets.credentials.
The path exists exactly once, in utils/manager/credential_key.py, and
OVERRIDE_SECTION is derived from its two segments rather than spelled beside
them, so the dotted form used by --set and the nested form used by the
inventory cannot drift. Six consumers read it: the loader, the generator, the
CLI override parser, the two emit helpers, and the four lookup validators.
--set now accepts exactly one key shape,
applications.<app>.secrets.credentials.<key>. The three short forms are
rejected instead of guessed at: the same short key names different credentials
on different apps. That also retired primary_app_id, which only existed to
disambiguate them.
Three defects surfaced while wiring this up. _apply_one_role_schema still wrote
to the old root, so an app's own credentials would have moved while its
providers' stayed behind. web-app-pgadmin reads through
lookup('applications', ...).credentials.<key>, an access path no regex over
lookup('config', ...) could see, and would have deployed with an empty postgres
password. And _META_NON_GENERIC_TOPICS still listed 'schema' — a bare topic
name, invisible to any path search — so the generic file-root loop assigned the
raw schema under secrets and the follow-up write landed inside the cached
document, truncating keycloak's four credentials to one for every later
reader.
tests/integration/ansible/lookups/config/test_credentials_roundtrip.py pins both
directions of the seam and the cache invariant that last defect broke.
Existing inventories still carry the pre-rename root; migrating them is
deliberately out of scope here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Covered by PR #380 rather than merged on its own. This branch already carries
the Tor onion-service provider across the whole role set, and #495 touches the
same nextcloud Playwright specs; landing them separately would mean resolving
the same conflicts twice, once here and once on main. Reviewing #495 as part of
#380 keeps the resolution in one place.
One conflict, in the external-sites addon spec. Nextcloud 34 renamed the admin
section from #external to #external-vue and replaced the add-site control with
a 'New site' button, so the incoming selectors win — retargeting them is what
the PR is for. This branch's resolveTimeout wrappers stay on both assertions.
make quality green on the merge result.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gino0631/nextcloud-metadata has no appstore release for server major
34, so `occ app:install metadata` reports the app as incompatible and
the role skips enable/configure — it was the only addon of ~50 to do
so. The spec asserts the opposite: OC.appswebroots must carry the
metadata key and the Files page must load apps/metadata/*, both true
only for an installed and enabled app.
`required` is the second half of the Playwright gate — addon_env_flags
emits <ADDON>_ADDON_ENABLED only when enabled AND required — so
flipping it lets skipUnlessAddonEnabled skip instead of asserting a
surface that cannot exist. `enabled` stays true: the install is still
attempted every run, and a compatible release only needs `required`
flipped back. Deploy behaviour is untouched; the abort condition reads
config.mandatory, not required. Same lever as integration_suitecrm.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`|-` preserves the newlines that surround the `{% if %}` and `{% endif %}`
lines, so the rendered value carried leading and trailing whitespace instead
of a bare filter. An LDAP filter with leading whitespace is rejected by the
server, and Nextcloud answers with an empty user list rather than an error.
A double quoted scalar cannot hold a newline by construction. The branch is
unchanged: the full user filter when the user directory is enabled, `()`
otherwise.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nextcloud reached its OpenAI-compatible backend through Flowise, which
only forwarded to the LiteLLM sidecar that Flowise no longer runs. The
consumer flag, the activation condition and the endpoint now bind to
svc-ai-litellm directly, one hop shorter.
The endpoint switches from Flowise' public TLS URL to the internal
gateway address plus /v1, so a container-to-container call no longer
travels through the reverse proxy.
The api_key stops crossing a role boundary: it was read from
web-app-flowise credentials.llm_master_key, handing Nextcloud
administrative access to the whole gateway, and is now this role's own
per-consumer virtual key.
The litellm variant pin sits on variant 7 rather than 3 because adding
the gateway to variant 3 pushed that round past the matrix memory budget.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
files_linkeditor 1.1.25 registers its New-link entries via the legacy fileList.newFileMenu API that Nextcloud removed in the v28 Files rewrite, so the entries never render on nc33 (the menu opens and every other addon registers, this one is absent) - network-independent, not Tor-related. Disable the addon and let its spec skip via skipUnlessAddonEnabled until an upstream version using addNewFileMenuEntry ships.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
OnlyOffice declares fileslibreofficeedit (and richdocuments) incompatible and
disables them on install, so the LibreOffice editor's E2E spec failed wherever
OnlyOffice was co-deployed. Enable fileslibreofficeedit only when the OnlyOffice
addon is off (addons.onlyoffice.enabled), and add the required service-coupled
addon integration hook.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An addon's enabled flag must defer to the owning role's service flag
(lookup config services.<x>.enabled), a literal true/false, or api lookups —
never a raw '<role>' in group_names test. group_names belongs in
meta/services.yml (the SPOT); addons read the resolved flag so one source
decides partner presence. New lint scans roles/*/meta/addons/*.yml (87 files)
with a '# nocheck: addon-enabled-group-names' escape.
deck.yml is the one legitimate exception (anti-dependency on web-app-taiga,
which has no nextcloud service flag) and is nochecked accordingly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
mbake line-wrap of the long configvalue jinja expression. Cosmetic only;
the rendered value is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- svc-ai-ollama variant-0: preload smollm2:135m (smallest instruct model for CI)
- integration_openai meta: Service URL -> {base}/api for the OpenWebUI backend
- web-app-openwebui: enable_api_keys config flag (services.yml + vars) rendered into
ENABLE_API_KEYS so sk- API keys authenticate against the OpenAI-compatible API
- integration_openai spec: drop the DOM-gated skip, assert the partner backend host
(openwebui chat.ai / flowise flow.ai), distinct from Nextcloud
- openwebui: OLLAMA_SERVICE_ENABLED playwright flag + test-ollama-chat.js asserting
the preloaded model answers a chat completion over OpenWebUI -> Ollama
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- meta: required true -> false; upstream julien-nc/integration_suitecrm needs
Nextcloud server major <= 24 and never enables on the deployed newer major, so
the addon gate skips its spec cleanly instead of asserting a surface that can
never render
- spec: drop the DOM-gated skips; assert the personal settings panel and the
#suitecrm-content OAuth section render and the instance URL points at the
partner host (clean once a compatible app version ships)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per-addon tasks/addons hooks contain only exception logic beyond the generic loader (DRY/SPOT): config-complete/per-user integrations are documented SPOT comments; mattermost/openai/openproject/suitecrm perform genuine bidirectional OAuth/API provisioning (register client on partner, write client_id/secret back) via files/{mattermost,openproject,suitecrm} scripts, gated on the partner container running. meta/addons keys for gitlab/mastodon/matrix/peertube/zammad fixed from the inert url to the admin oauth_instance_url/instances the apps actually read.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add plugins/lookup/playwright_workers.py computing a conservative parallel-worker count (CPU/RAM-derived, app-under-test concurrency-capped, reduced on CI) and wire test_e2e_playwright_workers + test_e2e_playwright_fully_parallel + test_e2e_playwright_test_timeout into playwright.config.js (read from env) and 02_run_one.yml (passed via -e). fullyParallel stays false so parallelism is file-level only and intra-file ordering assumptions stay safe. Raise run_timeout to 3600 for addon-heavy roles (nextcloud ~50 specs).
Split the role's variables by Ansible precedence and intent: derived/fixed constants live in vars/main.yml (UPPERCASE, high precedence, not operator-tuned), operator-tunable knobs live in defaults/main.yml (lowercase, overridable). VARIANT_INDEX stays UPPERCASE with a single definition. Update references across tasks/templates/inventory/docs accordingly. Includes a unit test for playwright_workers and an autoformat pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
github, google and jira nextcloud integrations read client_id/client_secret via lookup('api', '<provider>.client_id'/'client_secret') and enable only when those credentials are present in the API SPOT. Builds on the API SPOT lookup plugin and dict.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Schema-migrate every nextcloud addon to the unified per-file addon syntax (mechanism/source/required/bridges/config); add the non-proprietary integration addons (matrix, mattermost, moodle, suitecrm, zammad) and the local gitlab bridge; wire the bridged service blocks into meta/services.yml and pin every new (service,enabled)/(service,shared) pair in meta/variants.yml for variant coverage; relocate the per-addon user_ldap hook from tasks/plugin/hooks/ to tasks/addons/ for a uniform tasks/addons/ layout, dropping the obsolete vars/plugins README.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Declares integration_* addons (discourse/github/gitlab/google/mastodon/openai/openproject/peertube) per the 027 integration matrix. Disabled by default; not yet end-to-end wired/tested (acceptance criteria 265-272 remain open).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each Nextcloud app declared as a per-file addon; sociallogin bridges sso, user_ldap bridges ldap, bbb/onlyoffice/richdocuments/spreed/whiteboard/xwiki bridge their partner services. Per-plugin runtime config absorbed from vars/plugins/* (now deleted); install/configure tasks read the addon config via include_vars for lazy var rendering.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>