--- type: api title: Config Schema description: Required/optional environment variables, validation rules, and the typed Config dataclass returned by tenbackward.config.load_config. tags: [config, env, schema] timestamp: 2026-08-04T17:51:00Z --- # Purpose `tenbackward.config.load_config()` is the single boundary that turns process environment + optional `.env` file into a typed `Config` dataclass. Job 1083 changes every variable that ships in `.env.example` so that it is **explicitly required** at every layer (`entrypoint.sh`, `config.validate_config`, and the rendered cron). # Required Environment Variables All eight of these must be present and non-empty in the process environment for the container to boot. | Key | Purpose | Validation | |----------------------|--------------------------------------------------------|------------------------------------------------------------------| | `MASTODON_BASE_URL` | Mastodon instance to post against. | Must parse with `http` or `https` scheme and a non-empty netloc. | | `MASTODON_ACCESS_TOKEN` | OAuth token used by the future Mastodon.py client. | Required string. | | `VISIBILITY` | Default post visibility. | Must be `public` or `unlisted`. | | `SITE_URL` | Source blog URL (used by the future clone step). | Must parse with `http` or `https` scheme and a non-empty netloc. | | `HASHTAGS` | Comma-separated hashtags appended to every throwback. | Required string (formatting handled by the future poster). | | `THROWNBACK_PREFIX` | Per-post title prefix (default: `Throwback:`). | Required string. | | `MAX_RETRIES` | Number of additional retries after the first attempt. | Non-negative integer (parsed by `_parse_max_retries`). | | `RUN_AT` | Daily fire time for the cron entry. | `HH:MM` 24-hour format. | | `TZ` | IANA timezone used by cron + the container clock. | Must resolve via `zoneinfo.ZoneInfo`. | > **Note** — All nine keys are required since Job 1083. The previously > optional `MASTODON_VISIBILITY`, `THROWBACK_PREFIX`, and `RETRY_COUNT` > were promoted to first-class citizens and renamed to `VISIBILITY`, > `THROWNBACK_PREFIX`, and `MAX_RETRIES`. # Defaults `DEFAULTS` in `config.py` provides fallback strings that `apply_defaults` fills into the merged map **before** validation runs: | Default key | Default value | |-----------------------|-------------------------------------------| | `VISIBILITY` | `public` | | `THROWNBACK_PREFIX` | `Throwback:` | | `MAX_RETRIES` | `3` | | `TZ` | `Europe/Berlin` | | `RUN_AT` | `09:00` | | `BLOG_REPO_URL` | `https://git.chaospott.de/Chaospott/site` | | `BLOG_DIR` | `blog` (resolved relative to `data_dir`) | Because every required key carries a default, a freshly-initialised container can still boot to validate the scaffold, but the operator must supply `MASTODON_BASE_URL` and `MASTODON_ACCESS_TOKEN` (and ideally `SITE_URL`) to make a real run. # `Config` Dataclass | Field | Type | Source | |-------------------------|-----------|----------------------------------------------| | `mastodon_base_url` | `str` | `MASTODON_BASE_URL` | | `mastodon_access_token` | `str` | `MASTODON_ACCESS_TOKEN` | | `visibility` | `str` | `VISIBILITY` | | `site_url` | `str` | `SITE_URL` | | `hashtags` | `str` | `HASHTAGS` | | `throwback_prefix` | `str` | `THROWNBACK_PREFIX` | | `max_retries` | `int` | `MAX_RETRIES` (parsed as non-negative int) | | `run_at` | `str` | `RUN_AT` | | `tz` | `str` | `TZ` | | `data_dir` | `Path` | Constant: `Path("/app/data")` | | `blog_repo_url` | `str` | `BLOG_REPO_URL` | | `blog_dir` | `Path` | `BLOG_DIR` resolved against `data_dir` | # Validation Behaviour `validate_config(values)` collects all failures before raising so the operator sees every problem at once. A single `ConfigError` lists each problem joined with `"; "`. Validation covers: * Required keys are non-empty. * `RUN_AT` parses as `HH:MM`. * `MASTODON_BASE_URL` and `SITE_URL` parse via `urllib.parse.urlparse` with an `http`/`https` scheme and a non-empty `netloc`. * `VISIBILITY` is one of `{"public", "unlisted"}` (`ALLOWED_VISIBILITY`). * `BLOG_REPO_URL` (optional, has default) parses via `urllib.parse.urlparse` with an `http`/`https` scheme and a non-empty `netloc` when present. * `TZ` resolves via `zoneinfo.ZoneInfo`. * `MAX_RETRIES` parses as a non-negative integer (rejects bools). # Exit Codes | Code | Source | Meaning | |------|-----------------------------------------|----------------------------------------------------------------------| | `1` | `main.main()` (retry budget exhausted) | Pipeline failed on every attempt after `MAX_RETRIES + 1` tries. | | `2` | `main.main()` (`ConfigError`) | Configuration was present but invalid; details are logged. | # Citations * [1] `src/tenbackward/config.py` — `REQUIRED_KEYS`, `DEFAULTS`, `ALLOWED_VISIBILITY`, `Config`, `load_config`, `validate_config`. * [2] `.env.example` — canonical env-var list. * [3] `entrypoint.sh` — shell-side mirror of the required keys. # Related * [System Architecture](/architecture/system-overview.md) * [Logging & Run Summary](/architecture/logging.md) * [Environment Variable Setup](/operations/environment-setup.md)