Files
kevinveenbirkenbachandClaude Opus 5 f713ba8350 refactor(credentials): address them at secrets.credentials, like every other meta file
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>
2026-08-12 16:51:56 +02:00

76 lines
3.4 KiB
Django/Jinja

{% include 'roles/sys-svc-container/templates/env.j2' %}
LOCAL_DOMAIN={{ lookup('domain',application_id) }}
ALTERNATE_DOMAINS={{ lookup('domains', 'web-app-mastodon')[1:] | join(',') }}
SINGLE_USER_MODE={{ lookup('config', application_id, 'services.mastodon.single_user_mode') }}
ALLOWED_PRIVATE_ADDRESSES={{ MASTODON_ALLOWED_PRIVATE_ADDRESSES }}
RAILS_ENV=production
{% if MODE_DEBUG | bool %}
RAILS_LOG_LEVEL=debug
DEBUG=*
{% endif %}
SECRET_KEY_BASE={{ lookup('config', application_id, 'secrets.credentials.secret_key_base') }}
OTP_SECRET={{ lookup('config', application_id, 'secrets.credentials.otp_secret') }}
VAPID_PRIVATE_KEY={{ lookup('config', application_id, 'secrets.credentials.vapid_private_key') }}
VAPID_PUBLIC_KEY={{ lookup('config', application_id, 'secrets.credentials.vapid_public_key') }}
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY={{ lookup('config', application_id, 'secrets.credentials.active_record_encryption_deterministic_key') }}
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT={{ lookup('config', application_id, 'secrets.credentials.active_record_encryption_key_derivation_salt') }}
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY={{ lookup('config', application_id, 'secrets.credentials.active_record_encryption_primary_key') }}
DB_HOST={{ lookup('database', application_id, 'host') }}
DB_PORT={{ lookup('database', application_id, 'port') }}
DB_NAME={{ lookup('database', application_id, 'name') }}
DB_USER={{ lookup('database', application_id, 'username') }}
DB_PASS={{ lookup('database', application_id, 'password') }}
DB_POOL={{ POSTGRES_ALLOWED_AVG_CONNECTIONS }}
RAILS_MAX_THREADS={{ POSTGRES_ALLOWED_AVG_CONNECTIONS }}
REDIS_HOST={{ lookup('engine', 'redis', application_id, 'host') }}
REDIS_PORT={{ lookup('engine', 'redis', application_id, 'port') }}
REDIS_PASSWORD={{ lookup('engine', 'redis', application_id, 'password') | dotenv_quote }}
{% if lookup('objstore', application_id, 'enabled') | bool %}
S3_ENABLED=true
S3_BUCKET={{ lookup('objstore', application_id, 'bucket') }}
AWS_ACCESS_KEY_ID={{ lookup('objstore', application_id, 'access_key') }}
AWS_SECRET_ACCESS_KEY={{ lookup('objstore', application_id, 'secret_key') }}
S3_ENDPOINT={{ lookup('objstore', application_id, 'url') }}
S3_REGION={{ lookup('objstore', application_id, 'region') }}
S3_PROTOCOL={{ lookup('tls', lookup('objstore', application_id, 'id'), 'protocols.web') }}
S3_HOSTNAME={{ lookup('objstore', application_id, 'endpoint') }}
S3_ALIAS_HOST={{ lookup('objstore', application_id, 'public_bucket') }}
{% endif %}
{% set email = lookup('email', application_id) %}
{% if email.enabled | bool %}
SMTP_SERVER={{ email.host }}
SMTP_PORT={{ email.port }}
SMTP_LOGIN={{ email.username }}
SMTP_PASSWORD={{ email.password | dotenv_quote }}
SMTP_AUTH_METHOD=plain
SMTP_OPENSSL_VERIFY_MODE=none
SMTP_ENABLE_STARTTLS=auto
SMTP_FROM_ADDRESS=Mastodon <{{ email['from'] }}>
{% endif %}
{% if MASTODON_OIDC_ENABLED | bool %}
OIDC_ENABLED={{ MASTODON_OIDC_ENABLED | string | lower }}
OIDC_DISPLAY_NAME={{ OIDC.BUTTON_TEXT }}
OIDC_ISSUER={{ OIDC.CLIENT.ISSUER_URL }}
OIDC_DISCOVERY=true
OIDC_SCOPE=openid,profile,email
OIDC_UID_FIELD={{ OIDC.ATTRIBUTES.USERNAME }}
OIDC_CLIENT_ID={{ OIDC.CLIENT.ID }}
OIDC_REDIRECT_URI={{ lookup('tls', application_id, 'url.base') }}auth/auth/openid_connect/callback
OIDC_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true
OIDC_CLIENT_SECRET={{ OIDC.CLIENT.SECRET }}
OMNIAUTH_ONLY=true
ALLOW_UNSAFE_AUTH_PROVIDER_REATTACH=true
ONE_CLICK_SSO_LOGIN=true
{% endif %}