3.9 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Development Commands
# Local setup
python -m venv .venv
source .venv/Scripts/activate # Windows Git Bash
pip install -r requirements.txt
cp .env.example .env # fill in values
# Run locally (pick any free port; earlier dev sessions may occupy 8080–8094)
uvicorn app.main:app --reload --port 8080
# Skip Authentik login entirely during local dev
AUTH_DISABLED=true uvicorn app.main:app --reload --port 8080
# Docker
docker build -t qwe-salus:latest .
docker compose up
There are no tests and no linter configured.
Architecture
Request flow
Browser → FastAPI (main.py)
├── auth.py – Authentik OIDC + session management
├── omada.py – Omada Controller API client (singleton)
└── database.py – SQLAlchemy / SQLite audit log
Templates extend base.html (nav, footer, toast system). TailwindCSS is loaded from CDN — no build step.
Critical: module-level os.getenv() ordering
app/main.py calls load_dotenv() before any from app.* imports. omada.py and auth.py read their config via os.getenv() at module load time, so .env must be loaded first. Moving load_dotenv() below those imports silently breaks all env var config.
OmadaClient (app/omada.py)
A module-level singleton (omada_client). Key behaviour:
- Two separate
httpx.AsyncClientinstances:_get_client()carries the web session cookie (TPOMADA_SESSIONID);_get_openapi_client()is cookie-free. Mixing them causes 401s on the OpenAPI token endpoint. - Auth flow:
_fetch_omadac_id()→_login()(username/password → CSRF token + session cookie) →_fetch_sites()._ensure_ready()gates all public methods. - Sites are fetched from
GET /api/v2/users/current→result.privilege.sites[], not/api/v2/sites(which requires root admin and returns empty for regular admins). - Devices list:
GET /{omadacId}/api/v2/sites/{siteKey}/devicesfiltered bytype == "ap". There is no/apsendpoint in v6.x. - Reboot:
POST /{omadacId}/api/v2/sites/{siteKey}/cmd/devices/{mac}/rebootvia the web session client. ResponseerrorCode: -39009with"Rebooting... Please wait."is the success response — treat it as non-error alongside0. OpenAPI client credentials (OMADA_CLIENT_ID/OMADA_CLIENT_SECRET) are no longer used for reboot. - Session expiry is handled in
_request_with_retry: error codes-1006,-1003,-30109trigger automatic re-login. - Uptime safety check:
reboot_ap()re-fetches live uptime before issuing the command; it raises ifuptimeLong < 300seconds (5 minutes). The UI also disables the reboot button client-side for the same condition.
Authentication (app/auth.py)
Authentik OIDC Authorization Code Flow. The JWT id_token is decoded without signature verification (Authentik is a trusted internal IdP). User dict {username, email, name, sub} is stored in the Starlette signed-cookie session.
AUTH_DISABLED=true injects a hardcoded DEV_USER on /auth/login — no Authentik required.
All POST API endpoints (/api/reboot, /api/reboot-bulk) validate a CSRF token stored in the session and echoed in the JSON request body.
Database (app/database.py)
Single table reboot_log written on every reboot attempt (success and error). DB_PATH defaults to /data/audit.db (Docker volume mount). Override to ./audit.db for local dev.
Deployment
- Docker exposes port
8080;docker-compose.ymlmaps it to host port8098. - Uvicorn runs with
--proxy-headers --forwarded-allow-ips=*to trustX-Forwarded-Protofrom Nginx Proxy Manager. - The
/healthendpoint is used by the Docker healthcheck. /debug/sitesis a diagnostic endpoint that lists all accessible Omada sites — useful for verifying credentials without touching the UI.