Files
Salus/CLAUDE.md
Christoph Gasser 284924e86d Initial release — Salus by Stranto v1.6.1.0
FastAPI/Jinja2 web app for viewing and rebooting TP-Link Omada APs
across all sites. Authentik OIDC auth, SQLite audit log, Docker deploy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 14:36:02 +02:00

74 lines
3.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Development Commands
```bash
# 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 80808094)
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 omada-ap-manager: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.AsyncClient` instances**: `_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}/devices` filtered by `type == "ap"`. There is no `/aps` endpoint in v6.x.
- **Reboot**: `POST /{omadacId}/api/v2/sites/{siteKey}/cmd/devices/{mac}/reboot` via the web session client. Response `errorCode: -39009` with `"Rebooting... Please wait."` is the success response — treat it as non-error alongside `0`. 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`, `-30109` trigger automatic re-login.
- **Uptime safety check**: `reboot_ap()` re-fetches live uptime before issuing the command; it raises if `uptimeLong < 300` seconds (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.yml` maps it to host port `8098`.
- Uvicorn runs with `--proxy-headers --forwarded-allow-ips=*` to trust `X-Forwarded-Proto` from Nginx Proxy Manager.
- The `/health` endpoint is used by the Docker healthcheck.
- `/debug/sites` is a diagnostic endpoint that lists all accessible Omada sites — useful for verifying credentials without touching the UI.