The Mattermost <-> Nextcloud OAuth provisioning ran its container probe and
mmctl steps on the manager, but in swarm the mattermost task runs on a
worker, so the probe found no container and the whole coupling was skipped.
The connect button then stayed disabled and the integration e2e timed out.
Resolve the mattermost hosting node and node-local container id via
resolve_host_cid and delegate every mmctl step, and the OAuth-app
provisioning, to that node, mirroring the pattern web-app-mattermost's own
role uses. The OAuth-app step keeps running bash on the host because the
image has no shell, but now on the mattermost node so its localhost URL
resolves. Drop the now-unused running probe and the
NEXTCLOUD_MATTERMOST_CONTAINER_ADDRESS var.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The provision include used a templated path, which the container-exec lint cannot follow, so its argv-form 'container exec' into the OpenProject container read as ungated and would fire on swarm workers (resolver exits 64). Switch to a static include the lint follows and carry IS_STACK_HOST on the include when, so workers skip the whole coupling as a unit. Also picks up the autoformat reflow of the diagnostics daemon-journal capture.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The mattermost and openproject integration probes plus the openproject provisioning execs addressed the partner by its bare compose container name. Under swarm the running task name differs, so container inspect found nothing, the partner-running probe returned false, and the bidirectional OAuth provisioning was skipped. Route them through the container_address lookup (bare name in compose, resolve-container-id subshell in swarm) so the probe resolves and provisioning fires in both deploy modes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The guide deploys to a .localhost target, where letsencrypt cannot issue (Cloudflare DNS-01 needs a real domain). Default TLS_MODE=self_signed in the generated Production block so the guide uses the working self-signed provider. Document every deploy parameter as a top-level shell var (APP, HOST, TLS_MODE, SSH_PUBLIC_KEY) instead of an inline placeholder.
The compose guide now extracts and runs the whole README block, sourcing params from it (no more -e injection); HOST is forced to localhost. Skip checkout/login under ACT so the guide workflow is iterable locally.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reverts b9a5aa816. It rewrote the Mattermost OAuth provisioning to
'container exec ... bash -s', but the mattermost-team-edition:11.9.0 image
has no shell at all (verified: 'stat /bin/sh: no such file'), so the task
fails rc127 in BOTH swarm and compose (run 29715516546, web-app-nextcloud 3,
jobs 88269803451 + 88270969917). Restores the previous provisioning form.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The README Production block went provision -> deploy with no admin-key step, so user-administrator failed early (authorized_keys empty) for both the Guide CI jobs and any real user copying the README. Add a visible 'provision --vars' step that sets users.administrator.authorized_keys (merged into host_vars) in both Production variants of the role README template, regenerate all role READMEs, and replace the invisible CI payload splice with a visible substitution of the documented <your-ssh-public-key> placeholder.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The role README Production section now runs provision and deploy in a
single docker call: APP and HOST are shell variables passed via -e,
INVENTORY is the in-container SPOT for the duplicated paths, and the
redundant --vars-file (optional, default None) and --id (the inventory
is already narrowed by --include) flags are gone. All 153 role READMEs
regenerated from the template; test-run.yml executes exactly this block.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rewrite all 254 role descriptions to a uniform noun-phrase schema
(function-first, no leading verb, no trailing period, <=120 chars) and
enforce it via the extended description lint. Rename 12 role README H1
titles for clarity (e.g. Backup Container to Local -> Backup Docker
Volumes, CSS Service -> Corporate Design, NGINX Legal -> Imprint) and
regenerate their Cosmos and Quick Setup sections. Overview table
regenerated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Declare the proxy_resolvable overlay (swarm mode only) with the app domain as alias, following the committed keycloak/seaweedfs pattern: the front proxy harvests the alias and resolves the app cross-node over the overlay instead of 502ing on an unreachable node-local network. Compose deployments are unaffected since the overlay is gated to modes: [swarm].
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Roles declared under meta/main.yml dependencies now appear on both the
Dependencies and Dependents side, labelled with the glossary gear
symbol and connected by a fixed 1:1 edge; the legend explains it. All
role READMEs regenerated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Edges now carry their relation: solid 1:1 for fixed consumption, dashed
0..1 for conditional (group-membership gated) consumption. Below every
diagram the generator renders a reading key for the edge kinds and the
node markers, with the glyphs drawn from the shared symbol glossary.
All role READMEs regenerated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add cli/build/readme: derives each role README's managed sections from
templates/roles/README.md.j2.tmpl (the Cosmos mermaid diagram from
meta/services.yml, an invokable-gated Quick Setup, and Credits).
The Cosmos diagram shows dependencies, every declared service, and
dependents (roles that consume this one, matched by role name and by
provides provision-name in both directions). Role nodes carry deploy-mode
emoji markers (compose/swarm/host); disabled and shared services get a
stop mark; dependents are capped at 12.
test_readme enforces the Quick Setup body is byte-exact with the template
render. `make readme-generate` and `make cosmos` regenerate; `make cosmos`
also runs inside `make quality`.
Refactor: utils/symbol_glossary.py is the single source of truth for the
word/emoji mapping (unique project-wide), consumed by the complexity
matrix and the Cosmos builder. utils/roles/deploy.py holds role_has_stack
and role_deploy_modes, shared by both so the logic never drifts.
Add `make lint-mermaid` and its CI workflow, rendering every diagram via
mermaid-cli; the render step provisions chrome-headless-shell and unzip
on demand so it works in the minimal container.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per-role deploy-mode opt-out moves from meta/tests.yml 'skip:' to meta/services.yml <entity>.modes.{compose,swarm}.enabled on the primary entity. get_role_skip now derives from it; get_role_mode_enabled added. A new lint requires every role shipping templates/*compose*.yml.j2 to declare both mode flags. Migrates 107 services.yml and drops 11 skip-only tests.yml. Also removes the variant_status deployability gate so swarm runs one job per variant unconditionally (variant_status.py and deployable_variant_indices deleted, complexity model simplified). Adds the i8matix alias.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
drawio, fileslibreofficeedit and metadata are appstore/upstream apps, so Nextcloud serves their frontend bundles from /custom_apps/<app>/, not the read-only shipped /apps/<app>/. The specs asserted the bundle with a leading-slash locator script[src*="/apps/<app>/"], which cannot substring-match /custom_apps/<app>/ (the char before 'apps' is '_'), so the enabled-app bundle check deterministically returned count 0 across every variant.
Drop the leading slash (script[src*="apps/<app>/"]): it matches both /apps/ and /custom_apps/ webroots, so it is correct for shipped and appstore apps alike. These are the only 3 addon specs in the suite that assert a bundle asset; the served URLs (/custom_apps/drawio/js/main.js etc.) are captured in the CI trace.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New roles/sys-svc-container/templates/env.j2 sets TZ={{ HOST_TIMEZONE }} and LANG=C.UTF-8. Every role env.j2 includes it at the top; it renders via stack_host_template, the same engine that resolves the includes in compose.yml.j2, so it works in both compose and swarm. TZ/LANG are removed from the individual role env.j2 so each value stays single-source.
New lint tests/lint/ansible/test_env_base_include.py verifies every role env.j2 includes the base and never re-sets a base-owned key (keys derived from the base file).
mattermost and gitea meta/volumes.yml drop the /etc/timezone host-path bind: swarm hard-rejects a task whose bind source is missing on the node (Debian-trixie nodes have no /etc/timezone). TZ now comes from the base env. /etc/localtime bind also dropped; the container timezone follows TZ.
Non-directive comments were removed from the touched env.j2 (banners, restatements, reference URLs, narration) so the changed files pass the comment-lint; only tool directives remain.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The custom Dockerfile ran mkdir -p /var/www/html/config, which broke the
stock image's empty-/var/www/html invariant: Docker then had image content
at the shared nextcloud_data mount point and did a non-idempotent copy-up
os.Mkdir(config) per container. compose up --force-recreate starts
nextcloud and cron concurrently, so both raced that mkdir and the loser
crashed with 'failed to mkdir .../_data/config: file exists'. Reproduced
char-for-char locally (31/60 with the infinito bind, 7/80 without, so the
bind is only an aggravator); removing the image content gives 0/80.
Drop the Dockerfile mkdir so /var/www/html stays empty and the stock
entrypoint (reached via nextcloud-init-gate.sh -> /entrypoint.sh) creates
config/ as www-data with no copy-up. The mkdir only existed to pre-seed
config as www-data because binding infinito at /var/www/html/config/infinito
forced Docker to create config/ root:root; move that bind (and its
NEXTCLOUD_DOCKER_CONF_ADD_PATH loader path) out to /var/www/infinito so
config/ is owned by www-data and config.php stays writable. Keeps type:
bind, so the swarm config-object fix from 699008a16 is not reverted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The internal proxy declared a static upstream to nextcloud:9000, which
nginx resolves at config-parse time and treats as fatal (exit 1) on
NXDOMAIN. In swarm the nextcloud service name only appears once a task
passes its healthcheck, and the NFS-backed first-run init held the lock
~25s, so every proxy task that started in that window crashed with
'host not found in upstream', swarmkit's recent-failure penalty pinned
all replacements onto the manager, and the worker-placement gate failed.
Drop the static upstream and resolve nextcloud:9000 through a variable
(set $php_handler; fastcgi_pass $php_handler) using the http-level
resolver already emitted, mirroring the whiteboard/talk runtime
resolution in the same file: nginx now starts and returns 502 until the
app is healthy instead of crash-looping, so replicas keep their worker
placement. Validated with nginx -t.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The nextcloud compose job bundled variants 2,3 into one runner and
needed ~332min of deploy time against the 300min step budget inside
GitHub's hard 360min job cap; raising the timeout leaves no headroom
for the measured runtime variance and starves the post-steps that
upload artifacts. Add a variant_bundle_size override in meta/tests.yml,
consumed by the variant bundle packer and the complexity report alike,
and set it to 1 for web-app-nextcloud only: eight ~190min jobs with
over 100min of headroom while every other app keeps the global
bundling.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The partner probe and both provisioning execs addressed the bare
compose container name zammad-railsserver, which does not resolve in
swarm; the probe silently skipped the whole coupling and the connect
control never rendered. Route them through the container_address
resolver with the shell module (unquoted, subshell expansion), gate the
provision include on IS_STACK_HOST, and retry the rails provision to
absorb Zammad convergence lag. Compose behavior is unchanged: the
lookup returns the bare name there.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The onion guard on the 01_core include skipped the whole bbb deploy on
onion nodes, so no vhost existed and the E2E readiness probe against
bbb's clearnet canonical failed with TLSV1_UNRECOGNIZED_NAME. bbb pins
tor.enabled false, so its canonical stays clearnet and the role deploys
byte-identically on onion and clearnet nodes.
Drop the nextcloud onion app:disable for cloud_bbb accordingly: with
bbb deployed again its api.url is a valid clearnet URL and the addon
spec already skips on onion targets.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Over Tor the redirect chain behind a login/logout/OIDC click routinely exceeds Playwright's fixed 30s action timeout, so the click throws before the flow's scaled waits run. Sweep all role Playwright suites and the shared persona helpers: give every navigation/auth-transition click an onion-scaled timeout (.click({ timeout: resolveTimeout(30_000) })) and route the remaining raw millisecond literals (timeout options, waitForTimeout, sleep-promise setTimeout, positional wait args) through resolveTimeout. Local UI clicks (menus, toggles, modal dismissals, DOM clicks inside evaluate) stay untouched.
Two new lints keep it that way: test_no_raw_timeout_literals forbids raw numeric timeout literals in Playwright specs (opt-out: nocheck: raw-timeout), test_persona_clicks_scaled requires every click in the shared persona auth flows to carry a resolveTimeout timeout (opt-out: nocheck: unscaled-click).
Verified: node --check on all 133 changed JS files, all 13 playwright lints green, make autoformat clean, make test green (2745+196+163+4 tests OK).
Co-Authored-By: Claude Opus 4.8 (1M context) <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>
Scale the login helper's waitForVisibleCandidate call-sites and the Sign In / Log out clicks through resolveTimeout, and wait for network idle after logout so the slow keycloak end-session redirect chain over Tor settles before the logged-out state is probed.
Skip the Talk-admin and SeaweedFS-filer specs on onion targets (WebRTC HPB/STUN/TURN and a clearnet-only filer UI). Assert the XWiki partner host against XWIKI_BASE_URL from the env instead of a hard-coded clearnet host, so the check holds on both clearnet and onion.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On an onion node the OIDC issuer (Keycloak) is a .onion; libcurl (oidc_login's Guzzle client) refuses to resolve .onion per RFC 7686, so the server-side OIDC backchannel (discovery/token/jwks) fails. Set Nextcloud's system proxy to socks5h://host.docker.internal:<tor-socks> and expose the host Tor SOCKS to the app container via extra_hosts, both gated on SSO being enabled and the Keycloak domain being an onion. No-op on clearnet nodes.
Hoist the repeated services.sso.enabled and Keycloak-domain lookups into NEXTCLOUD_SSO_ENABLED and NEXTCLOUD_OIDC_HOST constants (mirroring MATRIX_OIDC_HOST) and use them across the role.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BigBlueButton is WebRTC (SRTP/UDP with ICE/STUN) which Tor cannot carry. Guard the role's core include on the resolved Keycloak domain not being an onion (alongside the existing run_once guard), disable the co-installed cloud_bbb Nextcloud app on onion at runtime, and skip the BBB Playwright specs on onion targets.
Relax the run_once schema test (test_service_core_first_task_run_once) to accept a when-list that contains the run_once guard, so a role can add extra include conditions while staying protected against duplicate execution.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The occ-status probes (1m interval, 10s timeout, 3 retries) flipped
unhealthy during the recognize plugin install (575MB onto the NFS html
volume saturates the lab node), so swarm reaped the fpm and cron tasks
mid-install and wedged the DinD dockerd the retries kept exec-ing into.
Raise both probes to 30s timeout / 10 retries so a load storm outlasts
the healthcheck instead of the deploy.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The custom flag moves from 33 hardcoded template kwargs into the merged
service config: 'custom: true' names the shared <entity>_custom build,
a string names a second distinct '<string>_custom' image inside one app
(nextcloud whiteboard). The image lookup reads the declaration as a
fallback, so config consumers (e.g. backup image matching) resolve the
exact deployed reference from one declaration point. nextcloud cron now
runs the nextcloud_custom build like the app service instead of the
upstream image.
A new services lint couples the declaration to reality in both
directions: custom requires a role Dockerfile, and a role Dockerfile
requires a custom declaration. The eight legacy roles deploying local
builds under upstream tags or ad-hoc image= overrides are ratcheted via
'# nocheck: dockerfile-custom'.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lookup('canonical_url', app_id[, key][, consumer=app]) resolves the web base
URL for an app or one of its named canonicals (element/synapse/filer),
following the onion-injected merged-domain map: .onion targets resolve to
http (Tor provides transport security), everything else follows TLS_ENABLED.
The consumer= kwarg returns '' unless the consuming app has the target's
service binding enabled, replacing the recurring
"(lookup(...) if lookup('config', app, 'services.X.enabled') else '')"
template branching.
- utils/cache/domains.py: inject the node onion into dict-form
(named-canonical) domain declarations, not just list/str forms
- keycloak redirect_uris: onion redirect URIs are http
- 13 playwright.env.j2: manual scheme branching -> canonical_url(consumer=)
- lint: forbid manual '://' concatenation and to_onion_url in role templates
- unit tests: 10 cases (primary/named x clearnet/onion, consumer gating)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New lint rejects literal port numbers in roles/*/vars/main.yml: the port
belongs in meta/services.yml (services.<svc>.ports.<scope>.<name>) where
band governance and stack rendering read it; vars resolve it via
lookup('config', ...). Migrated the 16 remaining literals across
elasticsearch, registry-docker, erpnext, flowise, funkwhale, magento,
moodle, nextcloud, penpot and shopware.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rename the role, its test tree, the primary entity key
(volume-2-local), the derived vars (BKP_VOLUME_2_LOCAL_*,
SYS_SERVICE_BACKUP_VOLUME_2_LOCAL), the run-once fact, and every
group_names consumer flag, include path, doc, and lint reference. The
container_backup service key and the on-disk backup-docker-to-local
tree keep their names: both are contracts with existing consumer
entries, snapshot trees, and the baudolo pull tooling.
Operators must rename the svc-bkp-container-2-local inventory group and
disable the previous versioned units on already-provisioned hosts; the
pre-deploy snapshot glob derives its base from the new role name and
will not find them.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Variant 0 pins the backup consumer flag enabled so the matrix baseline exercises the in-group branch; every other variant pins it disabled (shared: false carries the static-override suppression since the base value stays literally true). The matrix and wordpress variant entries move their services mapping to the list-item head so the static-override line parser resolves the override paths; yaml semantics are unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Every invokable stack role that declares named volumes in meta/volumes.yml now carries the standard container_backup consumer entry: enabled resolves via 'svc-bkp-container-2-local' in group_names, shared stays literally true since there is no per-consumer backup instance. Playwright/dynamic-flag/bond-baseline suppressions ride on the block per the established idioms; stale explanatory comments in the touched files are dropped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First boot on loaded fork runners exceeded the healthcheck windows: swarm reaped moodle's fpm container after 60s+5x1m (exit 137 mid-install) and nextcloud's occ-status containers right at the 10m+3x1m boundary. Extend to 15m/20m following the joomla/espocrm/suitecrm precedent; container exits still reap immediately, only hanging unhealthy checks wait longer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Variant 1 runs redis without a password; the unconditional env line handed the image an empty REDIS_HOST_PASSWORD, phpredis sent AUTH to a passwordless redis (ERR Client sent AUTH), the session backend died and every login bounced in a CSRF loop. Emit the line only when the engine actually has a password; the rendered env is byte-identical whenever one is set.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New lint test_no_raw_goto.py forbids raw `page.goto(...)` in role playwright specs + persona flows (the gotoOnion wrapper in personas/utils is exempt) so navigation retries transient Tor-transport errors over onion. Migrates every raw call across ~75 roles (343 sites) to `gotoOnion(page, ...)` and teaches gotoOnion to detect relative onion URLs (`isOnionCanonical`). gotoOnion is a behaviour-identical drop-in on clearnet (single attempt, same return). Verified: no-raw-goto lint green, eslint 0 errors, make test green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
make autoformat normalised inline-comment spacing (` #` -> ` #`) in role meta/services.yml + meta/variants.yml and re-wrapped a few Python files (ruff line-length). Pure formatting — verified via git diff -w (whitespace-only) and diff inspection (ruff line-wraps only, no logic change).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the global TIMEOUT_FACTOR and a shared timeouts.js helper: resolveTimeout(base) scales by TIMEOUT_FACTOR and, over a .onion target, an extra multiplier, so Tor's per-request latency no longer trips hard-coded timeouts. Migrate every raw timeout in the specs (timeout:/timeout=/setTimeout/waitForTimeout) to resolveTimeout, stage the helper in all three staging paths, and add test_no_raw_timeouts to forbid raw numeric timeouts. Clearnet behaviour is unchanged (factor 1: resolveTimeout(N) === N).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A curl against an accepted-but-silent peer hangs forever: task retries
never fire because the first attempt never returns, and the CI job dies
at the 6h runner cut (proven by the mailu admin-API hang that took the
suitecrm/peertube swarm legs down). The new `curl` filter renders the
hardened base command ("{{ 30 | curl }}" -> curl -s --connect-timeout 5
--max-time 30); scaling composes with lookup('timeout').
test_curl_via_filter enforces it: bare curl invocations in role tasks,
templates and shipped scripts fail the lint with a pointer to the
filter. Plain .sh files, which cannot render Jinja, pass by pinning
--max-time inline; Docker healthchecks are exempt (their timeout field
caps them); `# nocheck: curl-timeout` opts out (catalogued in
suppression.md).
Convert all 34 existing invocations: mailu (replacing the short-lived
MAILU_CURL var), elasticsearch, checkmk, yourls (300s for the
schema-creating installer POST), the telegram alert script, and the
plain scripts of jellyfin, svc-runner, apt_repo, moodle (600s tarball)
and the mattermost provisioner. Mailu's user/token tasks additionally
carry task-level timeouts so a wedged container exec cannot stall the
play.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The container_address lookup emits $(...) shell syntax in swarm and
documents that callers must use the shell module; the 22 occ tasks
still used command:, so the resolution reached docker literally and
every plugin/LDAP/integration task died on all swarm variants. Convert
them to shell: (args stay single-quoted or [A-Za-z0-9] slugs, so
compose semantics are unchanged).
NFS mounts use local_lock=flock, which makes the stock entrypoint's
init flock node-local: concurrent replicas each won "exclusive" locks
and shredded the rsync install (occ 255, converge livelock). A small
init gate now fronts the entrypoint: only swarm task slot 1 runs the
init, other slots wait for version parity plus config.php; without
TASK_SLOT (compose) it degenerates to a plain exec. The gate ships as
files/nextcloud-init-gate.sh, copied into the build context on the
stack host and referenced through the NEXTCLOUD_INIT_GATE_FILE SPOT.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Keycloak's realm import enforces the ^[a-z0-9]+$ username pattern, so
the LDAP-disabled seeding path (Ensure non-reserved users exist) died
on every hyphenated bot: "Username is reserved or contains invalid
characters". These are API/basic-auth accounts that never need an SSO
identity; accounts: [] takes them out of the seeding loop (semaphore's
breakglass-administrator convention), and no-reply keeps only its
mailbox account.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Deploy GitLab from the official CNG CE images as six services: webservice (puma) and sidekiq scale to the topology default, workhorse is the sole HTTP entry, gitaly and a one-shot migrations job are pinned singletons, shell runs gitlab-sshd. The Omnibus container, its Dockerfile and the GITLAB_OMNIBUS_* config groups are removed. All rails/workhorse/shell/gitaly configs and shared secrets are pre-rendered by Ansible and mounted read-only; eleven credentials land in meta/schema.yml including an rsa_pem_2048 OIDC signing key (new value_generator algorithm). Postgres, redis and S3 object storage ride the existing lookups; OIDC, SMTP, the public SSH port and the root password are preserved. compose_volumes gains a per-volume nfs:false opt-out so the gitaly repositories volume stays node-local in swarm (upstream forbids git repositories on NFS); nfs_prep skips volumes without driver_opts accordingly. The Nextcloud gitlab integration execs the CNG rails runner. ruby_single_line dies with its only consumer; ruby_dq keeps test coverage. Ports (8181/8080/2222/8075/3808/9122) and service names resolve from single SPOT declarations in meta/services.yml via vars constants; the version pin sits on the webservice image pair so the updater can bump it. Live-validated in compose up to all six services healthy and migrations completed; fresh-install-only semantics for existing Omnibus data are documented.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Onion routing is transport-layer with no persona/UI surface, so tor cannot be gated from the per-persona Playwright specs. Mark the tor block with '# nocheck: playwright-service-flag' in the 60 domain roles that ship a playwright.env.j2, per test_env_services_match's documented exemption for services that legitimately cannot be Playwright-gated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each role's meta/variants.yml now pins the tor onion gate both ways so the matrix exercises it: variant 0 pins services.tor.enabled: true, every non-baseline variant pins enabled: false + shared: false (# nocheck: variants-static-override, since tor.shared is a static literal). This gives real test_coverage / test_bond_baseline coverage without the removed per-key code exemptions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Domain-serving roles reference the svc-net-tor provider via a tor: block. Consumers declare enabled (dynamic group_names gate) and shared: true (static — onion is always the shared node model, inline nocheck: dynamic-flag). The provider svc-net-tor additionally declares exclusive/primary defaults that consumers inherit via resolve_service_config unless overridden.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>