Files
10Backward/docs/guides/daily-run.md
T
2026-08-04 20:48:13 +00:00

5.5 KiB
Raw Blame History

type, title, description, tags, timestamp
type title description tags timestamp
guide Daily Run Guide Operator- and tester-focused walkthrough of one daily cron tick — repository synchronization, anniversary matching, deduplication, logging, and retry behaviour.
guide
run
daily
tester
2026-08-04T17:51:00Z

Purpose

A tester or operator needs to know what should happen each time the cron tick fires. This guide describes the contract from the outside of the container, in observable terms: log lines, exit codes, files appearing on the volume, and the "silent on no matches" behaviour that the runner enforces.

Triggering a Run Manually

docker compose exec tenbackward /usr/local/bin/python -m tenbackward

This is the same command the container's cron entry issues once per day at RUN_AT. Use it to verify behaviour without waiting for the scheduled tick.

What the Run Does

Before scanning, the process ensures the configured blog repository exists under /app/data/blog and is fast-forwarded from BLOG_REPO_URL. It then walks _posts/blog/**/*.md, finds posts whose date is exactly ten years before today, skips IDs already present in posted.json, composes a single Mastodon status listing each new match's title and canonical URL followed by the configured hashtags, and — only after the Mastodon API call succeeds — records the relative paths of the published posts in posted.json. The scheduled wrapper executes this Python process as the unprivileged bot user.

What You Should See

The bot uses a JSON-per-line logger that writes to /app/data/cron.log (because the cron line redirects with >> /app/data/cron.log 2>&1). Inspecting the file with docker compose exec tenbackward tail -n 100 /app/data/cron.log should show output similar to:

{"ts": "2026-08-04T09:00:00+00:00", "level": "INFO", "logger": "tenbackward", "message": "startup", "event": "startup", "version": "0.1.0", "site_url": "https://blog.example.com", "run_at": "09:00", "tz": "Europe/Berlin", "hashtags": "#throwback,#10backward", "throwback_prefix": "Heute vor 10 Jahren:"}

On the current scaffold (_iter_candidates is intentionally empty), this is the only line you should see — the runner emits no run_complete when no candidates were found. This is the "silent on no matches" contract.

Expected Container Behaviour

Scenario Lines in cron.log Container exit
Config valid, no anniversary matches 1× startup 0
Blog clone or pull fails 1× blog_repo_error 1
Config valid, matching IDs are new 1× startup, then 1× run_complete with posted=N 0
Config valid, all matches already posted 1× startup, then 1× run_complete with posted=0 0
Pipeline raises once, recovers retry_attempt, then run_complete 0
Pipeline keeps raising (budget exhausted) retry_attempt events, then retry_exhausted with operation and error 1
Config invalid 1× configuration_error (extras describe what failed) 2

Tester tipdocker compose ps should report the container as running after a pipeline_failed exit only if cron has not yet fired again. A single failed pipeline tick does not kill the container itself; cron re-runs it the next day.

Files the Tester Should Look For

Path (inside container) When it appears
/app/data/cron.log Always (cron appends stdout/stderr here).
/app/data/posted.json Created when the matcher or a state check first loads the store; rewritten only when a new ID is posted. The file contains relative Jekyll paths under a posted list.

To verify these from the host:

docker compose exec tenbackward ls -la /app/data
docker compose exec tenbackward cat /app/data/cron.log

Visual Elements

This is a server-side bot, so there is no UI. The "UI" consists of:

  • Log lines on stdout — one JSON object per INFO/ERROR event.
  • Exit code0 (healthy), 1 (pipeline exhausted), or 2 (config).
  • posted.json — JSON state file; its mtime changes whenever a new id is persisted.
  • /app/data/blog — synchronized local clone containing the Jekyll posts scanned for anniversaries.

Examples

Healthy scaffold tick

$ docker compose exec tenbackward python -m tenbackward
{"ts":"...","event":"startup",...}
$ echo $?
0

Bad RUN_AT

$ RUN_AT=25:99 docker compose exec tenbackward python -m tenbackward
ERROR: RUN_AT='25:99' must be in HH:MM (24-hour) format
$ docker compose ps
tenbackward  Exit 1

(The error message lands on stderr in the entrypoint path and is captured by cron.log.)

Missing required env var

Unset MAX_RETRIES, restart the container:

$ docker compose up --build
tenbackward  | ERROR: missing required environment variable(s): MAX_RETRIES
tenbackward  exited with code 1

Related