Roster Formatter

A local browser app that turns the current one-page roster PDF into the established formatted PNG. The web app and command-line tool use the same renderer.

Start with Docker Compose

From this folder:

docker compose up -d --build

Open http://127.0.0.1:8787, upload the roster PDF, choose its month, and select Format roster.

Common commands:

docker compose restart       # restart it later
docker compose logs -f app   # watch logs
docker compose down          # stop it
docker compose up -d --build # rebuild after code changes

The Compose service restarts automatically unless you explicitly stop it. Its direct browser port binds only to 127.0.0.1.

The service also joins the existing external tabernet Docker network, so Caddy can reach it at roster-formatter:8787. For a Caddy hostname, add that hostname to .env:

ROSTER_ALLOWED_HOSTS=roster.example.com

Then a minimal Caddy route is:

roster.example.com {
    reverse_proxy roster-formatter:8787
}

The tabernet network must already exist. Because roster images contain personal information, add authentication in Caddy before exposing this service outside your private network.

Saved files and automatic cleanup

Every successful PNG is returned to the browser with a Download PNG button and saved in output/. The Compose container mounts that folder, so saved PNGs survive rebuilds and restarts.

Layout defaults are persistent too. Change values under Advanced adjustments, then select Save as defaults to prefill them for future rosters. Reset changes restores the currently saved values, while Use original values loads the production geometry without saving it. The server stores these defaults in output/.roster-layout-defaults.json, inside the same mounted volume, and the roster cleanup never removes that settings file.

On startup, every six hours, and after each successful format, the app removes only filenames matching its roster pattern, such as MARCH 2026 Roster.png, when either its roster month or saved-file age is outside the three-calendar-month window. The file-age check prevents an accidentally future-dated filename from bypassing cleanup. Other files are not removed. Cleanup is permanent.

Change the retention window or browser port with a .env file beside compose.yaml:

ROSTER_RETENTION_MONTHS=6
ROSTER_PORT=8788
TZ=America/Chicago

Set ROSTER_RETENTION_MONTHS=0 to disable cleanup.

The image runs as UID/GID 1000:1000, matching this machine. On a host with a different checkout owner, set ROSTER_UID and ROSTER_GID in .env, then rebuild so the container can write to output/:

ROSTER_UID=1001
ROSTER_GID=1001

Layout guarantees

With the advanced fields untouched, the renderer preserves the original production values:

Setting Default
Canvas 792 × 612 PDF points
PNG export 1650 × 1275 pixels at 150 DPI
Roster vertical shift 100
Bottom trim 60
Logo position X 50, Y -10
Logo scale 0.27
Date position X 460, Y 88
Date font size 48

The web page exposes the layout values in a collapsed Advanced adjustments section. Only an intentional field change—or explicitly saving that change as the new default—alters logo/date scale or placement. The canvas size and PNG DPI are not editable. The original production values remain available through Use original values.

The page theme defaults to System and follows the browser/operating-system color preference. The System / Light / Dark control remembers the choice in that browser. Theme changes affect only the webpage and never the generated PNG.

Input is deliberately limited to a one-page, unrotated, 11 × 8.5 inch landscape PDF. This prevents a differently sized upload from silently changing the established geometry.

Run without Docker

The existing venv already contains the formatter dependencies. Start the web app with:

venv/bin/python server.py

Or create a fresh environment:

python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python server.py

The original CLI workflow is still available and now accepts an explicit input path:

venv/bin/python main.py --input input/original.pdf --month "July 2026"

Tests

Run the standard-library test suite with:

PYTHONDONTWRITEBYTECODE=1 venv/bin/python -m unittest discover -v

Uploads and intermediate PDFs are created in isolated temporary directories and removed immediately after formatting. Only the final PNG is retained in output/.

S
Description
roster image generator
Readme
51 MiB
Languages
Python 91.5%
C++ 5.5%
C 2.7%