From 2aa04795b36e15049a1ce323f5fad9096d049280 Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 28 Dec 2025 23:32:09 +0100 Subject: [PATCH 01/13] docs: start update client stories --- CHANGELOG.md | 1 + project-management/PROJECT_STATUS.md | 6 +++--- .../requirements/stories/US_000026.md | 2 +- .../requirements/stories/US_000027.md | 2 +- .../requirements/stories/US_000028.md | 2 +- .../requirements/tasks/TASK_000027.md | 20 ++++++++++++++++++ .../requirements/tasks/TASK_000028.md | 21 +++++++++++++++++++ .../requirements/tasks/TASK_000029.md | 20 ++++++++++++++++++ 8 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 project-management/requirements/tasks/TASK_000027.md create mode 100644 project-management/requirements/tasks/TASK_000028.md create mode 100644 project-management/requirements/tasks/TASK_000029.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e2d2d9..c139e2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ By: Codex (GPT-5) | 28.12.2025 | ๐Ÿ—๏ธ Planning | ID: Update-Format festgelegt (JSON + tar.gz). By: Codex (GPT-5) | | 28.12.2025 | ๐Ÿ—๏ธ Planning | ID: Update-Service URL auf update.wlkns.org festgelegt. By: Codex (GPT-5) | | 28.12.2025 | ๐Ÿ—๏ธ Planning | ID: EPIC_000009 Update Webservice (External Team) dokumentiert. By: Codex (GPT-5) | +| 28.12.2025 | ๐Ÿ—๏ธ Planning | ID: TASK_000027-TASK_000029 fuer EPIC_000008 ausgearbeitet. By: Codex (GPT-5) | --- ## Legende diff --git a/project-management/PROJECT_STATUS.md b/project-management/PROJECT_STATUS.md index 6371fc8..0d31036 100644 --- a/project-management/PROJECT_STATUS.md +++ b/project-management/PROJECT_STATUS.md @@ -10,9 +10,9 @@ Sicheres, remote steuerbares System zum Sperren/Entsperren lokaler Nutzerkonten. โœ… Stabilization ## Aktueller Fokus -1. Dokumentierter Ist-Zustand der Module. -2. Pflege der Anforderungen bei neuen Features. -3. Doku und Ops-Automation aktuell halten. +1. Client-Update-Mechanik planen (EPIC_000008). +2. Dokumentierter Ist-Zustand der Module. +3. Pflege der Anforderungen bei neuen Features. ## Projekt-Tagebuch (Kurz, optional) | Datum | Typ | Beschreibung | diff --git a/project-management/requirements/stories/US_000026.md b/project-management/requirements/stories/US_000026.md index 4472a65..a0b71c8 100644 --- a/project-management/requirements/stories/US_000026.md +++ b/project-management/requirements/stories/US_000026.md @@ -3,7 +3,7 @@ By: Codex (GPT-5) # US_000026: Client bezieht Updates (Pull) -Status: Draft +Status: In Progress Als Betreiber moechte ich, dass der Client Updates per Pull von einem Update-Service bezieht, damit Deployments ohne SSH moeglich sind. diff --git a/project-management/requirements/stories/US_000027.md b/project-management/requirements/stories/US_000027.md index afc6b89..f0fd242 100644 --- a/project-management/requirements/stories/US_000027.md +++ b/project-management/requirements/stories/US_000027.md @@ -3,7 +3,7 @@ By: Codex (GPT-5) # US_000027: Client verifiziert und wendet Updates an -Status: Draft +Status: In Progress Als Betreiber moechte ich, dass der Client Updates verifiziert und sicher anwendet, damit fehlerhafte Pakete keine Ausfaelle verursachen. diff --git a/project-management/requirements/stories/US_000028.md b/project-management/requirements/stories/US_000028.md index 27848c0..fef2260 100644 --- a/project-management/requirements/stories/US_000028.md +++ b/project-management/requirements/stories/US_000028.md @@ -3,7 +3,7 @@ By: Codex (GPT-5) # US_000028: Client meldet Update-Status -Status: Draft +Status: In Progress Als Betreiber moechte ich Statusmeldungen vom Client erhalten, damit Update-Ergebnisse nachvollziehbar sind. diff --git a/project-management/requirements/tasks/TASK_000027.md b/project-management/requirements/tasks/TASK_000027.md new file mode 100644 index 0000000..2e6d2a4 --- /dev/null +++ b/project-management/requirements/tasks/TASK_000027.md @@ -0,0 +1,20 @@ +ID: TASK_000027 | Version: 0.1.0 | Status: Draft +By: Codex (GPT-5) + +# TASK_000027: Update endpoint config + +## Outcome +Client kennt Update-Endpoint, Auth und Polling-Konfiguration. + +## Story-Bezug +US_000026 + +## Beschreibung +- Konfigurationskeys definieren (z.B. `SKD_UPDATE_URL`, `SKD_UPDATE_TOKEN`, `SKD_UPDATE_INTERVAL`). +- Default auf `https://update.wlkns.org` festlegen. +- Lesen der Config in Settings/ENV beschreiben. + +## Definition of Done (DoD) +- Konfigurations-Keys dokumentiert. +- Default-URL ist festgelegt. +- Client kann Update-Endpoint ueber Konfiguration aufloesen. diff --git a/project-management/requirements/tasks/TASK_000028.md b/project-management/requirements/tasks/TASK_000028.md new file mode 100644 index 0000000..8dacf51 --- /dev/null +++ b/project-management/requirements/tasks/TASK_000028.md @@ -0,0 +1,21 @@ +ID: TASK_000028 | Version: 0.1.0 | Status: Draft +By: Codex (GPT-5) + +# TASK_000028: Verify and apply update + +## Outcome +Update-Artefakt wird verifiziert und atomar angewendet. + +## Story-Bezug +US_000027 + +## Beschreibung +- JSON-Manifest lesen (Version, `artifact_url`, `sha256`, optional `sig_url`). +- `tar.gz` herunterladen und SHA256 pruefen. +- Update atomar anwenden (staging, swap, rollback). +- Fehlerfall dokumentieren (Rollback, Status). + +## Definition of Done (DoD) +- Verifikation (Checksum/Signatur) ist beschrieben. +- Atomare Anwendung und Rollback-Strategie sind definiert. +- Fehlerbilder sind erfasst. diff --git a/project-management/requirements/tasks/TASK_000029.md b/project-management/requirements/tasks/TASK_000029.md new file mode 100644 index 0000000..37ae192 --- /dev/null +++ b/project-management/requirements/tasks/TASK_000029.md @@ -0,0 +1,20 @@ +ID: TASK_000029 | Version: 0.1.0 | Status: Draft +By: Codex (GPT-5) + +# TASK_000029: Report update status + +## Outcome +Client meldet Update-Status mit Version und Fehlerbild. + +## Story-Bezug +US_000028 + +## Beschreibung +- Status-Schema definieren (Version, Ergebnis, Fehler, Zeitpunkt). +- Transportweg festlegen (z.B. HTTP POST oder Log-Export). +- Erfolg/Fehler konsistent dokumentieren. + +## Definition of Done (DoD) +- Status-Schema dokumentiert. +- Uebertragungspfad beschrieben. +- Erfolg/Fehler werden eindeutig gemeldet. From 8a16a8633bb6c841e4ebbff4458015f6d49f963c Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 28 Dec 2025 23:39:10 +0100 Subject: [PATCH 02/13] docs: add update client config keys --- CHANGELOG.md | 1 + README.md | 4 ++++ env.example | 4 ++++ project-management/requirements/tasks/TASK_000027.md | 10 +++++----- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c139e2e..c13e86e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ By: Codex (GPT-5) | 28.12.2025 | ๐Ÿ—๏ธ Planning | ID: Update-Service URL auf update.wlkns.org festgelegt. By: Codex (GPT-5) | | 28.12.2025 | ๐Ÿ—๏ธ Planning | ID: EPIC_000009 Update Webservice (External Team) dokumentiert. By: Codex (GPT-5) | | 28.12.2025 | ๐Ÿ—๏ธ Planning | ID: TASK_000027-TASK_000029 fuer EPIC_000008 ausgearbeitet. By: Codex (GPT-5) | +| 28.12.2025 | โš™๏ธ Code | ID: Update-Config Keys in Settings/ENV/README definiert. By: Codex (GPT-5) | --- ## Legende diff --git a/README.md b/README.md index 1563bac..4a5dbd0 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,10 @@ Set in `/etc/skd/env` (see `env.example`): - `SKD_DEFAULT_COUNTDOWN`, `SKD_DEFAULT_SOUND`, `SKD_NOTIFY_TIMEOUT`: behavior defaults. - `SKD_DRY_RUN=true` to test without real account changes or shutdown. - `SKD_SOUND_PLAYER`/`SKD_SOUND_FILE`, `SKD_NOTIFY_SEND_PATH` if defaults differ. +- Update client: + - `SKD_UPDATE_URL` (default `https://update.wlkns.org`) + - `SKD_UPDATE_TOKEN` (API token for update service) + - `SKD_UPDATE_INTERVAL` (seconds; default 3600) Notes: - `./scripts/install.sh` will create `/etc/skd/env` from `env.example` if missing (edit afterwards) and ensure the `skd` service user/group exist. diff --git a/env.example b/env.example index 41120b9..9c7368b 100644 --- a/env.example +++ b/env.example @@ -21,5 +21,9 @@ SKD_OIDC_STATE_COOKIE_NAME=skd_oidc_state SKD_DEFAULT_COUNTDOWN=60 SKD_DEFAULT_SOUND=false SKD_NOTIFY_TIMEOUT=5 +# Update client configuration +SKD_UPDATE_URL=https://update.wlkns.org +SKD_UPDATE_TOKEN= +SKD_UPDATE_INTERVAL=3600 # Set to true to test without performing real system changes SKD_DRY_RUN=false diff --git a/project-management/requirements/tasks/TASK_000027.md b/project-management/requirements/tasks/TASK_000027.md index 2e6d2a4..35f6b95 100644 --- a/project-management/requirements/tasks/TASK_000027.md +++ b/project-management/requirements/tasks/TASK_000027.md @@ -1,4 +1,4 @@ -ID: TASK_000027 | Version: 0.1.0 | Status: Draft +ID: TASK_000027 | Version: 0.1.0 | Status: In Progress By: Codex (GPT-5) # TASK_000027: Update endpoint config @@ -10,11 +10,11 @@ Client kennt Update-Endpoint, Auth und Polling-Konfiguration. US_000026 ## Beschreibung -- Konfigurationskeys definieren (z.B. `SKD_UPDATE_URL`, `SKD_UPDATE_TOKEN`, `SKD_UPDATE_INTERVAL`). +- Konfigurationskeys definieren (`SKD_UPDATE_URL`, `SKD_UPDATE_TOKEN`, `SKD_UPDATE_INTERVAL`). - Default auf `https://update.wlkns.org` festlegen. - Lesen der Config in Settings/ENV beschreiben. ## Definition of Done (DoD) -- Konfigurations-Keys dokumentiert. -- Default-URL ist festgelegt. -- Client kann Update-Endpoint ueber Konfiguration aufloesen. +- Konfigurations-Keys dokumentiert (`env.example`, README). +- Default-URL ist festgelegt (`https://update.wlkns.org`). +- Settings laden die Update-Konfiguration aus ENV. From b508f5c58ca33fcd866a29c25139bc07abe382d8 Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 28 Dec 2025 23:39:20 +0100 Subject: [PATCH 03/13] code: load update client config --- backend/settings.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/settings.py b/backend/settings.py index e4b1165..1878e03 100644 --- a/backend/settings.py +++ b/backend/settings.py @@ -39,6 +39,9 @@ class Settings: self.default_sound: bool = os.getenv("SKD_DEFAULT_SOUND", "false").lower() == "true" self.notify_timeout: int = int(os.getenv("SKD_NOTIFY_TIMEOUT", "5")) self.dry_run: bool = os.getenv("SKD_DRY_RUN", "false").lower() == "true" + self.update_url: str = os.getenv("SKD_UPDATE_URL", "https://update.wlkns.org") + self.update_token: str = os.getenv("SKD_UPDATE_TOKEN", "") + self.update_interval: int = int(os.getenv("SKD_UPDATE_INTERVAL", "3600")) # Paths/tools self.notify_send_path: str = os.getenv("SKD_NOTIFY_SEND_PATH", "notify-send") self.sound_player: str = os.getenv("SKD_SOUND_PLAYER", "paplay") From 2d829b74dd79f6bcad83ceefa214e953ad34b065 Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 28 Dec 2025 23:44:00 +0100 Subject: [PATCH 04/13] docs: add update client flow --- CHANGELOG.md | 2 + docs/update-client.md | 44 +++++++++++++++++++ .../requirements/tasks/TASK_000028.md | 4 +- 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 docs/update-client.md diff --git a/CHANGELOG.md b/CHANGELOG.md index c13e86e..a02a301 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ By: Codex (GPT-5) | 28.12.2025 | ๐Ÿ—๏ธ Planning | ID: EPIC_000009 Update Webservice (External Team) dokumentiert. By: Codex (GPT-5) | | 28.12.2025 | ๐Ÿ—๏ธ Planning | ID: TASK_000027-TASK_000029 fuer EPIC_000008 ausgearbeitet. By: Codex (GPT-5) | | 28.12.2025 | โš™๏ธ Code | ID: Update-Config Keys in Settings/ENV/README definiert. By: Codex (GPT-5) | +| 28.12.2025 | ๐Ÿ“ Req | ID: Update-Flow fuer Client dokumentiert. By: Codex (GPT-5) | +| 28.12.2025 | ๐Ÿ“ Req | ID: Update-Flow Prototyp dokumentiert. By: Codex (GPT-5) | --- ## Legende diff --git a/docs/update-client.md b/docs/update-client.md new file mode 100644 index 0000000..f19a3da --- /dev/null +++ b/docs/update-client.md @@ -0,0 +1,44 @@ +ID: DOC_000004 | Version: 0.1.0 | Status: Draft +By: Codex (GPT-5) + +# Client Update Flow (Kiddo) + +## Purpose +Definiert den Client-seitigen Ablauf fuer das Pull-Update vom Update-Service. + +## Manifest Format (JSON) +Beispiel: +```json +{ + "version": "0.1.2", + "artifact_url": "https://update.wlkns.org/kiddo/kiddo-0.1.2.tar.gz", + "sha256": "", + "sig_url": "https://update.wlkns.org/kiddo/kiddo-0.1.2.sig" +} +``` + +## Flow (High Level) +1. Manifest abrufen (auth optional via Bearer Token). +2. `artifact_url` herunterladen. +3. SHA256 pruefen (Signatur optional). +4. In Staging-Verzeichnis entpacken. +5. Service stoppen. +6. Atomic swap: aktuelles Verzeichnis sichern, Staging nach `/opt/sk` verschieben. +7. Service starten. +8. Bei Fehlern Rollback auf Backup. + +## Prototype Script +- `scripts/update_client.sh` implementiert den Flow als CLI-Prototyp. +- Erfordert `curl`, `tar`, `sha256sum`, `python3` und `systemctl`. + +## Rollback +- Wenn Start fehlschlaegt: Backup nach `/opt/sk` zurueck, Service neu starten. +- Backup-Verzeichnis benoetigt genuegend Speicher. + +## Security Notes +- Artefakte muessen checksum-verifiziert sein. +- Token-Handling ueber `SKD_UPDATE_TOKEN`. + +## Constraints +- Update-Service ist extern (update.wlkns.org). +- Service muss als root stoppen/starten koennen. diff --git a/project-management/requirements/tasks/TASK_000028.md b/project-management/requirements/tasks/TASK_000028.md index 8dacf51..fa055da 100644 --- a/project-management/requirements/tasks/TASK_000028.md +++ b/project-management/requirements/tasks/TASK_000028.md @@ -1,4 +1,4 @@ -ID: TASK_000028 | Version: 0.1.0 | Status: Draft +ID: TASK_000028 | Version: 0.1.0 | Status: In Progress By: Codex (GPT-5) # TASK_000028: Verify and apply update @@ -14,8 +14,10 @@ US_000027 - `tar.gz` herunterladen und SHA256 pruefen. - Update atomar anwenden (staging, swap, rollback). - Fehlerfall dokumentieren (Rollback, Status). +- Ablauf dokumentiert in `docs/update-client.md`. ## Definition of Done (DoD) - Verifikation (Checksum/Signatur) ist beschrieben. - Atomare Anwendung und Rollback-Strategie sind definiert. - Fehlerbilder sind erfasst. +- Prototyp-Skript ist als Referenz vorhanden. From c41a4560c17fbb757dbb658d7dafc87587fefeab Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 28 Dec 2025 23:44:19 +0100 Subject: [PATCH 05/13] code: add update client prototype script --- CHANGELOG.md | 1 + scripts/update_client.sh | 112 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100755 scripts/update_client.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index a02a301..5da2326 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ By: Codex (GPT-5) | 28.12.2025 | โš™๏ธ Code | ID: Update-Config Keys in Settings/ENV/README definiert. By: Codex (GPT-5) | | 28.12.2025 | ๐Ÿ“ Req | ID: Update-Flow fuer Client dokumentiert. By: Codex (GPT-5) | | 28.12.2025 | ๐Ÿ“ Req | ID: Update-Flow Prototyp dokumentiert. By: Codex (GPT-5) | +| 28.12.2025 | โš™๏ธ Code | ID: Update-Client Prototyp-Skript hinzugefuegt. By: Codex (GPT-5) | --- ## Legende diff --git a/scripts/update_client.sh b/scripts/update_client.sh new file mode 100755 index 0000000..b9aea5c --- /dev/null +++ b/scripts/update_client.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +set -euo pipefail + +SERVICE_NAME="${SERVICE_NAME:-skd}" +UPDATE_URL="${SKD_UPDATE_URL:-https://update.wlkns.org}" +UPDATE_TOKEN="${SKD_UPDATE_TOKEN:-}" +INSTALL_DIR="${INSTALL_DIR:-/opt/sk}" +STAGING_DIR="" +MANIFEST_FILE="" +ARTIFACT_FILE="" +BACKUP_DIR="" + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" +} + +cleanup() { + if [[ -n "${STAGING_DIR}" && -d "${STAGING_DIR}" ]]; then + rm -rf "${STAGING_DIR}" + fi + if [[ -n "${MANIFEST_FILE}" && -f "${MANIFEST_FILE}" ]]; then + rm -f "${MANIFEST_FILE}" + fi + if [[ -n "${ARTIFACT_FILE}" && -f "${ARTIFACT_FILE}" ]]; then + rm -f "${ARTIFACT_FILE}" + fi +} +trap cleanup EXIT + +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 tar +require_cmd sha256sum +require_cmd python3 + +log "Fetching update manifest from ${UPDATE_URL}..." +MANIFEST_FILE="$(mktemp)" +if [[ -n "${UPDATE_TOKEN}" ]]; then + curl -fsS -H "Authorization: Bearer ${UPDATE_TOKEN}" "${UPDATE_URL}" -o "${MANIFEST_FILE}" +else + curl -fsS "${UPDATE_URL}" -o "${MANIFEST_FILE}" +fi + +read_manifest() { + python3 - <<'PY' "${MANIFEST_FILE}" +import json +import sys +path = sys.argv[1] +with open(path, "r", encoding="utf-8") as fh: + data = json.load(fh) +for key in ("version", "artifact_url", "sha256"): + if key not in data: + sys.exit(f"Manifest missing '{key}'") +print(data["version"]) +print(data["artifact_url"]) +print(data["sha256"]) +PY +} + +readarray -t manifest_values < <(read_manifest) +VERSION="${manifest_values[0]}" +ARTIFACT_URL="${manifest_values[1]}" +ARTIFACT_SHA256="${manifest_values[2]}" + +log "Downloading artifact ${ARTIFACT_URL} (version ${VERSION})..." +ARTIFACT_FILE="$(mktemp --suffix=.tar.gz)" +if [[ -n "${UPDATE_TOKEN}" ]]; then + curl -fsS -H "Authorization: Bearer ${UPDATE_TOKEN}" "${ARTIFACT_URL}" -o "${ARTIFACT_FILE}" +else + curl -fsS "${ARTIFACT_URL}" -o "${ARTIFACT_FILE}" +fi + +log "Verifying checksum..." +CALC_SHA="$(sha256sum "${ARTIFACT_FILE}" | awk '{print $1}')" +if [[ "${CALC_SHA}" != "${ARTIFACT_SHA256}" ]]; then + echo "Checksum mismatch: expected ${ARTIFACT_SHA256} got ${CALC_SHA}" >&2 + exit 1 +fi + +STAGING_DIR="$(mktemp -d /opt/sk_update.XXXXXX)" +log "Extracting to staging ${STAGING_DIR}..." + tar -xzf "${ARTIFACT_FILE}" -C "${STAGING_DIR}" + +BACKUP_DIR="/opt/sk_backup_${VERSION}_$(date +%s)" +log "Stopping service ${SERVICE_NAME}..." + sudo systemctl stop "${SERVICE_NAME}.service" + +log "Swapping ${INSTALL_DIR} -> ${BACKUP_DIR}..." +if [[ -d "${INSTALL_DIR}" ]]; then + sudo mv "${INSTALL_DIR}" "${BACKUP_DIR}" +fi +sudo mv "${STAGING_DIR}" "${INSTALL_DIR}" +STAGING_DIR="" + +log "Starting service ${SERVICE_NAME}..." +if sudo systemctl start "${SERVICE_NAME}.service"; then + log "Update applied successfully." +else + log "Service failed to start, rolling back..." + sudo rm -rf "${INSTALL_DIR}" + if [[ -d "${BACKUP_DIR}" ]]; then + sudo mv "${BACKUP_DIR}" "${INSTALL_DIR}" + fi + sudo systemctl start "${SERVICE_NAME}.service" || true + exit 1 +fi From 37087e792530e5ba344d1314cdfb3e60723b4971 Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 28 Dec 2025 23:51:01 +0100 Subject: [PATCH 06/13] docs: define update status reporting --- CHANGELOG.md | 1 + README.md | 1 + docs/update-client.md | 4 +++ docs/update-status.md | 25 +++++++++++++++++++ env.example | 1 + .../requirements/tasks/TASK_000029.md | 3 ++- 6 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 docs/update-status.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 5da2326..c343ab0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ By: Codex (GPT-5) | 28.12.2025 | โš™๏ธ Code | ID: Update-Config Keys in Settings/ENV/README definiert. By: Codex (GPT-5) | | 28.12.2025 | ๐Ÿ“ Req | ID: Update-Flow fuer Client dokumentiert. By: Codex (GPT-5) | | 28.12.2025 | ๐Ÿ“ Req | ID: Update-Flow Prototyp dokumentiert. By: Codex (GPT-5) | +| 28.12.2025 | ๐Ÿ“ Req | ID: Update-Status-Schema dokumentiert. By: Codex (GPT-5) | | 28.12.2025 | โš™๏ธ Code | ID: Update-Client Prototyp-Skript hinzugefuegt. By: Codex (GPT-5) | --- diff --git a/README.md b/README.md index 4a5dbd0..7380a34 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ Set in `/etc/skd/env` (see `env.example`): - `SKD_UPDATE_URL` (default `https://update.wlkns.org`) - `SKD_UPDATE_TOKEN` (API token for update service) - `SKD_UPDATE_INTERVAL` (seconds; default 3600) + - `SKD_UPDATE_STATUS_URL` (default `https://update.wlkns.org/status`) Notes: - `./scripts/install.sh` will create `/etc/skd/env` from `env.example` if missing (edit afterwards) and ensure the `skd` service user/group exist. diff --git a/docs/update-client.md b/docs/update-client.md index f19a3da..35fb815 100644 --- a/docs/update-client.md +++ b/docs/update-client.md @@ -42,3 +42,7 @@ Beispiel: ## Constraints - Update-Service ist extern (update.wlkns.org). - Service muss als root stoppen/starten koennen. + +## Status Reporting +- Status wird per HTTP POST an `https://update.wlkns.org/status` gemeldet. +- Schema siehe `docs/update-status.md`. diff --git a/docs/update-status.md b/docs/update-status.md new file mode 100644 index 0000000..a83af27 --- /dev/null +++ b/docs/update-status.md @@ -0,0 +1,25 @@ +ID: DOC_000005 | Version: 0.1.0 | Status: Draft +By: Codex (GPT-5) + +# Update Status Reporting + +## Purpose +Definiert das Status-Schema fuer Update-Resultate und den Uebertragungsweg. + +## Status Schema (JSON) +```json +{ + "device_id": "", + "version": "0.1.2", + "status": "success|failed", + "error": "", + "timestamp": "2025-12-28T12:34:56Z" +} +``` + +## Transport +- HTTP POST an `https://update.wlkns.org/status` +- Auth: Bearer Token (`SKD_UPDATE_TOKEN`) + +## Notes +- Statusmeldungen sind best-effort; Fehler beim Senden blockieren kein Update. diff --git a/env.example b/env.example index 9c7368b..a2f1822 100644 --- a/env.example +++ b/env.example @@ -25,5 +25,6 @@ SKD_NOTIFY_TIMEOUT=5 SKD_UPDATE_URL=https://update.wlkns.org SKD_UPDATE_TOKEN= SKD_UPDATE_INTERVAL=3600 +SKD_UPDATE_STATUS_URL=https://update.wlkns.org/status # Set to true to test without performing real system changes SKD_DRY_RUN=false diff --git a/project-management/requirements/tasks/TASK_000029.md b/project-management/requirements/tasks/TASK_000029.md index 37ae192..39fdd76 100644 --- a/project-management/requirements/tasks/TASK_000029.md +++ b/project-management/requirements/tasks/TASK_000029.md @@ -1,4 +1,4 @@ -ID: TASK_000029 | Version: 0.1.0 | Status: Draft +ID: TASK_000029 | Version: 0.1.0 | Status: In Progress By: Codex (GPT-5) # TASK_000029: Report update status @@ -13,6 +13,7 @@ US_000028 - Status-Schema definieren (Version, Ergebnis, Fehler, Zeitpunkt). - Transportweg festlegen (z.B. HTTP POST oder Log-Export). - Erfolg/Fehler konsistent dokumentieren. +- Schema dokumentiert in `docs/update-status.md`. ## Definition of Done (DoD) - Status-Schema dokumentiert. From 132604de2dbda32564b6162cbe9f08fc25a4cb21 Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 28 Dec 2025 23:51:20 +0100 Subject: [PATCH 07/13] code: report update status --- CHANGELOG.md | 1 + backend/settings.py | 3 +++ scripts/update_client.sh | 22 ++++++++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c343ab0..e76f8c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ By: Codex (GPT-5) | 28.12.2025 | ๐Ÿ“ Req | ID: Update-Flow fuer Client dokumentiert. By: Codex (GPT-5) | | 28.12.2025 | ๐Ÿ“ Req | ID: Update-Flow Prototyp dokumentiert. By: Codex (GPT-5) | | 28.12.2025 | ๐Ÿ“ Req | ID: Update-Status-Schema dokumentiert. By: Codex (GPT-5) | +| 28.12.2025 | โš™๏ธ Code | ID: Update-Status-URL und Report im Client-Prototyp. By: Codex (GPT-5) | | 28.12.2025 | โš™๏ธ Code | ID: Update-Client Prototyp-Skript hinzugefuegt. By: Codex (GPT-5) | --- diff --git a/backend/settings.py b/backend/settings.py index 1878e03..73a2ba7 100644 --- a/backend/settings.py +++ b/backend/settings.py @@ -42,6 +42,9 @@ class Settings: self.update_url: str = os.getenv("SKD_UPDATE_URL", "https://update.wlkns.org") self.update_token: str = os.getenv("SKD_UPDATE_TOKEN", "") self.update_interval: int = int(os.getenv("SKD_UPDATE_INTERVAL", "3600")) + self.update_status_url: str = os.getenv( + "SKD_UPDATE_STATUS_URL", "https://update.wlkns.org/status" + ) # Paths/tools self.notify_send_path: str = os.getenv("SKD_NOTIFY_SEND_PATH", "notify-send") self.sound_player: str = os.getenv("SKD_SOUND_PLAYER", "paplay") diff --git a/scripts/update_client.sh b/scripts/update_client.sh index b9aea5c..2e0195d 100755 --- a/scripts/update_client.sh +++ b/scripts/update_client.sh @@ -4,6 +4,7 @@ set -euo pipefail SERVICE_NAME="${SERVICE_NAME:-skd}" UPDATE_URL="${SKD_UPDATE_URL:-https://update.wlkns.org}" UPDATE_TOKEN="${SKD_UPDATE_TOKEN:-}" +STATUS_URL="${SKD_UPDATE_STATUS_URL:-https://update.wlkns.org/status}" INSTALL_DIR="${INSTALL_DIR:-/opt/sk}" STAGING_DIR="" MANIFEST_FILE="" @@ -14,6 +15,24 @@ log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" } +report_status() { + local status="$1" + local error="${2:-}" + local timestamp + timestamp="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" + local payload + payload=$(cat </dev/null || true + else + curl -sS -X POST -H "Content-Type: application/json" -d "${payload}" "${STATUS_URL}" >/dev/null || true + fi +} + cleanup() { if [[ -n "${STAGING_DIR}" && -d "${STAGING_DIR}" ]]; then rm -rf "${STAGING_DIR}" @@ -80,6 +99,7 @@ log "Verifying checksum..." CALC_SHA="$(sha256sum "${ARTIFACT_FILE}" | awk '{print $1}')" if [[ "${CALC_SHA}" != "${ARTIFACT_SHA256}" ]]; then echo "Checksum mismatch: expected ${ARTIFACT_SHA256} got ${CALC_SHA}" >&2 + report_status "failed" "checksum mismatch" exit 1 fi @@ -101,6 +121,7 @@ STAGING_DIR="" log "Starting service ${SERVICE_NAME}..." if sudo systemctl start "${SERVICE_NAME}.service"; then log "Update applied successfully." + report_status "success" "" else log "Service failed to start, rolling back..." sudo rm -rf "${INSTALL_DIR}" @@ -108,5 +129,6 @@ else sudo mv "${BACKUP_DIR}" "${INSTALL_DIR}" fi sudo systemctl start "${SERVICE_NAME}.service" || true + report_status "failed" "service start failed" exit 1 fi From 312365a5f6248b1f99e3e1beaec134298613f2eb Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 28 Dec 2025 23:59:26 +0100 Subject: [PATCH 08/13] docs: require backup before update/rollback --- CHANGELOG.md | 3 +++ .../requirements/stories/US_000031.md | 19 +++++++++++++++++++ .../requirements/stories/US_000033.md | 19 +++++++++++++++++++ 3 files changed, 41 insertions(+) create mode 100644 project-management/requirements/stories/US_000031.md create mode 100644 project-management/requirements/stories/US_000033.md diff --git a/CHANGELOG.md b/CHANGELOG.md index e76f8c5..c22fe7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,9 @@ By: Codex (GPT-5) | 28.12.2025 | ๐Ÿ“ Req | ID: Update-Flow Prototyp dokumentiert. By: Codex (GPT-5) | | 28.12.2025 | ๐Ÿ“ Req | ID: Update-Status-Schema dokumentiert. By: Codex (GPT-5) | | 28.12.2025 | โš™๏ธ Code | ID: Update-Status-URL und Report im Client-Prototyp. By: Codex (GPT-5) | +| 28.12.2025 | ๐Ÿ—๏ธ Planning | ID: US_000029-US_000032 fuer Update-UI dokumentiert. By: Codex (GPT-5) | +| 28.12.2025 | ๐Ÿ—๏ธ Planning | ID: US_000033 Rollback-UI dokumentiert. By: Codex (GPT-5) | +| 28.12.2025 | ๐Ÿ—๏ธ Planning | ID: Update-UI Anforderungen um Backup-Voraussetzung ergaenzt. By: Codex (GPT-5) | | 28.12.2025 | โš™๏ธ Code | ID: Update-Client Prototyp-Skript hinzugefuegt. By: Codex (GPT-5) | --- diff --git a/project-management/requirements/stories/US_000031.md b/project-management/requirements/stories/US_000031.md new file mode 100644 index 0000000..7482775 --- /dev/null +++ b/project-management/requirements/stories/US_000031.md @@ -0,0 +1,19 @@ +ID: US_000031 | Version: 0.1.0 | Status: Draft +By: Codex (GPT-5) + +# US_000031: Update im Web-UI anstossen + +Status: Draft + +Als Admin moechte ich ein Update im Web-UI anstossen, damit der Client die neue Version installiert. + +## Akzeptanzkriterien +- Given ein Update ist verfuegbar +- And eine vorherige Version ist gesichert oder wird vor dem Start gesichert +- When ich "Update installieren" ausloese +- Then wird der Update-Client gestartet +- And der Fortschritt/Status wird im UI angezeigt +- And Fehler werden klar im UI gemeldet + +## Task-Platzhalter +- TASK_000032: UI update apply action (Details bei Story-Start) diff --git a/project-management/requirements/stories/US_000033.md b/project-management/requirements/stories/US_000033.md new file mode 100644 index 0000000..1c52ff3 --- /dev/null +++ b/project-management/requirements/stories/US_000033.md @@ -0,0 +1,19 @@ +ID: US_000033 | Version: 0.1.0 | Status: Draft +By: Codex (GPT-5) + +# US_000033: Rollback im Web-UI anstossen + +Status: Draft + +Als Admin moechte ich einen Rollback im Web-UI anstossen, damit ich nach einem fehlerhaften Update schnell zur letzten Version zurueckkehre. + +## Akzeptanzkriterien +- Given ein vorheriger Update-Stand ist verfuegbar und wurde gesichert +- And der Rollback darf nur starten, wenn das Backup verfuegbar ist +- When ich "Rollback" ausloese +- Then wird der Rollback-Mechanismus gestartet +- And der Status/Fortschritt wird im UI angezeigt +- And Fehler werden klar im UI gemeldet + +## Task-Platzhalter +- TASK_000034: UI rollback action (Details bei Story-Start) From 2fc189a467b39a4c0bff8a57af732fba73364558 Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 29 Dec 2025 00:01:22 +0100 Subject: [PATCH 09/13] docs: start update ui stories --- CHANGELOG.md | 1 + project-management/PROJECT_STATUS.md | 10 ++++++++++ .../requirements/epics/EPIC_000008.md | 5 +++++ .../requirements/stories/US_000029.md | 17 ++++++++++++++++ .../requirements/stories/US_000030.md | 17 ++++++++++++++++ .../requirements/stories/US_000031.md | 2 +- .../requirements/stories/US_000032.md | 17 ++++++++++++++++ .../requirements/stories/US_000033.md | 2 +- .../requirements/tasks/TASK_000030.md | 18 +++++++++++++++++ .../requirements/tasks/TASK_000031.md | 18 +++++++++++++++++ .../requirements/tasks/TASK_000032.md | 20 +++++++++++++++++++ .../requirements/tasks/TASK_000033.md | 18 +++++++++++++++++ .../requirements/tasks/TASK_000034.md | 19 ++++++++++++++++++ 13 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 project-management/requirements/stories/US_000029.md create mode 100644 project-management/requirements/stories/US_000030.md create mode 100644 project-management/requirements/stories/US_000032.md create mode 100644 project-management/requirements/tasks/TASK_000030.md create mode 100644 project-management/requirements/tasks/TASK_000031.md create mode 100644 project-management/requirements/tasks/TASK_000032.md create mode 100644 project-management/requirements/tasks/TASK_000033.md create mode 100644 project-management/requirements/tasks/TASK_000034.md diff --git a/CHANGELOG.md b/CHANGELOG.md index c22fe7c..31ab832 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ By: Codex (GPT-5) | 28.12.2025 | ๐Ÿ—๏ธ Planning | ID: US_000029-US_000032 fuer Update-UI dokumentiert. By: Codex (GPT-5) | | 28.12.2025 | ๐Ÿ—๏ธ Planning | ID: US_000033 Rollback-UI dokumentiert. By: Codex (GPT-5) | | 28.12.2025 | ๐Ÿ—๏ธ Planning | ID: Update-UI Anforderungen um Backup-Voraussetzung ergaenzt. By: Codex (GPT-5) | +| 28.12.2025 | ๐Ÿ—๏ธ Planning | ID: TASK_000030-TASK_000034 fuer Update-UI ausgearbeitet. By: Codex (GPT-5) | | 28.12.2025 | โš™๏ธ Code | ID: Update-Client Prototyp-Skript hinzugefuegt. By: Codex (GPT-5) | --- diff --git a/project-management/PROJECT_STATUS.md b/project-management/PROJECT_STATUS.md index 0d31036..6e8235c 100644 --- a/project-management/PROJECT_STATUS.md +++ b/project-management/PROJECT_STATUS.md @@ -92,6 +92,16 @@ Sicheres, remote steuerbares System zum Sperren/Entsperren lokaler Nutzerkonten. - [ ] TASK_000028: Verify and apply update - [ ] US_000028: Client meldet Update-Status - [ ] TASK_000029: Report update status +- [ ] US_000029: Update-Status im Web-UI anzeigen +- [ ] TASK_000030: UI update status view +- [ ] US_000030: Update-Check im Web-UI ausloesen +- [ ] TASK_000031: UI update check trigger +- [ ] US_000031: Update im Web-UI anstossen +- [ ] TASK_000032: UI update apply action +- [ ] US_000032: Update-Logs im Web-UI anzeigen +- [ ] TASK_000033: UI update logs view +- [ ] US_000033: Rollback im Web-UI anstossen +- [ ] TASK_000034: UI rollback action ### EPIC_000009: Update Webservice (External Team) - [ ] US_000026: Client bezieht Updates (Pull) diff --git a/project-management/requirements/epics/EPIC_000008.md b/project-management/requirements/epics/EPIC_000008.md index 39aeb13..e0bd41c 100644 --- a/project-management/requirements/epics/EPIC_000008.md +++ b/project-management/requirements/epics/EPIC_000008.md @@ -39,3 +39,8 @@ Ermoegliche einen robusten Client-Update-Flow mit Verifikation und Rollback. - US_000026: Client bezieht Updates (Pull) - US_000027: Client verifiziert und wendet Updates an - US_000028: Client meldet Update-Status +- US_000029: Update-Status im Web-UI anzeigen +- US_000030: Update-Check im Web-UI ausloesen +- US_000031: Update im Web-UI anstossen +- US_000032: Update-Logs im Web-UI anzeigen +- US_000033: Rollback im Web-UI anstossen diff --git a/project-management/requirements/stories/US_000029.md b/project-management/requirements/stories/US_000029.md new file mode 100644 index 0000000..f6c3c08 --- /dev/null +++ b/project-management/requirements/stories/US_000029.md @@ -0,0 +1,17 @@ +ID: US_000029 | Version: 0.1.0 | Status: Draft +By: Codex (GPT-5) + +# US_000029: Update-Status im Web-UI anzeigen + +Status: In Progress + +Als Admin moechte ich die aktuelle Version und den Update-Status im Web-UI sehen, damit ich den Zustand schnell pruefen kann. + +## Akzeptanzkriterien +- Given die Web-UI ist erreichbar +- When ich den Update-Bereich aufrufe +- Then ich sehe die aktuell laufende Version +- And ich sehe den letzten Update-Status (success/failed) mit Zeitstempel + +## Task-Platzhalter +- TASK_000030: UI update status view (Details bei Story-Start) diff --git a/project-management/requirements/stories/US_000030.md b/project-management/requirements/stories/US_000030.md new file mode 100644 index 0000000..a9233dc --- /dev/null +++ b/project-management/requirements/stories/US_000030.md @@ -0,0 +1,17 @@ +ID: US_000030 | Version: 0.1.0 | Status: Draft +By: Codex (GPT-5) + +# US_000030: Update-Check im Web-UI ausloesen + +Status: In Progress + +Als Admin moechte ich manuell nach Updates suchen koennen, damit ich Updates sofort pruefen kann. + +## Akzeptanzkriterien +- Given die Web-UI ist erreichbar +- When ich auf "Nach Updates suchen" klicke +- Then wird ein Check gegen `https://update.wlkns.org` gestartet +- And das Ergebnis (neue Version verfuegbar/keine Updates/Fehler) wird angezeigt + +## Task-Platzhalter +- TASK_000031: UI update check trigger (Details bei Story-Start) diff --git a/project-management/requirements/stories/US_000031.md b/project-management/requirements/stories/US_000031.md index 7482775..bc26f3b 100644 --- a/project-management/requirements/stories/US_000031.md +++ b/project-management/requirements/stories/US_000031.md @@ -3,7 +3,7 @@ By: Codex (GPT-5) # US_000031: Update im Web-UI anstossen -Status: Draft +Status: In Progress Als Admin moechte ich ein Update im Web-UI anstossen, damit der Client die neue Version installiert. diff --git a/project-management/requirements/stories/US_000032.md b/project-management/requirements/stories/US_000032.md new file mode 100644 index 0000000..ffba7e3 --- /dev/null +++ b/project-management/requirements/stories/US_000032.md @@ -0,0 +1,17 @@ +ID: US_000032 | Version: 0.1.0 | Status: Draft +By: Codex (GPT-5) + +# US_000032: Update-Logs im Web-UI anzeigen + +Status: In Progress + +Als Admin moechte ich Update-Logs im Web-UI einsehen, damit Fehler nachvollziehbar sind. + +## Akzeptanzkriterien +- Given ein Update-Versuch wurde ausgefuehrt +- When ich die Update-Logs oeffne +- Then sehe ich eine chronologische Liste mit Zeitstempel und Ergebnis +- And sensible Daten (Tokens) werden nicht angezeigt + +## Task-Platzhalter +- TASK_000033: UI update logs view (Details bei Story-Start) diff --git a/project-management/requirements/stories/US_000033.md b/project-management/requirements/stories/US_000033.md index 1c52ff3..c7760ff 100644 --- a/project-management/requirements/stories/US_000033.md +++ b/project-management/requirements/stories/US_000033.md @@ -3,7 +3,7 @@ By: Codex (GPT-5) # US_000033: Rollback im Web-UI anstossen -Status: Draft +Status: In Progress Als Admin moechte ich einen Rollback im Web-UI anstossen, damit ich nach einem fehlerhaften Update schnell zur letzten Version zurueckkehre. diff --git a/project-management/requirements/tasks/TASK_000030.md b/project-management/requirements/tasks/TASK_000030.md new file mode 100644 index 0000000..1d3f108 --- /dev/null +++ b/project-management/requirements/tasks/TASK_000030.md @@ -0,0 +1,18 @@ +ID: TASK_000030 | Version: 0.1.0 | Status: In Progress +By: Codex (GPT-5) + +# TASK_000030: UI update status view + +## Outcome +Web-UI zeigt aktuelle Version und letzten Update-Status. + +## Story-Bezug +US_000029 + +## Beschreibung +- Update-Status und Version im UI anzeigen. +- Letzten Status mit Zeitstempel visualisieren. + +## Definition of Done (DoD) +- UI zeigt Version + letzten Status. +- Fehler/keine Daten werden sauber angezeigt. diff --git a/project-management/requirements/tasks/TASK_000031.md b/project-management/requirements/tasks/TASK_000031.md new file mode 100644 index 0000000..88daf3c --- /dev/null +++ b/project-management/requirements/tasks/TASK_000031.md @@ -0,0 +1,18 @@ +ID: TASK_000031 | Version: 0.1.0 | Status: In Progress +By: Codex (GPT-5) + +# TASK_000031: UI update check trigger + +## Outcome +Web-UI kann einen Update-Check gegen `https://update.wlkns.org` ausloesen. + +## Story-Bezug +US_000030 + +## Beschreibung +- Button/Action fuer "Nach Updates suchen". +- Ergebnisanzeige (Update verfuegbar/keine Updates/Fehler). + +## Definition of Done (DoD) +- UI zeigt Ergebnis des Update-Checks. +- Fehler werden klar angezeigt. diff --git a/project-management/requirements/tasks/TASK_000032.md b/project-management/requirements/tasks/TASK_000032.md new file mode 100644 index 0000000..cbeea74 --- /dev/null +++ b/project-management/requirements/tasks/TASK_000032.md @@ -0,0 +1,20 @@ +ID: TASK_000032 | Version: 0.1.0 | Status: In Progress +By: Codex (GPT-5) + +# TASK_000032: UI update apply action + +## Outcome +Web-UI kann ein Update installieren. + +## Story-Bezug +US_000031 + +## Beschreibung +- "Update installieren" Action. +- Vor Start Backup-Pruefung/Backup-Erstellung erzwingen. +- Fortschritt/Status im UI. + +## Definition of Done (DoD) +- Update startet nur mit vorhandenem Backup. +- UI zeigt Fortschritt/Status. +- Fehler sind sichtbar. diff --git a/project-management/requirements/tasks/TASK_000033.md b/project-management/requirements/tasks/TASK_000033.md new file mode 100644 index 0000000..f50de1b --- /dev/null +++ b/project-management/requirements/tasks/TASK_000033.md @@ -0,0 +1,18 @@ +ID: TASK_000033 | Version: 0.1.0 | Status: In Progress +By: Codex (GPT-5) + +# TASK_000033: UI update logs view + +## Outcome +Web-UI zeigt Update-Logs chronologisch. + +## Story-Bezug +US_000032 + +## Beschreibung +- Logliste mit Zeitstempel und Ergebnis. +- Keine sensiblen Daten anzeigen. + +## Definition of Done (DoD) +- Logs sind sichtbar und chronologisch sortiert. +- Sensible Daten sind ausgefiltert. diff --git a/project-management/requirements/tasks/TASK_000034.md b/project-management/requirements/tasks/TASK_000034.md new file mode 100644 index 0000000..dcab0cc --- /dev/null +++ b/project-management/requirements/tasks/TASK_000034.md @@ -0,0 +1,19 @@ +ID: TASK_000034 | Version: 0.1.0 | Status: In Progress +By: Codex (GPT-5) + +# TASK_000034: UI rollback action + +## Outcome +Web-UI kann einen Rollback ausloesen. + +## Story-Bezug +US_000033 + +## Beschreibung +- "Rollback" Action. +- Nur aktiv, wenn Backup vorhanden. +- Fortschritt/Status im UI. + +## Definition of Done (DoD) +- Rollback startet nur bei verfuegbarem Backup. +- UI zeigt Status/Fehler. From fde207a3e6d667b1ef1c65185448b67bcdebc5fd Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 29 Dec 2025 00:08:16 +0100 Subject: [PATCH 10/13] docs: define update backend api --- CHANGELOG.md | 1 + docs/update-api.md | 60 +++++++++++++++++++ .../requirements/tasks/TASK_000030.md | 1 + .../requirements/tasks/TASK_000031.md | 1 + .../requirements/tasks/TASK_000032.md | 1 + .../requirements/tasks/TASK_000033.md | 1 + .../requirements/tasks/TASK_000034.md | 1 + 7 files changed, 66 insertions(+) create mode 100644 docs/update-api.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 31ab832..6f1a624 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ By: Codex (GPT-5) | 28.12.2025 | ๐Ÿ—๏ธ Planning | ID: US_000033 Rollback-UI dokumentiert. By: Codex (GPT-5) | | 28.12.2025 | ๐Ÿ—๏ธ Planning | ID: Update-UI Anforderungen um Backup-Voraussetzung ergaenzt. By: Codex (GPT-5) | | 28.12.2025 | ๐Ÿ—๏ธ Planning | ID: TASK_000030-TASK_000034 fuer Update-UI ausgearbeitet. By: Codex (GPT-5) | +| 28.12.2025 | ๐Ÿ“ Req | ID: Update-API Endpunkte dokumentiert. By: Codex (GPT-5) | | 28.12.2025 | โš™๏ธ Code | ID: Update-Client Prototyp-Skript hinzugefuegt. By: Codex (GPT-5) | --- diff --git a/docs/update-api.md b/docs/update-api.md new file mode 100644 index 0000000..5a7b6fd --- /dev/null +++ b/docs/update-api.md @@ -0,0 +1,60 @@ +ID: DOC_000006 | Version: 0.1.0 | Status: Draft +By: Codex (GPT-5) + +# Update API (Kiddo Backend) + +## Purpose +Definiert interne API-Endpunkte fuer Update-Status, Check, Apply, Rollback und Logs. + +## Endpoints + +### GET /update/status +Antwort: +```json +{ + "current_version": "0.1.2", + "last_status": "success|failed|unknown", + "last_error": "", + "last_timestamp": "2025-12-28T12:34:56Z" +} +``` + +### POST /update/check +Antwort: +```json +{ + "available": true, + "latest_version": "0.1.3", + "artifact_url": "https://update.wlkns.org/kiddo/kiddo-0.1.3.tar.gz", + "sha256": "", + "message": "" +} +``` + +### POST /update/apply +Body (optional): +```json +{ "version": "0.1.3" } +``` +Antwort: +```json +{ "started": true, "message": "update started" } +``` + +### POST /update/rollback +Antwort: +```json +{ "started": true, "message": "rollback started" } +``` + +### GET /update/logs +Antwort: +```json +[ + {"timestamp":"2025-12-28T12:34:56Z","status":"success","message":"updated to 0.1.2"} +] +``` + +## Notes +- Alle Endpunkte erfordern Auth (Session/Bearer). +- Apply/Rollback starten async; UI pollt /update/status. diff --git a/project-management/requirements/tasks/TASK_000030.md b/project-management/requirements/tasks/TASK_000030.md index 1d3f108..10b4387 100644 --- a/project-management/requirements/tasks/TASK_000030.md +++ b/project-management/requirements/tasks/TASK_000030.md @@ -12,6 +12,7 @@ US_000029 ## Beschreibung - Update-Status und Version im UI anzeigen. - Letzten Status mit Zeitstempel visualisieren. +- API-Basis: `GET /update/status` (siehe `docs/update-api.md`). ## Definition of Done (DoD) - UI zeigt Version + letzten Status. diff --git a/project-management/requirements/tasks/TASK_000031.md b/project-management/requirements/tasks/TASK_000031.md index 88daf3c..09371fa 100644 --- a/project-management/requirements/tasks/TASK_000031.md +++ b/project-management/requirements/tasks/TASK_000031.md @@ -12,6 +12,7 @@ US_000030 ## Beschreibung - Button/Action fuer "Nach Updates suchen". - Ergebnisanzeige (Update verfuegbar/keine Updates/Fehler). +- API-Basis: `POST /update/check` (siehe `docs/update-api.md`). ## Definition of Done (DoD) - UI zeigt Ergebnis des Update-Checks. diff --git a/project-management/requirements/tasks/TASK_000032.md b/project-management/requirements/tasks/TASK_000032.md index cbeea74..1314b54 100644 --- a/project-management/requirements/tasks/TASK_000032.md +++ b/project-management/requirements/tasks/TASK_000032.md @@ -13,6 +13,7 @@ US_000031 - "Update installieren" Action. - Vor Start Backup-Pruefung/Backup-Erstellung erzwingen. - Fortschritt/Status im UI. +- API-Basis: `POST /update/apply` (siehe `docs/update-api.md`). ## Definition of Done (DoD) - Update startet nur mit vorhandenem Backup. diff --git a/project-management/requirements/tasks/TASK_000033.md b/project-management/requirements/tasks/TASK_000033.md index f50de1b..4705ee1 100644 --- a/project-management/requirements/tasks/TASK_000033.md +++ b/project-management/requirements/tasks/TASK_000033.md @@ -12,6 +12,7 @@ US_000032 ## Beschreibung - Logliste mit Zeitstempel und Ergebnis. - Keine sensiblen Daten anzeigen. +- API-Basis: `GET /update/logs` (siehe `docs/update-api.md`). ## Definition of Done (DoD) - Logs sind sichtbar und chronologisch sortiert. diff --git a/project-management/requirements/tasks/TASK_000034.md b/project-management/requirements/tasks/TASK_000034.md index dcab0cc..7e5e399 100644 --- a/project-management/requirements/tasks/TASK_000034.md +++ b/project-management/requirements/tasks/TASK_000034.md @@ -13,6 +13,7 @@ US_000033 - "Rollback" Action. - Nur aktiv, wenn Backup vorhanden. - Fortschritt/Status im UI. +- API-Basis: `POST /update/rollback` (siehe `docs/update-api.md`). ## Definition of Done (DoD) - Rollback startet nur bei verfuegbarem Backup. From b64cc5981c41f51e2bdf3fff04049e8cdc1b5dcb Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 29 Dec 2025 00:22:16 +0100 Subject: [PATCH 11/13] docs: define update status/log files --- CHANGELOG.md | 1 + README.md | 2 ++ docs/update-client.md | 1 + env.example | 2 ++ 4 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f1a624..f4981b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ By: Codex (GPT-5) | 28.12.2025 | ๐Ÿ—๏ธ Planning | ID: Update-UI Anforderungen um Backup-Voraussetzung ergaenzt. By: Codex (GPT-5) | | 28.12.2025 | ๐Ÿ—๏ธ Planning | ID: TASK_000030-TASK_000034 fuer Update-UI ausgearbeitet. By: Codex (GPT-5) | | 28.12.2025 | ๐Ÿ“ Req | ID: Update-API Endpunkte dokumentiert. By: Codex (GPT-5) | +| 28.12.2025 | ๐Ÿ“ Req | ID: Update-Status/Log Dateien dokumentiert. By: Codex (GPT-5) | | 28.12.2025 | โš™๏ธ Code | ID: Update-Client Prototyp-Skript hinzugefuegt. By: Codex (GPT-5) | --- diff --git a/README.md b/README.md index 7380a34..1ea6ed8 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ Set in `/etc/skd/env` (see `env.example`): - `SKD_UPDATE_TOKEN` (API token for update service) - `SKD_UPDATE_INTERVAL` (seconds; default 3600) - `SKD_UPDATE_STATUS_URL` (default `https://update.wlkns.org/status`) + - `SKD_UPDATE_STATUS_FILE` (default `/var/lib/skd/update_status.json`) + - `SKD_UPDATE_LOG_FILE` (default `/var/lib/skd/update_logs.jsonl`) Notes: - `./scripts/install.sh` will create `/etc/skd/env` from `env.example` if missing (edit afterwards) and ensure the `skd` service user/group exist. diff --git a/docs/update-client.md b/docs/update-client.md index 35fb815..adc1ada 100644 --- a/docs/update-client.md +++ b/docs/update-client.md @@ -46,3 +46,4 @@ Beispiel: ## Status Reporting - Status wird per HTTP POST an `https://update.wlkns.org/status` gemeldet. - Schema siehe `docs/update-status.md`. +- Lokaler Status/Logs liegen unter `/var/lib/skd` (konfigurierbar via ENV). diff --git a/env.example b/env.example index a2f1822..5aae8f8 100644 --- a/env.example +++ b/env.example @@ -26,5 +26,7 @@ SKD_UPDATE_URL=https://update.wlkns.org SKD_UPDATE_TOKEN= SKD_UPDATE_INTERVAL=3600 SKD_UPDATE_STATUS_URL=https://update.wlkns.org/status +SKD_UPDATE_STATUS_FILE=/var/lib/skd/update_status.json +SKD_UPDATE_LOG_FILE=/var/lib/skd/update_logs.jsonl # Set to true to test without performing real system changes SKD_DRY_RUN=false From 107cdabe8d00613b8084a2fa97a80d305d67b440 Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 30 Dec 2025 11:18:34 +0100 Subject: [PATCH 12/13] feat: implement update backend API and client logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- CHANGELOG.md | 4 + CLAUDE.md | 145 ++++++++++++++++++ backend/app.py | 74 ++++++++- backend/models.py | 29 ++++ backend/settings.py | 6 + backend/update.py | 142 +++++++++++++++++ project-management/PROJECT_STATUS.md | 12 +- .../requirements/stories/US_000026.md | 4 +- .../requirements/stories/US_000027.md | 4 +- .../requirements/stories/US_000028.md | 4 +- .../requirements/tasks/TASK_000027.md | 2 +- .../requirements/tasks/TASK_000028.md | 2 +- .../requirements/tasks/TASK_000029.md | 2 +- scripts/rollback_client.sh | 76 +++++++++ scripts/update_client.sh | 43 +++++- 15 files changed, 530 insertions(+), 19 deletions(-) create mode 100644 CLAUDE.md create mode 100644 backend/update.py create mode 100755 scripts/rollback_client.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index f4981b5..fab9693 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,10 @@ By: Codex (GPT-5) | 28.12.2025 | ๐Ÿ“ Req | ID: Update-API Endpunkte dokumentiert. By: Codex (GPT-5) | | 28.12.2025 | ๐Ÿ“ Req | ID: Update-Status/Log Dateien dokumentiert. By: Codex (GPT-5) | | 28.12.2025 | โš™๏ธ Code | ID: Update-Client Prototyp-Skript hinzugefuegt. By: Codex (GPT-5) | +| 29.12.2025 | โš™๏ธ Code | ID: Update-Backend Logik implementiert (backend/update.py). By: Codex (GPT-5) | +| 29.12.2025 | โš™๏ธ Code | ID: Update-Models und Settings erweitert (Models, Status-File Paths). By: Codex (GPT-5) | +| 29.12.2025 | โš™๏ธ Code | ID: Rollback-Script hinzugefuegt (scripts/rollback_client.sh). By: Codex (GPT-5) | +| 30.12.2025 | โš™๏ธ Code | ID: Update-API Endpunkte implementiert (GET /update/status, POST /update/check, POST /update/apply, POST /update/rollback, GET /update/logs). By: Claude Sonnet 4.5 | --- ## Legende diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..ff9e637 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,145 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Safe Kiddo Daemon (SKD) is a FastAPI-based service for managing local user accounts on kids' laptops. It provides account locking/unlocking with countdown notifications, optional sound alerts, and shutdown capabilities. The service exposes a REST API with bearer token auth (PAM or OIDC) and serves a minimal web UI. + +## Commands + +### Development +```bash +# Run the service manually (uses .venv, binds to 0.0.0.0:80) +./scripts/run.sh + +# Install service and dependencies +sudo make install + +# Service management +sudo make up # Start service +sudo make down # Stop service +sudo make update # Pull latest from git, reinstall deps, restart + +# Generate/set API token +make token + +# Health check (requires token) +make healthcheck +``` + +### Installation & Deployment +```bash +# Full install (creates service user, venv, systemd unit, PAM config) +sudo ./scripts/install.sh + +# Deploy to remote host (requires deploy_hosts.yml) +./scripts/deploy.sh + +# Manual update on target +ssh user@target 'cd /opt/sk && ./scripts/update.sh' +``` + +### Testing +```bash +# Python syntax validation +python -m py_compile backend/*.py + +# Run specific tests (no formal test runner yet; tests/ is empty) +# Use curl for API testing: +token=$(curl -s -X POST -H "Content-Type: application/json" \ + -d '{"username":"root","password":"..."}' \ + http://localhost/login | jq -r .token) +curl -H "Authorization: Bearer $token" http://localhost/users +``` + +## Architecture + +### Core Structure +- `backend/`: FastAPI application + - `app.py`: Main FastAPI app with route handlers + - `actions.py`: User management actions (lock/unlock, notifications, shutdown logic) + - `auth.py`: PAM authentication, JWT tokens, user/group authorization checks + - `oidc.py`: OIDC client (dynamic discovery, token exchange, claims validation) + - `update.py`: Update client logic (check/status/logs, triggers async update/rollback scripts) + - `settings.py`: Environment-based configuration (Settings class, singleton via lru_cache) + - `models.py`: Pydantic models for API requests/responses + - `templates/`: Jinja2 templates for web UI +- `scripts/`: Deployment and lifecycle scripts + - `install.sh`: System setup (user, venv, systemd, PAM config) + - `run.sh`: Manual service start + - `update.sh`: Local git pull and service restart + - `update_client.sh`: Full update flow with backup/rollback + - `rollback_client.sh`: Restore from backup if update fails + - `deploy.sh`: SSH-based deployment to remote hosts + - `register_oidc_client.sh`: OIDC dynamic client registration helper +- `sk.sh`: Legacy bash script (CLI fallback for direct SSH use) +- `src/`: Hexagonal architecture skeleton (core/ports/adapters/ui) - currently empty placeholders +- `docs/`: Detailed specs for OIDC validation, update API, status/log formats +- `Makefile`: Convenience targets for install, service control, updates, token management + +### Key Architectural Patterns + +**Dual Authentication**: PAM-based local auth (root/sudo users) is always available; OIDC is optional if `SKD_OIDC_ISSUER`, `SKD_OIDC_CLIENT_ID`, and `SKD_OIDC_CLIENT_SECRET` are configured. Both modes issue JWT bearer tokens. + +**Settings Management**: All config via environment variables (loaded from `/etc/skd/env` in production). `settings.py` provides a singleton `Settings` instance via `get_settings()` using `lru_cache`. FastAPI dependencies inject settings into route handlers. + +**Action Execution**: `actions.py` wraps all privileged operations (usermod, pkill, shutdown) via `_run()` helper. Dry-run mode (`SKD_DRY_RUN=true`) logs commands without executing them. + +**Update Flow**: `update.py` checks remote update service for new versions, writes status to JSON files, and triggers async scripts (`update_client.sh`, `rollback_client.sh`) that create backups, apply updates, and handle rollbacks on failure. + +**Authorization**: `auth.py` checks both user allowlists (`SKD_AUTH_ALLOWED_USERS`) and group membership (`SKD_AUTH_ALLOWED_GROUPS`, defaults to `sudo`). UID 0 (root) always allowed for PAM. OIDC validates against `preferred_username`, `email`, or `sub` claims. + +**Manageable Users**: Only system users with UID >= 1000, real shells (not nologin/false), and optional allowlist (`SKD_ALLOWED_USERS`) are exposed via API. Root accounts are never manageable. + +## Configuration + +Deployment config lives in `/etc/skd/env` (see `env.example` in repo root): +- `SKD_AUTH_SECRET`: HMAC secret for JWT signing (must be strong in production) +- `SKD_AUTH_ALLOWED_USERS`: Comma-separated user allowlist (for login and OIDC claims) +- `SKD_AUTH_ALLOWED_GROUPS`: Groups whose members may log in (PAM only, default `sudo`) +- `SKD_AUTH_PAM_SERVICE`: PAM service name (Ubuntu/Debian use `skd`, others may use `login`) +- `SKD_OIDC_*`: OIDC provider config (ISSUER, CLIENT_ID, CLIENT_SECRET, REDIRECT_URI, SCOPES) +- `SKD_ALLOWED_USERS`: Comma-separated list of manageable system accounts (optional) +- `SKD_DRY_RUN`: If `true`, logs all privileged commands without executing +- `SKD_UPDATE_*`: Update service URL, token, interval, status/log file paths + +## Important Workflows + +### Disable User Flow +1. API call to `/users/{username}/disable` with optional `{countdown, sound, message}` +2. `actions.disable_user()` locks account via `usermod -L` +3. If user logged in: sends desktop notifications, plays sound (if enabled), countdown loop with periodic reminders +4. Terminates sessions via `pkill -KILL -u` +5. Triggers `shutdown now` only if user was logged in + +### OIDC Login Flow +1. User accesses `/login/oidc/start` โ†’ redirected to provider with state cookie +2. Provider redirects to `/login/oidc/callback` with code + state +3. Validates state, exchanges code for tokens, extracts username from claims +4. Issues JWT session cookie if user in allowlist + +### Update Flow +1. `check_update()` polls remote update service for latest manifest (version, artifact_url, sha256) +2. `start_update()` writes "in_progress" status, launches `update_client.sh` in background +3. Script creates backup, downloads artifact, verifies checksum, installs, restarts service +4. On failure: `rollback_client.sh` restores from backup +5. Status/logs written to JSON files at `SKD_UPDATE_STATUS_FILE` and `SKD_UPDATE_LOG_FILE` + +## Security Considerations + +- Service runs as root by default (required for PAM, usermod, pkill, shutdown). Limit exposure via firewall. +- Set strong `SKD_AUTH_SECRET` and rotate by changing value + restarting service. +- Restrict API/Web UI to LAN/VPN; consider mTLS or IP allowlisting. +- `skd` user/group created by install script; consider sudoers rules to limit privileges to specific commands. +- OIDC redirect URI must match exactly (no wildcards); re-register client if host/port changes. +- Validate TLS certificates in production; self-signed certs require CA trust or fallback to PAM. + +## Notes + +- Legacy `sk.sh` remains for emergency CLI fallback; API is preferred for all operations. +- `src/` hexagonal architecture skeleton is currently unused; logic lives in `backend/`. +- `tests/` directory exists but is empty; use manual curl-based API testing. +- Both German and English comments exist in code; favor English going forward. +- Deployment via `deploy.sh` supports both YAML (`deploy_hosts.yml`) and JSON host configs. +- PAM service file (`/etc/pam.d/skd`) created by `scripts/install.sh` on Ubuntu/Debian; other distros may need manual setup. diff --git a/backend/app.py b/backend/app.py index b8e83e9..6dc1fa9 100644 --- a/backend/app.py +++ b/backend/app.py @@ -15,9 +15,20 @@ from backend.auth import ( issue_token, list_manageable_users, ) -from backend.models import ActionRequest, ActionResponse, LoginRequest, LoginResponse, UserStatus +from backend.models import ( + ActionRequest, + ActionResponse, + LoginRequest, + LoginResponse, + UpdateActionResponse, + UpdateCheckResponse, + UpdateLogEntry, + UpdateStatus, + UserStatus, +) from backend.oidc import OIDCClient, OIDCError from backend.settings import Settings, get_settings +from backend import update logging.basicConfig( level=logging.INFO, @@ -219,6 +230,67 @@ def enable_user( ) +@app.get("/update/status", response_model=UpdateStatus, dependencies=[Depends(get_current_admin)]) +def update_status(settings: Settings = Depends(get_settings)) -> UpdateStatus: + status_data = update.get_status(settings) + return UpdateStatus(**status_data) + + +@app.post("/update/check", response_model=UpdateCheckResponse, dependencies=[Depends(get_current_admin)]) +def update_check(settings: Settings = Depends(get_settings)) -> UpdateCheckResponse: + try: + check_data = update.check_update(settings) + except Exception as exc: + logger.exception("Update check failed") + raise HTTPException( + status_code=status.HTTP_503_SERVICE_UNAVAILABLE, + detail=f"Update check failed: {str(exc)}", + ) from exc + return UpdateCheckResponse(**check_data) + + +@app.post("/update/apply", response_model=UpdateActionResponse, dependencies=[Depends(get_current_admin)]) +def update_apply( + settings: Settings = Depends(get_settings), + payload: dict | None = Body(default=None), +) -> UpdateActionResponse: + version = payload.get("version") if payload else None + try: + update.start_update(settings, version) + except Exception as exc: + logger.exception("Failed to start update") + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Failed to start update: {str(exc)}", + ) from exc + return UpdateActionResponse(started=True, message="Update started") + + +@app.post("/update/rollback", response_model=UpdateActionResponse, dependencies=[Depends(get_current_admin)]) +def update_rollback(settings: Settings = Depends(get_settings)) -> UpdateActionResponse: + try: + update.start_rollback(settings) + except Exception as exc: + logger.exception("Failed to start rollback") + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Failed to start rollback: {str(exc)}", + ) from exc + return UpdateActionResponse(started=True, message="Rollback started") + + +@app.get("/update/logs", dependencies=[Depends(get_current_admin)]) +def update_logs(settings: Settings = Depends(get_settings), limit: int = 200) -> list[dict]: + try: + return update.get_logs(settings, limit) + except Exception as exc: + logger.exception("Failed to retrieve update logs") + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Failed to retrieve logs: {str(exc)}", + ) from exc + + @app.get("/", response_class=HTMLResponse) def index(request: Request) -> HTMLResponse: return templates.TemplateResponse("index.html", {"request": request}) diff --git a/backend/models.py b/backend/models.py index b290194..d6f83ca 100644 --- a/backend/models.py +++ b/backend/models.py @@ -34,3 +34,32 @@ class LoginRequest(BaseModel): class LoginResponse(BaseModel): token: str expires_in: int + + +class UpdateStatus(BaseModel): + current_version: str + last_status: str + last_error: Optional[str] = None + last_timestamp: Optional[str] = None + + +class UpdateCheckResponse(BaseModel): + available: bool + latest_version: str + artifact_url: str + sha256: str + message: Optional[str] = None + + +class UpdateActionResponse(BaseModel): + started: bool + message: str + + +class UpdateLogEntry(BaseModel): + timestamp: str + status: str + message: Optional[str] = None + version: Optional[str] = None + error: Optional[str] = None + device_id: Optional[str] = None diff --git a/backend/settings.py b/backend/settings.py index 73a2ba7..8d7663a 100644 --- a/backend/settings.py +++ b/backend/settings.py @@ -45,6 +45,12 @@ class Settings: self.update_status_url: str = os.getenv( "SKD_UPDATE_STATUS_URL", "https://update.wlkns.org/status" ) + self.update_status_file: str = os.getenv( + "SKD_UPDATE_STATUS_FILE", "/var/lib/skd/update_status.json" + ) + self.update_log_file: str = os.getenv( + "SKD_UPDATE_LOG_FILE", "/var/lib/skd/update_logs.jsonl" + ) # Paths/tools self.notify_send_path: str = os.getenv("SKD_NOTIFY_SEND_PATH", "notify-send") self.sound_player: str = os.getenv("SKD_SOUND_PLAYER", "paplay") diff --git a/backend/update.py b/backend/update.py new file mode 100644 index 0000000..8629dc5 --- /dev/null +++ b/backend/update.py @@ -0,0 +1,142 @@ +import json +import os +import subprocess +from datetime import datetime, timezone +from pathlib import Path +from typing import Any, Dict, List + +import httpx + +from backend.settings import Settings + + +def _project_root() -> Path: + return Path(__file__).resolve().parents[1] + + +def _read_version() -> str: + try: + return (_project_root() / "VERSION").read_text(encoding="utf-8").strip() + except OSError: + return "unknown" + + +def _status_path(settings: Settings) -> Path: + return Path(settings.update_status_file) + + +def _log_path(settings: Settings) -> Path: + return Path(settings.update_log_file) + + +def _ensure_parent(path: Path) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + + +def _write_status(settings: Settings, status: str, version: str, error: str | None = None) -> None: + status_path = _status_path(settings) + _ensure_parent(status_path) + payload = { + "device_id": os.uname().nodename, + "version": version, + "status": status, + "error": error or "", + "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), + } + status_path.write_text(json.dumps(payload), encoding="utf-8") + _append_log(settings, payload) + + +def _append_log(settings: Settings, payload: Dict[str, Any]) -> None: + log_path = _log_path(settings) + _ensure_parent(log_path) + with log_path.open("a", encoding="utf-8") as handle: + handle.write(json.dumps(payload) + "\n") + + +def get_status(settings: Settings) -> Dict[str, Any]: + current_version = _read_version() + status_path = _status_path(settings) + if status_path.exists(): + try: + data = json.loads(status_path.read_text(encoding="utf-8")) + except json.JSONDecodeError: + data = {} + else: + data = {} + + return { + "current_version": current_version, + "last_status": data.get("status", "unknown"), + "last_error": data.get("error") or None, + "last_timestamp": data.get("timestamp"), + } + + +def _parse_version(value: str) -> List[int]: + return [int(part) for part in value.split(".")] + + +def check_update(settings: Settings) -> Dict[str, Any]: + headers = {} + if settings.update_token: + headers["Authorization"] = f"Bearer {settings.update_token}" + + with httpx.Client(timeout=10.0) as client: + response = client.get(settings.update_url, headers=headers) + response.raise_for_status() + manifest = response.json() + + latest_version = manifest.get("version", "") + artifact_url = manifest.get("artifact_url", "") + sha256 = manifest.get("sha256", "") + message = manifest.get("message") + + available = False + current_version = _read_version() + try: + available = _parse_version(latest_version) > _parse_version(current_version) + except ValueError: + if latest_version and latest_version != current_version: + available = True + + return { + "available": available, + "latest_version": latest_version, + "artifact_url": artifact_url, + "sha256": sha256, + "message": message, + } + + +def _run_async(script_path: Path, settings: Settings) -> None: + env = os.environ.copy() + env["SKD_UPDATE_STATUS_FILE"] = settings.update_status_file + env["SKD_UPDATE_LOG_FILE"] = settings.update_log_file + subprocess.Popen([str(script_path)], env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + +def start_update(settings: Settings, version: str | None = None) -> None: + _write_status(settings, "in_progress", version or _read_version()) + script = _project_root() / "scripts" / "update_client.sh" + _run_async(script, settings) + + +def start_rollback(settings: Settings) -> None: + _write_status(settings, "in_progress", _read_version()) + script = _project_root() / "scripts" / "rollback_client.sh" + _run_async(script, settings) + + +def get_logs(settings: Settings, limit: int = 200) -> List[Dict[str, Any]]: + log_path = _log_path(settings) + if not log_path.exists(): + return [] + lines = log_path.read_text(encoding="utf-8").splitlines() + entries: List[Dict[str, Any]] = [] + for line in lines[-limit:]: + try: + entries.append(json.loads(line)) + except json.JSONDecodeError: + continue + return entries diff --git a/project-management/PROJECT_STATUS.md b/project-management/PROJECT_STATUS.md index 6e8235c..7c952f7 100644 --- a/project-management/PROJECT_STATUS.md +++ b/project-management/PROJECT_STATUS.md @@ -86,12 +86,12 @@ Sicheres, remote steuerbares System zum Sperren/Entsperren lokaler Nutzerkonten. - [x] TASK_000023: README runbook notes ### EPIC_000008: Client-Side Update Mechanism -- [ ] US_000026: Client bezieht Updates (Pull) -- [ ] TASK_000027: Update endpoint config -- [ ] US_000027: Client verifiziert und wendet Updates an -- [ ] TASK_000028: Verify and apply update -- [ ] US_000028: Client meldet Update-Status -- [ ] TASK_000029: Report update status +- [x] US_000026: Client bezieht Updates (Pull) +- [x] TASK_000027: Update endpoint config +- [x] US_000027: Client verifiziert und wendet Updates an +- [x] TASK_000028: Verify and apply update +- [x] US_000028: Client meldet Update-Status +- [x] TASK_000029: Report update status - [ ] US_000029: Update-Status im Web-UI anzeigen - [ ] TASK_000030: UI update status view - [ ] US_000030: Update-Check im Web-UI ausloesen diff --git a/project-management/requirements/stories/US_000026.md b/project-management/requirements/stories/US_000026.md index a0b71c8..08c02cc 100644 --- a/project-management/requirements/stories/US_000026.md +++ b/project-management/requirements/stories/US_000026.md @@ -1,9 +1,9 @@ -ID: US_000026 | Version: 0.1.0 | Status: Draft +ID: US_000026 | Version: 0.1.0 | Status: Done By: Codex (GPT-5) # US_000026: Client bezieht Updates (Pull) -Status: In Progress +Status: Done Als Betreiber moechte ich, dass der Client Updates per Pull von einem Update-Service bezieht, damit Deployments ohne SSH moeglich sind. diff --git a/project-management/requirements/stories/US_000027.md b/project-management/requirements/stories/US_000027.md index f0fd242..7a8a24d 100644 --- a/project-management/requirements/stories/US_000027.md +++ b/project-management/requirements/stories/US_000027.md @@ -1,9 +1,9 @@ -ID: US_000027 | Version: 0.1.0 | Status: Draft +ID: US_000027 | Version: 0.1.0 | Status: Done By: Codex (GPT-5) # US_000027: Client verifiziert und wendet Updates an -Status: In Progress +Status: Done Als Betreiber moechte ich, dass der Client Updates verifiziert und sicher anwendet, damit fehlerhafte Pakete keine Ausfaelle verursachen. diff --git a/project-management/requirements/stories/US_000028.md b/project-management/requirements/stories/US_000028.md index fef2260..9a3e182 100644 --- a/project-management/requirements/stories/US_000028.md +++ b/project-management/requirements/stories/US_000028.md @@ -1,9 +1,9 @@ -ID: US_000028 | Version: 0.1.0 | Status: Draft +ID: US_000028 | Version: 0.1.0 | Status: Done By: Codex (GPT-5) # US_000028: Client meldet Update-Status -Status: In Progress +Status: Done Als Betreiber moechte ich Statusmeldungen vom Client erhalten, damit Update-Ergebnisse nachvollziehbar sind. diff --git a/project-management/requirements/tasks/TASK_000027.md b/project-management/requirements/tasks/TASK_000027.md index 35f6b95..b400851 100644 --- a/project-management/requirements/tasks/TASK_000027.md +++ b/project-management/requirements/tasks/TASK_000027.md @@ -1,4 +1,4 @@ -ID: TASK_000027 | Version: 0.1.0 | Status: In Progress +ID: TASK_000027 | Version: 0.1.0 | Status: Done By: Codex (GPT-5) # TASK_000027: Update endpoint config diff --git a/project-management/requirements/tasks/TASK_000028.md b/project-management/requirements/tasks/TASK_000028.md index fa055da..0cc4207 100644 --- a/project-management/requirements/tasks/TASK_000028.md +++ b/project-management/requirements/tasks/TASK_000028.md @@ -1,4 +1,4 @@ -ID: TASK_000028 | Version: 0.1.0 | Status: In Progress +ID: TASK_000028 | Version: 0.1.0 | Status: Done By: Codex (GPT-5) # TASK_000028: Verify and apply update diff --git a/project-management/requirements/tasks/TASK_000029.md b/project-management/requirements/tasks/TASK_000029.md index 39fdd76..0b1d02c 100644 --- a/project-management/requirements/tasks/TASK_000029.md +++ b/project-management/requirements/tasks/TASK_000029.md @@ -1,4 +1,4 @@ -ID: TASK_000029 | Version: 0.1.0 | Status: In Progress +ID: TASK_000029 | Version: 0.1.0 | Status: Done By: Codex (GPT-5) # TASK_000029: Report update status diff --git a/scripts/rollback_client.sh b/scripts/rollback_client.sh new file mode 100755 index 0000000..85026b2 --- /dev/null +++ b/scripts/rollback_client.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +set -euo pipefail + +SERVICE_NAME="${SERVICE_NAME:-skd}" +INSTALL_DIR="${INSTALL_DIR:-/opt/sk}" +STATUS_FILE="${SKD_UPDATE_STATUS_FILE:-/var/lib/skd/update_status.json}" +LOG_FILE="${SKD_UPDATE_LOG_FILE:-/var/lib/skd/update_logs.jsonl}" + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" +} + +write_status() { + local status="$1" + local error="${2:-}" + local version="$3" + SKD_STATUS="${status}" SKD_ERROR="${error}" SKD_VERSION="${version}" \ + SKD_STATUS_FILE="${STATUS_FILE}" SKD_LOG_FILE="${LOG_FILE}" python3 - <<'PY' +import json +import os +from datetime import datetime, timezone +from pathlib import Path + +status = os.environ["SKD_STATUS"] +error = os.environ.get("SKD_ERROR", "") +version = os.environ.get("SKD_VERSION", "unknown") +status_file = Path(os.environ["SKD_STATUS_FILE"]) +log_file = Path(os.environ["SKD_LOG_FILE"]) + +status_file.parent.mkdir(parents=True, exist_ok=True) +log_file.parent.mkdir(parents=True, exist_ok=True) + +payload = { + "device_id": os.uname().nodename, + "version": version, + "status": status, + "error": error, + "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), +} +status_file.write_text(json.dumps(payload), encoding="utf-8") +with log_file.open("a", encoding="utf-8") as handle: + handle.write(json.dumps(payload) + "\n") +PY +} + +LATEST_BACKUP="$(ls -dt /opt/sk_backup_* 2>/dev/null | head -1 || true)" +if [[ -z "${LATEST_BACKUP}" ]]; then + log "No backup found; rollback aborted." + write_status "failed" "no backup found" "unknown" + exit 1 +fi + +VERSION="unknown" +if [[ -f "${LATEST_BACKUP}/VERSION" ]]; then + VERSION="$(cat "${LATEST_BACKUP}/VERSION" | tr -d '\n')" +fi + +log "Stopping service ${SERVICE_NAME}..." +sudo systemctl stop "${SERVICE_NAME}.service" + +FAILED_DIR="${INSTALL_DIR}_failed_$(date +%s)" +log "Swapping ${INSTALL_DIR} -> ${FAILED_DIR}..." +if [[ -d "${INSTALL_DIR}" ]]; then + sudo mv "${INSTALL_DIR}" "${FAILED_DIR}" +fi +sudo mv "${LATEST_BACKUP}" "${INSTALL_DIR}" + +log "Starting service ${SERVICE_NAME}..." +if sudo systemctl start "${SERVICE_NAME}.service"; then + log "Rollback completed." + write_status "success" "" "${VERSION}" +else + log "Rollback failed." + write_status "failed" "service start failed" "${VERSION}" + exit 1 +fi diff --git a/scripts/update_client.sh b/scripts/update_client.sh index 2e0195d..d3b4782 100755 --- a/scripts/update_client.sh +++ b/scripts/update_client.sh @@ -5,6 +5,8 @@ SERVICE_NAME="${SERVICE_NAME:-skd}" UPDATE_URL="${SKD_UPDATE_URL:-https://update.wlkns.org}" UPDATE_TOKEN="${SKD_UPDATE_TOKEN:-}" STATUS_URL="${SKD_UPDATE_STATUS_URL:-https://update.wlkns.org/status}" +STATUS_FILE="${SKD_UPDATE_STATUS_FILE:-/var/lib/skd/update_status.json}" +LOG_FILE="${SKD_UPDATE_LOG_FILE:-/var/lib/skd/update_logs.jsonl}" INSTALL_DIR="${INSTALL_DIR:-/opt/sk}" STAGING_DIR="" MANIFEST_FILE="" @@ -20,6 +22,35 @@ report_status() { local error="${2:-}" local timestamp timestamp="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" + python3 - <<'PY' +import json +import os +from datetime import datetime, timezone +from pathlib import Path + +device_id = os.uname().nodename +version = os.environ.get("SKD_VERSION", "unknown") +status = os.environ.get("SKD_STATUS", "unknown") +error = os.environ.get("SKD_ERROR", "") +timestamp = os.environ.get("SKD_TIMESTAMP") or datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") +status_file = Path(os.environ["SKD_STATUS_FILE"]) +log_file = Path(os.environ["SKD_LOG_FILE"]) + +status_file.parent.mkdir(parents=True, exist_ok=True) +log_file.parent.mkdir(parents=True, exist_ok=True) + +payload = { + "device_id": device_id, + "version": version, + "status": status, + "error": error, + "timestamp": timestamp, +} + +status_file.write_text(json.dumps(payload), encoding="utf-8") +with log_file.open("a", encoding="utf-8") as handle: + handle.write(json.dumps(payload) + "\n") +PY local payload payload=$(cat <&2 - report_status "failed" "checksum mismatch" + SKD_VERSION="${VERSION}" SKD_STATUS="failed" SKD_ERROR="checksum mismatch" \ + SKD_TIMESTAMP="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" SKD_STATUS_FILE="${STATUS_FILE}" \ + SKD_LOG_FILE="${LOG_FILE}" report_status "failed" "checksum mismatch" exit 1 fi @@ -121,7 +154,9 @@ STAGING_DIR="" log "Starting service ${SERVICE_NAME}..." if sudo systemctl start "${SERVICE_NAME}.service"; then log "Update applied successfully." - report_status "success" "" + SKD_VERSION="${VERSION}" SKD_STATUS="success" SKD_ERROR="" \ + SKD_TIMESTAMP="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" SKD_STATUS_FILE="${STATUS_FILE}" \ + SKD_LOG_FILE="${LOG_FILE}" report_status "success" "" else log "Service failed to start, rolling back..." sudo rm -rf "${INSTALL_DIR}" @@ -129,6 +164,8 @@ else sudo mv "${BACKUP_DIR}" "${INSTALL_DIR}" fi sudo systemctl start "${SERVICE_NAME}.service" || true - report_status "failed" "service start failed" + SKD_VERSION="${VERSION}" SKD_STATUS="failed" SKD_ERROR="service start failed" \ + SKD_TIMESTAMP="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" SKD_STATUS_FILE="${STATUS_FILE}" \ + SKD_LOG_FILE="${LOG_FILE}" report_status "failed" "service start failed" exit 1 fi From d799c0f042df2183ac830973eaaeb9738b9d8fd8 Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 30 Dec 2025 11:32:39 +0100 Subject: [PATCH 13/13] feat: implement update management web UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add complete update management interface to web UI: - Update status display (current version, last status, timestamp, errors) - Check for updates button with availability indicator - Apply update button with confirmation dialog and backup warnings - Rollback button with confirmation dialog - Update logs viewer (collapsible, reverse chronological) - Auto-refresh after update/rollback actions (5s delay) Features: - Uses existing Pico CSS framework for consistent styling - Integrates with existing auth system (Bearer token/session cookies) - Real-time feedback with loading states and error handling - German UI language matching existing interface - All API calls use existing api() helper function Complete US_000029-033 and TASK_000030-034: - US_000029: Display update status in web UI - US_000030: Trigger update check from UI - US_000031: Apply updates from UI - US_000032: Display update logs in UI - US_000033: Trigger rollback from UI EPIC_000008 (Client-Side Update Mechanism) now fully complete. 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 --- CHANGELOG.md | 1 + backend/templates/index.html | 184 ++++++++++++++++++ project-management/PROJECT_STATUS.md | 20 +- .../requirements/stories/US_000029.md | 4 +- .../requirements/stories/US_000030.md | 4 +- .../requirements/stories/US_000031.md | 4 +- .../requirements/stories/US_000032.md | 4 +- .../requirements/stories/US_000033.md | 4 +- .../requirements/tasks/TASK_000030.md | 2 +- .../requirements/tasks/TASK_000031.md | 2 +- .../requirements/tasks/TASK_000032.md | 2 +- .../requirements/tasks/TASK_000033.md | 2 +- .../requirements/tasks/TASK_000034.md | 2 +- 13 files changed, 210 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fab9693..c593dac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ By: Codex (GPT-5) | 29.12.2025 | โš™๏ธ Code | ID: Update-Models und Settings erweitert (Models, Status-File Paths). By: Codex (GPT-5) | | 29.12.2025 | โš™๏ธ Code | ID: Rollback-Script hinzugefuegt (scripts/rollback_client.sh). By: Codex (GPT-5) | | 30.12.2025 | โš™๏ธ Code | ID: Update-API Endpunkte implementiert (GET /update/status, POST /update/check, POST /update/apply, POST /update/rollback, GET /update/logs). By: Claude Sonnet 4.5 | +| 30.12.2025 | โš™๏ธ Code | ID: Update-UI im Web-Frontend implementiert (Status-Anzeige, Check/Apply/Rollback Buttons, Logs-Viewer). By: Claude Sonnet 4.5 | --- ## Legende diff --git a/backend/templates/index.html b/backend/templates/index.html index d355c44..1bd3bdd 100644 --- a/backend/templates/index.html +++ b/backend/templates/index.html @@ -84,6 +84,27 @@
+
+

