Compare commits

...

2 Commits

Author SHA1 Message Date
4eb20e2449 docs: add project management structure 2025-12-28 09:16:22 +01:00
3991362e67 feature: add oidc login flow 2025-12-28 09:16:16 +01:00
59 changed files with 2303 additions and 14 deletions

16
.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
__pycache__/
*.py[cod]
*$py.class
.venv/
venv/
ENV/
.env
.vscode/
.idea/
*.log
output/
temp/
.project_analysis/
*.sqlite3
*.db
.DS_Store

18
CHANGELOG.md Normal file
View File

@ -0,0 +1,18 @@
ID: DOC_000001 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# Projekt-Logbuch (Changelog)
| Datum | Typ | Beschreibung |
|---|---|---|
| 28.12.2025 | 🚀 Init | ID: SETUP_000005 Struktur, VERSION, CHANGELOG, .gitignore initialisiert. By: Codex (GPT-5) |
| 28.12.2025 | 🏗️ Planning | ID: EPIC_000001-EPIC_000006 und US_000001-US_000019 dokumentiert. By: Codex (GPT-5) |
| 28.12.2025 | 🏗️ Planning | ID: EPIC_000007 und US_000020-US_000023 dokumentiert. By: Codex (GPT-5) |
| 28.12.2025 | 🏗️ Planning | ID: US_000024 dokumentiert; OIDC- und Login-Stories praezisiert. By: Codex (GPT-5) |
| 28.12.2025 | 🏗️ Planning | ID: US_000024 Theme-Assets unter assets/design vorbereitet. By: Codex (GPT-5) |
---
## Legende
* 🚀 Init = Setup | 📝 Req = Requirements | 🏛️ Arch = Architektur
* 🎨 UI = Design | ✅ Done = Abgeschlossen | ⚙️ Code = Implementation
* 🔊 Audio = Audio-Features | 🗣️ UX = User Experience | ✨ Feature = Neu | 🛠️ CRUD = Management

View File

@ -20,10 +20,13 @@ Then open `http://localhost:8000/` and set the API token in the UI.
## Configuration ## Configuration
Set in `/etc/skd/env` (see `env.example`): Set in `/etc/skd/env` (see `env.example`):
- `SKD_AUTH_SECRET`: HMAC secret for bearer tokens (set a strong value). - `SKD_AUTH_MODE`: `pam` (default) or `oidc`.
- `SKD_AUTH_SECRET`: HMAC secret for bearer tokens/cookies (set a strong value).
- `SKD_TOKEN_TTL_SECONDS`: token lifetime (default 900s). - `SKD_TOKEN_TTL_SECONDS`: token lifetime (default 900s).
- `SKD_AUTH_ALLOWED_USERS`: optional comma list of accounts allowed to log in. - `SKD_AUTH_ALLOWED_USERS`: optional comma list of accounts allowed to log in (used for PAM and as an allowlist for OIDC claims).
- `SKD_AUTH_ALLOWED_GROUPS`: groups whose members may log in (default `sudo`). - `SKD_AUTH_ALLOWED_GROUPS`: groups whose members may log in (PAM only, default `sudo`).
- `SKD_OIDC_*`: `ISSUER`, `CLIENT_ID`, `CLIENT_SECRET`, `REDIRECT_URI`, `SCOPES` to point at your OIDC provider; set `SKD_SESSION_COOKIE_SECURE=true` for HTTPS.
- OIDC dynamic registration helper: `scripts/register_oidc_client.sh` (requires `OIDC_INITIAL_ACCESS_TOKEN` and `SKD_OIDC_ISSUER`; uses `SKD_OIDC_REDIRECT_URI` for the redirect). Run once during setup if your provider issues initial access tokens for client creation.
- `SKD_ALLOWED_USERS`: optional comma list to limit manageable accounts (must exist on the system). - `SKD_ALLOWED_USERS`: optional comma list to limit manageable accounts (must exist on the system).
- `SKD_DEFAULT_COUNTDOWN`, `SKD_DEFAULT_SOUND`, `SKD_NOTIFY_TIMEOUT`: behavior defaults. - `SKD_DEFAULT_COUNTDOWN`, `SKD_DEFAULT_SOUND`, `SKD_NOTIFY_TIMEOUT`: behavior defaults.
- `SKD_DRY_RUN=true` to test without real account changes or shutdown. - `SKD_DRY_RUN=true` to test without real account changes or shutdown.
@ -34,7 +37,8 @@ Notes:
## Running ## Running
- Service: managed by systemd; `./scripts/install.sh` writes the unit dynamically to `/etc/systemd/system/skd.service` with the current repo path and restarts it. - Service: managed by systemd; `./scripts/install.sh` writes the unit dynamically to `/etc/systemd/system/skd.service` with the current repo path and restarts it.
- Manual run: `./scripts/run.sh` (uses `.venv`, defaults to `0.0.0.0:8000`). - Manual run: `./scripts/run.sh` (uses `.venv`, defaults to `0.0.0.0:8000`).
- Login: `curl -X POST -H "Content-Type: application/json" -d '{"username":"root","password":"..."}' http://localhost:8000/login` - Login (PAM): `curl -X POST -H "Content-Type: application/json" -d '{"username":"root","password":"..."}' http://localhost:8000/login`
- Login (OIDC): open `http://localhost:8000/login/oidc/start` → provider → redirected back with session cookie set.
- Health: `curl -H "Authorization: Bearer <token>" http://localhost:8000/health` - Health: `curl -H "Authorization: Bearer <token>" http://localhost:8000/health`
## API (Bearer token via `/login`) ## API (Bearer token via `/login`)
@ -42,6 +46,7 @@ Notes:
- `POST /users/{name}/disable` with JSON `{countdown?, sound?, message?}` - `POST /users/{name}/disable` with JSON `{countdown?, sound?, message?}`
- `POST /users/{name}/enable` - `POST /users/{name}/enable`
- `GET /health` - `GET /health`
- `GET /me` (returns current user + auth mode when a session/bearer token is present)
Example: Example:
```bash ```bash
@ -53,7 +58,7 @@ curl -X POST -H "Authorization: Bearer $token" \
``` ```
## Web UI ## Web UI
Served at `/`. Login mit Root-Account, danach werden verfügbare System-User angezeigt; Aktionen senden Bearer Token automatisch. Served at `/`. Nutze den Button „Login via OIDC“ (setzt Session-Cookie) oder das PAM-Formular, falls OIDC deaktiviert; danach werden verfügbare System-User angezeigt und Aktionen senden Token/Cookies automatisch.
## Updates ## Updates
- Remote update via SSH: `ssh user@kid-laptop 'cd /opt/sk && ./scripts/update.sh'` (fetch/reset to `origin/main`, reinstalls deps, restarts service). - Remote update via SSH: `ssh user@kid-laptop 'cd /opt/sk && ./scripts/update.sh'` (fetch/reset to `origin/main`, reinstalls deps, restarts service).

1
VERSION Normal file
View File

@ -0,0 +1 @@
0.1.0

View File

@ -0,0 +1,62 @@
# Theme: Watchtower (Sci-Fi / Dark)
Dieses Theme basiert auf der `minecraft-watchtower` UI. Es ist ein **High-Contrast Dark Mode** mit Neon-Akzenten, ausgelegt auf technische Dashboards und "Immersive UIs".
## Verwendung
Binde statt `tokens.css` die Datei `tokens_watchtower.css` ein und ergänze `theme_watchtower.css`.
```html
<link rel="stylesheet" href="design/tokens_watchtower.css">
<link rel="stylesheet" href="design/components.css"> <!-- Standard Components -->
<link rel="stylesheet" href="design/theme_watchtower.css"> <!-- Theme Overrides -->
```
Zusätzlich sollte die Klasse `.bg-noise` direkt nach dem `<body>` Tag eingefügt werden:
```html
<body>
<div class="bg-noise"></div>
...
</body>
```
---
## TUI (Terminal User Interface) Adaption
Da dieses Design oft in CLI-Tools oder TUIs verwendet wird, gelten folgende Mappings für Terminals (16/256 Farben).
### Farb-Palette
| Rolle | CSS Variable | ANSI Color (16) | ANSI Code | Hex Fallback |
| :--- | :--- | :--- | :--- | :--- |
| **Background** | `--bg-dark` | Black | `\e[40m` | `#0b0f14` |
| **Text** | `--text` | White (Bright) | `\e[97m` | `#e6edf5` |
| **Muted Text** | `--text-dim` | Cyan (Dim) | `\e[36m` | `#9aa7b8` |
| **Accent** | `--accent` | Cyan (Bright) | `\e[96m` | `#00e08f` |
| **Success** | `--success` | Green (Bright) | `\e[92m` | `#00e08f` (Teal) |
| **Warning** | `--warning` | Yellow (Bright) | `\e[93m` | `#ffb454` |
| **Error** | `--danger` | Red (Bright) | `\e[91m` | `#ff5e5e` |
### Block-Elemente & Rahmen
Für TUI-Rahmen nutzen wir "Heavy" oder "Double" Lines, um den technischen Look zu imitieren.
* **Box Border:** `═` (Double Horizontal), `║` (Double Vertical), `╔ ╗ ╚ ╝` (Corners)
* **Progress Bar:** `█` (Full Block) für den Füllstand, `░` (Light Shade) für den Hintergrund.
### Beispiel (Charm / Bubble Tea - Go)
```go
var (
ColorAccent = lipgloss.Color("#00e08f")
ColorBg = lipgloss.Color("#0b0f14")
ColorText = lipgloss.Color("#e6edf5")
StyleCard = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(ColorAccent).
Padding(1, 2).
Background(ColorBg)
)
```

View File

@ -0,0 +1,135 @@
/*
wlkns-dev-standards
Component Theme: Watchtower
Adapts standard components to the Sci-Fi/Dark aesthetic.
*/
/* =========================
Inputs & Forms
========================= */
input, select, textarea {
background: rgba(10, 14, 20, 0.6) !important;
border-color: rgba(255, 255, 255, 0.15) !important;
color: var(--color-text) !important;
transition: all 0.2s ease;
}
input:focus, select:focus, textarea:focus {
border-color: var(--color-accent) !important;
box-shadow: 0 0 15px var(--wt-glow);
}
/* =========================
Buttons
========================= */
.button {
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 600;
border-radius: 999px !important; /* Pill shape */
}
.button.primary {
box-shadow: 0 0 15px var(--wt-glow);
color: #081015; /* Dark text on bright accent */
}
.button.secondary {
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.2);
color: var(--color-text);
}
.button.secondary:hover {
border-color: var(--color-text);
background: rgba(255,255,255,0.05);
}
.button.danger {
box-shadow: 0 0 10px rgba(255, 94, 94, 0.4);
color: #1b0d0d;
}
/* =========================
Tables
========================= */
table {
border-collapse: separate;
border-spacing: 0 4px; /* Space between rows */
}
thead th {
border-bottom: 1px solid var(--color-accent);
text-transform: uppercase;
letter-spacing: 0.1em;
font-size: 0.75rem;
color: var(--color-accent);
background: transparent;
}
tbody tr {
background: rgba(255, 255, 255, 0.03);
transition: transform 0.2s;
}
tbody tr:hover {
background: rgba(255, 255, 255, 0.06);
transform: scale(1.01);
}
tbody td {
border: none;
}
tbody td:first-child {
border-top-left-radius: 8px;
border-bottom-left-radius: 8px;
}
tbody td:last-child {
border-top-right-radius: 8px;
border-bottom-right-radius: 8px;
}
/* =========================
Alerts & Toasts
========================= */
.alert, .toast {
background: rgba(10, 14, 20, 0.95);
backdrop-filter: blur(4px);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
}
.alert.success, .toast.success {
border-color: var(--color-success);
box-shadow: 0 0 10px rgba(0, 224, 143, 0.2);
color: var(--color-text);
}
.alert.warning, .toast.warning {
border-color: var(--color-warning);
color: var(--color-text);
}
.alert.error, .toast.error {
border-color: var(--color-error);
box-shadow: 0 0 10px rgba(255, 94, 94, 0.2);
color: var(--color-text);
}
/* =========================
Pagination
========================= */
.pagination .page {
background: transparent;
border: 1px solid rgba(255,255,255,0.1);
color: var(--color-text-muted);
}
.pagination .page.active {
background: var(--color-accent);
color: #081015;
border-color: var(--color-accent);
box-shadow: 0 0 10px var(--wt-glow);
}

View File

