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>
7.3 KiB
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
# 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
# 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
# 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 applicationapp.py: Main FastAPI app with route handlersactions.py: User management actions (lock/unlock, notifications, shutdown logic)auth.py: PAM authentication, JWT tokens, user/group authorization checksoidc.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/responsestemplates/: Jinja2 templates for web UI
scripts/: Deployment and lifecycle scriptsinstall.sh: System setup (user, venv, systemd, PAM config)run.sh: Manual service startupdate.sh: Local git pull and service restartupdate_client.sh: Full update flow with backup/rollbackrollback_client.sh: Restore from backup if update failsdeploy.sh: SSH-based deployment to remote hostsregister_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 placeholdersdocs/: Detailed specs for OIDC validation, update API, status/log formatsMakefile: 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, defaultsudo)SKD_AUTH_PAM_SERVICE: PAM service name (Ubuntu/Debian useskd, others may uselogin)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: Iftrue, logs all privileged commands without executingSKD_UPDATE_*: Update service URL, token, interval, status/log file paths
Important Workflows
Disable User Flow
- API call to
/users/{username}/disablewith optional{countdown, sound, message} actions.disable_user()locks account viausermod -L- If user logged in: sends desktop notifications, plays sound (if enabled), countdown loop with periodic reminders
- Terminates sessions via
pkill -KILL -u - Triggers
shutdown nowonly if user was logged in
OIDC Login Flow
- User accesses
/login/oidc/start→ redirected to provider with state cookie - Provider redirects to
/login/oidc/callbackwith code + state - Validates state, exchanges code for tokens, extracts username from claims
- Issues JWT session cookie if user in allowlist
Update Flow
check_update()polls remote update service for latest manifest (version, artifact_url, sha256)start_update()writes "in_progress" status, launchesupdate_client.shin background- Script creates backup, downloads artifact, verifies checksum, installs, restarts service
- On failure:
rollback_client.shrestores from backup - Status/logs written to JSON files at
SKD_UPDATE_STATUS_FILEandSKD_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_SECRETand rotate by changing value + restarting service. - Restrict API/Web UI to LAN/VPN; consider mTLS or IP allowlisting.
skduser/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.shremains for emergency CLI fallback; API is preferred for all operations. src/hexagonal architecture skeleton is currently unused; logic lives inbackend/.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.shsupports both YAML (deploy_hosts.yml) and JSON host configs. - PAM service file (
/etc/pam.d/skd) created byscripts/install.shon Ubuntu/Debian; other distros may need manual setup.