Files
kevinveenbirkenbachandClaude Opus 5 29015311b6 feat(lint): derive container paths from meta/volumes.yml everywhere
meta/volumes.yml says where a volume lands inside its container. Wherever a
second file spelled the same path out, the two drifted independently: the mount
moves, the consumer keeps pointing at the old place, and nothing fails until the
process looks for content that is not there. e031b7f61 fixed exactly this for
web-app-moodle; this generalises it and makes the rule hold.

The lookup gains what a caller needs. `targets` maps each mounting service to
its container path, and `target` is the one path an entry mounts at -- present
only when every mount agrees. An entry that lands at different paths per service
omits `target` rather than picking one, and the caller names the service:

    "{{ lookup('volume', application_id, 'prometheus_config').target }}"
    "{{ lookup('volume', 'web-app-prometheus', 'host_sys').targets['node-exporter'] }}"

66 literals across 48 role files now derive. Replacement only happened where the
path stood alone -- /srv/app inside /srv/app/data is a different path -- and
every rewritten file was re-parsed, Jinja and YAML alike. Three sites the tool
refused rather than guess were done by hand: zammad's path sat in a Jinja list
literal, seaweedfs' inside a quoted string feeding a filter, and openresty's
/var/run/docker.sock is not a mount target at all but the socket on the host,
which only happens to spell the same string. That one carries the single
nocheck.

