feat: implement update backend API and client logic
Add complete update mechanism for client-side updates: - backend/update.py: Core update logic (check, apply, rollback, status/logs) - backend/app.py: REST API endpoints (GET /update/status, POST /update/check, POST /update/apply, POST /update/rollback, GET /update/logs) - backend/models.py: Pydantic models for update API responses - backend/settings.py: Update config (status/log file paths) - scripts/rollback_client.sh: Rollback script for failed updates - scripts/update_client.sh: Enhanced update client script - CLAUDE.md: Documentation for future Claude Code instances Complete US_000026-028 and TASK_000027-029: - US_000026: Client pulls updates from remote service - US_000027: Client verifies and applies updates atomically - US_000028: Client reports update status to backend All endpoints require authentication. Updates run asynchronously. Documentation updated per SOP (CHANGELOG, PROJECT_STATUS, stories/tasks). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
145
CLAUDE.md
Normal file
145
CLAUDE.md
Normal file
@ -0,0 +1,145 @@
|
||||
# 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 <host-name>
|
||||
|
||||
# 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.
|
||||
Reference in New Issue
Block a user