Initial provisioning server
This commit is contained in:
commit
c61a2e5994
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
.venv/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
activation.log
|
||||
125
app.py
Normal file
125
app.py
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
40
config.json
Normal file
40
config.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
requirements.txt
Normal file
13
requirements.txt
Normal file
@ -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
|
||||
Loading…
x
Reference in New Issue
Block a user