Updated Configs and Claud v2

This commit is contained in:
2026-02-24 16:47:47 -06:00
parent f5ffa204c6
commit d2c8079231
3 changed files with 99 additions and 0 deletions

2
.gitignore vendored
View File

@@ -6,3 +6,5 @@ venv/
*.egg-info/ *.egg-info/
dist/ dist/
build/ build/
config.toml
.claude/

62
CLAUDE.md Normal file
View File

@@ -0,0 +1,62 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## What this is
A Pygame framebuffer clock for a Le Potato (ARM Debian) connected to an auditorium TV. Renders directly to `/dev/fb0` — no X11/Wayland. Staff push announcements via a browser dashboard or API webhook.
## Running locally
**Server + dashboard only (no pygame needed — works on WSL/Windows):**
```bash
python3 main.py --no-display
```
**Windowed display mode (requires a graphical environment):**
```bash
python3 main.py --dev
```
Press **Escape** to close. On WSL2 this needs WSLg or VcXsrv.
**First run:** if `config.toml` has no `token`, one is auto-generated and saved. If `password_hash` is empty, a setup wizard appears at `http://localhost:8080/setup`.
**Copy config from example before first run:**
```bash
cp config.toml.example config.toml
```
## Architecture
The app has two concurrent execution contexts that communicate through `MessageState`:
- **`DisplayThread`** (`display.py`) — a daemon thread running pygame. Polls `state.get()` on every frame tick. When a message is active it replaces the clock entirely; font size is binary-searched to fill 88% of the screen.
- **`ClockServer`** (`server.py`) — aiohttp running in the asyncio main thread. Updates `state` in response to dashboard/API requests.
`MessageState` (`state.py`) is the only shared object between the two contexts. It uses a `threading.Lock` and stores expiry as a `time.monotonic()` timestamp. Expiry is checked lazily on `get()` — there is no background timer.
`AppConfig` (`config.py`) is loaded once at startup from `config.toml`. The only post-startup writes are `save_password_hash()` (on first-run setup) and auto-generated token (on first run with empty token). Both use regex replacement on the raw TOML file to avoid reformatting.
## Server routes
| Route | Auth | Purpose |
|---|---|---|
| `GET /` | Session cookie | Serve dashboard `index.html` |
| `GET /setup`, `POST /setup` | None | First-run password wizard |
| `GET /login`, `POST /login` | None | Dashboard login |
| `POST /logout` | Session cookie | Dashboard logout |
| `POST /dashboard/message` | Session cookie | Send message (from dashboard JS) |
| `DELETE /dashboard/message` | Session cookie | Clear message (from dashboard JS) |
| `GET /dashboard/status` | Session cookie | Polled every 5 s by dashboard JS |
| `POST /api/message` | Bearer token + rate limit | Webhook / external integrations |
| `DELETE /api/message` | Bearer token + rate limit | Clear via API |
| `GET /api/status` | Bearer token | Check state via API |
The dashboard (`dashboard/index.html`, `style.css`, `app.js`) calls the `/dashboard/*` routes, not `/api/*`. Static assets are served from `dashboard/` at `/static/`.
## Key behaviours to preserve
- `display.py` is **not imported** when `--no-display` is set — this allows the server to run without pygame installed.
- Message font layout is **cached** in `DisplayThread.run()` and only recomputed when the message text changes.
- `_update_config_field` in `config.py` uses regex on the raw TOML — it only rewrites the matched field, leaving all comments and formatting intact.
- `secrets.compare_digest` is used for bearer token comparison to prevent timing attacks.

35
config.toml.example Normal file
View File

@@ -0,0 +1,35 @@
[display]
# Screen resolution. Leave commented to auto-detect from the framebuffer.
# width = 1920
# height = 1080
# Display refresh rate in frames per second. 10 is plenty for a clock.
fps = 10
# Paths to TTF font files. Leave blank to auto-detect from system fonts.
# The clock uses a monospace font; messages use a sans-serif bold font.
clock_font_path = ""
message_font_path = ""
[server]
port = 8080
# Default message duration in seconds when the caller doesn't specify one.
default_duration_seconds = 20
[api]
# Bearer token for /api/* routes.
# Leave blank — a token is auto-generated on first run and saved here.
token = ""
[rate_limit]
# Maximum API requests per minute per source IP.
requests_per_minute = 20
[dashboard]
# Bcrypt hash of the dashboard password.
# Leave blank — a setup wizard runs on the first browser visit to set the password.
password_hash = ""
# How many hours of inactivity before a dashboard session expires.
session_timeout_hours = 8