tests/lint/repository/spot/test_volumes_path_single_spot.py keeps it that way.
roles/*/files/ is out of scope by construction: it is copied verbatim into an
image and executed there, so a Dockerfile or entrypoint has no Ansible to
evaluate a lookup with.

One defect this found, and it was in the tooling rather than the roles: deciding
ambiguity AFTER filtering short paths made web-app-prometheus' host_sys look
single-valued -- it mounts /sys for cadvisor and /host/sys for node-exporter,
and /sys is below the length filter. The template would have rendered an
undefined `.target`. Both the converter and the rule now decide ambiguity over
every target and filter afterwards, and that call reads targets['node-exporter'].

Verified by resolving rather than by reading: all 70 accesses in the diff were
run against the real registry and every one yields a non-empty path equal to the
literal it replaced. make test-lint: 328 passed.

Not verified: no Ansible render, so a call site whose surrounding context cannot
build the applications registry would still fail at deploy.

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

81 lines
3.5 KiB
Django/Jinja

{% include 'roles/sys-svc-container/templates/env.j2' %}
BOOKWYRM_URL={{ BOOKWYRM_URL }}
DOMAIN={{ BOOKWYRM_HOSTNAME }}
ALLOWED_HOSTS={{ BOOKWYRM_HOSTNAME }},127.0.0.1,localhost
PORT={{ lookup('tls', application_id, 'ports.web') }}
TLS_ENABLED={{ lookup('tls', application_id, 'protocols.web') }}
MEDIA_ROOT={{ lookup('volume', 'web-app-bookwyrm', 'media').target }}
DATA_ROOT={{ lookup('volume', 'web-app-bookwyrm', 'data').target }}
REGISTRATION_OPEN={{ BOOKWYRM_REGISTRATION_OPEN }}
ALLOW_INVITE_REQUESTS={{ BOOKWYRM_ALLOW_INVITE_REQUESTS }}
PROXY_HEADER_SSO={{ BOOKWYRM_SSO_PROXY_GATED | string | lower }}
{% if lookup('objstore', application_id, 'enabled') | bool %}
USE_S3=true
AWS_ACCESS_KEY_ID={{ lookup('objstore', application_id, 'access_key') }}
AWS_SECRET_ACCESS_KEY={{ lookup('objstore', application_id, 'secret_key') }}
AWS_STORAGE_BUCKET_NAME={{ lookup('objstore', application_id, 'bucket') }}
AWS_S3_ENDPOINT_URL={{ lookup('objstore', application_id, 'url') }}
AWS_S3_REGION_NAME={{ lookup('objstore', application_id, 'region') }}
AWS_S3_CUSTOM_DOMAIN={{ lookup('objstore', application_id, 'public_bucket') }}
USE_S3_FOR_EXPORTS=false
{% else %}
USE_S3=false
{% endif %}
SECRET_KEY={{ BOOKWYRM_SECRET_KEY }}
EMAIL_HOST={{ EMAIL_HOST }}
EMAIL_PORT={{ EMAIL_PORT }}
EMAIL_USE_TLS={{ EMAIL_USE_TLS }}
EMAIL_USE_SSL={{ EMAIL_USE_SSL }}
EMAIL_HOST_USER={{ EMAIL_HOST_USER }}
EMAIL_HOST_PASSWORD={{ EMAIL_HOST_PASSWORD | dotenv_quote }}
DEFAULT_FROM_EMAIL={{ EMAIL_DEFAULT_FROM }}
POSTGRES_DB={{ lookup('database', application_id, 'name') }}
POSTGRES_USER={{ lookup('database', application_id, 'username') }}
POSTGRES_PASSWORD={{ lookup('database', application_id, 'password') | dotenv_quote }}
POSTGRES_HOST={{ lookup('database', application_id, 'host') }}
POSTGRES_PORT={{ lookup('database', application_id, 'port') }}
DATABASE_URL=postgres://{{ lookup('database', application_id, 'username') }}:{{ lookup('database', application_id, 'password') }}@{{ lookup('database', application_id, 'host') }}:{{ lookup('database', application_id, 'port') }}/{{ lookup('database', application_id, 'name') }}
REDIS_HOST={{ BOOKWYRM_REDIS_HOST }}
REDIS_PORT={{ BOOKWYRM_REDIS_PORT }}
REDIS_URL={{ BOOKWYRM_REDIS_CACHE_URL }}
REDIS_CACHE_URL={{ BOOKWYRM_REDIS_CACHE_URL }}
CACHE_URL={{ BOOKWYRM_REDIS_CACHE_URL }}
DJANGO_REDIS_URL={{ BOOKWYRM_REDIS_CACHE_URL }}
BROKER_URL={{ BOOKWYRM_BROKER_URL }}
REDIS_BROKER_URL={{ BOOKWYRM_REDIS_BROKER_URL }}
REDIS_BROKER_HOST={{ BOOKWYRM_REDIS_HOST }}
REDIS_BROKER_PORT={{ BOOKWYRM_REDIS_PORT }}
REDIS_BROKER_DB_INDEX={{ BOOKWYRM_REDIS_BROKER_DB }}
CELERY_BROKER_URL={{ BOOKWYRM_REDIS_BROKER_URL }}
REDIS_ACTIVITY_HOST={{ BOOKWYRM_REDIS_HOST }}
REDIS_ACTIVITY_PORT={{ BOOKWYRM_REDIS_PORT }}
REDIS_ACTIVITY_DB_INDEX={{ BOOKWYRM_REDIS_ACTIVITY_DB }}
REDIS_ACTIVITY_URL={{ BOOKWYRM_REDIS_ACTIVITY_URL }}
FORWARDED_ALLOW_IPS=*
USE_X_FORWARDED_HOST=true
SECURE_PROXY_SSL_HEADER={{ lookup('tls', application_id, 'enabled') | string | lower }}
{% if BOOKWYRM_OIDC_ENABLED %}
OIDC_TITLE={{ BOOKWYRM_OIDC_LABEL | dotenv_quote }}
OIDC_ISSUER={{ BOOKWYRM_OIDC_ISSUER }}
OIDC_AUTHORIZATION_ENDPOINT={{ BOOKWYRM_OIDC_AUTH_URL }}
OIDC_TOKEN_ENDPOINT={{ BOOKWYRM_OIDC_TOKEN_URL }}
OIDC_USERINFO_ENDPOINT={{ BOOKWYRM_OIDC_USERINFO_URL }}
OIDC_END_SESSION_ENDPOINT={{ BOOKWYRM_OIDC_LOGOUT_URL }}
OIDC_JWKS_URI={{ BOOKWYRM_OIDC_JWKS_URL }}
OIDC_CLIENT_ID={{ BOOKWYRM_OIDC_CLIENT_ID }}
OIDC_CLIENT_SECRET={{ BOOKWYRM_OIDC_CLIENT_SECRET }}
OIDC_SCOPES={{ BOOKWYRM_OIDC_SCOPES }}
OIDC_UNIQUE_ATTRIBUTE={{ BOOKWYRM_OIDC_UNIQUE_ATTRIBUTE }}
{% endif %}