commit 37e8bb1d55700f5b7e2edc1ad10d589bfb84e778 Author: Thomas Stallinger Date: Sun Jun 14 21:29:45 2026 +0200 Initial provisioning client diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..99756a6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__/ +*.pyc +/tmp/ diff --git a/provisioning-client.py b/provisioning-client.py new file mode 100644 index 0000000..384deef --- /dev/null +++ b/provisioning-client.py @@ -0,0 +1,110 @@ +import json +import socket +import urllib.request + + +BASE_URL = "https://anode.stallinux.de" +CLIENT_VERSION = "0.1.0" +SELECTION_PATH = "/tmp/provisioning-selection.json" + + +def get_machine_id(): + try: + with open("/etc/machine-id", "r", encoding="utf-8") as f: + return f.read().strip() + except FileNotFoundError: + return "unknown" + + +def request_json(method, path, data=None): + body = None + headers = {"Content-Type": "application/json"} + + if data is not None: + body = json.dumps(data).encode("utf-8") + + req = urllib.request.Request( + BASE_URL + path, + data=body, + headers=headers, + method=method, + ) + + with urllib.request.urlopen(req, timeout=15) as response: + return json.loads(response.read().decode("utf-8")) + + +hostname = socket.gethostname() +machine_id = get_machine_id() + +activation_code = input("Aktivierungscode: ").strip() + +activate_payload = { + "activation_code": activation_code, + "hostname": hostname, + "machine_id": machine_id, + "client_version": CLIENT_VERSION, +} + +result = request_json("POST", "/api/v1/activate", activate_payload) + +if not result.get("success"): + print("Aktivierung fehlgeschlagen:") + print(result.get("message", "Unbekannter Fehler")) + raise SystemExit(1) + +profiles = result["profiles"] + +print() +print("Aktivierung erfolgreich.") +print("Kunde:", result["customer"]["name"]) +print() +print("Verfügbare Profile:") + +for index, profile in enumerate(profiles, start=1): + print(f"{index}) {profile['label']} ({profile['distribution']} {profile['version']})") + +print() +choice = input("Profil wählen: ").strip() + +try: + choice_index = int(choice) - 1 + selected_profile = profiles[choice_index] +except (ValueError, IndexError): + print("Ungültige Auswahl.") + raise SystemExit(1) + +bootstrap_result = request_json( + "GET", + f"/api/v1/profiles/{selected_profile['id']}/bootstrap" +) + +if not bootstrap_result.get("success"): + print("Bootstrap-Konfiguration konnte nicht geladen werden:") + print(bootstrap_result.get("message", "Unbekannter Fehler")) + raise SystemExit(1) + +selection = { + "customer_id": result["customer"]["id"], + "customer_name": result["customer"]["name"], + "hostname": hostname, + "machine_id": machine_id, + "profile": bootstrap_result["profile"], + "installer": bootstrap_result["installer"], + "ansible": bootstrap_result["ansible"], + "ssh": bootstrap_result["ssh"] +} + +with open(SELECTION_PATH, "w", encoding="utf-8") as f: + json.dump(selection, f, indent=2) + +print() +print("Bootstrap-Konfiguration geladen.") +print("Auswahl gespeichert:") +print(SELECTION_PATH) + +print() +print("Profil:", selection["profile"]["label"]) +print("Installer:", selection["installer"]["type"]) +print("Installer URL:", selection["installer"]["url"]) +print("Ansible Repo:", selection["ansible"]["repo"])