Files
computer-playbook/roles/web-app-zammad/templates/env.j2
kevinveenbirkenbachandClaude Opus 5 57831d402f feat(zammad): bind Smart Assist to the platform gateway on 7.1.2
Zammad 6.5.0 has no AI surface to configure: neither `ai_provider` nor
`ai_provider_config` exists in that line, so the role could not point Smart
Assist anywhere. 7.1.2 is the newest version whose contract is still that
setting pair; from 7.2 the provider moves into an `AI::ProviderConnection`
record and the shape changes, so the bump lands here rather than on latest.
Elasticsearch is unaffected: 7.1.2 accepts >= 7.8, < 10 and the role's 8.13.4
pin stays.

The provider is written by piping files/scripts/apply/ai_provider.rb into
`rails r -` over stdin, with url, model and token passed as environment
variables instead of interpolated into Ruby source, so no quoting path can
mangle a virtual key. Order inside the script is load-bearing and documented
there: `ai_provider` is the boolean "a provider is configured" and its
validator reads `ai_provider_config`, so the config hash has to land first or
the flag is refused. `UserInfo.current_user_id` is set because `Setting.set`
needs an acting user.

This is the strongest evidence in the requirement, because it is not a
read-back of something the deploy just wrote: `Setting.set("ai_provider_config")`
runs `AI::Provider::CustomOpenAI.ping!` upstream, so the write itself is a live
round trip. An unreachable gateway or a rejected key fails the task.

Two consequences of the bump ride along, because the version does not work
without them.

`/api/v1/getting_started` answers 403 from Zammad 7.0 on, so the Playwright
helper could no longer lift a csrf token there; it uses `/api/v1/signshow`,
the unauthenticated seed. Its old fallback read `csrf_token` from a JSON body
that endpoint never returns, so the failure surfaced as a confusing throw
rather than the 403 it was.

env.j2 drops the hand-built DATABASE_URL in favour of POSTGRESQL_OPTIONS
`?pool=`. Zammad composes its DSN from the POSTGRESQL_* parts itself, so the
explicit URL both duplicated and competed with them while leaving the
connection pool at its default.

The spec decodes ZAMMAD_LITELLM_BASE_URL and ZAMMAD_LITELLM_MODEL through
decodeDotenvQuotedValue. Without it the values arrive still wrapped in the
literal quotes dotenv_quote adds and the strict comparison can never match in
compose mode -- measured as expected `"\"http://litellm:4000/v1\""` against
received `http://litellm:4000/v1`.

Verified: the spec passes against the live stack (1 passed, via
scripts/tests/e2e/rerun-spec.sh --grep=Assist, which restages the spec from the
repo), and both the contract and the Smart Assist write ran ok in a variant-2
deploy. `make quality` green on test-lint, test-unit, test-integration,
test-external.
Not covered: the no-gateway path (ZAMMAD_LITELLM_ENABLED false) has not run
since the bump, and Zammad features this deploy does not exercise -- email
channel, LDAP sync, ticket workflows -- are unproven on 7.1.2.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 12:47:36 +02:00

34 lines
1.3 KiB
Django/Jinja

{% include 'roles/sys-svc-container/templates/env.j2' %}
POSTGRESQL_HOST={{ lookup('database', application_id, 'host') }}
POSTGRESQL_PORT={{ lookup('database', application_id, 'port') }}
POSTGRESQL_DB={{ lookup('database', application_id, 'name') }}
POSTGRESQL_USER={{ lookup('database', application_id, 'username') }}
POSTGRESQL_PASS={{ lookup('database', application_id, 'password') | dotenv_quote }}
POSTGRESQL_OPTIONS=?pool={{ ZAMMAD_DB_POOL }}
REDIS_URL={{ lookup('engine', 'redis', application_id, 'url') }}
MEMCACHE_SERVERS={{ lookup('engine', 'memcached', application_id, 'host') }}:{{ lookup('engine', 'memcached', application_id, 'port') }}
ELASTICSEARCH_HOST={{ ZAMMAD_SEARCH_SERVICE }}
ELASTICSEARCH_PORT={{ ZAMMAD_SEARCH_PORT }}
ELASTICSEARCH_SCHEMA=http
ELASTICSEARCH_NAMESPACE=zammad
ELASTICSEARCH_REINDEX=true
RAILS_ENV=production
RAILS_LOG_TO_STDOUT=true
RAILS_EAGER_LOAD=false
NGINX_SERVER_NAME={{ ZAMMAD_HOSTNAME }}
NGINX_SERVER_SCHEME={{ ZAMMAD_WEB_PROTOCOL }}
ZAMMAD_RAILSSERVER_HOST={{ ZAMMAD_RAILS_SERVICE }}
ZAMMAD_RAILSSERVER_PORT={{ ZAMMAD_RAILS_PORT }}
ZAMMAD_WEBSOCKET_HOST={{ ZAMMAD_WEBSOCKET_SERVICE }}
ZAMMAD_WEBSOCKET_PORT={{ ZAMMAD_WEBSOCKET_PORT }}
ZAMMAD_WEB_CONCURRENCY=0
ZAMMAD_SECRET_KEY_BASE={{ ZAMMAD_SECRET_KEY_BASE | dotenv_quote }}
RAILS_TRUSTED_PROXIES={{ NETWORK_TRUSTED_PROXY_CIDRS | join(',') }}