@ -0,0 +1,69 @@
/*
wlkns-dev-standards
Theme: Watchtower (Dark Glow)
Version: v1.0
Based on: minecraft-watchtower UI
*/
:root {
/* =========================
Core Palette (Watchtower)
========================= */
--wt-bg-dark: #0b0f14;
--wt-bg-panel: #10151d;
--wt-accent: #00e08f;
--wt-warning: #ffb454;
--wt-danger: #ff5e5e;
--wt-text: #e6edf5;
--wt-text-dim: #9aa7b8;
--wt-glow: rgba(0, 224, 143, 0.35);
/* =========================
Mapping -> Standard Tokens
========================= */
/* Farben – Identity & Status */
--color-primary: #1a2230; /* Deep Blue/Grey from Gradient */
--color-accent: var(--wt-accent);
--color-success: var(--wt-accent); /* Watchtower uses accent as success */
--color-warning: var(--wt-warning);
--color-error: var(--wt-danger);
/* Farben – Neutrals (Dark Mode Override) */
--color-bg: var(--wt-bg-dark);
--color-surface: var(--wt-bg-panel);
--color-border: rgba(255, 255, 255, 0.08); /* Subtle white border */
--color-text: var(--wt-text);
--color-text-muted: var(--wt-text-dim);
/* Typografie (Optional: Falls Space Grotesk geladen wird) */
--font-ui: 'Space Grotesk', 'Inter', system-ui, sans-serif;
--font-mono: 'JetBrains Mono', monospace;
/* Radius & Spacing */
--radius-sm: 4px;
--radius-md: 12px; /* Watchtower uses larger radii */
--radius-lg: 16px;
/* Shadows becomes Glows in this theme */
--shadow-sm: 0 0 10px rgba(0,0,0,0.5);
--shadow-glow: 0 0 15px var(--wt-glow);
}
/* Global Theme Overrides */
body {
background: radial-gradient(circle at top, #1a2230 0%, #0b0f14 55%), linear-gradient(135deg, #0b0f14, #101623 60%);
min-height: 100vh;
}
/* Noise Texture helper class */
.bg-noise {
position: fixed;
inset: 0;
pointer-events: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160' viewBox='0 0 160 160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.04'/%3E%3C/svg%3E");
mix-blend-mode: soft-light;
z-index: -1;
}

View File

@ -1,14 +1,21 @@
import logging import logging
from typing import List from typing import List
from fastapi import Body, Depends, FastAPI, HTTPException, Request, status from fastapi import Body, Depends, FastAPI, HTTPException, Request, Response, status
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from backend import actions from backend import actions
from backend.actions import ActionError from backend.actions import ActionError
from backend.auth import authenticate_admin_user, get_current_admin, issue_token, list_manageable_users from backend.auth import (
authenticate_admin_user,
get_current_admin,
is_authorized_admin,
issue_token,
list_manageable_users,
)
from backend.models import ActionRequest, ActionResponse, LoginRequest, LoginResponse, UserStatus from backend.models import ActionRequest, ActionResponse, LoginRequest, LoginResponse, UserStatus
from backend.oidc import OIDCClient, OIDCError
from backend.settings import Settings, get_settings from backend.settings import Settings, get_settings
logging.basicConfig( logging.basicConfig(
@ -21,6 +28,20 @@ app = FastAPI(title="Safe Kiddo Daemon", version="1.0.0")
templates = Jinja2Templates(directory="backend/templates") templates = Jinja2Templates(directory="backend/templates")
def get_oidc_client(settings: Settings = Depends(get_settings)) -> OIDCClient:
if settings.auth_mode != "oidc":
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="OIDC auth not enabled",
)
try:
return OIDCClient(settings)
except OIDCError as exc:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=str(exc)
) from exc
def validate_user(username: str, settings: Settings = Depends(get_settings)) -> str: def validate_user(username: str, settings: Settings = Depends(get_settings)) -> str:
allowed = set(list_manageable_users(settings)) allowed = set(list_manageable_users(settings))
if username not in allowed: if username not in allowed:
@ -33,13 +54,84 @@ def health(settings: Settings = Depends(get_settings)) -> dict:
return {"status": "ok", "dry_run": settings.dry_run} return {"status": "ok", "dry_run": settings.dry_run}
@app.get("/me")
def whoami(
current_user: str = Depends(get_current_admin),
settings: Settings = Depends(get_settings),
) -> dict:
return {"user": current_user, "auth_mode": settings.auth_mode}
@app.post("/login", response_model=LoginResponse) @app.post("/login", response_model=LoginResponse)
def login(payload: LoginRequest, settings: Settings = Depends(get_settings)) -> LoginResponse: def login(
payload: LoginRequest,
response: Response,
settings: Settings = Depends(get_settings),
) -> LoginResponse:
authenticate_admin_user(payload.username, payload.password, settings) authenticate_admin_user(payload.username, payload.password, settings)
token = issue_token(payload.username, settings) token = issue_token(payload.username, settings)
response.set_cookie(
settings.session_cookie_name,
token,
max_age=settings.token_ttl_seconds,
httponly=True,
secure=settings.session_cookie_secure,
samesite="lax",
)
return LoginResponse(token=token, expires_in=settings.token_ttl_seconds) return LoginResponse(token=token, expires_in=settings.token_ttl_seconds)
@app.get("/login/oidc/start")
def oidc_start(
settings: Settings = Depends(get_settings),
oidc: OIDCClient = Depends(get_oidc_client),
):
state = oidc.build_state_token()
redirect = RedirectResponse(url=oidc.authorization_url(state))
redirect.set_cookie(
settings.oidc_state_cookie_name,
state,
max_age=300,
httponly=True,
secure=settings.session_cookie_secure,
samesite="lax",
)
return redirect
@app.get("/login/oidc/callback")
def oidc_callback(
request: Request,
code: str,
state: str,
settings: Settings = Depends(get_settings),
oidc: OIDCClient = Depends(get_oidc_client),
):
stored_state = request.cookies.get(settings.oidc_state_cookie_name, "")
if not oidc.is_state_valid(state, stored_state):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid OIDC state")
claims = oidc.exchange_code_for_claims(code)
username = oidc.extract_username(claims)
if not username:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Missing username claim")
if not is_authorized_admin(username, settings):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User not authorized to log in")
token = issue_token(username, settings)
redirect = RedirectResponse(url="/")
redirect.set_cookie(
settings.session_cookie_name,
token,
max_age=settings.token_ttl_seconds,
httponly=True,
secure=settings.session_cookie_secure,
samesite="lax",
)
redirect.delete_cookie(settings.oidc_state_cookie_name)
return redirect
@app.get("/users", response_model=List[UserStatus], dependencies=[Depends(get_current_admin)]) @app.get("/users", response_model=List[UserStatus], dependencies=[Depends(get_current_admin)])
def users(settings: Settings = Depends(get_settings)) -> List[UserStatus]: def users(settings: Settings = Depends(get_settings)) -> List[UserStatus]:
logged_in = set(actions.list_logged_in_users()) logged_in = set(actions.list_logged_in_users())

View File

@ -24,6 +24,11 @@ def _is_member_of(username: str, groups: Set[str]) -> bool:
def is_authorized_admin(username: str, settings: Settings) -> bool: def is_authorized_admin(username: str, settings: Settings) -> bool:
if settings.auth_mode == "oidc":
allowed_users = set(settings.auth_allowed_users)
if allowed_users and username not in allowed_users:
return False
return True
# UID 0 always allowed # UID 0 always allowed
try: try:
entry = pwd.getpwnam(username) entry = pwd.getpwnam(username)
@ -41,6 +46,11 @@ def is_authorized_admin(username: str, settings: Settings) -> bool:
def authenticate_admin_user(username: str, password: str, settings: Settings) -> None: def authenticate_admin_user(username: str, password: str, settings: Settings) -> None:
if settings.auth_mode != "pam":
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Password login disabled; OIDC is configured",
)
if not is_authorized_admin(username, settings): if not is_authorized_admin(username, settings):
raise HTTPException( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, status_code=status.HTTP_403_FORBIDDEN,
@ -82,9 +92,13 @@ def get_current_admin(
settings: Settings = Depends(get_settings), settings: Settings = Depends(get_settings),
) -> str: ) -> str:
auth_header = request.headers.get("authorization") auth_header = request.headers.get("authorization")
if not auth_header or not auth_header.lower().startswith("bearer "): token = None
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Missing token") if auth_header and auth_header.lower().startswith("bearer "):
token = auth_header.split(" ", 1)[1].strip() token = auth_header.split(" ", 1)[1].strip()
if not token:
token = request.cookies.get(settings.session_cookie_name)
if not token:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Missing token")
return decode_token(token, settings) return decode_token(token, settings)

140
backend/oidc.py Normal file
View File

@ -0,0 +1,140 @@
import hashlib
import hmac
import secrets
import urllib.parse
from dataclasses import dataclass
from functools import lru_cache
from typing import Any, Dict, Optional
import httpx
import jwt
from fastapi import HTTPException, status
from jwt import PyJWKClient
from backend.settings import Settings
@dataclass
class OIDCConfig:
issuer: str
authorization_endpoint: str
token_endpoint: str
jwks_uri: str
userinfo_endpoint: Optional[str] = None
class OIDCError(Exception):
"""Raised when the OIDC provider cannot be used."""
@lru_cache(maxsize=1)
def _load_provider_config(issuer: str) -> OIDCConfig:
discovery_url = urllib.parse.urljoin(issuer.rstrip("/") + "/", ".well-known/openid-configuration")
try:
with httpx.Client(timeout=5.0) as client:
resp = client.get(discovery_url)
resp.raise_for_status()
except httpx.HTTPError as exc: # pragma: no cover - network failure branch
raise OIDCError(f"Failed to load discovery document: {exc}") from exc
data = resp.json()
required = ("issuer", "authorization_endpoint", "token_endpoint", "jwks_uri")
if not all(key in data for key in required):
raise OIDCError("Discovery document missing required fields")
return OIDCConfig(
issuer=data["issuer"],
authorization_endpoint=data["authorization_endpoint"],
token_endpoint=data["token_endpoint"],
jwks_uri=data["jwks_uri"],
userinfo_endpoint=data.get("userinfo_endpoint"),
)
class OIDCClient:
def __init__(self, settings: Settings) -> None:
self.settings = settings
if not settings.oidc_issuer:
raise OIDCError("SKD_OIDC_ISSUER not configured")
if not settings.oidc_client_id or not settings.oidc_client_secret:
raise OIDCError("SKD_OIDC_CLIENT_ID/SECRET must be set")
self.config = _load_provider_config(settings.oidc_issuer)
self.jwk_client = PyJWKClient(self.config.jwks_uri)
def build_state_token(self) -> str:
raw = secrets.token_urlsafe(24)
sig = hmac.new(self.settings.auth_secret.encode(), raw.encode(), hashlib.sha256).hexdigest()
return f"{raw}.{sig}"
def is_state_valid(self, provided: str, stored: str) -> bool:
if not provided or not stored or provided != stored:
return False
try:
raw, sig = provided.split(".", 1)
except ValueError:
return False
expected = hmac.new(self.settings.auth_secret.encode(), raw.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(sig, expected)
def authorization_url(self, state: str) -> str:
params = {
"client_id": self.settings.oidc_client_id,
"redirect_uri": self.settings.oidc_redirect_uri,
"response_type": "code",
"scope": self.settings.oidc_scopes,
"state": state,
}
return f"{self.config.authorization_endpoint}?{urllib.parse.urlencode(params)}"
def exchange_code_for_claims(self, code: str) -> Dict[str, Any]:
payload = {
"grant_type": "authorization_code",
"code": code,
"redirect_uri": self.settings.oidc_redirect_uri,
"client_id": self.settings.oidc_client_id,
"client_secret": self.settings.oidc_client_secret,
}
headers = {"Content-Type": "application/x-www-form-urlencoded"}
try:
with httpx.Client(timeout=10.0) as client:
resp = client.post(self.config.token_endpoint, data=payload, headers=headers)
except httpx.HTTPError as exc: # pragma: no cover - network failure branch
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail=f"OIDC token request failed: {exc}",
) from exc
if resp.status_code != status.HTTP_200_OK:
detail = resp.text or "token request failed"
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=f"OIDC token exchange failed: {detail}",
)
token_response = resp.json()
id_token = token_response.get("id_token")
if not id_token:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="OIDC response missing id_token",
)
signing_key = self.jwk_client.get_signing_key_from_jwt(id_token).key
try:
claims = jwt.decode(
id_token,
signing_key,
algorithms=["RS256"],
audience=self.settings.oidc_client_id,
issuer=self.config.issuer,
)
except jwt.PyJWTError as exc:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=f"Invalid ID token: {exc}",
) from exc
return claims
@staticmethod
def extract_username(claims: Dict[str, Any]) -> Optional[str]:
return claims.get("preferred_username") or claims.get("email") or claims.get("sub")

View File

@ -5,3 +5,5 @@ jinja2==3.1.4
PyJWT==2.9.0 PyJWT==2.9.0
python-pam==2.0.2 python-pam==2.0.2
six==1.16.0 six==1.16.0
httpx==0.27.2
cryptography==43.0.1

View File

