commit c61a2e5994654f525094098802b06d623d4b18ee Author: root Date: Sun Jun 14 19:08:19 2026 +0000 Initial provisioning server diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4172497 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.venv/ +__pycache__/ +*.pyc +activation.log diff --git a/app.py b/app.py new file mode 100644 index 0000000..faa1f64 --- /dev/null +++ b/app.py @@ -0,0 +1,125 @@ +from pathlib import Path +from datetime import datetime, timezone +import json + +from fastapi import FastAPI, Request +from pydantic import BaseModel + + +CONFIG_PATH = Path("config.json") +LOG_PATH = Path("activation.log") + +app = FastAPI(title="Provisioning Activation Server", version="0.1.0") + + +class ActivationRequest(BaseModel): + activation_code: str + hostname: str | None = None + machine_id: str | None = None + client_version: str | None = None + + +def load_config() -> dict: + with CONFIG_PATH.open("r", encoding="utf-8") as f: + return json.load(f) + + +def write_log(entry: dict) -> None: + with LOG_PATH.open("a", encoding="utf-8") as f: + f.write(json.dumps(entry, ensure_ascii=False) + "\n") + + +@app.get("/health") +def health(): + return { + "status": "ok", + "service": "provisioning-server", + "version": "0.1.0" + } + + +@app.post("/api/v1/activate") +def activate(payload: ActivationRequest, request: Request): + config = load_config() + activation_entry = config.get("activation_codes", {}).get(payload.activation_code) + + success = activation_entry is not None + + write_log({ + "timestamp": datetime.now(timezone.utc).isoformat(), + "event": "activate", + "success": success, + "remote_ip": request.client.host if request.client else None, + "activation_code": payload.activation_code, + "hostname": payload.hostname, + "machine_id": payload.machine_id, + "client_version": payload.client_version + }) + + if not success: + return { + "success": False, + "error": "invalid_activation_code", + "message": "Der Aktivierungscode ist ungültig." + } + + profiles = config.get("profiles", {}) + available_profiles = [ + profiles[profile_id] + for profile_id in activation_entry.get("profile_ids", []) + if profile_id in profiles + ] + + return { + "success": True, + "customer": activation_entry["customer"], + "profiles": available_profiles + } + + +@app.get("/api/v1/profiles/{profile_id}") +def get_profile(profile_id: str): + config = load_config() + profile = config.get("profiles", {}).get(profile_id) + + if not profile: + return { + "success": False, + "error": "profile_not_found", + "message": "Das angeforderte Profil wurde nicht gefunden." + } + + return { + "success": True, + "profile": profile + } + +@app.get("/api/v1/profiles/{profile_id}/bootstrap") +def get_profile_bootstrap(profile_id: str): + config = load_config() + profile = config.get("profiles", {}).get(profile_id) + + if not profile: + return { + "success": False, + "error": "profile_not_found", + "message": "Das angeforderte Profil wurde nicht gefunden." + } + + return { + "success": True, + "profile": { + "id": profile["id"], + "label": profile["label"], + "distribution": profile["distribution"], + "version": profile["version"] + }, + "installer": profile.get("installer"), + "ansible": { + "repo": profile.get("ansible_repo"), + "mode": "pull" + }, + "ssh": { + "authorized_keys_url": "https://anode.stallinux.de/keys/default.pub" + } + } diff --git a/config.json b/config.json new file mode 100644 index 0000000..0d7c722 --- /dev/null +++ b/config.json @@ -0,0 +1,40 @@ +{ + "activation_codes": { + "LAB-2026-START": { + "customer": { + "id": "default", + "name": "Default Lab" + }, + "profile_ids": [ + "mint-desktop", + "fedora-workstation" + ] + } + }, + "profiles": { + "mint-desktop": { + "id": "mint-desktop", + "label": "Linux Mint Desktop", + "distribution": "linux-mint", + "version": "22", + "description": "Standard-Desktop-Profil für Linux Mint.", + "ansible_repo": "https://git.example.com/stallinux/mint-desktop.git", + "installer": { + "type": "autoinstall", + "url": "https://anode.stallinux.de/installers/mint-desktop/user-data" + } + }, + "fedora-workstation": { + "id": "fedora-workstation", + "label": "Fedora Workstation", + "distribution": "fedora", + "version": "40", + "description": "Standard-Workstation-Profil für Fedora.", + "ansible_repo": "https://git.example.com/stallinux/fedora-workstation.git", + "installer": { + "type": "kickstart", + "url": "https://anode.stallinux.de/installers/fedora-workstation/ks.cfg" + } + } + } +} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..185360b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,13 @@ +annotated-doc==0.0.4 +annotated-types==0.7.0 +anyio==4.13.0 +click==8.4.1 +fastapi==0.136.3 +h11==0.16.0 +idna==3.18 +pydantic==2.13.4 +pydantic_core==2.46.4 +starlette==1.3.1 +typing-inspection==0.4.2 +typing_extensions==4.15.0 +uvicorn==0.49.0