Compare commits
2 Commits
ace1b3bd27
...
d2c8079231
| Author | SHA1 | Date | |
|---|---|---|---|
| d2c8079231 | |||
| f5ffa204c6 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,3 +6,5 @@ venv/
|
||||
*.egg-info/
|
||||
dist/
|
||||
build/
|
||||
config.toml
|
||||
.claude/
|
||||
|
||||
62
CLAUDE.md
Normal file
62
CLAUDE.md
Normal 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.
|
||||
Reference in New Issue
Block a user