--- type: architecture title: System Architecture description: Component map of 10Backward — how config, logging, the pipeline runner, state persistence, and container entrypoint are wired together. tags: [architecture, overview] timestamp: 2026-08-04T17:51:00Z --- # Overview `10Backward` is a Mastodon daily-throwback bot that runs as a single containerised cron job. Job 1083 adds a structured logging layer and a retry-capable pipeline runner on top of the scaffold shipped by Job 1082. The container boots, validates environment configuration, renders a `/etc/cron.d/tenbackward` entry that fires once per day at the configured `RUN_AT`, and then runs `python -m tenbackward` in the foreground of the `cron` process. Each scheduled invocation calls `tenbackward.main.main()`, which: 1. Installs the JSON logging formatter on the root logger. 2. Loads and validates the `Config`. 3. Ensures the data directory exists. 4. Emits a `startup` log line. 5. Executes one pipeline pass wrapped in a retry loop. 6. Emits a `run_complete` summary (skipped silently if there were no candidates). # Components | Component | Responsibility | |--------------------------|------------------------------------------------------------------------------------------------------------| | `entrypoint.sh` | Validates required env vars, renders the cron file from `RUN_AT`/`TZ`, then `exec cron -f`. | | `tenbackward.main` | CLI entry point; orchestrates startup logging, retry-wrapped pipeline pass, and run summary. | | `tenbackward.config` | Loads `.env` + process env, applies defaults, validates schema, produces a typed `Config` dataclass. | | `tenbackward.logging_setup` | JSON formatter (one log record per line), secret redaction, and `log_startup` / `log_error` / `log_run_summary` helpers. | | `tenbackward.state` | Read/write of `posted.json` (atomic temp-file replace). | | `tenbackward.blog` | GitPython `ensure_repo()` — clones the configured blog repo on first run, fast-forwards it via `pull --ff-only` thereafter, with exponential-backoff retries on transient network errors. | | `/etc/cron.d/tenbackward`| Rendered cron file. One daily line that `cd /app` and runs `python -m tenbackward`. | # Communication & Wiring ``` +-------------------+ env vars +-------------------------+ | entrypoint | -------------------> | config.load_config | | (.sh) | | (Config dataclass) | +---------+---------+ +-----------+-------------+ | | | installs cron file | feeds v v +-------------------+ +-------------------------+ | /etc/cron.d/ | -- daily fires --> | tenbackward.main.main | | tenbackward | | - configure logging | +-------------------+ | - retry-wrapped pass | +-----------+-------------+ | v +-------------------------+ | _run_with_retry(...) | | -> _run_once(...) | | -> state.{load,save}| +-------------------------+ ``` * **Env → Config.** `load_config()` merges a `.env` file (when present) under the process environment via `dotenv_values` + `load_dotenv`, applies defaults, then validates the merged map before producing the `Config` dataclass. * **Cron → main.** Each scheduled tick re-runs `python -m tenbackward`, so every run is a fresh interpreter invocation. * **Pipeline state.** `_run_once` reads `posted.json` via `state.load_posted()` for dedupe, then writes it back via `state.save_posted()` only when at least one new post was recorded. # Key Files | Path | Responsibility | |-------------------------------------|---------------------------------------------------------------------------------| | `/repo/entrypoint.sh` | Bootstraps cron; validates required env vars; renders `/etc/cron.d/tenbackward`. | | `/repo/src/tenbackward/main.py` | CLI entry, retry wrapper, pipeline counters. | | `/repo/src/tenbackward/config.py` | Env merging, defaults, validation, `Config` dataclass, `ConfigError`. | | `/repo/src/tenbackward/logging_setup.py` | JSON formatter, secret redaction, structured event helpers. | | `/repo/src/tenbackward/state.py` | `posted.json` read/write with atomic temp-file replace. | | `/repo/src/tenbackward/blog.py` | `ensure_repo()` — GitPython clone + fast-forward pull with `2^n` retry/backoff; raises `BlogRepoError` on local modifications or exhausted retries. | | `/repo/.env.example` | Canonical list of environment variables. | | `/repo/docker-compose.yml` | Service definition; binds env vars from the host `.env`. | | `/repo/Dockerfile` | Builds the runtime image. | | `/repo/crontab/tenbackward.cron` | Cron template shipped in the image. | | `/repo/crontab/install-cron.sh` | Installs the rendered cron file with mode 0644; refuses placeholder leftovers. | | `/repo/tests/` | Pytest suite covering config, logging, runner, cron, and entrypoint validation. | # Related * [Config Schema](/architecture/config-schema.md) * [Logging & Run Summary](/architecture/logging.md) * [Pipeline Runner](/architecture/pipeline-runner.md) * [Daily Run Guide](/guides/daily-run.md) * [Environment Variable Setup](/operations/environment-setup.md) * [Cron Lifecycle](/operations/cron-lifecycle.md)