185 lines
7.8 KiB
Python
Executable File
185 lines
7.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
import subprocess
|
|
import os
|
|
import sys
|
|
import argparse
|
|
import time
|
|
import json
|
|
|
|
|
|
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():
|
|
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.")
|
|
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")
|
|
print("LXD ist verfügbar. WICHTIG: Es wird davon ausgegangen, dass 'lxd init' bereits ausgeführt wurde.")
|
|
|
|
if not os.path.exists("out"):
|
|
os.makedirs("out")
|
|
with open(csv_path, "w") as f:
|
|
f.write("name,ip\n")
|
|
elif not os.path.isdir("out"):
|
|
# os.remove("out")
|
|
os.makedirs("out")
|
|
|
|
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}")
|
|
|
|
run_command(f"lxc delete {vm_name} --force",
|
|
f"{vm_name} vorsorglich löschen", ignore_errors=True)
|
|
run_command(f"lxc launch {args.image} {
|
|
vm_name}", f"Starte {vm_name} von Image")
|
|
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"
|
|
elif distro_id in ["ubuntu", "debian"]:
|
|
install_cmd = "sh -c 'export DEBIAN_FRONTEND=noninteractive; apt-get update && apt-get install -y python3'"
|
|
elif distro_id == "fedora":
|
|
install_cmd = "dnf install -y python3"
|
|
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}'", 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:
|
|
# f.write("name,ip\n")
|
|
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()
|