# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview Safe Kiddo Daemon (SKD) is a FastAPI-based service for managing local user accounts on kids' laptops. It provides account locking/unlocking with countdown notifications, optional sound alerts, and shutdown capabilities. The service exposes a REST API with bearer token auth (PAM or OIDC) and serves a minimal web UI. ## Commands ### Development ```bash # Run the service manually (uses .venv, binds to 0.0.0.0:80) ./scripts/run.sh # Install service and dependencies sudo make install # Service management sudo make up # Start service sudo make down # Stop service sudo make update # Pull latest from git, reinstall deps, restart # Generate/set API token make token # Health check (requires token) make healthcheck ``` ### Installation & Deployment ```bash # Full install (creates service user, venv, systemd unit, PAM config) sudo ./scripts/install.sh # Deploy to remote host (requires deploy_hosts.yml) ./scripts/deploy.sh # Manual update on target ssh user@target 'cd /opt/sk && ./scripts/update.sh' ``` ### Testing ```bash # Python syntax validation python -m py_compile backend/*.py # Run specific tests (no formal test runner yet; tests/ is empty) # Use curl for API testing: token=$(curl -s -X POST -H "Content-Type: application/json" \ -d '{"username":"root","password":"..."}' \ http://localhost/login | jq -r .token) curl -H "Authorization: Bearer $token" http://localhost/users ``` ## Architecture ### Core Structure - `backend/`: FastAPI application - `app.py`: Main FastAPI app with route handlers - `actions.py`: User management actions (lock/unlock, notifications, shutdown logic) - `auth.py`: PAM authentication, JWT tokens, user/group authorization checks - `oidc.py`: OIDC client (dynamic discovery, token exchange, claims validation) - `update.py`: Update client logic (check/status/logs, triggers async update/rollback scripts) - `settings.py`: Environment-based configuration (Settings class, singleton via lru_cache) - `models.py`: Pydantic models for API requests/responses - `templates/`: Jinja2 templates for web UI - `scripts/`: Deployment and lifecycle scripts - `install.sh`: System setup (user, venv, systemd, PAM config) - `run.sh`: Manual service start - `update.sh`: Local git pull and service restart - `update_client.sh`: Full update flow with backup/rollback - `rollback_client.sh`: Restore from backup if update fails - `deploy.sh`: SSH-based deployment to remote hosts - `register_oidc_client.sh`: OIDC dynamic client registration helper - `sk.sh`: Legacy bash script (CLI fallback for direct SSH use) - `src/`: Hexagonal architecture skeleton (core/ports/adapters/ui) - currently empty placeholders - `docs/`: Detailed specs for OIDC validation, update API, status/log formats - `Makefile`: Convenience targets for install, service control, updates, token management ### Key Architectural Patterns **Dual Authentication**: PAM-based local auth (root/sudo users) is always available; OIDC is optional if `SKD_OIDC_ISSUER`, `SKD_OIDC_CLIENT_ID`, and `SKD_OIDC_CLIENT_SECRET` are configured. Both modes issue JWT bearer tokens. **Settings Management**: All config via environment variables (loaded from `/etc/skd/env` in production). `settings.py` provides a singleton `Settings` instance via `get_settings()` using `lru_cache`. FastAPI dependencies inject settings into route handlers. **Action Execution**: `actions.py` wraps all privileged operations (usermod, pkill, shutdown) via `_run()` helper. Dry-run mode (`SKD_DRY_RUN=true`) logs commands without executing them. **Update Flow**: `update.py` checks remote update service for new versions, writes status to JSON files, and triggers async scripts (`update_client.sh`, `rollback_client.sh`) that create backups, apply updates, and handle rollbacks on failure. **Authorization**: `auth.py` checks both user allowlists (`SKD_AUTH_ALLOWED_USERS`) and group membership (`SKD_AUTH_ALLOWED_GROUPS`, defaults to `sudo`). UID 0 (root) always allowed for PAM. OIDC validates against `preferred_username`, `email`, or `sub` claims. **Manageable Users**: Only system users with UID >= 1000, real shells (not nologin/false), and optional allowlist (`SKD_ALLOWED_USERS`) are exposed via API. Root accounts are never manageable. ## Configuration Deployment config lives in `/etc/skd/env` (see `env.example` in repo root): - `SKD_AUTH_SECRET`: HMAC secret for JWT signing (must be strong in production) - `SKD_AUTH_ALLOWED_USERS`: Comma-separated user allowlist (for login and OIDC claims) - `SKD_AUTH_ALLOWED_GROUPS`: Groups whose members may log in (PAM only, default `sudo`) - `SKD_AUTH_PAM_SERVICE`: PAM service name (Ubuntu/Debian use `skd`, others may use `login`) - `SKD_OIDC_*`: OIDC provider config (ISSUER, CLIENT_ID, CLIENT_SECRET, REDIRECT_URI, SCOPES) - `SKD_ALLOWED_USERS`: Comma-separated list of manageable system accounts (optional) - `SKD_DRY_RUN`: If `true`, logs all privileged commands without executing - `SKD_UPDATE_*`: Update service URL, token, interval, status/log file paths ## Important Workflows ### Disable User Flow 1. API call to `/users/{username}/disable` with optional `{countdown, sound, message}` 2. `actions.disable_user()` locks account via `usermod -L` 3. If user logged in: sends desktop notifications, plays sound (if enabled), countdown loop with periodic reminders 4. Terminates sessions via `pkill -KILL -u` 5. Triggers `shutdown now` only if user was logged in ### OIDC Login Flow 1. User accesses `/login/oidc/start` → redirected to provider with state cookie 2. Provider redirects to `/login/oidc/callback` with code + state 3. Validates state, exchanges code for tokens, extracts username from claims 4. Issues JWT session cookie if user in allowlist ### Update Flow 1. `check_update()` polls remote update service for latest manifest (version, artifact_url, sha256) 2. `start_update()` writes "in_progress" status, launches `update_client.sh` in background 3. Script creates backup, downloads artifact, verifies checksum, installs, restarts service 4. On failure: `rollback_client.sh` restores from backup 5. Status/logs written to JSON files at `SKD_UPDATE_STATUS_FILE` and `SKD_UPDATE_LOG_FILE` ## Security Considerations - Service runs as root by default (required for PAM, usermod, pkill, shutdown). Limit exposure via firewall. - Set strong `SKD_AUTH_SECRET` and rotate by changing value + restarting service. - Restrict API/Web UI to LAN/VPN; consider mTLS or IP allowlisting. - `skd` user/group created by install script; consider sudoers rules to limit privileges to specific commands. - OIDC redirect URI must match exactly (no wildcards); re-register client if host/port changes. - Validate TLS certificates in production; self-signed certs require CA trust or fallback to PAM. ## Notes - Legacy `sk.sh` remains for emergency CLI fallback; API is preferred for all operations. - `src/` hexagonal architecture skeleton is currently unused; logic lives in `backend/`. - `tests/` directory exists but is empty; use manual curl-based API testing. - Both German and English comments exist in code; favor English going forward. - Deployment via `deploy.sh` supports both YAML (`deploy_hosts.yml`) and JSON host configs. - PAM service file (`/etc/pam.d/skd`) created by `scripts/install.sh` on Ubuntu/Debian; other distros may need manual setup.