update ssh keys authorization file

This commit is contained in:
duffyduck 2026-03-04 22:27:31 +01:00
parent 2ad6a8dbc4
commit 82ddb25c01
3 changed files with 109 additions and 18 deletions

View File

@ -91,11 +91,15 @@ def parse_ceph_conf(content: str) -> CephConfig:
if m: if m:
config.cluster_network = m.group(1) config.cluster_network = m.group(1)
# Extract mon_host # Extract mon_host (can be comma-separated, space-separated, or both)
m = re.search(r'mon.host\s*=\s*(.+)', content) m = re.search(r'mon.host\s*=\s*(.+)', content)
if m: if m:
hosts_str = m.group(1).strip() hosts_str = m.group(1).strip()
config.mon_hosts = [h.strip() for h in hosts_str.split(',') if h.strip()] # Split by comma or whitespace
config.mon_hosts = [
h.strip() for h in re.split(r'[,\s]+', hosts_str)
if h.strip()
]
# Extract [mon.X] sections # Extract [mon.X] sections
mon_sections = re.findall( mon_sections = re.findall(

View File

@ -36,33 +36,39 @@ class Migrator:
if not self._distribute_configs(plan, configs, dry_run): if not self._distribute_configs(plan, configs, dry_run):
return False return False
# Step 2: Stop Corosync on all nodes # Step 2: Preserve SSH keys before stopping pve-cluster
print("\n[2/7] Corosync stoppen auf allen Nodes...") # /etc/pve/priv/authorized_keys gets unmounted when pve-cluster stops!
print("\n[2/8] SSH-Keys sichern (werden nach pve-cluster stop benötigt)...")
if not self._preserve_ssh_keys(reachable_nodes, dry_run):
return False
# Step 3: Stop Corosync on all nodes
print("\n[3/8] Corosync stoppen auf allen Nodes...")
if not self._stop_corosync(reachable_nodes, dry_run): if not self._stop_corosync(reachable_nodes, dry_run):
return False return False
# Step 3: Stop pve-cluster (pmxcfs) to release corosync.conf # Step 4: Stop pve-cluster (pmxcfs) to release corosync.conf
print("\n[3/7] pve-cluster stoppen...") print("\n[4/8] pve-cluster stoppen...")
if not self._stop_pve_cluster(reachable_nodes, dry_run): if not self._stop_pve_cluster(reachable_nodes, dry_run):
return False return False
# Step 4: Write corosync config directly # Step 5: Write corosync config directly
print("\n[4/7] Corosync-Konfiguration aktualisieren...") print("\n[5/8] Corosync-Konfiguration aktualisieren...")
if not self._update_corosync(reachable_nodes, configs, dry_run): if not self._update_corosync(reachable_nodes, configs, dry_run):
return False return False
# Step 5: Update /etc/hosts on all nodes # Step 6: Update /etc/hosts on all nodes
print("\n[5/7] /etc/hosts aktualisieren...") print("\n[6/8] /etc/hosts aktualisieren...")
if not self._update_hosts(plan, configs, dry_run): if not self._update_hosts(plan, configs, dry_run):
return False return False
# Step 6: Update network interfaces and restart networking # Step 7: Update network interfaces and restart networking
print("\n[6/7] Netzwerk-Interfaces aktualisieren und Netzwerk neu starten...") print("\n[7/8] Netzwerk-Interfaces aktualisieren und Netzwerk neu starten...")
if not self._update_network(plan, configs, dry_run): if not self._update_network(plan, configs, dry_run):
return False return False
# Step 7: Start services back up # Step 8: Start services back up
print("\n[7/7] Services starten...") print("\n[8/8] Services starten...")
if not self._start_services(plan, configs, dry_run): if not self._start_services(plan, configs, dry_run):
return False return False
@ -157,6 +163,56 @@ class Migrator:
return True return True
def _preserve_ssh_keys(self, nodes: list, dry_run: bool) -> bool:
"""Copy /etc/pve/priv/authorized_keys to ~/.ssh/ on all nodes.
When pve-cluster (pmxcfs) is stopped, /etc/pve gets unmounted and
the cluster SSH keys disappear. This breaks SSH between nodes.
We temporarily copy them to ~/.ssh/authorized_keys so SSH keeps working.
"""
for node in nodes:
if dry_run:
print(f" [{node.name}] Würde SSH-Keys sichern")
continue
# Append pve keys to ~/.ssh/authorized_keys (avoid duplicates)
cmd = (
"if [ -f /etc/pve/priv/authorized_keys ]; then "
" mkdir -p /root/.ssh && "
" cp /root/.ssh/authorized_keys /root/.ssh/authorized_keys.pre_migration 2>/dev/null; "
" cat /etc/pve/priv/authorized_keys >> /root/.ssh/authorized_keys && "
" sort -u -o /root/.ssh/authorized_keys /root/.ssh/authorized_keys && "
" chmod 600 /root/.ssh/authorized_keys && "
" echo ok; "
"else "
" echo no_pve_keys; "
"fi"
)
rc, stdout, err = self.ssh.run_on_node(
node.ssh_host, cmd, node.is_local
)
if rc == 0 and "ok" in stdout:
print(f" [{node.name}] SSH-Keys gesichert")
elif "no_pve_keys" in stdout:
print(f" [{node.name}] Keine PVE-Keys gefunden (übersprungen)")
else:
print(f" [{node.name}] WARNUNG SSH-Keys: {err}")
return True
def _restore_ssh_keys(self, nodes: list):
"""Restore original ~/.ssh/authorized_keys after migration."""
for node in nodes:
new_host = node.new_ip if not node.is_local else node.ssh_host
cmd = (
"if [ -f /root/.ssh/authorized_keys.pre_migration ]; then "
" mv /root/.ssh/authorized_keys.pre_migration /root/.ssh/authorized_keys && "
" echo restored; "
"else "
" echo no_backup; "
"fi"
)
self.ssh.run_on_node(new_host, cmd, node.is_local)
def _stop_corosync(self, nodes: list, dry_run: bool) -> bool: def _stop_corosync(self, nodes: list, dry_run: bool) -> bool:
"""Stop corosync on all nodes.""" """Stop corosync on all nodes."""
for node in nodes: for node in nodes:
@ -337,6 +393,10 @@ class Migrator:
if configs.get('ceph'): if configs.get('ceph'):
self._update_ceph(plan, configs) self._update_ceph(plan, configs)
# Restore original SSH keys (pve-cluster manages them again now)
print("\n SSH-Keys wiederherstellen...")
self._restore_ssh_keys(plan.nodes)
# Cleanup staging directories # Cleanup staging directories
print("\n Staging-Verzeichnisse aufräumen...") print("\n Staging-Verzeichnisse aufräumen...")
for node in plan.nodes: for node in plan.nodes:

View File

@ -34,11 +34,38 @@ class Planner:
# Detect old network from first node # Detect old network from first node
if nodes: if nodes:
old_ip = ipaddress.ip_address(nodes[0].current_ip) old_ip = ipaddress.ip_address(nodes[0].current_ip)
for iface in nodes[0].interfaces: # Try to find matching interface
if iface.address == str(old_ip): for node in nodes:
plan.old_network = f"{ipaddress.ip_network(f'{iface.address}/{iface.cidr}', strict=False)}" for iface in node.interfaces:
if iface.address == str(old_ip) or (
iface.address and iface.cidr and
ipaddress.ip_address(iface.address) in
ipaddress.ip_network(f'{iface.address}/{iface.cidr}', strict=False) and
old_ip in ipaddress.ip_network(f'{iface.address}/{iface.cidr}', strict=False)
):
plan.old_network = str(ipaddress.ip_network(
f'{iface.address}/{iface.cidr}', strict=False
))
plan.bridge_name = iface.name plan.bridge_name = iface.name
break break
if plan.old_network:
break
# Fallback: try to guess from corosync IPs
if not plan.old_network:
# Find common network from all corosync node IPs
for cidr_guess in [24, 16, 8]:
net = ipaddress.ip_network(
f'{nodes[0].current_ip}/{cidr_guess}', strict=False
)
if all(ipaddress.ip_address(n.current_ip) in net for n in nodes):
plan.old_network = str(net)
break
if plan.old_network:
print(f" Erkanntes altes Netzwerk: {plan.old_network}")
else:
print(" [!] Altes Netzwerk konnte nicht erkannt werden")
# Generate IP mapping suggestions # Generate IP mapping suggestions
print("\n[IP-Mapping]") print("\n[IP-Mapping]")