Configuration: every administrator's setting settable by an environment variable, with one registry, naming rules and generated documentation #1186
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#1186
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The maintainer's request (2026-10-31): every feature and every setting can be set, or overridden, by an environment variable. Each one is documented properly, and the names, the defaults and the syntax of their values follow clear rules.
The point is that an instance can be configured whole from its
compose.ymlor its orchestrator: reproducible, reviewable, and the same on every restart, with no step done by hand in the Server pages.What exists today (checked against
mainat8eedc02de)Configuration lives in three places, and they agree only in part.
POSTULO_*names, mostly throughdjango-environinconfig/settings/base.py, plusDJANGO_SETTINGS_MODULEandPOSTGRES_PASSWORD.POSTULO_ALLOW_WILDCARD_HOSTS,POSTULO_CACHE_MAX_ENTRIES,POSTULO_EMAIL_CODE_ATTEMPTS,…_RESENDSand…_TIMEOUT,POSTULO_MEDIA_SWEEP,…_GRACE_HOURS,POSTULO_NUMBER_RATE,POSTULO_PROXY_ADDRandPOSTULO_RATELIMIT_DIR..env.example.POSTULO_SKIP_GEONAMESandPOSTULO_SKIP_PLUGIN_SYNC.SiteSettings(core/models.py:1260). The administrator sets 44 fields in the Server pages.POSTULO_EMAIL_*), map tiles (POSTULO_MAP_*), the geocoder (POSTULO_GEOCODER*), capture (POSTULO_CAPTURE_*) andPOSTULO_REGISTRATION_OPEN.instance_name,tagline,motto,sso_is_second_factor,email_sign_in,offered_languages,default_time_zone,default_paper,geocoder_consent, and the mail transport and text-message transport choices with their configuration.PluginPolicy(plugins/models.py:107). The administrator's decision on each plugin (on, off, or on by default) has no environment form at all. The same goes for a plugin's instance-wide settings.The names follow no written rule.
_MAX_MB(POSTULO_BACKUP_UPLOAD_MAX_MB,POSTULO_UPLOAD_CEILING_MB) beside_MAX_BYTES(POSTULO_CAPTURE_SOURCE_MAX_BYTES,POSTULO_LOG_MAX_BYTES), and_SECONDS,_HOURS,_DAYSand a bare_TIMEOUT(POSTULO_EMAIL_TIMEOUT) all for durations.POSTULO_MEDIA_SWEEP) and sometimes a phrase (POSTULO_CAPTURE_IGNORE_ROBOTS)._RATEsettings take a value like30/h. They are checked at start (core/checks.py:55), but the syntax is written down nowhere a reader of the wiki finds it.Proposal
One registry of settings, the single source. A module (
postulo.core.configurationor similar) declares each setting once:config/settings/*.pyreads through it; it does not callenv(...)itself.Everything an administrator can set has an environment name. That covers every
SiteSettingsfield, everyPluginPolicydecision (POSTULO_PLUGIN_<NAME>=on|off|default-on|default-off), and each plugin's instance-wide setting (POSTULO_PLUGIN_<NAME>_<SETTING>, declared by the plugin throughpostulo.plugins.api).One precedence, shown where it applies: environment, then the database, then the default.
POSTULO_…" beside it, as mail's fields should already be.Naming rules, written in
CONTRIBUTING.mdand enforced by a test:POSTULO_+ the area + the thing, in upper snake case. The area is the Server page or the plugin it belongs to._BYTES(a size),_SECONDS(a duration),_RATE(a rate). Sizes and durations also accept a suffixed value (25MB,90s,2h,7d), so no name needs to carry its unit twice.POSTULO_REGISTRATION_OPEN,POSTULO_CAPTURE_KEEP_SOURCE. They taketrue/false/1/0/yes/no/on/off, case-insensitive.<count>/<s|m|h|d>. Choices take their documented spellings only.<NAME>_FILE, a path to a file holding it (Docker and Kubernetes secrets).Documentation generated from the registry, never written by hand:
.env.example;manage.py settingscommand (or--list) that prints the effective value of each setting and where it came from (environment, database or default), with secrets hidden.A test fails when the registry, the generated files and the code disagree, as the README language tables are already checked.
Decisions (recommended option first)
SiteSettings, plugin policies and plugins' instance-wide settings.compose.yml._BYTES,_SECONDS), and the value may carry a suffix (25MB,2h)..env.example, by a script CI checks.docs/CONFIGURATION.mdin the repository.Done when
.env.exampleandmanage.py settingscome from it, and a test keeps them in step.POSTULO_*name or a malformed value, and names the syntax it expected.Decided by the maintainer (2026-11-12)
The three open points of stage 1 are left to the implementer, choosing what is best rather than what is easiest:
.env.examplekeeps an active line for every variable a real deployment must set or that the old file set with an example value (the sender addressPOSTULO_DEFAULT_FROM_EMAILamong them), so a freshcp .env.example .envbehaves as before.🤖 Generated with Claude Code