#!/usr/bin/env python3 # -*- coding: utf-8 -*- # geschrieben vmit der Hilfe von Gemini2.5-pro import subprocess import os import sys import argparse import time import json import pylxd def run_command(command_str, description, ignore_errors=False): print(f"\n--- {description} ---") try: process = subprocess.run( command_str, shell=True, text=True, check=not ignore_errors, capture_output=True) if process.returncode == 0: print(f"--- ERFOLG: {description} ---") return process else: raise subprocess.CalledProcessError( process.returncode, command_str, output=process.stdout, stderr=process.stderr) except subprocess.CalledProcessError as e: print(f"FEHLER bei '{description}': Befehl '{ e.cmd}' gab den Exit-Code {e.returncode} zurück", file=sys.stderr) if e.stdout: print(f"STDOUT:\n{e.stdout}", file=sys.stderr) if e.stderr: print(f"STDERR:\n{e.stderr}", file=sys.stderr) if not ignore_errors: sys.exit(1) return None def wait_for_network(container_name): print(f"\n--- Warte auf Netzwerk für Container '{container_name}' ---") for i in range(30): print(f"Versuch {i+1}/30...") try: result = run_command(f"lxc list { container_name} --format json", "Frage Container-Status ab", ignore_errors=True) if result and result.returncode == 0: data = json.loads(result.stdout) if data and data[0]["state"] and data[0]["state"]["network"] and "eth0" in data[0]["state"]["network"]: addresses = data[0]["state"]["network"]["eth0"]["addresses"] for addr in addresses: if addr["family"] == "inet": print(f"Container '{ container_name}' hat IP-Adresse {addr['address']} erhalten.") return except (json.JSONDecodeError, IndexError, KeyError): pass time.sleep(2) print(f"FEHLER: Container '{ container_name}' hat nach 60 Sekunden keine IP-Adresse erhalten.", file=sys.stderr) sys.exit(1) def get_container_ip(container_name): for _ in range(15): try: result = run_command(f"lxc list { container_name} --format json", "Frage Container-IP ab", ignore_errors=True) if result and result.returncode == 0: data = json.loads(result.stdout) if data and data[0]["state"] and data[0]["state"]["network"] and "eth0" in data[0]["state"]["network"]: addresses = data[0]["state"]["network"]["eth0"]["addresses"] for addr in addresses: if addr["family"] == "inet": return addr['address'] except (json.JSONDecodeError, IndexError, KeyError): pass time.sleep(2) return None def main(): os.environ['PYLXD_WARNINGS'] = 'none' host_folder = "/home/jonnybravo/lxc_folder" parser = argparse.ArgumentParser( description="Erstellt und konfiguriert mehrere LXD-Container.") parser.add_argument("-c", "--count", type=int, default=1, help="Anzahl der zu erstellenden Container.") parser.add_argument("-b", "--basename", default="vm", help="Basisname für die Container.") parser.add_argument("--hostname-template", default="{basename}-{i:02d}", help="Vorlage für den Hostnamen, z.B. 'webserver-{i}'. Unterstützt {basename} und {i}.") parser.add_argument("-p", "--root-password", required=True, help="Root-Passwort für alle Container.") parser.add_argument("--port-start", type=int, default=2201, help="Start-Port für die SSH-Weiterleitung.") parser.add_argument("--image", default="images:archlinux", help="Zu verwendendes Image (z.B. images:archlinux, ubuntu:22.04).") parser.add_argument("--no-port-forward", action="store_true", help="Überspringt die Einrichtung der SSH-Port-Weiterleitung.") parser.add_argument("--ssh-pub-key-file", default="~/.ssh/ansible-test.pub", help="Pfad zur öffentlichen SSH-Schlüsseldatei, die für den Benutzer 'jonnybravo' autorisiert werden soll.") args = parser.parse_args() if args.count > 1 and '{i}' not in args.hostname_template: print( "FEHLER: Bei --count > 1 muss die --hostname-template den Platzhalter '{i}' enthalten, um eindeutige Namen zu gewährleisten.", file=sys.stderr) sys.exit(1) run_command("command -v lxc >/dev/null", "Prüfe, ob LXD installiert ist") client = pylxd.Client(project='default') # Überprüfe, ob das Profil 'myprofile' existiert, und erstelle es, falls nicht try: client.profiles.get("myprofile") print("\n--- Profil 'myprofile' existiert bereits. Aktualisiere es. ---") profile = client.profiles.get("myprofile") profile.config = {"security.nesting": "true", "security.privileged": "true"} profile.devices = { "root": { "path": "/", "pool": "default", "type": "disk", }, "eth0": { "name": "eth0", "network": "lxdbr0", "type": "nic", }, "public": { "path": "/public", "source": host_folder, "type": "disk", } } profile.save() except pylxd.exceptions.NotFound: print("\n--- Profil 'myprofile' nicht gefunden. Erstelle es. ---") profile_config = {"security.nesting": "true", "security.privileged": "true"} profile_devices = { "root": { "path": "/", "pool": "default", "type": "disk", }, "eth0": { "name": "eth0", "network": "lxdbr0", "type": "nic", }, "public": { "path": "/public", "source": host_folder, "type": "disk", } } client.profiles.create("myprofile", profile_config, profile_devices) # Erstelle den Ordner auf dem Host, falls er nicht existiert if not os.path.exists(host_folder): print(f"--- Erstelle Host-Ordner: {host_folder} ---") os.makedirs(host_folder) else: print(f"--- Host-Ordner {host_folder} existiert bereits. ---") # Konfiguriere lxdbr0, falls nicht bereits geschehen try: run_command("lxc network show lxdbr0", "Prüfe, ob lxdbr0 existiert", ignore_errors=True) except subprocess.CalledProcessError: print("\n--- lxdbr0 nicht gefunden. Konfiguriere es. ---") run_command("lxc network set lxdbr0 ipv4.address 10.0.4.1/24", "Setze IPv4-Adresse für lxdbr0") run_command("lxc network set lxdbr0 ipv4.nat true", "Aktiviere IPv4-NAT für lxdbr0") run_command("lxc network set lxdbr0 ipv6.address none", "Deaktiviere IPv6 für lxdbr0") else: print("\n--- lxdbr0 existiert bereits. ---") # Lese den öffentlichen SSH-Schlüssel ssh_key_path = os.path.expanduser(args.ssh_pub_key_file) ssh_key_content = "" if os.path.exists(ssh_key_path): with open(ssh_key_path, 'r') as f: ssh_key_content = f.read().strip() print(f"--- SSH-Schlüssel von {ssh_key_path} geladen. ---") else: print(f"--- WARNUNG: SSH-Schlüsseldatei nicht gefunden unter { ssh_key_path}. Der Schlüssel wird nicht kopiert. ---") # Importiere pylxd und initialisiere den Client # client = pylxd.Client() # Dies sollte bereits oben geschehen sein # Initialisiere LXD, falls nicht bereits geschehen # try: # client.api.get('/') # print("\n--- LXD ist bereits initialisiert. ---") # except pylxd.exceptions.ClientConnectionFailed: # print("\n--- LXD ist nicht initialisiert. Initialisiere es. ---") # run_command("lxd init --auto", "Initialisiere LXD") # Erstelle den 'out'-Ordner, falls er nicht existiert if not os.path.exists("out"): os.makedirs("out") # Bereinige die CSV-Datei von alten Einträgen csv_path = "out/clients.csv" if os.path.exists(csv_path): print(f"\n--- Bereinige {csv_path} von alten Einträgen ---") try: result = run_command("lxc list --format json", "Frage alle Container ab", ignore_errors=True) if result and result.returncode == 0: existing_containers = {c['name'] for c in json.loads(result.stdout)} valid_clients = [] with open(csv_path, 'r') as f: lines = f.readlines() if lines: header = lines[0] valid_clients.append(header) for line in lines[1:]: try: name = line.strip().split(',')[0] if name in existing_containers: valid_clients.append(line) except IndexError: pass # Ignoriere leere oder fehlerhafte Zeilen with open(csv_path, 'w') as f: f.writelines(valid_clients) print(f"--- {csv_path} erfolgreich bereinigt. ---") except (json.JSONDecodeError, FileNotFoundError): print(f"--- WARNUNG: Konnte {csv_path} nicht bereinigen. ---") # Stelle sicher, dass die CSV-Datei existiert und einen Header hat if not os.path.exists(csv_path) or os.path.getsize(csv_path) == 0: with open(csv_path, "w") as f: f.write("name,ip\n") clients = [] for i in range(1, args.count + 1): vm_name = args.hostname_template.format(basename=args.basename, i=i) ssh_port = args.port_start + i - 1 print(f"{'=' * 20} ERSTELLE CONTAINER: {vm_name} {'=' * 20}") try: client.containers.get(vm_name) print( f"--- HINWEIS: Container '{vm_name}' existiert bereits und wird übersprungen. ---") continue except pylxd.exceptions.NotFound: pass run_command(f"lxc launch {args.image} { vm_name} -p myprofile", f"Starte {vm_name} von Image mit myprofile") wait_for_network(vm_name) distro_id_command = "lxc exec {} -- cat /etc/os-release | grep '^ID=' | cut -d'=' -f2" distro_id_result = run_command(distro_id_command.format( vm_name), f"Ermittle Distributions-ID für {vm_name}") distro_id = distro_id_result.stdout.strip().replace('"', '') install_cmd = "" if distro_id == "arch": install_cmd = "pacman -Sy --noconfirm python vim " elif distro_id in ["ubuntu", "debian"]: install_cmd = "sh -c 'export DEBIAN_FRONTEND=noninteractive; apt-get update && apt-get install -y python3 vim'" elif distro_id == "fedora": install_cmd = "dnf install -y python3 vim" else: print(f"FEHLER: Nicht unterstützte Distribution: { distro_id}", file=sys.stderr) sys.exit(1) run_command(f"lxc exec {vm_name} -- {install_cmd}", f"Installiere Python in {vm_name}") run_command(f"lxc file push setup_container.py { vm_name}/root/", f"Kopiere Setup-Skript nach {vm_name}") run_command(f"lxc exec {vm_name} -- python3 /root/setup_container.py '{ args.root_password}' '{vm_name}' '{distro_id}' '{ssh_key_content}'", f"Führe Konfiguration in {vm_name} aus") ssh_info = "" if not args.no_port_forward: run_command(f"lxc config device add {vm_name} ssh-proxy proxy listen=tcp:0.0.0.0:{ ssh_port} connect=tcp:127.0.0.1:22", f"Richte Port-Weiterleitung für {vm_name} ein") ssh_info = f"SSH-Zugang: ssh root@localhost -p {ssh_port}" else: print("\n--- Überspringe Port-Weiterleitung ---") ip_address = get_container_ip(vm_name) if ip_address: clients.append({"name": vm_name, "ip": ip_address}) print(f"\n>>> Container { vm_name} erfolgreich erstellt und konfiguriert! <<<") if ssh_info: print(ssh_info) print(f"IP-Adresse: {ip_address}") else: print(f"\nFEHLER: IP-Adresse für Container { vm_name} konnte nicht abgerufen werden.", file=sys.stderr) if clients: csv_path = "out/clients.csv" with open(csv_path, "a") as f: for client in clients: f.write(f"{client['name']},{client['ip']}\n") print(f"\nAlle Container wurden erstellt und in { csv_path} gespeichert.") else: print("\nKeine Container wurden erstellt.") if __name__ == "__main__": main()