Update-Verwaltung

+
+ Lade Update-Status... +
+ +
+ + + +
+ +
+ +
+ Update-Logs anzeigen + +
+
+
+ diff --git a/project-management/PROJECT_STATUS.md b/project-management/PROJECT_STATUS.md index 7c952f7..0d88290 100644 --- a/project-management/PROJECT_STATUS.md +++ b/project-management/PROJECT_STATUS.md @@ -92,16 +92,16 @@ Sicheres, remote steuerbares System zum Sperren/Entsperren lokaler Nutzerkonten. - [x] TASK_000028: Verify and apply update - [x] US_000028: Client meldet Update-Status - [x] TASK_000029: Report update status -- [ ] US_000029: Update-Status im Web-UI anzeigen -- [ ] TASK_000030: UI update status view -- [ ] US_000030: Update-Check im Web-UI ausloesen -- [ ] TASK_000031: UI update check trigger -- [ ] US_000031: Update im Web-UI anstossen -- [ ] TASK_000032: UI update apply action -- [ ] US_000032: Update-Logs im Web-UI anzeigen -- [ ] TASK_000033: UI update logs view -- [ ] US_000033: Rollback im Web-UI anstossen -- [ ] TASK_000034: UI rollback action +- [x] US_000029: Update-Status im Web-UI anzeigen +- [x] TASK_000030: UI update status view +- [x] US_000030: Update-Check im Web-UI ausloesen +- [x] TASK_000031: UI update check trigger +- [x] US_000031: Update im Web-UI anstossen +- [x] TASK_000032: UI update apply action +- [x] US_000032: Update-Logs im Web-UI anzeigen +- [x] TASK_000033: UI update logs view +- [x] US_000033: Rollback im Web-UI anstossen +- [x] TASK_000034: UI rollback action ### EPIC_000009: Update Webservice (External Team) - [ ] US_000026: Client bezieht Updates (Pull) diff --git a/project-management/requirements/stories/US_000029.md b/project-management/requirements/stories/US_000029.md index f6c3c08..82b2e78 100644 --- a/project-management/requirements/stories/US_000029.md +++ b/project-management/requirements/stories/US_000029.md @@ -1,9 +1,9 @@ -ID: US_000029 | Version: 0.1.0 | Status: Draft +ID: US_000029 | Version: 0.1.0 | Status: Done By: Codex (GPT-5) # US_000029: Update-Status im Web-UI anzeigen -Status: In Progress +Status: Done Als Admin moechte ich die aktuelle Version und den Update-Status im Web-UI sehen, damit ich den Zustand schnell pruefen kann. diff --git a/project-management/requirements/stories/US_000030.md b/project-management/requirements/stories/US_000030.md index a9233dc..afce1e0 100644 --- a/project-management/requirements/stories/US_000030.md +++ b/project-management/requirements/stories/US_000030.md @@ -1,9 +1,9 @@ -ID: US_000030 | Version: 0.1.0 | Status: Draft +ID: US_000030 | Version: 0.1.0 | Status: Done By: Codex (GPT-5) # US_000030: Update-Check im Web-UI ausloesen -Status: In Progress +Status: Done Als Admin moechte ich manuell nach Updates suchen koennen, damit ich Updates sofort pruefen kann. diff --git a/project-management/requirements/stories/US_000031.md b/project-management/requirements/stories/US_000031.md index bc26f3b..19e3fee 100644 --- a/project-management/requirements/stories/US_000031.md +++ b/project-management/requirements/stories/US_000031.md @@ -1,9 +1,9 @@ -ID: US_000031 | Version: 0.1.0 | Status: Draft +ID: US_000031 | Version: 0.1.0 | Status: Done By: Codex (GPT-5) # US_000031: Update im Web-UI anstossen -Status: In Progress +Status: Done Als Admin moechte ich ein Update im Web-UI anstossen, damit der Client die neue Version installiert. diff --git a/project-management/requirements/stories/US_000032.md b/project-management/requirements/stories/US_000032.md index ffba7e3..81cc7b2 100644 --- a/project-management/requirements/stories/US_000032.md +++ b/project-management/requirements/stories/US_000032.md @@ -1,9 +1,9 @@ -ID: US_000032 | Version: 0.1.0 | Status: Draft +ID: US_000032 | Version: 0.1.0 | Status: Done By: Codex (GPT-5) # US_000032: Update-Logs im Web-UI anzeigen -Status: In Progress +Status: Done Als Admin moechte ich Update-Logs im Web-UI einsehen, damit Fehler nachvollziehbar sind. diff --git a/project-management/requirements/stories/US_000033.md b/project-management/requirements/stories/US_000033.md index c7760ff..67540c1 100644 --- a/project-management/requirements/stories/US_000033.md +++ b/project-management/requirements/stories/US_000033.md @@ -1,9 +1,9 @@ -ID: US_000033 | Version: 0.1.0 | Status: Draft +ID: US_000033 | Version: 0.1.0 | Status: Done By: Codex (GPT-5) # US_000033: Rollback im Web-UI anstossen -Status: In Progress +Status: Done Als Admin moechte ich einen Rollback im Web-UI anstossen, damit ich nach einem fehlerhaften Update schnell zur letzten Version zurueckkehre. diff --git a/project-management/requirements/tasks/TASK_000030.md b/project-management/requirements/tasks/TASK_000030.md index 10b4387..8d75e27 100644 --- a/project-management/requirements/tasks/TASK_000030.md +++ b/project-management/requirements/tasks/TASK_000030.md @@ -1,4 +1,4 @@ -ID: TASK_000030 | Version: 0.1.0 | Status: In Progress +ID: TASK_000030 | Version: 0.1.0 | Status: Done By: Codex (GPT-5) # TASK_000030: UI update status view diff --git a/project-management/requirements/tasks/TASK_000031.md b/project-management/requirements/tasks/TASK_000031.md index 09371fa..c60340e 100644 --- a/project-management/requirements/tasks/TASK_000031.md +++ b/project-management/requirements/tasks/TASK_000031.md @@ -1,4 +1,4 @@ -ID: TASK_000031 | Version: 0.1.0 | Status: In Progress +ID: TASK_000031 | Version: 0.1.0 | Status: Done By: Codex (GPT-5) # TASK_000031: UI update check trigger diff --git a/project-management/requirements/tasks/TASK_000032.md b/project-management/requirements/tasks/TASK_000032.md index 1314b54..3f85017 100644 --- a/project-management/requirements/tasks/TASK_000032.md +++ b/project-management/requirements/tasks/TASK_000032.md @@ -1,4 +1,4 @@ -ID: TASK_000032 | Version: 0.1.0 | Status: In Progress +ID: TASK_000032 | Version: 0.1.0 | Status: Done By: Codex (GPT-5) # TASK_000032: UI update apply action diff --git a/project-management/requirements/tasks/TASK_000033.md b/project-management/requirements/tasks/TASK_000033.md index 4705ee1..c476874 100644 --- a/project-management/requirements/tasks/TASK_000033.md +++ b/project-management/requirements/tasks/TASK_000033.md @@ -1,4 +1,4 @@ -ID: TASK_000033 | Version: 0.1.0 | Status: In Progress +ID: TASK_000033 | Version: 0.1.0 | Status: Done By: Codex (GPT-5) # TASK_000033: UI update logs view diff --git a/project-management/requirements/tasks/TASK_000034.md b/project-management/requirements/tasks/TASK_000034.md index 7e5e399..1848b52 100644 --- a/project-management/requirements/tasks/TASK_000034.md +++ b/project-management/requirements/tasks/TASK_000034.md @@ -1,4 +1,4 @@ -ID: TASK_000034 | Version: 0.1.0 | Status: In Progress +ID: TASK_000034 | Version: 0.1.0 | Status: Done By: Codex (GPT-5) # TASK_000034: UI rollback action