fribbe-status-checker
September 25, 2026 · View on GitHub
A FastAPI-based status checker for Fribbe Beach, running at status.fribbe-beach.de. It combines real-time presence detection with occupancy data scraped from the Fribbe Beach website to give a live status overview.
Features
- Presence detection — Polls a Huawei LTE router to count connected Wi-Fi devices and derive a presence level (
empty/few/many). Configurable thresholds via the API. - Occupancy parsing — Scrapes the Fribbe Beach weekly plan and event calendar to determine booking status for any given date.
- Wardens — Named device watchers that track specific people by MAC address or device name, showing who is currently on-site.
- Notifications — Create and manage Markdown-formatted notifications with optional validity windows. Includes a dedicated builder UI at
/notification-create. - API key management — Admin-only web page at
/api-keysto create, list, and delete API keys without using the REST API or cURL. Accessible via the floating key button on the index page. - Push notifications — Browser Web Push (VAPID) alerts when someone first arrives at the Fribbe on a given day or when a notification becomes active. Topic-based subscriptions (
presence,notifications). - Weather-aware messages — Optional OpenWeatherMap integration for temperature- and weather-state-aware status and push messages.
- REST API — JSON API with hybrid authentication (API key header or opaque server-side session cookie). Interactive docs at
/docs. - Web UI — Jinja2-templated HTML pages with CSS/JS frontend, service worker support, and dark mode (follows system preference, override via toggle). Static assets served directly by the app.
- Impressum & Datenschutz — To activate legal compliance, set both
OPERATOR_NAMEandOPERATOR_EMAILenvironment variables. When configured, a GDPR-compliant legal page is served at/legaland a link appears in the UI footer.
Development
Dev container & VS Code
A dev container configuration is included. To use it:
- Install the Dev Containers extension in VS Code.
- Open the workspace folder and select Dev Containers: Reopen in Container from the command palette.
The container includes all necessary prerequisites pre-installed.
Alternatively, use the Python: Debug launch configuration to run locally without the container.
Prerequisites
- Python >= 3.12
- uv — dependency management and script runner
Setup
uv sync # install dependencies
cp .env.template .env.dev # configure env vars (see .env.template for all options)
Run locally
uv run dev # start app at http://localhost:8007
Test, lint, format
uv run test # run tests (--cov for coverage)
uv run lint --fix # backend and frontend lint + auto-fix
uv run test runs three separate suites and fails if any of them fails:
| Suite | Location | Report |
|---|---|---|
| Application (pytest) | tests/ | junit/test-results.xml |
| Script & tooling (pytest) | tests/tooling/ | junit/script-test-results.xml |
| Frontend (vitest) | tests/js/, .github/tests/js/ | junit/js-test-results.xml |
Tests that cover tooling rather than the app (release/changelog scripts, cliff.toml) live in
tests/tooling/, which is excluded from the application suite and its coverage. Run just that
suite with uv run pytest tests/tooling. A plain uv run pytest still collects everything.
The VS Code Test Explorer discovers both pytest suites: .vscode/settings.json sets
python.testing.pytestArgs to tests and tests/tooling (pytest dedupes the nested path, so
each test is collected once). To run only the tooling suite from the Test Explorer, run the
tooling folder node.
Configuration
All environment variables are declared in app/config.py. See .env.template for the full list with defaults.
Required: APP_URL, SESSION_SECRET_KEY, LOCAL_DATA_PATH, API_KEYS_PATH.
Each optional feature is silently disabled when its variables are absent.
Set IGNORED_MAC_ADDRESSES to a comma-separated list of MAC addresses (e.g. 2C:CF:67:DD:46:23,54:60:09:EE:19:28) to exclude infrastructure devices from presence detection. Matching is case-insensitive. Leave unset or empty to ignore no addresses.
Generate VAPID keys
uv run generate-vapid-keys # prints VAPID_PRIVATE_KEY, VAPID_PUBLIC_KEY, VAPID_CLAIM_SUBJECT
Test push notification
uv run test-push-notification # default title/body
uv run test-push-notification "Title" "Body text"
Authentication
The app uses HybridAuth: an API key passed via the api_key header or an opaque server-side session cookie. Browser sessions are protected by a per-session CSRF token (X-CSRF-Token).
Access roles
Every authenticated subject carries an AccessRole (READER < NOTIFICATION_OPERATOR < ADMIN). Higher roles inherit all permissions of lower roles.
| Role | Permissions |
|---|---|
READER | Read-only access to all protected endpoints. |
NOTIFICATION_OPERATOR | Everything in READER, plus create / update / delete notifications. |
ADMIN | Full access — API key management, warden CRUD, config changes, and all of the above. |
ADMIN_TOKENalways maps toADMIN.- API keys carry a
rolefield. New keys default toREADER; specify"role": 3(or"role": "admin") inPOST /api/internal/api_keyto set a higher role. - Existing stored keys without a
rolefield fallback toREADERfor backward compatibility.
ADMIN_TOKEN
A master credential accepted on all protected endpoints. When neither ADMIN_TOKEN nor a valid admin API key is configured, a warning banner is shown on the home page. Generate a suitable value with:
python -c "import secrets; print(secrets.token_urlsafe(48))"
Deployment
Docker image
Pre-built images are published to GitHub Container Registry on every push to main:
docker pull ghcr.io/florianobermayer/fribbe-status-checker:latest
Available image tags:
| Tag | Description |
|---|---|
latest | Most recent build from main. |
<version> (e.g. 0.5.0) | Immutable tag, created once when the version in pyproject.toml is bumped. |
<version>-<run> (e.g. 0.5.0-42) | Unique per-build tag for traceability. |
Releases
The CI/CD pipeline (.github/workflows/ci-cd.yml) automates releases:
- Stable release — Created automatically (with changelog) when the version in
pyproject.tomlis bumped and pushed tomain. - Nightly pre-release — Updated on every subsequent push to
mainunder the same version. Taggednightly.
Run uv run release [patch|minor|major] to bump the version, refresh the lock file and licenses, push a release/vX.Y.Z branch and open a PR against main.
If you are already on a release branch, that branch is updated in place — the script stays on it, merges the latest main, refreshes the lock file and licenses, then pushes. Otherwise, if another release branch is still open (local or on origin, i.e. not yet merged into main), it reuses the one with the smallest version jump above the current project version instead of creating a new branch. Pass --dry-run to preview the steps without changing anything.
The pipeline can also be triggered manually via workflow_dispatch.
See Dockerfile and examples/docker-compose.yml for container and setup details.