@ -7,6 +7,9 @@ class Settings:
"""Application settings loaded from environment.""" """Application settings loaded from environment."""
def __init__(self) -> None: def __init__(self) -> None:
self.auth_mode: str = os.getenv("SKD_AUTH_MODE", "pam").lower()
if self.auth_mode not in ("pam", "oidc"):
self.auth_mode = "pam"
self.allowed_users: List[str] = self._parse_list(os.getenv("SKD_ALLOWED_USERS", "")) self.allowed_users: List[str] = self._parse_list(os.getenv("SKD_ALLOWED_USERS", ""))
self.auth_secret: str = os.getenv("SKD_AUTH_SECRET", "change-me-secret") self.auth_secret: str = os.getenv("SKD_AUTH_SECRET", "change-me-secret")
self.token_ttl_seconds: int = int(os.getenv("SKD_TOKEN_TTL_SECONDS", "900")) self.token_ttl_seconds: int = int(os.getenv("SKD_TOKEN_TTL_SECONDS", "900"))
@ -15,6 +18,20 @@ class Settings:
os.getenv("SKD_AUTH_ALLOWED_GROUPS", "sudo") os.getenv("SKD_AUTH_ALLOWED_GROUPS", "sudo")
) )
self.auth_pam_service: str = os.getenv("SKD_AUTH_PAM_SERVICE", "login") self.auth_pam_service: str = os.getenv("SKD_AUTH_PAM_SERVICE", "login")
self.oidc_issuer: str = os.getenv("SKD_OIDC_ISSUER", "")
self.oidc_client_id: str = os.getenv("SKD_OIDC_CLIENT_ID", "")
self.oidc_client_secret: str = os.getenv("SKD_OIDC_CLIENT_SECRET", "")
self.oidc_redirect_uri: str = os.getenv(
"SKD_OIDC_REDIRECT_URI", "http://localhost:8000/login/oidc/callback"
)
self.oidc_scopes: str = os.getenv("SKD_OIDC_SCOPES", "openid profile email")
self.session_cookie_name: str = os.getenv("SKD_SESSION_COOKIE_NAME", "skd_session")
self.session_cookie_secure: bool = (
os.getenv("SKD_SESSION_COOKIE_SECURE", "false").lower() == "true"
)
self.oidc_state_cookie_name: str = os.getenv(
"SKD_OIDC_STATE_COOKIE_NAME", "skd_oidc_state"
)
self.default_countdown: int = int(os.getenv("SKD_DEFAULT_COUNTDOWN", "60")) self.default_countdown: int = int(os.getenv("SKD_DEFAULT_COUNTDOWN", "60"))
self.default_sound: bool = os.getenv("SKD_DEFAULT_SOUND", "false").lower() == "true" self.default_sound: bool = os.getenv("SKD_DEFAULT_SOUND", "false").lower() == "true"
self.notify_timeout: int = int(os.getenv("SKD_NOTIFY_TIMEOUT", "5")) self.notify_timeout: int = int(os.getenv("SKD_NOTIFY_TIMEOUT", "5"))

View File

