Files
create_lxc/setup_host.py

321 lines
13 KiB
Python
Executable File

#!/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()