actions/checkout records two includeif targets per repo: one under
the host path (/home/runner/work/_temp/...) and one under the
container-internal path (/github/runner_temp/...). Only the host path
exists on the runner; the container path is a placeholder for
docker-based actions.
`truncate` (without -c) aborts on the missing container path with
exit 123. Add `-c` (--no-create) so missing paths are skipped
silently and the existing credentials file still gets blanked.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
actions/checkout v6 doesn't put `http.https://github.com/.extraheader`
inside .git/config; it writes that header to an external file at
/home/runner/work/_temp/git-credentials-*.config and pulls it in via
local `includeif.gitdir:<repo>/.git.path = <credfile>` directives.
That's why every earlier `git config --local --unset-all` /
`--remove-section` round was a silent no-op (verified by the previous
debug commit's dump: the extraheader survives because the source-of-
truth lives outside .git/config).
Read every includeif `.path` value from local config, dedupe, and
`truncate -s 0` the targets so the included header chunk becomes
empty. The App installation token then enters the wire via the
URL-embedded `x-access-token:${GH_TOKEN}` credentials with no
competing Authorization header.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The previous strip targeted only `--local` config and the resulting
CI log showed local was already empty, yet the push still authed
as github-actions[bot]. The auth has to be coming from --global,
--system, --worktree, or GH_TOKEN itself is GITHUB_TOKEN.
Dump every scope's http/credential/url entries before and after the
strip, and probe /installation/repositories with GH_TOKEN so the
HTTP status alone (no secret in logs) tells us whether it's an App
installation token or the workflow GITHUB_TOKEN.
Will replace this with the real fix once the next CI run surfaces
where the rogue Authorization actually originates.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Previous attempts targeted a specific config key path
(`http.https://github.com/.extraheader`). If actions/checkout v6
stores it under a different subsection naming, our unset/remove-section
no-ops silently and the GITHUB_TOKEN-bearing header keeps overriding
the App-token URL credentials.
Walk every `http.*.extraheader` entry the local config holds, pipe
the matched key names through xargs to unset-all each one, and dump
the remaining `http.*` config before pushing so the next CI run
proves what was stripped. The push itself still uses an
x-access-token URL so the App's installation token is the only
Authorization that reaches the wire.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`--replace-all` on http.https://github.com/.extraheader still left the
wire with two Authorization headers ("remote: Duplicate header" → 400).
Drop the existing extraheader **and** the whole `http.https://github.com/`
subsection from local config, then push to a URL that embeds
`x-access-token:${GH_TOKEN}` directly so only one Authorization header
(the App's installation token) reaches the server.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Last attempt (unset + URL-embedded token) still pushed as
github-actions[bot] because actions/checkout's
http.https://github.com/.extraheader Authorization survived the
unset (likely silenced by `|| true`) and outranked URL-embedded
credentials on the wire.
Force-replace the header value with the App installation token's
HTTP Basic equivalent (`x-access-token:<token>` base64'd, same shape
checkout itself uses for GITHUB_TOKEN). Drops the URL rewrite, drops
the unset-then-rewrite dance, and guarantees a single Authorization
header that GitHub honours as the App.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`git config --unset-all "http.https://github.com/.extraheader"` is a
git config key path, not a fetchable URL, but
tests/external/repository/test_urls_reachable.py extracts the URL-like
substring and probes it, which returns 404 and fails the suite.
Tag the line with `# nocheck: url` so the URL test skips it.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`actions/checkout` configures git's http.extraheader to authenticate
as GITHUB_TOKEN, which update.yml scopes to `contents: read`. The
subsequent `git push --force` in open_pr.sh therefore tripped 403
("Permission to infinito-nexus/core.git denied to github-actions[bot]")
even though the token-minting step had already produced an App
installation token with `contents: write` in GH_TOKEN.
Drop checkout's extraheader and rewrite origin's URL to embed
GH_TOKEN as x-access-token before pushing, so the App's permissions
actually reach the wire.
Also pin two actionlint `-ignore` patterns in scripts/lint/action.sh
to silence false positives for the `client-id` input on
actions/create-github-app-token@v3 (the action's action.yml carries
the input and marks `app-id` deprecated; actionlint 1.7.12's bundled
schema lags behind the upstream release).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
actions/create-github-app-token@v3 emits "Input 'app-id' has been
deprecated with message: Use 'client-id' instead." on every run.
Swap the input + rename the corresponding repository secret from
BOT_APP_ID to BOT_APP_CLIENT_ID so the value reflects what the
action actually expects: the OAuth-style Client ID (`Iv…` format
shown on the App's General page), not the numeric App ID.
Requires the operator to create a fresh `BOT_APP_CLIENT_ID`
repository secret on infinito-nexus/core before merging; the old
`BOT_APP_ID` secret becomes orphaned and SHOULD be deleted in the
same settings pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
open_pr.sh's PR-dedup loop relied on `gh pr diff <num>` which the
GitHub API refuses with HTTP 406 once a PR exceeds 300 changed files.
Under `set -euo pipefail` that killed the workflow whenever any
unrelated large PR was open against the same base branch.
Replace the diff-hash check with a tree-SHA comparison: fetch each
PR's head commit shallowly, compare `git log -1 --format=%T` against
HEAD's tree. Same-tree means same end-state regardless of base SHA,
whitespace, file order, or patch context. Unfetchable heads (deleted
branches, lost permissions) fall through as non-duplicates instead of
aborting the run.
image_versions.sh / repository_refs.sh also pick up an explicit
`# shellcheck disable=SC1091` above their `. .env` source since the
dotenv file is generated at runtime and never present during
shellcheck's static parse.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`git config --unset-all "http.https://github.com/.extraheader"` is a
git config key path, not a fetchable URL, but
tests/external/repository/test_urls_reachable.py extracts the URL-like
substring and probes it, which returns 404 and fails the suite.
Tag the line with `# nocheck: url` so the URL test skips it.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`actions/checkout` configures git's http.extraheader to authenticate
as GITHUB_TOKEN, which update.yml scopes to `contents: read`. The
subsequent `git push --force` in open_pr.sh therefore tripped 403
("Permission to infinito-nexus/core.git denied to github-actions[bot]")
even though the token-minting step had already produced an App
installation token with `contents: write` in GH_TOKEN.
Drop checkout's extraheader and rewrite origin's URL to embed
GH_TOKEN as x-access-token before pushing, so the App's permissions
actually reach the wire.
Also pin two actionlint `-ignore` patterns in scripts/lint/action.sh
to silence false positives for the `client-id` input on
actions/create-github-app-token@v3 (the action's action.yml carries
the input and marks `app-id` deprecated; actionlint 1.7.12's bundled
schema lags behind the upstream release).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
actions/create-github-app-token@v3 emits "Input 'app-id' has been
deprecated with message: Use 'client-id' instead." on every run.
Swap the input + rename the corresponding repository secret from
BOT_APP_ID to BOT_APP_CLIENT_ID so the value reflects what the
action actually expects: the OAuth-style Client ID (`Iv…` format
shown on the App's General page), not the numeric App ID.
Requires the operator to create a fresh `BOT_APP_CLIENT_ID`
repository secret on infinito-nexus/core before merging; the old
`BOT_APP_ID` secret becomes orphaned and SHOULD be deleted in the
same settings pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
open_pr.sh's PR-dedup loop relied on `gh pr diff <num>` which the
GitHub API refuses with HTTP 406 once a PR exceeds 300 changed files.
Under `set -euo pipefail` that killed the workflow whenever any
unrelated large PR was open against the same base branch.
Replace the diff-hash check with a tree-SHA comparison: fetch each
PR's head commit shallowly, compare `git log -1 --format=%T` against
HEAD's tree. Same-tree means same end-state regardless of base SHA,
whitespace, file order, or patch context. Unfetchable heads (deleted
branches, lost permissions) fall through as non-duplicates instead of
aborting the run.
image_versions.sh / repository_refs.sh also pick up an explicit
`# shellcheck disable=SC1091` above their `. .env` source since the
dotenv file is generated at runtime and never present during
shellcheck's static parse.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`update.yml` jobs (Docker image versions, repository refs) shelled
straight into `python3 -m cli.contributing.update.{docker,repository}`
without sourcing the project dotenv. After eb4bcd23a removed the
`.get(..., default)` fallback in resolve_max_fetch_workers(),
KeyError: 'INFINITO_WORKER_FETCH' surfaced on every run.
Both wrapper scripts now `make dotenv` (idempotent) and `set -a; . .env;
set +a` before exec'ing the updater, restoring the SPOT env chain on
CI runners that don't go through compose.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
v1.15.2 ships the `alias: infinito` entry in pkgmgr's
src/pkgmgr/config/defaults.yaml so `pkgmgr install infinito`
(and `pkgmgr path/version infinito`) resolves again. Without
this bump, sys-cli, web-app-navigator and the test-install-pkgmgr
CI job stay broken on the upstream identifier-rename fallout.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`pkgmgr install infinito` started failing in CI after the floating
`pkgmgr-<distro>:stable` base image was rebuilt with kpmx 1.15.x, which
no longer registers the `infinito` identifier in its default config:
Identifier 'infinito' did not match any repository in config.
The error string then flowed through `pkgmgr path infinito` in
entry.sh and was treated as the install directory, leading to
`make install` in a bogus cwd. This broke test-development,
test-install-pkgmgr, and build-ci-images for every distro.
The pkgmgr step in the dev Dockerfile is redundant — `make install`
runs `pip install -e .` which already produces the `infinito` CLI
shim via pyproject.toml's [project.scripts] entry. Drop the pkgmgr
calls, the rsync-into-INFINITO_PATH dance, and the `INFINITO_PATH`
variable; everything operates on `INFINITO_SRC_DIR` directly.
Production `pkgmgr install` paths (roles/sys-cli, roles/web-app-navigator,
.github/workflows/test-install-pkgmgr.yml) are intentionally untouched —
the test-install-pkgmgr job is what surfaces the upstream kpmx breakage.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three docs still referenced the pre-collapse service keys:
* docs/contributing/artefact/files/role/playwright.specs.js.md — biber
and administrator auth-chain diagrams listed
`skipUnlessServiceEnabled('oidc' | 'oauth2' | 'ldap')`; the unified gate
is `'sso' | 'ldap'`.
* docs/contributing/design/role/services/base.md — the `lookup('service',
...)` example block carried the now-invalid `'oidc'` key; replaced
with `'sso'`.
* docs/requirements/019-playwright-meta-services-parity.md — the
prometheus, matomo, and mailu rows still spelled
`safeSkipUnlessEnabled("oauth2"|"oidc")` and
`isServiceEnabled("oauth2")`; all rewritten to `"sso"`.
`lookup('service', 'oidc')` blew up during render after the oauth2+oidc →
sso collapse — the `oidc` key no longer exists in the service registry.
Rename the lookup to `sso` so the WordPress Playwright env renders again.
`skipUnlessServiceEnabled("oidc")` tripped service-gating's unknown-service
guard after the oauth2+oidc → sso collapse. Rename the gate to `sso` so
both admin and biber persona tests skip/run via the rendered
`SSO_SERVICE_ENABLED` flag.
Post-req-21 both flags were bound on `isServiceEnabled("sso")`, so any
sso-enabled variant entered the oauth2-proxy redirect branch — which
timed out because Taiga is `flavor: oidc` (in-app `taiga-contrib-oidc-auth`,
no oauth2-proxy sidecar). Render `SSO_FLAVOR` from the unified `sso`
resolver and split the predicates so taiga{Oauth2,Oidc}Enabled track the
actual flavor.
Installs kpmx and runs `pkgmgr archive docs/requirements` in one
shot. The requirements-completeness lint failure message now points
contributors at this target instead of asking them to invoke pkgmgr
directly.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The lint test only used two tiny helpers from kpmx
(iter_archivable_files, count_unchecked_items). Inlining them is ~15
lines and lets us drop kpmx from dev dependencies entirely, so neither
host nor container has to install the package just for a lint that
walks docs/requirements/ and counts task-list markers. The
pkgmgr archive CLI remains the canonical archival tool; the failure
message still points there.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
tests/lint/repository/documentation/test_requirements_completeness.py
imports pkgmgr.actions.archive after the v8.0.x archive-CLI move out
of cli/contributing/requirements/. The test container installs dev
dependencies via `pip install -e .[dev]`, so kpmx must be listed
there or the lint test fails with `ModuleNotFoundError: pkgmgr`
during collection.
Pinned to >=1.15.0 because the archive subcommand and the
pkgmgr.actions.archive package only ship in that release.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The fully-checked-Markdown archive helper used to live under
cli/contributing/requirements/archive/ in this repo. It is a generic
spec-folder-management primitive (numbered NNN-topic.md files,
task-list completeness check, README ## Archive section append) and
now ships in pkgmgr.actions.archive so every kpmx-managed repository
can rely on the same convention.
Changes here:
* tests/lint/repository/documentation/test_requirements_completeness.py
imports the archive primitives from pkgmgr.actions.archive instead
of the vendored cli.contributing.requirements.archive, and points
contributors at `pkgmgr archive docs/requirements` in the failure
message.
* cli/contributing/requirements/README.md becomes a stub that
redirects readers to the kpmx archive subcommand.
* The vendored cli/contributing/requirements/archive/ package and its
unit tests under tests/unit/ are deleted.
* The pkgmgr role pin bumps to v1.15.0 to pick up the new archive
subcommand on deployed hosts.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Five eslint errors blocking make lint-javascript:
* personas/guest.js, web-app-opencloud/playwright.spec.js,
web-app-opentalk/playwright.spec.js — re-thrown wrapped errors now
carry { cause: err } so the symptom keeps a link to the root cause.
* personas/utils/csp.js — drop let blockedHost = "" initial value;
the catch arm returns early, so the empty string was never read.
* web-app-wordpress/_shared.js — drop let match = null initial value;
both branches assign before the readers fire.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
v1.14.0 ships:
* `pkgmgr release --retry` re-deploys an existing HEAD release without
re-tagging or modifying any files (re-push tag + re-align latest +
re-run publish), so a release whose post-tag push or PyPI upload
failed mid-flight can be recovered without code churn.
* package_name now resolves from packaging/debian/control,
packaging/arch/PKGBUILD, or RPM .spec instead of the repo folder
basename, so renaming a repo no longer flips the distro-package
identity. This is what unblocks the upcoming infinito-nexus-core
Debian builds whose changelog/control mismatch caused
`dpkg-source --before-build` to exit 2.
Explicit pin (vs the prior floating `stable`) gives reproducible
deploys across hosts and locks the new package_name resolver behaviour
to a known version.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three iteration-rule sharpenings, all surfaced by repeated foot-guns in
the web-app-zammad role-build loop:
- Promote "validate the fix in-container before redeploying" from SHOULD
to MUST. Adds an explicit reproducible-there carve-out (container
crashes at startup, misconfigured env var, image-side bug, in-container
script failure) so the rule is unambiguous on when it applies; the
only exception is failures that genuinely depend on multi-container or
post-deploy Ansible state.
- Playwright-specific addendum: when the failing unit is a Playwright
spec, "in-container validation" means running
`make compose-playwright role=<role>` against the live stack — not
just `curl` against the underlying API. The spec runs in a browser
context with env loading, locator strategies, and cookie state that
`curl` does not exercise.
- New "Full-matrix iteration flow" subsection under "Matrix variants"
documenting the variant-pinned `mode=update` → per-remaining-variant
`mode=reinstall full_cycle=true` → final unpinned full-matrix
`mode=reinstall full_cycle=true` recipe. Closes the gap where the
existing "Matrix variants" rules only described focused single-variant
debug and full-matrix initiation, but not the cycle that keeps full
cross-variant coverage as the iteration target.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fork/feature/web-app-zammad held an older variant of the same 5
Zammad-role commits (different SHAs after a local rebase). HEAD is a
content superset (adds the docs-iteration sharpen + the
basic_auth_password→api_bot_password rename + SPOT migrations), so all
15 conflicted paths resolved with --ours. Cherry-picked the
nginx-resolv comment from the incoming side into compose.yml.j2 — it
documents WHY the entrypoint sed-patch exists and is worth keeping.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Rename credential basic_auth_password → api_bot_password and var
ZAMMAD_ADMINISTRATOR_PASSWORD → ZAMMAD_API_BOT_PASSWORD to reflect
that the value is the role-internal bot's local password, not the
administrator's (reused for the wizard-bypass admin only because both
need the same shell-safe alphanumeric form).
- Introduce ORGANIZATION constant (defaults to SOFTWARE_NAME) and migrate
organization-string consumers (mattermost team, shopware shop title,
dashboard login/logout descriptions, simpleicons "provided by",
user-administrator description/lastname).
- Add HOST_LL_CC_TAG (BCP-47 form, e.g. en-gb) and use in auto_wizard.
- Add NETWORK_TRUSTED_PROXY_CIDRS SPOT and consume from suitecrm,
shopware and zammad reverse-proxy configs.
- Extract ZAMMAD_WEB_PROTOCOL via lookup('tls', …, 'protocols.web')
instead of inline 'https' if … else 'http'.
- Use database_query for CREATEDB grant so it works on shared+non-shared
databases; back the SQL with files/sql/grant_createdb.sql SPOT.
- Read administrator firstname/lastname from users lookup in
auto_wizard.json.j2 instead of hardcoding.
- Drop redundant elasticsearch: block from web-app-zammad services.yml
(no consumer reads it).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three iteration-rule sharpenings, all surfaced by repeated foot-guns in
the web-app-zammad role-build loop:
- Promote "validate the fix in-container before redeploying" from SHOULD
to MUST. Adds an explicit reproducible-there carve-out (container
crashes at startup, misconfigured env var, image-side bug, in-container
script failure) so the rule is unambiguous on when it applies; the
only exception is failures that genuinely depend on multi-container or
post-deploy Ansible state.
- Playwright-specific addendum: when the failing unit is a Playwright
spec, "in-container validation" means running
`make compose-playwright role=<role>` against the live stack — not
just `curl` against the underlying API. The spec runs in a browser
context with env loading, locator strategies, and cookie state that
`curl` does not exercise.
- New "Full-matrix iteration flow" subsection under "Matrix variants"
documenting the variant-pinned `mode=update` → per-remaining-variant
`mode=reinstall full_cycle=true` → final unpinned full-matrix
`mode=reinstall full_cycle=true` recipe. Closes the gap where the
existing "Matrix variants" rules only described focused single-variant
debug and full-matrix initiation, but not the cycle that keeps full
cross-variant coverage as the iteration target.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds 7 new Playwright scenarios covering helpdesk-core workflows and
infrastructure regressions:
- test-csp-headers — CSP response + meta-tag parity
- test-alias-domain — server-name alias serves 2xx, not 30x
- test-rest-api-ticket-create — POST /api/v1/tickets via Basic auth
- test-ticket-create-as-customer — biber SPA → fill form → submit
- test-ticket-reply-as-agent — API-seeded ticket; admin posts a reply
- test-websocket-realtime — open ticket; API appends article;
expect realtime push into the open SPA
- test-mail-to-ticket — raw-socket SMTP to Mailu helpdesk
mailbox; poll Zammad until ticket
appears
Auto-configures Zammad's Email channel (inbound IMAP + outbound SMTP)
against Mailu via a new post-bootstrap rails-runner step. The Ruby for
both bootstrap steps lives in standalone files and is piped through
`container exec -i ... bundle exec rails r -`; the previous wrapper
shell scripts (`apply_*.sh`) are removed in favour of inline `shell:`
calls in tasks/01_core.yml. Env vars are forwarded into the container
via `container exec -e VAR ...` so the Ruby uses `ENV.fetch` and stays
value-free / portable.
vars/main.yml grows three constants:
- ZAMMAD_OMNIAUTH_INIT_CONTAINER_PATH — bind-mount target inside the
container (was hard-coded four times in compose.yml.j2)
- ZAMMAD_HELPDESK_USER / _EMAIL / _PASS — derived from the `helpdesk`
mail-bot user provisioned via meta/users.yml
- ZAMMAD_MAIL / ZAMMAD_EMAIL_ENABLED — derived from the central email
lookup + the services.email gate
playwright.env.j2 grows MAIL_SMTP_HOST/PORT/USER/PASS + HELPDESK_EMAIL
for the SMTP injection in the mail-to-ticket scenario, sorted ASC per
the env-key lint.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Playwright spec split into per-test partials, mirroring the web-app-kix
layout (lint-conform: top-level `playwright.spec.js` requires the
partials and shared module):
- `files/playwright/_shared.js` — env vars, `signInViaZammadOidc`,
`zammadLogout`, `beforeEach`
- `files/playwright/test-landing.js`
- `files/playwright/test-login-administrator.js`
- `files/playwright/test-login-biber.js`
- `files/playwright/test-guest-persona.js`
Variable dedup in `vars/main.yml`:
- `ZAMMAD_VOLUMES_DIR` — single base path
- `ZAMMAD_AUTO_WIZARD_HOST_PATH` — derived
- `ZAMMAD_OMNIAUTH_INIT_HOST_PATH` — derived (was repeated 4x in compose)
- `ZAMMAD_OIDC_ENABLED` — single source for the OIDC gate
All consumers in `tasks/01_core.yml`, `templates/compose.yml.j2`,
`templates/auto_wizard.json.j2`, `templates/playwright.env.j2` now read
the constants instead of inlining the `lookup(...)` expressions.
Drive-by: `meta/main.yml` description narrowed to describe Zammad itself
(role-specific text moved out of the upstream-facing galaxy_info field).
`make autoformat` clean, `make test` green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`make autoformat` line-wraps the api_token credential description in
meta/schema.yml at the standard yamllint column. No semantic change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds roles/web-app-zammad/ — a brand-new web-app role deploying Zammad as
the Infinito.Nexus helpdesk with direct OpenID Connect SSO against the
shared Keycloak client. Tracks requirement 022.
Stack: ghcr.io/zammad/zammad 6.5.0 (init / railsserver / websocket /
scheduler / nginx, all driven by upstream command modes), bundled
elasticsearch 8.13.4 + bundled memcached (no central svc role exists),
central svc-db-postgres reused via lookup('database', application_id).
Variant matrix mirrors web-app-kix: V1 oidc+ldap, V2 all-false, V3 ldap-only.
Bootstrap flow (tasks/01_core.yml):
- Stage auto_wizard.json into the role's volumes dir AFTER sys-stk-full
creates the directory; bind-mount it into zammad-init so the upstream
rake zammad:setup:auto_wizard bypasses the setup UI on first boot.
- Stage zz_omniauth_allow_get.rb initializer + bind-mount into rails /
websocket / scheduler / nginx, since the Zammad SPA's SSO link is a
plain GET and omniauth 2.x forbids GET by default.
- ALTER ROLE <zammad-db-user> WITH CREATEDB so Zammad's unconditional
`rake db:create` doesn't trip on insufficient privilege when the
database is pre-created by svc-db-postgres.
- Apply post-bootstrap settings via apply_oidc_settings.sh
(rails-runner inside zammad-railsserver): fqdn, http_type so Zammad
emits the correct redirect_uri to Keycloak; auth_openid_connect +
auth_openid_connect_credentials so the SSO button works.
Workarounds embedded in compose.yml.j2:
- zammad-nginx command override that copies /docker-entrypoint.sh to
/tmp and patches `grep "nameserver"` to `grep -E "^nameserver"`,
because Docker writes comment lines like `# Overrides: …` into
/etc/resolv.conf that the upstream grep matches as substrings,
yielding an invalid `resolver … Overrides: …` directive nginx rejects.
- DATABASE_URL pre-populated in env.j2 so post-bootstrap
`container exec rails r` calls (which bypass the entrypoint) can
load Rails without database.yml.
Playwright (files/playwright/playwright.spec.js, single-file lint-conform
layout): landing-reachable + administrator OIDC + biber OIDC + guest
public-landing scenarios. Personas skip-correctly via
skipUnlessServiceEnabled('oidc') under V2 / V3.
V1 end-to-end green: admin + biber Playwright pass through the full
Keycloak SSO round-trip. V2 (all-false) and V3 (ldap-only) green on
baseline reinstall with full_cycle=true.
Follow-ups indexed in roles/web-app-zammad/TODO.md: Zammad LdapSource
auto-create for V3, mail channel auto-config + end-to-end mail-to-ticket
test, Keycloak-group-to-Zammad-role mapping (Zammad doesn't ship a
built-in claim-to-role translator — currently all OIDC logins default to
Customer per Decision #5 fallback), Playwright assertion for the
`zammad.helpdesk.*` server-name alias.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New requirement for a `web-app-zammad` role that integrates Zammad as
the Infinito.Nexus helpdesk with OIDC-direct SSO against Keycloak.
Captures 11 operator-confirmed design decisions before implementation:
- Canonical `helpdesk.infinito.example` + true server-name alias
`zammad.helpdesk.infinito.example` (no 301 redirect).
- SSO flavor: OIDC-direct via Zammad's native OmniAuth strategy
(no oauth2-proxy sidecar). Uses the legacy `services.oidc.*` shape
(pre-021); does not block on 021.
- Keycloak OIDC client auto-provisioned via `web-app-keycloak`.
- Keycloak group -> Zammad role mapping (Admin/Agent/Customer) if
Zammad's OIDC strategy supports group claims natively; otherwise
documented limitation.
- Upstream `ghcr.io/zammad/zammad` image pinned to latest stable
semver. Self-built image (a la 015 Moodle) explicitly out of scope.
- Central-service reuse: `svc-db-postgres`, `svc-db-redis`,
`svc-db-memcached`. Elasticsearch bundled in-role (no central
`svc-db-elasticsearch` exists today).
- Email in scope: SMTP outbound via `sys-svc-mail-smtp` /
`web-app-mailu`, IMAP inbound (mail-to-ticket) auto-wired.
- Setup wizard bypassed on first deploy via env-vars + post-start
API call so a fresh deploy lands a ready-to-use instance.
- Playwright biber + administrator personas in scope (per 019).
- `meta/variants.yml` defines three variants mirroring web-app-kix:
V1 oidc+ldap, V2 all-false, V3 ldap-only.
Acceptance criteria are grouped per concern (routing/TLS, role
layout & image, central-service reuse, SSO/OIDC, LDAP, email,
first-admin bootstrap, variants, playwright, health & quality,
documentation) so each item is independently verifiable.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Release 8.0.0 (12e53905f) bumped the changelog's top entry to
`infinito-nexus-core` while every other packaging artefact still
addresses the distro package as `infinito-nexus`:
* `packaging/arch/PKGBUILD` pkgname=infinito-nexus
* `packaging/fedora/infinito-nexus.spec` Name: infinito-nexus
* `packaging/debian/control` Source/Package: infinito-nexus
* `scripts/install/package.sh` globs for infinito-nexus_*.deb,
infinito-nexus-*.pkg.tar.*,
infinito-nexus-*.rpm
dpkg-source --before-build refused to build because the changelog said
`infinito-nexus-core` while control said `infinito-nexus`; after the
preceding (now reverted) "align control with changelog" attempt, the
dpkg output filename became `infinito-nexus-core_8.0.0-1_all.deb`,
which fails the installer's `infinito-nexus_*.deb` glob check.
The repository name (`infinito-nexus-core`) is unrelated to the
distro-package name. The packaging convention is the short form
`infinito-nexus`. Align the changelog with the rest of the packaging
fleet instead of dragging every other file along.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Release 8.0.0 (12e53905f) bumped the changelog's top entry to
`infinito-nexus-core` while every other packaging artefact still
addresses the distro package as `infinito-nexus`:
* `packaging/arch/PKGBUILD` pkgname=infinito-nexus
* `packaging/fedora/infinito-nexus.spec` Name: infinito-nexus
* `packaging/debian/control` Source/Package: infinito-nexus
* `scripts/install/package.sh` globs for infinito-nexus_*.deb,
infinito-nexus-*.pkg.tar.*,
infinito-nexus-*.rpm
dpkg-source --before-build refused to build because the changelog said
`infinito-nexus-core` while control said `infinito-nexus`; after the
preceding (now reverted) "align control with changelog" attempt, the
dpkg output filename became `infinito-nexus-core_8.0.0-1_all.deb`,
which fails the installer's `infinito-nexus_*.deb` glob check.
The repository name (`infinito-nexus-core`) is unrelated to the
distro-package name. The packaging convention is the short form
`infinito-nexus`. Align the changelog with the rest of the packaging
fleet instead of dragging every other file along.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`dpkg-source --before-build` exited with status 2 across every distro in
the CI test-development matrix + both build-ci-images jobs because the
control file still declared `Source: infinito-nexus` / `Package:
infinito-nexus` while the changelog top entry (Release 8.0.0,
12e53905f) already advertises the new project name
`infinito-nexus-core`. dpkg-source compares the two and refuses to
build when they disagree (Policy §5.2).
Rename `Source` + `Package` in control to match. Also pull the
substvars filename and build-dir paths in `.gitignore` along, and
update the cosmetic postinst echo so the user-visible install message
matches the actual package name.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Five eslint errors blocking `make lint-javascript`:
* `personas/guest.js`, `web-app-opencloud/playwright.spec.js`,
`web-app-opentalk/playwright.spec.js` — re-thrown wrapped errors now
carry `{ cause: err }` so the symptom keeps a link to the root cause.
* `personas/utils/csp.js` — drop `let blockedHost = ""` initial value;
the catch arm returns early, so the empty string was never read.
* `web-app-wordpress/_shared.js` — drop `let match = null` initial value;
both branches assign before the readers fire.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace "oauth2-proxy" descriptor with "SSO-proxy sidecar (provided by
web-app-keycloak)" so the Overview and Features sections both speak the
post-021 vocabulary. Upstream image name stays as-is.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>