@ -19,6 +19,10 @@
<section> <section>
<h3>Login (nur Root-User)</h3> <h3>Login (nur Root-User)</h3>
<p>Bevorzugt OIDC nutzen, falls konfiguriert. Die Anmeldung öffnet den Identity Provider und setzt eine Session-Cookie.</p>
<button id="oidcLogin" type="button">Login via OIDC</button>
<hr />
<p>Lokale Anmeldung (PAM) nur falls OIDC nicht verfügbar:</p>
<form id="loginForm"> <form id="loginForm">
<div class="grid"> <div class="grid">
<div> <div>
@ -91,7 +95,7 @@
currentToken = token; currentToken = token;
if (token) { if (token) {
sessionStorage.setItem(tokenKey, token); sessionStorage.setItem(tokenKey, token);
loginStatus.textContent = 'Angemeldet'; loginStatus.textContent = 'Angemeldet (Token gespeichert)';
} else { } else {
sessionStorage.removeItem(tokenKey); sessionStorage.removeItem(tokenKey);
loginStatus.textContent = 'Nicht angemeldet'; loginStatus.textContent = 'Nicht angemeldet';
@ -107,11 +111,31 @@
async function api(path, options = {}) { async function api(path, options = {}) {
const headers = { ...authHeaders(), ...(options.headers || {}) }; const headers = { ...authHeaders(), ...(options.headers || {}) };
const res = await fetch(path, { ...options, headers }); const res = await fetch(path, { ...options, headers, credentials: 'same-origin' });
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`); if (!res.ok) {
const text = await res.text();
const error = new Error(text || `${res.status} ${res.statusText}`);
error.status = res.status;
throw error;
}
return res.json(); return res.json();
} }
async function checkSession() {
try {
const data = await api('/me');
loginStatus.textContent = `Angemeldet als ${data.user} (${data.auth_mode})`;
return true;
} catch (err) {
if (err.status === 401) {
loginStatus.textContent = 'Nicht angemeldet';
} else {
loginStatus.textContent = `Session-Check fehlgeschlagen: ${err.message}`;
}
return false;
}
}
async function refreshUsers() { async function refreshUsers() {
statusDiv.textContent = 'Lade...'; statusDiv.textContent = 'Lade...';
try { try {
@ -149,6 +173,10 @@
} }
}); });
document.getElementById('oidcLogin').addEventListener('click', () => {
window.location.href = '/login/oidc/start';
});
document.getElementById('refreshBtn').addEventListener('click', refreshUsers); document.getElementById('refreshBtn').addEventListener('click', refreshUsers);
document.getElementById('actionForm').addEventListener('submit', async (e) => { document.getElementById('actionForm').addEventListener('submit', async (e) => {
@ -176,6 +204,8 @@
resultDiv.textContent = `Fehler: ${err.message}`; resultDiv.textContent = `Fehler: ${err.message}`;
} }
}); });
checkSession();
</script> </script>
</body> </body>
</html> </html>

View File

@ -3,9 +3,19 @@
SKD_ALLOWED_USERS=child1,child2 SKD_ALLOWED_USERS=child1,child2
SKD_AUTH_SECRET=change-me-secret SKD_AUTH_SECRET=change-me-secret
SKD_TOKEN_TTL_SECONDS=900 SKD_TOKEN_TTL_SECONDS=900
# Auth mode: pam (default) or oidc
SKD_AUTH_MODE=pam
SKD_AUTH_ALLOWED_USERS= SKD_AUTH_ALLOWED_USERS=
SKD_AUTH_ALLOWED_GROUPS=sudo SKD_AUTH_ALLOWED_GROUPS=sudo
SKD_AUTH_PAM_SERVICE=login SKD_AUTH_PAM_SERVICE=login
SKD_OIDC_ISSUER=
SKD_OIDC_CLIENT_ID=
SKD_OIDC_CLIENT_SECRET=
SKD_OIDC_REDIRECT_URI=http://localhost:8000/login/oidc/callback
SKD_OIDC_SCOPES=openid profile email
SKD_SESSION_COOKIE_NAME=skd_session
SKD_SESSION_COOKIE_SECURE=false
SKD_OIDC_STATE_COOKIE_NAME=skd_oidc_state
SKD_DEFAULT_COUNTDOWN=60 SKD_DEFAULT_COUNTDOWN=60
SKD_DEFAULT_SOUND=false SKD_DEFAULT_SOUND=false
SKD_NOTIFY_TIMEOUT=5 SKD_NOTIFY_TIMEOUT=5

View File

@ -0,0 +1,43 @@
ID: AGENTS_000001 | Version: 0.1.0 | Status: Draft
By: Codex (GPT-5)
# Repository Guidelines
This repository is the management layer for the Sound Architect project. It is documentation-first and centers on requirements, status tracking, and process prompts. Use it to plan and document work before implementation.
## Project Structure & Module Organization
- `onboarding.md` and `project-setup.md` define the SOP and initial setup.
- `PROJECT_STATUS.md` and `PROJECT_STATUS_TEMPLATE.md` track vision, focus, and backlog.
- `*.promt` files are process prompts (requirements, refactoring, architecture, etc.).
- `feedback/session-feedback.md` holds feedback notes.
- The setup guide describes a separate source layout (`src/`, `tests/`, `docs/architecture/`) that may live in a sibling repository. If those folders are added here, keep them aligned with the guide.
## Build, Test, and Development Commands
There are no build or runtime commands in this repo today. Typical work is editing Markdown and prompt files. If you introduce automation later, document it here with concise examples (e.g., `make lint`, `npm test`).
## Coding Style & Naming Conventions
- Keep files in Markdown with clear headings and short, direct paragraphs.
- Follow the ID header rule from onboarding for new requirement documents: `ID: <ID> | Version: <VERSION> | Status: Draft/Review/Final`.
- Use the naming patterns described in the SOP for requirements: `STD_EPIC_001`, `STD_STORY_001`, `STD_TASK_001`, `BUG_NNNNNN`.
- Prefer ASCII; avoid emojis in new technical docs unless the file already uses them.
## Testing Guidelines
No testing framework is defined in this repository. If you add code or automation, include a minimal test command and document it in this section.
## Commit & Pull Request Guidelines
There is no Git history in this repository, so commit conventions are not established. If you add Git, align commit messages with the changelog legend in `project-setup.md` (e.g., `🏗️ Planning: ...`, `📝 Req: ...`). For pull requests:
- Describe the change and link related IDs (story/bug/task).
- Update `PROJECT_STATUS.md` and the changelog if those files are part of your workflow.
- Note any new files, templates, or schema changes.
## Documentation Workflow Notes
- Document-first is mandatory: capture requirements before implementation.
- Keep `PROJECT_STATUS.md` aligned with its template and current focus.
- If a `VERSION` file exists, treat it as the single source of truth for versioning.

View File

@ -0,0 +1,90 @@
ID: STATUS_000001 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# Projekt-Status
## Vision
Sicheres, remote steuerbares System zum Sperren/Entsperren lokaler Nutzerkonten.
## Aktuelle Phase
✅ Stabilization
## Aktueller Fokus
1. Dokumentierter Ist-Zustand der Module.
2. Pflege der Anforderungen bei neuen Features.
3. Doku und Ops-Automation aktuell halten.
## Projekt-Tagebuch (Kurz, optional)
| Datum | Typ | Beschreibung |
|---|---|---|
| 28.12.2025 | 🏗️ Planning | Anforderungen als Epics und Stories dokumentiert. |
## Epic-Backlog (Uebersicht)
### EPIC_000001: Legacy CLI Account Control (sk.sh)
- [x] US_000001: Nutzerkonto per CLI deaktivieren
- [x] TASK_000001: Disable user countdown
- [x] US_000002: Nutzerkonto per CLI aktivieren
- [x] TASK_000002: Enable user account
### EPIC_000002: Backend API Service
- [x] US_000003: Health-Status abfragen
- [x] TASK_000003: Health response payload
- [x] US_000004: Verfuegbare Nutzer auflisten
- [x] TASK_000004: List users status
- [x] US_000005: Nutzer per API deaktivieren
- [x] TASK_000005: API disable action
- [x] US_000006: Nutzer per API aktivieren
- [x] TASK_000006: API enable action
- [x] US_000021: Konfiguration per ENV steuern
- [x] TASK_000021: ENV settings defaults
### EPIC_000003: Authentication & Sessions
- [x] US_000007: PAM-Login mit Token
- [x] TASK_000007: PAM login token
- [x] US_000008: OIDC-Login Flow
- [x] TASK_000008: OIDC auth callback
- [x] US_000009: Autorisierung und /me-Identitaet
- [x] TASK_000009: Authorization /me gate
### EPIC_000004: Web UI
- [x] US_000010: Index-Seite ausliefern
- [x] TASK_000010: Serve UI template
- [x] US_000022: Web-UI Aktionen ausfuehren
- [x] TASK_000022: UI login and actions
- [ ] US_000024: Watchtower Theme fuer Web-UI (zurueckgestellt)
- [ ] TASK_000024: Apply Watchtower theme (zurueckgestellt)
### EPIC_000005: Automation Scripts
- [x] US_000011: Virtualenv und Abhaengigkeiten erstellen
- [x] TASK_000011: Provision venv deps
- [x] US_000012: Service lokal starten
- [x] TASK_000012: Run uvicorn service
- [x] US_000013: Service installieren
- [x] TASK_000013: Install service setup
- [x] US_000014: Service aktualisieren
- [x] TASK_000014: Update service refresh
- [x] US_000015: Remote-Deployment durchfuehren
- [x] TASK_000015: Remote deploy package
- [x] US_000016: OIDC-Client registrieren
- [x] TASK_000016: OIDC client register
- [x] US_000020: Makefile-Automation bereitstellen
- [x] TASK_000020: Makefile ops targets
### EPIC_000006: Systemd & Deployment Artifacts
- [x] US_000017: Systemd-Unit im Repo
- [x] TASK_000017: Systemd unit template
- [x] US_000018: Konfigurations-Templates verfuegbar
- [x] TASK_000018: Config templates ready
- [x] US_000019: Deployment-Archiv vorhanden
- [x] TASK_000019: Deployment zip artifact
### EPIC_000007: Documentation & Runbook
- [x] US_000023: Runbook und Security-Hinweise dokumentieren
- [x] TASK_000023: README runbook notes
## Offene Risiken / Abhaengigkeiten
- Betrieb erfordert Root/sudo und lokale System-Tools (notify-send, sound player, uvicorn).
## Naechste Schritte
- Anforderungen beim naechsten Feature-Start erweitern.
- Tests fuer kritische Pfade evaluieren.

View File

@ -0,0 +1,36 @@
ID: PROJECT_STATUS_TEMPLATE | Version: 0.1.0 | Status: Draft
By: Codex (GPT-5)
# 📊 Projekt-Status (Template)
## Vision
Ein kurzer Satz, der das Ziel des Projekts beschreibt.
## Aktuelle Phase
Ein einzelnes Label, z.B. 🏗️ Planning / ⚙️ Implementation / ✅ Stabilization.
## Aktueller Fokus
1. Wichtigste Aufgabe oder Epic.
2. Zweiter Fokuspunkt.
3. Dritter Fokuspunkt (optional).
## Projekt-Tagebuch (Kurz, optional)
Optional als schneller Ueberblick. Das vollstaendige Logbuch liegt in CHANGELOG.md.
| Datum | Typ | Beschreibung |
|---|---|---|
| DD.MM.YYYY | 📝 Req | Kurzer Eintrag. |
| DD.MM.YYYY | ⚙️ Code | Kurzer Eintrag. |
## Epic-Backlog (Uebersicht)
### EPIC_000001: <Titel>
- [ ] US_000001: <Kurzbeschreibung>
- [ ] US_000002: <Kurzbeschreibung>
### EPIC_000002: <Titel>
- [x] US_000003: <Kurzbeschreibung>
## Offene Risiken / Abhaengigkeiten
- Kurzer Punkt, z.B. externer Dienst, fehlende Zugriffsrechte, Tests fehlen.
## Naechste Schritte
- Konkrete, kurzfristige Aktionen (2-5 Punkte).

View File

@ -0,0 +1,62 @@
# Theme: Watchtower (Sci-Fi / Dark)
Dieses Theme basiert auf der `minecraft-watchtower` UI. Es ist ein **High-Contrast Dark Mode** mit Neon-Akzenten, ausgelegt auf technische Dashboards und "Immersive UIs".
## Verwendung
Binde statt `tokens.css` die Datei `tokens_watchtower.css` ein und ergänze `theme_watchtower.css`.
```html
<link rel="stylesheet" href="design/tokens_watchtower.css">
<link rel="stylesheet" href="design/components.css"> <!-- Standard Components -->
<link rel="stylesheet" href="design/theme_watchtower.css"> <!-- Theme Overrides -->
```
Zusätzlich sollte die Klasse `.bg-noise` direkt nach dem `<body>` Tag eingefügt werden:
```html
<body>
<div class="bg-noise"></div>
...
</body>
```
---
## TUI (Terminal User Interface) Adaption
Da dieses Design oft in CLI-Tools oder TUIs verwendet wird, gelten folgende Mappings für Terminals (16/256 Farben).
### Farb-Palette
| Rolle | CSS Variable | ANSI Color (16) | ANSI Code | Hex Fallback |
| :--- | :--- | :--- | :--- | :--- |
| **Background** | `--bg-dark` | Black | `\e[40m` | `#0b0f14` |
| **Text** | `--text` | White (Bright) | `\e[97m` | `#e6edf5` |
| **Muted Text** | `--text-dim` | Cyan (Dim) | `\e[36m` | `#9aa7b8` |
| **Accent** | `--accent` | Cyan (Bright) | `\e[96m` | `#00e08f` |
| **Success** | `--success` | Green (Bright) | `\e[92m` | `#00e08f` (Teal) |
| **Warning** | `--warning` | Yellow (Bright) | `\e[93m` | `#ffb454` |
| **Error** | `--danger` | Red (Bright) | `\e[91m` | `#ff5e5e` |
### Block-Elemente & Rahmen
Für TUI-Rahmen nutzen wir "Heavy" oder "Double" Lines, um den technischen Look zu imitieren.
* **Box Border:** `═` (Double Horizontal), `║` (Double Vertical), `╔ ╗ ╚ ╝` (Corners)
* **Progress Bar:** `█` (Full Block) für den Füllstand, `░` (Light Shade) für den Hintergrund.
### Beispiel (Charm / Bubble Tea - Go)
```go
var (
ColorAccent = lipgloss.Color("#00e08f")
ColorBg = lipgloss.Color("#0b0f14")
ColorText = lipgloss.Color("#e6edf5")
StyleCard = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(ColorAccent).
Padding(1, 2).
Background(ColorBg)
)
```

View File

@ -0,0 +1,86 @@
# OIDC Integration: OICD (IdP) + Kiddo (Clients)
## Herangehensweise und Gedankengang
Ziel war es, beide Projekte so zu verbinden, dass Kiddo OICD als OIDC-Provider nutzt, ohne Codeaenderungen vorzunehmen. Wir haben die vorhandenen Konfigurationspfade, DCR-Faehigkeiten und OIDC-Endpunkte geprueft und daraus die minimalen, operationalen Schritte abgeleitet.
Leitfragen, die wir dabei beantwortet haben:
- Welche Konfiguration erwartet Kiddo fuer OIDC (Issuer, Client, Redirect, Cookies)?
- Welche OIDC-Funktionen liefert OICD (Discovery, JWKS, DCR, Admin-UI)?
- Wo liegen die harten OIDC-Anforderungen (Issuer-Match, exakte Redirect-URIs, TLS-Vertrauen)?
- Wie kann die Registrierung skalieren, wenn Geraete und Redirects erst spaeter feststehen?
### OICD (IdP) - Herangehensweise
Wir haben in OICD geprueft, welche OIDC-Endpunkte vorhanden sind (Discovery, JWKS, Token, DCR) und wie der Issuer erzeugt wird. Entscheidend ist, dass der Issuer exakt der externen URL entspricht, unter der OICD erreichbar ist. Zudem gibt es eine Admin-Funktion zur Erzeugung von Initial-Access-Tokens, die den DCR-Flow ermoeglichen. Daraus folgt: Stabiler Issuer (prod/dev), TLS trust, und ein standardisierter Weg zur Token-Erzeugung fuer DCR.
### Kiddo (Client) - Herangehensweise
Wir haben in Kiddo geprueft, welche Umgebungsvariablen fuer OIDC benoetigt werden und wie die Claims interpretiert werden. Kiddo validiert ID-Tokens gegen den Issuer und JWKS und benoetigt exakte Redirect-URIs. Da die Device-Hosts dynamisch sind, ist DCR der beste Weg, pro Geraet eigene Clients zu registrieren, sobald die finale URL bekannt ist. Daraus folgen: pro Geraet DCR, danach Env-Setup, optional Allowlist fuer OIDC-User.
Aus diesen Punkten ergab sich der Weg: OICD stellt stabile Issuer-URLs bereit, Redirect-URIs muessen konkret registriert werden, und DCR (mit Initial-Access-Token) ist der beste Weg, um pro Geraet eigene Clients dynamisch zu erzeugen. Daraus wurden die Stories, Akzeptanzkriterien und das Provisioning-Kommando abgeleitet.
## Epic: OIDC-Login fuer Kiddo (Client-Seite)
### Story 1: Zentrales Provisioning registriert pro Geraet einen OIDC-Client
Als Provisioner moechte ich pro Geraet einen OIDC-Client via DCR anlegen, damit jedes Geraet einen eigenen Client-ID/Secret hat.
Acceptance Criteria:
- DCR-Call mit `SKD_OIDC_ISSUER`, `SKD_OIDC_REDIRECT_URI` und `OIDC_INITIAL_ACCESS_TOKEN` erzeugt `client_id` und `client_secret`.
- Redirect-URI ist exakt `https://<device-host>[:port]/login/oidc/callback` (keine Wildcards).
Referenzen:
- `kiddo/scripts/register_oidc_client.sh`
### Story 2: Kiddo-Geraet ist per OIDC konfiguriert
Als Geraetebetreiber moechte ich ein Kiddo-Geraet so konfigurieren, dass es sich ueber OICD authentifiziert.
Acceptance Criteria:
- `SKD_AUTH_MODE=oidc` ist gesetzt.
- `SKD_OIDC_ISSUER`, `SKD_OIDC_CLIENT_ID`, `SKD_OIDC_CLIENT_SECRET`, `SKD_OIDC_REDIRECT_URI` sind gesetzt.
- Bei HTTPS ist `SKD_SESSION_COOKIE_SECURE=true`.
Referenzen:
- `kiddo/env.example`
- `kiddo/README.md`
- `kiddo/backend/settings.py`
### Story 3: Zugriffskontrolle auf OIDC-Login
Als Betreiber moechte ich steuern, welche Benutzer sich via OIDC anmelden duerfen.
Acceptance Criteria:
- `SKD_AUTH_ALLOWED_USERS` schraenkt Zugriff ein, basierend auf `preferred_username` oder `email` oder `sub` aus dem ID-Token.
Referenzen:
- `kiddo/backend/oidc.py`
- `kiddo/backend/auth.py`
### Story 4: Betrieb mit wechselnden Geraeten (DCR-Flow)
Als Betreiber moechte ich neue Geraete spaeter onboarden koennen, ohne OICD manuell zu konfigurieren.
Acceptance Criteria:
- DCR-Prozess ist dokumentiert und kann pro Geraet wiederholt werden.
- Bei Host/Port-Aenderung erfolgt Neuregistrierung (neuer Client/Secret).
Referenzen:
- `kiddo/scripts/register_oidc_client.sh`
## DCR Kommando (zentral, pro Geraet)
Prod:
```bash
export SKD_OIDC_ISSUER="https://auth.wlkns.org"
export SKD_OIDC_REDIRECT_URI="https://<device-host>[:port]/login/oidc/callback"
export OIDC_INITIAL_ACCESS_TOKEN="<initial-access-token>"
/home/stephan/applications/wlkns/kiddo/scripts/register_oidc_client.sh
```
Dev:
```bash
export SKD_OIDC_ISSUER="https://dev.wlkns.org"
export SKD_OIDC_REDIRECT_URI="https://<device-host>[:port]/login/oidc/callback"
export OIDC_INITIAL_ACCESS_TOKEN="<initial-access-token>"
/home/stephan/applications/wlkns/kiddo/scripts/register_oidc_client.sh
```
Hinweise:
- Redirect-URIs muessen exakt registriert sein (keine Wildcards).
- Bei Host/Port-Aenderung: neu registrieren und neue Client-Credentials setzen.

View File

@ -0,0 +1,135 @@
/*
wlkns-dev-standards
Component Theme: Watchtower
Adapts standard components to the Sci-Fi/Dark aesthetic.
*/
/* =========================
Inputs & Forms
========================= */
input, select, textarea {
background: rgba(10, 14, 20, 0.6) !important;
border-color: rgba(255, 255, 255, 0.15) !important;
color: var(--color-text) !important;
transition: all 0.2s ease;
}
input:focus, select:focus, textarea:focus {
border-color: var(--color-accent) !important;
box-shadow: 0 0 15px var(--wt-glow);
}
/* =========================
Buttons
========================= */
.button {
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 600;
border-radius: 999px !important; /* Pill shape */
}
.button.primary {
box-shadow: 0 0 15px var(--wt-glow);
color: #081015; /* Dark text on bright accent */
}
.button.secondary {
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.2);
color: var(--color-text);
}
.button.secondary:hover {
border-color: var(--color-text);
background: rgba(255,255,255,0.05);
}
.button.danger {
box-shadow: 0 0 10px rgba(255, 94, 94, 0.4);
color: #1b0d0d;
}
/* =========================
Tables
========================= */
table {
border-collapse: separate;
border-spacing: 0 4px; /* Space between rows */
}
thead th {
border-bottom: 1px solid var(--color-accent);
text-transform: uppercase;
letter-spacing: 0.1em;
font-size: 0.75rem;
color: var(--color-accent);
background: transparent;
}
tbody tr {
background: rgba(255, 255, 255, 0.03);
transition: transform 0.2s;
}
tbody tr:hover {
background: rgba(255, 255, 255, 0.06);
transform: scale(1.01);
}
tbody td {
border: none;
}
tbody td:first-child {
border-top-left-radius: 8px;
border-bottom-left-radius: 8px;
}
tbody td:last-child {
border-top-right-radius: 8px;
border-bottom-right-radius: 8px;
}
/* =========================
Alerts & Toasts
========================= */
.alert, .toast {
background: rgba(10, 14, 20, 0.95);
backdrop-filter: blur(4px);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
}
.alert.success, .toast.success {
border-color: var(--color-success);
box-shadow: 0 0 10px rgba(0, 224, 143, 0.2);
color: var(--color-text);
}
.alert.warning, .toast.warning {
border-color: var(--color-warning);
color: var(--color-text);
}
.alert.error, .toast.error {
border-color: var(--color-error);
box-shadow: 0 0 10px rgba(255, 94, 94, 0.2);
color: var(--color-text);
}
/* =========================
Pagination
========================= */
.pagination .page {
background: transparent;
border: 1px solid rgba(255,255,255,0.1);
color: var(--color-text-muted);
}
.pagination .page.active {
background: var(--color-accent);
color: #081015;
border-color: var(--color-accent);
box-shadow: 0 0 10px var(--wt-glow);
}

View File

@ -0,0 +1,69 @@
/*
wlkns-dev-standards
Theme: Watchtower (Dark Glow)
Version: v1.0
Based on: minecraft-watchtower UI
*/
:root {
/* =========================
Core Palette (Watchtower)
========================= */
--wt-bg-dark: #0b0f14;
--wt-bg-panel: #10151d;
--wt-accent: #00e08f;
--wt-warning: #ffb454;
--wt-danger: #ff5e5e;
--wt-text: #e6edf5;
--wt-text-dim: #9aa7b8;
--wt-glow: rgba(0, 224, 143, 0.35);
/* =========================
Mapping -> Standard Tokens
========================= */
/* Farben – Identity & Status */
--color-primary: #1a2230; /* Deep Blue/Grey from Gradient */
--color-accent: var(--wt-accent);
--color-success: var(--wt-accent); /* Watchtower uses accent as success */
--color-warning: var(--wt-warning);
--color-error: var(--wt-danger);
/* Farben – Neutrals (Dark Mode Override) */
--color-bg: var(--wt-bg-dark);
--color-surface: var(--wt-bg-panel);
--color-border: rgba(255, 255, 255, 0.08); /* Subtle white border */
--color-text: var(--wt-text);
--color-text-muted: var(--wt-text-dim);
/* Typografie (Optional: Falls Space Grotesk geladen wird) */
--font-ui: 'Space Grotesk', 'Inter', system-ui, sans-serif;
--font-mono: 'JetBrains Mono', monospace;
/* Radius & Spacing */
--radius-sm: 4px;
--radius-md: 12px; /* Watchtower uses larger radii */
--radius-lg: 16px;
/* Shadows becomes Glows in this theme */
--shadow-sm: 0 0 10px rgba(0,0,0,0.5);
--shadow-glow: 0 0 15px var(--wt-glow);
}
/* Global Theme Overrides */
body {
background: radial-gradient(circle at top, #1a2230 0%, #0b0f14 55%), linear-gradient(135deg, #0b0f14, #101623 60%);
min-height: 100vh;
}
/* Noise Texture helper class */
.bg-noise {
position: fixed;
inset: 0;
pointer-events: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160' viewBox='0 0 160 160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.04'/%3E%3C/svg%3E");
mix-blend-mode: soft-light;
z-index: -1;
}

View File

@ -0,0 +1,113 @@
ID: SOP_000001 | Version: 0.1.0 | Status: Draft
By: Codex (GPT-5)
# Onboarding: Arbeitsweise im Sound Architect Projekt
Willkommen im Team! Dieses Dokument erklärt, wie wir im Sound Architect Projekt arbeiten, Features planen und die Dokumentation pflegen. Wir folgen einer strikten SOP (Standard Operating Procedure) [cite: 1.1].
## Grundprinzip: Documentation-First Development
Wichtig: Wir dokumentieren bevor wir coden. Jedes Feature durchläuft diesen Workflow:
1. Anforderung → Story/Bug anlegen → Epic zuordnen
2. Epic erstellen → Stories definieren → Abhängigkeiten prüfen → CHANGELOG updaten
3. Story starten → Tasks ausarbeiten → Feature Branch → Implementation → Tests → Commit → Merge → CHANGELOG updaten
## WICHTIG: Neue Anforderungen behandeln
Wenn der Stakeholder/User eine neue Anforderung stellt:
- NICHT sofort anfangen zu coden.
- Entscheiden: Handelt es sich um ein Feature (Story US_NNNNNN) oder einen Fehler (Bug BUG_NNNNNN).
- Dokumentieren in `project-management/requirements/`.
- Loggen in der `CHANGELOG.md` im Root-Verzeichnis [cite: 1.2].
## Verzeichnisstruktur (Management-Layer)
Alle administrativen Dateien liegen im `project-management/` Ordner, außer dem Changelog und der README [cite: 1.1].
```
project-management/
├── PROJECT_STATUS.md # Vision, Aktueller Fokus & Backlog (Nutzt TMP_STATUS_001)
├── ONBOARDING.md # Diese SOP
└── requirements/ # Alle Anforderungen
├── epics/ # High-Level Features (Standard: STD_EPIC_001)
├── stories/ # User Stories (Standard: STD_STORY_001)
├── tasks/ # Technische Umsetzung (Standard: STD_TASK_001)
└── bugs/ # Bug-Reports (Fehlerdokumentation)
CHANGELOG.md # Root: Das tabellarische Logbuch (Die Wahrheit) [cite: 1.2]
VERSION # Root: Die zentrale Versionsdatei (Single Source of Truth)
```
## Unsere Standards & Phasen
### Dokument-Header Standard (PFLICHT)
Jedes Dokument (auch Anforderungen) muss mit folgendem Header beginnen. Der Header steht zusaetzlich zu bestehenden Template-Headern und bleibt immer die erste Zeile.
```
ID: [ID] | Version: [Inhalt aus /VERSION] | Status: [Draft/Review/Final]
By: [Name oder Agent]
```
### Projekt-Status (PROJECT_STATUS_TEMPLATE.md)
Die `PROJECT_STATUS.md` folgt strikt dem bereitgestellten Template `PROJECT_STATUS_TEMPLATE.md` und weist die globale Projektversion aus. Die Header-Regel gilt auch hier (inkl. By-Zeile).
### Phase 0: Dokumentations-Standards (Anforderungen)
EPIC (ID: STD_EPIC_001):
- Mission Statement: Das große Ganze (Fakten).
- Business Value & Metriken: Welchen KPI verbessern wir?
- In-Scope vs. Out-of-Scope: Wo ist die rote Linie?
- High-Level Akzeptanzkriterien: Definition of Done für das Epic.
- Technische Constraints & Risiken: Was könnte explodieren?
USER STORY (ID: STD_STORY_001):
- Format: "Als [Rolle] möchte ich [Funktion], damit [Nutzen]."
- Akzeptanzkriterien (Gherkin-Style): Given / When / Then.
- Qualitätsregeln: Objektiv prüfbar, eindeutig, unabhängig von Details.
- Rückfrage-Pflicht: Wenn Kriterien nicht ableitbar sind → Rückfrage an Stakeholder stellen.
### Phase 1: Planung & Log
Bei jeder Planung eines Epics oder einer Story muss die `CHANGELOG.md` im Root aktualisiert werden.
Format für Einträge:
```
| DD.MM.YYYY | 🏗️ Planning | ID: Kurze Beschreibung geplant. |
```
Hinweis: "ID: ..." ist ein Praefix im Beschreibungsfeld, kein eigenes Tabellenfeld.
Ergaenze am Ende der Beschreibung immer den Hinweis `By: <Name/Agent>` (z.B. `ID: ... By: Jane Doe`), damit klar ist, wer die Aenderung gemacht hat.
### Phase 2: Just-in-Time Tasks (ID: STD_TASK_001)
Technische Tasks werden erst bei Story-Start detailliert ausgearbeitet.
- Feingranular: Max. 1–8 Arbeitsstunden pro Task.
- Konkret: Technisch präzise und eindeutig abschließbar.
- Inhalt: Titel, Outcome, Story-Bezug, Beschreibung & Definition of Done (DoD).
### Phase 3: Versionierung & Release-Audit (BINDEND)
Wir arbeiten strikt nach dem Format Major.Minor.Small (z.B. 0.0.0).
- Zentrale Datei: Die Datei `VERSION` im Root ist die einzige Quelle (SSOT).
- Zwang: Die Version aus dieser Datei muss zwingend in alle Scripte, Dokumente (Header/Footer) und GUIs/TUIs eingebunden werden. Hardcoding ist verboten!
- Doku-Audit-Pflicht: Bei jedem Major- und Minor-Release (X.Y.0) ist ein Audit zwingend:
- Prüfung auf inhaltliche Übereinstimmung mit dem neuen Stand.
- Aktualisierung veralteter Anweisungen/Beschreibungen.
- Verifizierung der Versions-IDs in allen Headern.
## Die goldenen Regeln
- `CHANGELOG.md` ist die Wahrheit. [cite: 1.2]
- `PROJECT_STATUS.md` ist der Kompass.
- `VERSION` ist das Gesetz.
- ID-Pflicht für alles: Ohne ID existiert keine Anforderung.
- Code-First ist verboten!
Let's build some ghosts! 👻☕

View File

@ -0,0 +1,116 @@
ID: SETUP_000005 | Version: 0.1.0 | Status: Draft
By: Codex (GPT-5)
# 🧬 SETUP_GUIDE: Phase 0 - Project Genesis
Dieses Dokument ist die Master-Anweisung für das initiale Setup des Sound Architect Projekts. Wer hiervon abweicht, riskiert Daten-Chaos – und glaub mir, das willst du nicht.
## 1. Verzeichnis-Struktur (The Skeleton)
Erstelle die Verzeichnisse exakt so. Wir trennen Planung und Anforderungen (`project-management`) strikt von der Umsetzung (`src`).
```
# ID: SCRIPT_000001
# Management & Requirements
mkdir -p project-management/requirements/{epics,stories,tasks,bugs}
# Source Code (Hexagonal Architecture)
mkdir -p src/{core,ports,adapters,ui}
# Infrastructure & Docs
mkdir -p tests docs/architecture
```
## 2. Das Logbuch-Gesetz (CHANGELOG.md)
Das Logbuch im Hauptverzeichnis ist die einzige chronologische Wahrheit des Projekts. Es wird im Root-Verzeichnis abgelegt, um maximale Sichtbarkeit zu garantieren.
### Format
Das Logbuch folgt einem strikten Tabellenformat:
- Datum
- Typ
- Beschreibung
Beispiel: `DD.MM.YYYY | Icon + Kürzel | ID (optional): Präzise Beschreibung der Änderung.`
### Die Legende
Verwende ausschließlich diese Typen für das Logbuch und Commit-Messages:
| Icon | Typ | Bedeutung |
| --- | --- | --- |
| 🚀 | Init | Projekt-Setup & Initialisierung |
| 📝 | Req | Requirements / Anforderungen |
| 🏛️ | Arch | Architektur-Entscheidungen |
| 🎨 | UI | UI-Implementation / Design |
| ✅ | Done | Feature oder Story abgeschlossen |
| ⚙️ | Code | Allgemeine Code-Implementation |
| 🔊 | Audio | Spezifische Audio-Features |
| 🗣️ | UX | User Experience Verbesserungen |
| ✨ | Feature | Neues Feature / Funktionalität |
| 🛠️ | CRUD | Asset-Management (Delete/Rename/etc.) |
| 🏗️ | Planning | Grobplanung von Epics & Stories |
## 3. Die „No-Dirt“ Regel (.gitignore)
Erstelle eine `.gitignore` im Root, damit kein digitaler Müll unser sauberes Labor kontaminiert.
```
# ID: DOC_000002
__pycache__/
*.py[cod]
*$py.class
.venv/
venv/
ENV/
.env
.vscode/
.idea/
*.log
output/
temp/
```
## 🛠️ Genesis-Automatisierungsscript
Kopiere diesen Block in dein Terminal, um das Labor mit einem Knall hochzufahren.
```
# ID: SCRIPT_000002
# 1. Verzeichnisse anlegen
mkdir -p project-management/requirements/{epics,stories,tasks,bugs} src/{core,ports,adapters,ui} tests docs/architecture
# 2. Logbuch initialisieren (incl. Legende)
cat <<EOF > CHANGELOG.md
# 📜 Projekt-Logbuch (Changelog)
# ID: DOC_000001
| Datum | Typ | Beschreibung |
|---|---|---|
| $(date +%d.%m.%Y) | 🚀 Init | **PROJECT_SETUP**: Struktur nach SETUP_000005 initialisiert. |
---
## 🔑 Legende
* 🚀 **Init** = Setup | 📝 **Req** = Requirements | 🏛️ **Arch** = Architektur
* 🎨 **UI** = Design | ✅ **Done** = Abgeschlossen | ⚙️ **Code** = Implementation
* 🔊 **Audio** = Audio-Engine | 🗣️ **UX** = User Experience | ✨ **Feature** = Neu | 🛠️ **CRUD** = Management
EOF
# 3. .gitignore erstellen
cat <<EOF > .gitignore
__pycache__/
.venv/
.env
*.log
EOF
# 4. Status-File vorbereiten
echo "# 📊 Projekt-Status" > project-management/PROJECT_STATUS.md
# 5. Version initialisieren
echo "0.1.0" > VERSION
echo "Labor ist bereit. Let's create some ghosts! 👻☕"
```

View File

@ -0,0 +1,38 @@
ID: PROMPT_000008 | Version: 0.1.0 | Status: Draft
By: Codex (GPT-5)
# Requirements Engineer Prompt
Uebernimm die Rolle eines Senior Agile Product Owners und Requirements Engineers. Ich gebe dir eine Projektvision und eine technische Projektspezifikation. Deine Aufgabe ist es, daraus Epics und User Stories mit klaren Akzeptanzkriterien abzuleiten.
## Vorgehensweise
- Arbeite Epic fuer Epic vor.
- Stelle maximal eine Rueckfrage auf einmal, falls Informationen fehlen.
- Nach jedem Epic: Kurze Zusammenfassung & aktualisierte Backlog-Uebersicht.
## Standards
### EPIC (ID: STD_EPIC_001)
- Mission Statement (Beschreibung): Fakten-basiertes "Grosse Ganze".
- Business Value & Metriken: KPI-Verbesserung / Nutzen.
- In-Scope vs. Out-of-Scope: Grenzen ziehen.
- High-Level Akzeptanzkriterien: Epic-DoD.
- Technische Constraints & Risiken: Abhaengigkeiten, Security, Legacy.
### USER STORY (ID: STD_STORY_001)
- Format: "Als [Rolle] moechte ich [Funktion], damit [Nutzen]."
- Akzeptanzkriterien (Gherkin): Given / When / Then.
### TASK (ID: STD_TASK_001)
- Titel: Technisch, praezise.
- Ziel / Outcome: Messbares Ergebnis.
- Story-Bezug: Bezug zur US_NNNNNN.
- Definition of Done (DoD): Pruefbare Abschlusskriterien.
## Start
Beginne mit einer vollstaendigen Epic-Uebersicht basierend auf STD_EPIC_001. Danach arbeite Epic fuer Epic weiter.

View File

@ -0,0 +1,38 @@
ID: EPIC_000001 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# EPIC_000001: Legacy CLI Account Control (sk.sh)
## Beschreibung
Der Legacy-CLI-Workflow ermoeglicht das Sperren und Entsperren lokaler Nutzerkonten per Bash-Skript.
## Ziel / Business Value
Schnelle, direkte Steuerung ohne Web-Service als Notfall- oder SSH-Fallback.
## Mission Statement
Stelle eine robuste, direkte Konto-Steuerung fuer Admins bereit, wenn der Service nicht verfuegbar ist.
## Business Value & Metriken
- Weniger Support-Aufwand durch schnelle lokale Eingriffe.
- Erfolgsmetrik: Konto-Disable/Enable laesst sich per CLI ohne Zusatztools ausfuehren.
## In-Scope
- Sperren/Entsperren von Nutzerkonten.
- Optionaler Countdown, Benachrichtigungen und Sound.
- Shutdown nur bei aktivem Login.
## Out-of-Scope
- Web-UI oder API-Integration.
- Persistente Protokollierung im Backend.
## High-Level Akzeptanzkriterien
- Admin kann einen Nutzer per CLI deaktivieren oder aktivieren.
- Countdown/Benachrichtigung/Shutdown verhalten sich wie dokumentiert.
## Technische Constraints & Risiken
- Root-Rechte erforderlich.
- Abhaengigkeit von notify-send und Sound-Tools.
## Zugeordnete User Stories (Done)
- US_000001: Nutzerkonto deaktivieren mit Countdown
- US_000002: Nutzerkonto wieder aktivieren

View File

@ -0,0 +1,41 @@
ID: EPIC_000002 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# EPIC_000002: Backend API Service
## Beschreibung
Der Backend-Service bietet REST-Endpunkte fuer Health-Checks und Nutzeraktionen zum Sperren/Entsperren.
## Ziel / Business Value
Remote-Steuerung von Nutzerkonten mit klaren API-Antworten fuer Automatisierung.
## Mission Statement
Biete eine sichere, zentrale API zur Verwaltung lokaler Nutzerkonten.
## Business Value & Metriken
- Remote-Verwaltung ohne direkten SSH-Zugriff.
- Erfolgsmetrik: API liefert konsistente Status- und Action-Responses.
## In-Scope
- Health-Endpoint.
- Auflistung verwaltbarer Nutzer.
- Disable/Enable-Endpoints mit Rueckgabe der Schritte.
## Out-of-Scope
- Frontend-Design-Iteration.
- Persistente Datenbank.
## High-Level Akzeptanzkriterien
- Endpunkte sind erreichbar und liefern erwartete Payloads.
- Actions melden Aktion, Schritte und Login-Status.
## Technische Constraints & Risiken
- Abhaengigkeit von lokalen System-Befehlen fuer Aktionen.
- Fehler muessen als HTTP-Fehler sauber abgebildet werden.
## Zugeordnete User Stories (Done)
- US_000003: Health-Status abfragen
- US_000004: Verfuegbare Nutzer auflisten
- US_000005: Nutzer per API deaktivieren
- US_000006: Nutzer per API aktivieren
- US_000021: Konfiguration per ENV steuern

View File

@ -0,0 +1,40 @@
ID: EPIC_000003 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# EPIC_000003: Authentication & Sessions
## Beschreibung
Authentifizierung und Autorisierung fuer Admins via PAM oder OIDC inkl. Session-Handling.
## Ziel / Business Value
Sichere Zugriffskontrolle auf API und UI mit klarer Admin-Identitaet.
## Mission Statement
Stelle einen sicheren Admin-Login bereit, der Token oder Session-Cookies ausstellt.
## Business Value & Metriken
- Reduzierung unautorisierter Zugriffe.
- Erfolgsmetrik: Nur autorisierte Admins koennen Nutzeraktionen ausfuehren.
## In-Scope
- PAM-Login mit Token-Ausgabe.
- OIDC-Login Flow mit State-Validierung.
- Autorisierungs-Guards und /me-Endpoint.
## Out-of-Scope
- Multi-Faktor-Authentifizierung.
- Externe Session Stores.
## High-Level Akzeptanzkriterien
- PAM-Login liefert Token und setzt Session-Cookie.
- OIDC-Flow validiert State und setzt Session-Cookie.
- Nicht autorisierte Nutzer werden blockiert.
## Technische Constraints & Risiken
- Abhaengigkeit von PAM und OIDC-Provider-Verfuegbarkeit.
- Cookie-Sicherheit muss korrekt konfiguriert sein.
## Zugeordnete User Stories (Done)
- US_000007: PAM-Login mit Token
- US_000008: OIDC-Login Flow
- US_000009: Autorisierung und /me-Identitaet

View File

@ -0,0 +1,35 @@
ID: EPIC_000004 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# EPIC_000004: Web UI
## Beschreibung
Eine schlanke Web-Oberflaeche wird als HTML-Template vom Backend ausgeliefert.
## Ziel / Business Value
Remote-Bedienung ueber den Browser ohne separate Client-Installation.
## Mission Statement
Biete eine einfache UI fuer Admins zum Login und zur Nutzersteuerung.
## Business Value & Metriken
- Schnellere Bedienung fuer Nicht-CLI-Nutzer.
- Erfolgsmetrik: UI ist unter / erreichbar.
## In-Scope
- Auslieferung der Index-Seite.
- Einbindung der Login-Optionen im Template.
## Out-of-Scope
- Design-Overhaul oder umfassende Frontend-Architektur.
## High-Level Akzeptanzkriterien
- GET / liefert eine HTML-Seite aus dem Template-Verzeichnis.
## Technische Constraints & Risiken
- Template-Abhaengigkeit von korrekter Backend-Konfiguration.
## Zugeordnete User Stories (Done)
- US_000010: Index-Seite ausliefern
- US_000022: Web-UI Aktionen ausfuehren
- US_000024: Watchtower Theme fuer Web-UI

View File

@ -0,0 +1,43 @@
ID: EPIC_000005 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# EPIC_000005: Automation Scripts
## Beschreibung
Bash-Skripte automatisieren Installation, Start, Update und Deployment des Services.
## Ziel / Business Value
Schnelle und reproduzierbare Betriebsablaeufe auf Zielsystemen.
## Mission Statement
Minimiere manuelle Admin-Schritte durch standardisierte Skripte.
## Business Value & Metriken
- Zeitersparnis bei Setup und Updates.
- Erfolgsmetrik: Install/Update/Deploy laufen ohne manuelle Nacharbeit.
## In-Scope
- Virtualenv-Erstellung und Abhaengigkeiten.
- Service-Start und Installation.
- Update- und Deployment-Workflows.
- OIDC-Client-Registrierungshilfe.
## Out-of-Scope
- CI/CD-Pipelines.
- Monitoring oder Alerting.
## High-Level Akzeptanzkriterien
- Skripte decken lokale und remote Setups ab.
- Fehler brechen mit klarer Ausgabe ab.
## Technische Constraints & Risiken
- Abhaengigkeit von sudo, rsync, ssh, python3.
## Zugeordnete User Stories (Done)
- US_000011: Virtualenv und Abhaengigkeiten erstellen
- US_000012: Service lokal starten
- US_000013: Service installieren
- US_000014: Service aktualisieren
- US_000015: Remote-Deployment durchfuehren
- US_000016: OIDC-Client registrieren
- US_000020: Makefile-Automation bereitstellen

View File

@ -0,0 +1,37 @@
ID: EPIC_000006 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# EPIC_000006: Systemd & Deployment Artifacts
## Beschreibung
Service-Unit und Konfigurationsvorlagen stellen den Betrieb und die manuelle Verteilung sicher.
## Ziel / Business Value
Konsistente Service-Konfiguration und einfache Bereitstellungsvorlagen.
## Mission Statement
Stelle Service-Unit und Konfigurations-Templates fuer reproduzierbare Deployments bereit.
## Business Value & Metriken
- Reduzierte Fehlkonfigurationen durch Standardvorlagen.
- Erfolgsmetrik: Service startet mit Unit-Datei und Env-Template.
## In-Scope
- Systemd-Unit-Datei im Repo.
- Konfigurationsvorlagen fuer env und Deploy-Hosts.
- Manuelle Deployment-Archive.
## Out-of-Scope
- Automatisierte Release-Pipelines.
## High-Level Akzeptanzkriterien
- Unit- und Template-Dateien sind im Repo vorhanden.
- Deployment-Archiv steht fuer manuelle Nutzung bereit.
## Technische Constraints & Risiken
- Pfadannahmen muessen zum Zielsystem passen.
## Zugeordnete User Stories (Done)
- US_000017: Systemd-Unit im Repo
- US_000018: Konfigurations-Templates verfuegbar
- US_000019: Deployment-Archiv vorhanden

View File

@ -0,0 +1,32 @@
ID: EPIC_000007 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# EPIC_000007: Documentation & Runbook
## Beschreibung
Projekt-Dokumentation beschreibt Setup, Betrieb und Sicherheitsrichtlinien fuer Admins.
## Ziel / Business Value
Schnelleres Onboarding und sichere Bedienung durch klare Runbooks.
## Mission Statement
Stelle eine verlaessliche Betriebs- und Sicherheitsdokumentation bereit.
## Business Value & Metriken
- Reduzierter Support durch klare Anleitungen.
- Erfolgsmetrik: Operatoren koennen Installation und Betrieb aus der Doku nachvollziehen.
## In-Scope
- README mit Setup, Running, Updates und Security-Hinweisen.
## Out-of-Scope
- Externe Wiki- oder Ticket-Systeme.
## High-Level Akzeptanzkriterien
- README beschreibt Setup, Betrieb und Security Hardening.
## Technische Constraints & Risiken
- Dokumentation muss mit dem aktuellen Verhalten uebereinstimmen.
## Zugeordnete User Stories (Done)
- US_000023: Runbook und Security-Hinweise dokumentieren

View File

@ -0,0 +1,18 @@
ID: US_000001 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# US_000001: Nutzerkonto per CLI deaktivieren
Status: Done
Als Admin moechte ich ein Nutzerkonto per CLI deaktivieren, damit der Zugriff sofort unterbunden wird.
## Akzeptanzkriterien
- Given ein existierender Nutzer und Root-Rechte
- When das Skript mit `disable` und optionalen Countdown/Sound-Parametern aufgerufen wird
- Then das Konto ist gesperrt und ein aktiver Login wird erkannt
- And bei aktivem Login wird eine Warnung/Countdown versendet und anschliessend abgemeldet
- And ein Shutdown erfolgt nur, wenn der Nutzer zuvor eingeloggt war
## Task-Platzhalter
- TASK_000001: Disable user countdown (Details bei Story-Start)

View File

@ -0,0 +1,17 @@
ID: US_000002 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# US_000002: Nutzerkonto per CLI aktivieren
Status: Done
Als Admin moechte ich ein Nutzerkonto per CLI aktivieren, damit sich der Nutzer wieder anmelden kann.
## Akzeptanzkriterien
- Given ein existierender Nutzer und Root-Rechte
- When das Skript mit `enable` aufgerufen wird
- Then das Konto ist entsperrt und Login ist wieder moeglich
- And es wird kein Shutdown ausgeloest
## Task-Platzhalter
- TASK_000002: Enable user account (Details bei Story-Start)

View File

@ -0,0 +1,17 @@
ID: US_000003 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# US_000003: Health-Status abfragen
Status: Done
Als Admin moechte ich den Health-Status per API abfragen, damit ich den Service-Zustand sehe.
## Akzeptanzkriterien
- Given der Service laeuft
- When ein GET auf `/health` erfolgt
- Then die Antwort enthaelt `status` mit dem Wert `ok`
- And die Antwort enthaelt `dry_run` als Boolean
## Task-Platzhalter
- TASK_000003: Health response payload (Details bei Story-Start)

View File

@ -0,0 +1,17 @@
ID: US_000004 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# US_000004: Verfuegbare Nutzer auflisten
Status: Done
Als Admin moechte ich verwaltbare Nutzer per API auflisten, damit ich ihren Login-Status sehe.
## Akzeptanzkriterien
- Given ein autorisierter Admin-Login
- When ein GET auf `/users` erfolgt
- Then die Antwort ist eine Liste von Eintraegen mit `user` und `logged_in`
- And die Liste enthaelt nur verwaltbare, nicht-root Nutzer
## Task-Platzhalter
- TASK_000004: List users status (Details bei Story-Start)

View File

@ -0,0 +1,17 @@
ID: US_000005 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# US_000005: Nutzer per API deaktivieren
Status: Done
Als Admin moechte ich einen Nutzer per API deaktivieren, damit ich den Zugriff remote steuern kann.
## Akzeptanzkriterien
- Given ein autorisierter Admin-Login und ein erlaubter Nutzer
- When ein POST auf `/users/{username}/disable` mit optionalen Feldern `countdown`, `sound`, `message` erfolgt
- Then die Antwort enthaelt `user`, `action` = `disable`, `dry_run`, `steps` und `logged_in`
- And nicht erlaubte Nutzer werden mit 403 abgewiesen
## Task-Platzhalter
- TASK_000005: API disable action (Details bei Story-Start)

View File

@ -0,0 +1,17 @@
ID: US_000006 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# US_000006: Nutzer per API aktivieren
Status: Done
Als Admin moechte ich einen Nutzer per API aktivieren, damit ich den Zugriff remote wieder erlaube.
## Akzeptanzkriterien
- Given ein autorisierter Admin-Login und ein erlaubter Nutzer
- When ein POST auf `/users/{username}/enable` erfolgt
- Then die Antwort enthaelt `user`, `action` = `enable`, `dry_run`, `steps` und `logged_in`
- And nicht erlaubte Nutzer werden mit 403 abgewiesen
## Task-Platzhalter
- TASK_000006: API enable action (Details bei Story-Start)

View File

@ -0,0 +1,18 @@
ID: US_000007 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# US_000007: PAM-Login mit Token
Status: Done
Als Admin moechte ich mich per PAM-Login anmelden, damit ich ein Session-Token erhalte.
## Akzeptanzkriterien
- Given `SKD_AUTH_MODE=pam` und gueltige Admin-Credentials
- When ein POST auf `/login` mit Benutzername und Passwort erfolgt
- Then die Antwort enthaelt `token` und `expires_in`
- And ein Session-Cookie mit dem Token wird gesetzt
- And der Login ist ohne OIDC-Konfiguration als Schnellstart moeglich
## Task-Platzhalter
- TASK_000007: PAM login token (Details bei Story-Start)

View File

@ -0,0 +1,19 @@
ID: US_000008 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# US_000008: OIDC-Login Flow
Status: Done
Als Admin moechte ich mich per OIDC anmelden, damit ich ohne Passwort-Login zugreifen kann.
## Akzeptanzkriterien
- Given `SKD_AUTH_MODE=oidc` und ein erreichbarer OIDC-Provider
- When ein GET auf `/login/oidc/start` erfolgt
- Then der Nutzer wird zum Provider umgeleitet und ein State-Cookie gesetzt
- When der Provider auf `/login/oidc/callback` mit Code und State zurueckleitet
- Then der State wird validiert und ein Session-Cookie gesetzt
- And bei ungueltigem State erfolgt eine 400-Antwort
## Task-Platzhalter
- TASK_000008: OIDC auth callback (Details bei Story-Start)

View File

@ -0,0 +1,20 @@
ID: US_000009 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# US_000009: Autorisierung und /me-Identitaet
Status: Done
Als Admin moechte ich autorisiert werden und meine Identitaet abrufen, damit API-Zugriffe nachvollziehbar sind.
## Akzeptanzkriterien
- Given ein gueltiges Session-Token
- When ein GET auf `/me` erfolgt
- Then die Antwort enthaelt `user` und `auth_mode`
- Given kein oder ungueltiges Token
- When ein Zugriff auf geschuetzte Endpunkte erfolgt
- Then der Zugriff wird mit 401/403 verweigert
- And bei OIDC wird die Allowlist gegen `preferred_username`, `email` oder `sub` geprueft
## Task-Platzhalter
- TASK_000009: Authorization /me gate (Details bei Story-Start)

View File

@ -0,0 +1,16 @@
ID: US_000010 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# US_000010: Index-Seite ausliefern
Status: Done
Als Admin moechte ich die Web-UI im Browser oeffnen, damit ich mich anmelden und Aktionen starten kann.
## Akzeptanzkriterien
- Given der Service laeuft
- When ein GET auf `/` erfolgt
- Then die Antwort ist HTML aus dem Template-Verzeichnis
## Task-Platzhalter
- TASK_000010: Serve UI template (Details bei Story-Start)

View File

@ -0,0 +1,17 @@
ID: US_000011 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# US_000011: Virtualenv und Abhaengigkeiten erstellen
Status: Done
Als Operator moechte ich eine virtuelle Umgebung erstellen, damit der Service reproduzierbar laeuft.
## Akzeptanzkriterien
- Given ein verfuegbares Python-Executable
- When `scripts/create_venv.sh` ausgefuehrt wird
- Then eine `.venv` wird erstellt und aktiviert
- And die Abhaengigkeiten aus `backend/requirements.txt` sind installiert
## Task-Platzhalter
- TASK_000011: Provision venv deps (Details bei Story-Start)

View File

@ -0,0 +1,16 @@
ID: US_000012 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# US_000012: Service lokal starten
Status: Done
Als Operator moechte ich den Service lokal starten, damit ich die API ohne Systemd testen kann.
## Akzeptanzkriterien
- Given eine vorhandene `.venv`
- When `scripts/run.sh` ausgefuehrt wird
- Then `uvicorn` startet die App `backend.app:app` auf dem konfigurierten Host/Port
## Task-Platzhalter
- TASK_000012: Run uvicorn service (Details bei Story-Start)

View File

@ -0,0 +1,19 @@
ID: US_000013 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# US_000013: Service installieren
Status: Done
Als Operator moechte ich den Service installieren, damit er als Systemdienst laeuft.
## Akzeptanzkriterien
- Given sudo-Rechte und ein Zielpfad
- When `scripts/install.sh` ausgefuehrt wird
- Then der Service-User/-Group existiert oder wird erstellt
- And das Projekt wird ins Install-Verzeichnis synchronisiert
- And eine Env-Datei wird aus `env.example` erstellt, falls sie fehlt
- And eine Systemd-Unit wird geschrieben und der Service gestartet
## Task-Platzhalter
- TASK_000013: Install service setup (Details bei Story-Start)

View File

@ -0,0 +1,18 @@
ID: US_000014 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# US_000014: Service aktualisieren
Status: Done
Als Operator moechte ich den Service aktualisieren, damit Updates schnell eingespielt werden.
## Akzeptanzkriterien
- Given ein vorhandenes Repo-Checkout
- When `scripts/update.sh` ausgefuehrt wird
- Then das Repo wird auf den Ziel-Branch aktualisiert
- And die Abhaengigkeiten werden in der venv aktualisiert
- And der Systemdienst wird neu gestartet
## Task-Platzhalter
- TASK_000014: Update service refresh (Details bei Story-Start)

View File

@ -0,0 +1,18 @@
ID: US_000015 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# US_000015: Remote-Deployment durchfuehren
Status: Done
Als Operator moechte ich auf ein Zielsystem deployen, damit das Projekt remote aktualisiert wird.
## Akzeptanzkriterien
- Given ein Host-Eintrag in `deploy_hosts.yml` oder JSON-Konfig
- When `scripts/deploy.sh <host>` ausgefuehrt wird
- Then das Projekt wird als Archiv gepackt und auf den Host kopiert
- And der Inhalt wird im Install-Verzeichnis entpackt und Rechte werden gesetzt
- And Abhaengigkeiten werden installiert und der Service neu gestartet
## Task-Platzhalter
- TASK_000015: Remote deploy package (Details bei Story-Start)

View File

@ -0,0 +1,20 @@
ID: US_000016 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# US_000016: OIDC-Client registrieren
Status: Done
Als Operator moechte ich einen OIDC-Client registrieren, damit der Login-Flow konfiguriert werden kann.
## Akzeptanzkriterien
- Given `SKD_OIDC_ISSUER` und `OIDC_INITIAL_ACCESS_TOKEN` sind gesetzt
- When `scripts/register_oidc_client.sh` ausgefuehrt wird
- Then ein Registrierungsrequest wird an den Provider gesendet
- And Client-ID und Client-Secret werden ausgegeben
- And bei HTTP-Fehler wird mit Fehlermeldung abgebrochen
- And die Redirect-URI ist exakt `https://<device-host>[:port]/login/oidc/callback` (keine Wildcards)
- And bei Host/Port-Aenderung erfolgt eine Neuregistrierung mit neuen Credentials
## Task-Platzhalter
- TASK_000016: OIDC client register (Details bei Story-Start)

View File

@ -0,0 +1,16 @@
ID: US_000017 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# US_000017: Systemd-Unit im Repo
Status: Done
Als Operator moechte ich eine Systemd-Unit im Repo haben, damit der Service standardisiert betrieben werden kann.
## Akzeptanzkriterien
- Given die Datei `systemd/skd.service` existiert
- When die Unit inspiziert wird
- Then sie enthaelt Description, User/Group, WorkingDirectory, EnvironmentFile, ExecStart und Restart-Policy
## Task-Platzhalter
- TASK_000017: Systemd unit template (Details bei Story-Start)

View File

@ -0,0 +1,19 @@
ID: US_000018 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# US_000018: Konfigurations-Templates verfuegbar
Status: Done
Als Operator moechte ich Konfigurationsvorlagen im Repo haben, damit Installationen konsistent sind.
## Akzeptanzkriterien
- Given `env.example` liegt im Root-Verzeichnis
- When die Datei geprueft wird
- Then sie enthaelt SKD-Konfigurationswerte fuer Auth und Defaults
- Given `deploy_hosts.yml` liegt im Root-Verzeichnis
- When die Datei geprueft wird
- Then sie enthaelt eine Host-Liste mit Name, Host, User, Port, Install-Dir und Service-Parametern
## Task-Platzhalter
- TASK_000018: Config templates ready (Details bei Story-Start)

View File

@ -0,0 +1,16 @@
ID: US_000019 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# US_000019: Deployment-Archiv vorhanden
Status: Done
Als Operator moechte ich ein Deployment-Archiv im Repo haben, damit ich manuell ausrollen kann.
## Akzeptanzkriterien
- Given `sk_deploy.zip` liegt im Root-Verzeichnis
- When das Archiv geoeffnet wird
- Then es enthaelt das Projekt fuer die manuelle Bereitstellung
## Task-Platzhalter
- TASK_000019: Deployment zip artifact (Details bei Story-Start)

View File

@ -0,0 +1,18 @@
ID: US_000020 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# US_000020: Makefile-Automation bereitstellen
Status: Done
Als Operator moechte ich Standard-Targets fuer Installation, Start/Stop und Updates haben, damit Betriebsablaeufe vereinfacht werden.
## Akzeptanzkriterien
- Given ein Makefile im Root-Verzeichnis
- When die Targets `install`, `up`, `down`, `uninstall`, `update` ausgefuehrt werden
- Then die entsprechenden Service-Aktionen werden aufgerufen
- And `healthcheck` und `token` stehen als Hilfs-Targets bereit
- And `healthcheck` prueft nur die Erreichbarkeit von `/health` ohne Auth-Anforderung
## Task-Platzhalter
- TASK_000020: Makefile ops targets (Details bei Story-Start)

View File

@ -0,0 +1,20 @@
ID: US_000021 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# US_000021: Konfiguration per ENV steuern
Status: Done
Als Operator moechte ich Konfigurationen per ENV setzen, damit Verhalten und Defaults steuerbar sind.
## Akzeptanzkriterien
- Given Umgebungsvariablen aus `env.example`
- When der Service startet
- Then Auth- und Session-Settings werden aus ENV geladen (`SKD_AUTH_MODE`, `SKD_AUTH_SECRET`, `SKD_TOKEN_TTL_SECONDS`, `SKD_AUTH_ALLOWED_USERS`, `SKD_AUTH_ALLOWED_GROUPS`, `SKD_AUTH_PAM_SERVICE`, `SKD_SESSION_COOKIE_NAME`, `SKD_SESSION_COOKIE_SECURE`, `SKD_OIDC_STATE_COOKIE_NAME`)
- And OIDC-Settings werden aus ENV geladen (`SKD_OIDC_ISSUER`, `SKD_OIDC_CLIENT_ID`, `SKD_OIDC_CLIENT_SECRET`, `SKD_OIDC_REDIRECT_URI`, `SKD_OIDC_SCOPES`)
- And Allowlist/Defaults werden aus ENV geladen (`SKD_ALLOWED_USERS`, `SKD_DEFAULT_COUNTDOWN`, `SKD_DEFAULT_SOUND`, `SKD_NOTIFY_TIMEOUT`, `SKD_DRY_RUN`)
- And Sound/Notify-Pfade sind ueber ENV ueberschreibbar (`SKD_SOUND_PLAYER`, `SKD_SOUND_FILE`, `SKD_NOTIFY_SEND_PATH`)
- And der OIDC Issuer entspricht der externen URL des Providers
## Task-Platzhalter
- TASK_000021: ENV settings defaults (Details bei Story-Start)

View File

@ -0,0 +1,19 @@
ID: US_000022 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# US_000022: Web-UI Aktionen ausfuehren
Status: Done
Als Admin moechte ich mich im Web-UI anmelden, Nutzer laden und Aktionen ausfuehren, damit ich keine CLI benoetige.
## Akzeptanzkriterien
- Given die Web-UI ist erreichbar
- When ich mich per PAM oder OIDC anmelde
- Then der Session-Status wird angezeigt
- When ich Nutzer lade und eine Aktion sende
- Then die Aktionsergebnisse (Steps/Status) werden als Text angezeigt
- And Fehlerantworten werden als Text angezeigt
## Task-Platzhalter
- TASK_000022: UI login and actions (Details bei Story-Start)

View File

@ -0,0 +1,17 @@
ID: US_000023 | Version: 0.1.0 | Status: Final
By: Codex (GPT-5)
# US_000023: Runbook und Security-Hinweise dokumentieren
Status: Done
Als Operator moechte ich eine klare Betriebs- und Security-Dokumentation haben, damit der Service sicher betrieben werden kann.
## Akzeptanzkriterien
- Given das README im Root-Verzeichnis
- When ich es lese
- Then ich finde Abschnitte zu Quick Start, Configuration, Running, Updates und Deployment
- And Security Hardening ist als eigener Abschnitt beschrieben
## Task-Platzhalter
- TASK_000023: README runbook notes (Details bei Story-Start)

View File

@ -0,0 +1,22 @@
ID: US_000024 | Version: 0.1.0 | Status: Draft
By: Codex (GPT-5)
# US_000024: Watchtower Theme fuer Web-UI
Status: Zurueckgestellt
Als Admin moechte ich das Watchtower-Design verwenden, damit die Web-UI dem vereinbarten Dark-Mode-Branding entspricht.
## Akzeptanzkriterien
- Given die Web-UI wird aus dem Template ausgeliefert
- When das UI geladen wird
- Then das Watchtower-Theme praegt Farben, Typografie und Kontraste der UI
- And die Theme-Assets liegen unter `assets/` in einer klaren Design-Struktur (z.B. `assets/design/`)
- And das Theme nutzt die bereitgestellten Dateien `tokens_watchtower.css` und `theme_watchtower.css`
- And ein Hintergrundrauschen (`bg-noise`) ist sichtbar
## Notizen
- Assets sind unter `assets/design/` abgelegt (noch nicht eingebunden).
## Task-Platzhalter
- TASK_000024: Apply Watchtower theme (Details bei Story-Start)

View File

@ -0,0 +1,58 @@
ID: PROMPT_000006 | Version: 0.1.0 | Status: Draft
By: Codex (GPT-5)
# Restructure Prompt: Forensic Architect (Compliance Mode)
Ziel: Nachtraegliche Strukturierung nach dem Just-in-Time Prinzip.
Befolge strikt die beigefuegten Dokumente 'onboarding.md' und 'project-setup.md'.
## Referenz-Regelwerk
1. **ONBOARDING.md**: Nutze das Prinzip "Klarheit vor Code". Epics und Stories werden jetzt definiert.
2. **Just-in-Time Tasks**: Erstelle fuer die Stories NUR Task-Platzhalter (3-5 Woerter). Die detaillierte Task-Ausarbeitung (Phase 4) erfolgt NICHT jetzt, sondern erst bei Story-Start.
3. **ID-System**: Verwende strikt PREFIX_NNNNNN fuer alle Dokumente.
## Strenges Verbot
Aendere, loesche oder verschiebe KEINEN bestehenden Code. Der Code dient nur als Lese-Quelle.
## Format-Vorgaben
### Epic
- Titel
- Beschreibung
- Ziel / Business Value
### User Story
- "Als [Rolle] moechte ich [Funktion], damit [Nutzen]."
### Akzeptanzkriterien
- Given ...
- When ...
- Then ...
- (optional And ...)
### Qualitaetsregeln
- Jede User Story **muss** Akzeptanzkriterien haben.
- Kriterien muessen:
- objektiv pruefbar
- eindeutig
- unabhaengig von Implementierungsdetails sein.
- Wenn Akzeptanzkriterien nicht ableitbar sind -> **Rueckfrage stellen**.
- **Task-Erstellung**: Detaillierte Tasks werden **erst dann** erstellt, wenn die Arbeit an einer konkreten User Story beginnt. Vorher existieren nur Platzhalter.
## Dein Auftrag
1. **Genesis**: Erstelle die Verzeichnisse gemaess SETUP_000005.
2. **Versionierung**: Erstelle die Datei `VERSION` im Root mit Inhalt `0.1.0`.
3. **Logbuch**: Initialisiere 'CHANGELOG.md' (Typ 'Init').
4. **Epics & Stories**: Dokumentiere den Ist-Zustand unter Einhaltung der **FORMAT-VORGABEN**. Jedes Modul bekommt ein Epic und User Stories (Status: Done).
5. **Just-in-Time Tasks**: Fuege in die Stories nur Platzhalter ein (z.B. "TASK_000021: Titel (Details bei Story-Start)"). Erstelle **KEINE** detaillierten Task-Dateien. Die Ausarbeitung erfolgt erst bei Story-Start.
6. **Status-Zentrale**: Erstelle die 'project-management/PROJECT_STATUS.md' mit Backlog-Uebersicht (Checkboxen fuer Epics/Stories/Platzhalter-Tasks).
Gib mir eine Liste der erstellten Epics und Stories aus.

View File

@ -0,0 +1,28 @@
ID: PROMPT_000009 | Version: 0.1.0 | Status: Draft
By: Codex (GPT-5)
# Story Refactoring Prompt
Uebernimm die Rolle eines Senior Agile Product Owners & Requirements Refiners. Ich gebe dir bestehende Backlog-Eintraege und Dokumente. Deine Aufgabe ist es, diese systematisch zu pruefen und in unser Zielformat zu bringen.
## Ziel
- Bestehendes Material in Epics, Stories und Tasks zerlegen.
- Fehlende Infos nach unseren Standards (STD_EPIC/STORY/TASK_001) ergaenzen.
- Eindeutige IDs vergeben.
## Unser Standard-Arsenal
- EPIC (STD_EPIC_001): Mission, Value/KPI, In/Out-Scope, Epic-DoD, Risiken.
- USER STORY (STD_STORY_001): Format (Als... moechte ich... damit...), Gherkin-Kriterien.
- TASK (STD_TASK_001): Technischer Titel, Outcome, Story-Bezug, Beschreibung, DoD.
## Arbeitsweise
- Arbeite iterativ und markiere unsichere Annahmen.
- Ueberarbeitete Versionen klar von Originalen trennen.
- Nach jedem Abschnitt: Kurze Zusammenfassung & aktualisierte To-Do-Liste.
## Output-Format
Strikte Hierarchie: Epic (STD_EPIC_001) -> Stories (STD_STORY_001) -> Tasks (STD_TASK_001).

View File

@ -0,0 +1,33 @@
ID: PROMPT_000007 | Version: 0.1.0 | Status: Draft
By: Codex (GPT-5)
# The Stack-Master & System Architect
Rolle: Senior System Architect (Decision Maker). Kontext: Wir befinden uns in Phase 0 (Genesis), nachdem die Requirements definiert wurden.
## Ziel
Analysiere die vorliegenden Epics und User Stories und triff eine fundierte Entscheidung ueber den Software-Stack und das technische Design.
## Aufgaben
- Requirements-Analyse: Untersuche die Akzeptanzkriterien der User Stories auf technische Implikationen (z.B. Performance, Plattformen, APIs).
- Stack-Selektion: Waehle die Programmiersprache, Frameworks und Bibliotheken aus, die diese Kriterien am effizientesten erfuellen.
- Architektur-Blueprint: Entwirf die Struktur basierend auf der Hexagonalen Architektur.
- Definiere die Domain-Modelle im src/core.
- Definiere die Ports (Interfaces) fuer Driver und Driven Adapter.
- Skizziere die notwendigen Adapter.
## Architektur-Gesetze
- Keine Code-Implementierung: Erstelle nur die Struktur und die Definitionen, keinen produktiven Code.
- Just-in-Time: Definiere technische Details nur so weit, wie sie fuer das aktuelle Verstaendnis noetig sind.
- Abhaengigkeits-Regel: Der core darf keine externen Abhaengigkeiten haben.
## Output-Format (ARCHITECTURE.md)
- Status & Version: (z.B. v0.1.0).
- Technologie-Stack: Liste der gewaehlten Tools mit Begruendung basierend auf den Stories.
- Modul-Struktur: Verzeichnis-Layout gemaess SETUP_000005.
- Schnittstellen-Definition: Uebersicht der wichtigsten Ports.
- Changelog-Update: Erstelle den notwendigen Eintrag fuer die CHANGELOG.md (Typ: Arch).

74
scripts/register_oidc_client.sh Executable file
View File

@ -0,0 +1,74 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
require_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "Required command not found: $1" >&2
exit 1
fi
}
require_cmd curl
require_cmd python3
ISSUER="${SKD_OIDC_ISSUER:-}"
REG_ENDPOINT_DEFAULT=""
if [[ -n "${ISSUER}" ]]; then
REG_ENDPOINT_DEFAULT="${ISSUER%/}/connect/register"
fi
REG_ENDPOINT="${OIDC_REGISTRATION_ENDPOINT:-$REG_ENDPOINT_DEFAULT}"
INITIAL_TOKEN="${OIDC_INITIAL_ACCESS_TOKEN:-}"
CLIENT_NAME="${OIDC_CLIENT_NAME:-Safe Kiddo Daemon}"
REDIRECT_URI="${SKD_OIDC_REDIRECT_URI:-http://localhost:8000/login/oidc/callback}"
if [[ -z "${REG_ENDPOINT}" || -z "${INITIAL_TOKEN}" ]]; then
cat >&2 <<'EOF'
Missing configuration. Set:
SKD_OIDC_ISSUER (or OIDC_REGISTRATION_ENDPOINT)
OIDC_INITIAL_ACCESS_TOKEN
Optional:
OIDC_CLIENT_NAME (default: Safe Kiddo Daemon)
SKD_OIDC_REDIRECT_URI (default: http://localhost:8000/login/oidc/callback)
EOF
exit 1
fi
log "Registering client at ${REG_ENDPOINT} with redirect ${REDIRECT_URI}..."
TMP_RESP="$(mktemp)"
trap 'rm -f "${TMP_RESP}"' EXIT
HTTP_CODE=$(curl -sS -o "${TMP_RESP}" -w '%{http_code}' \
-X POST "${REG_ENDPOINT}" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${INITIAL_TOKEN}" \
-d "{\"client_name\":\"${CLIENT_NAME}\",\"redirect_uris\":[\"${REDIRECT_URI}\"]}")
if [[ "${HTTP_CODE}" != "200" && "${HTTP_CODE}" != "201" ]]; then
echo "Client registration failed (HTTP ${HTTP_CODE}):" >&2
cat "${TMP_RESP}" >&2
exit 1
fi
python3 - "$TMP_RESP" <<'PYCODE'
import json, sys
path = sys.argv[1]
data = json.load(open(path, "r"))
client_id = data.get("client_id")
client_secret = data.get("client_secret")
print("Client registered.")
if client_id:
print(f"Client ID: {client_id}")
if client_secret:
print(f"Client Secret: {client_secret}")
if not (client_id and client_secret):
print("Warning: Response missing client_id or client_secret", file=sys.stderr)
PYCODE