3.3 KiB
3.3 KiB
Bulk SSH Key Injection via Hetzner Rescue Mode
When deploying the itpp-infra key to multiple servers simultaneously, this pattern saves time over one-at-a-time rescue.
Process
- Register the SSH key in the Hetzner project (one-time per key, SKILL.md step 1)
- Enable rescue mode + reboot on all targets — batch via Python:
targets = [(SERVER_ID, "hostname", "IP"), ...] for sid, name, ip in targets: data = json.dumps({"type": "linux64", "ssh_keys": [KEY_ID]}) req = urllib.request.Request( f'https://api.hetzner.cloud/v1/servers/{sid}/actions/enable_rescue', data=data.encode(), headers=headers, method='POST') urllib.request.urlopen(req, timeout=15) req2 = urllib.request.Request( f'https://api.hetzner.cloud/v1/servers/{sid}/actions/reboot', data=b'{}', headers=headers, method='POST') urllib.request.urlopen(req2, timeout=15) - Wait 60-90s — poll SSH port on each:
for ip in "ip1" "ip2" ...; do timeout 5 bash -c "echo > /dev/tcp/$ip/22" 2>/dev/null && echo "$ip: UP" || echo "$ip: WAITING" done - Inject key on all — loop over each rescue host:
PUBKEY=$(cat ~/.ssh/itpp-infra.pub) for ip in ...; do ssh -o StrictHostKeyChecking=no -i ~/.ssh/itpp-infra root@$ip " mount /dev/sda1 /mnt 2>/dev/null || mount /dev/vda1 /mnt echo '$PUBKEY' >> /mnt/root/.ssh/authorized_keys chmod 600 /mnt/root/.ssh/authorized_keys umount /mnt " done - Disable rescue + reboot all (same pattern as step 2, but
disable_rescue+reboot) - Wait 90s, then verify SSH access with
hostnamecommand
Pitfalls
- Not all servers use
sda1— Hetzner rescue only has access to the raw block device. Check withlsblkon first SSH. On CPX11/CPX21 it's typicallysda1; on systems with NVMe it could benvme0n1p1. Fall back tofor part in sda1 vda1 nvme0n1p1; do mount /dev/$part /mnt 2>/dev/null && break; done. - Remount after first mount attempt — If a partition is already auto-mounted from a previous rescue, the second
mountfails. Checkmountpoint -q /mntbefore mounting. - Boot order matters — Enable rescue FIRST, then reboot. Doing them in the wrong order leaves the server off.
disable_rescueBEFOREreboot— Reboot with rescue still enabled just boots back into rescue.- Server POST times vary — CPX11 (~40s) vs CPX41 (~60s). Wait for SSH port before attempting connection.
- app1.itpropartner.com (87.99.144.163) has intermittent SSH delay — port 80 responds quickly but SSH can take an extra 30-60s. Retry with longer timeout.
- ai.itpropartner.com (178.156.167.181, CPX41) hosts the LLM proxy — Rebooting it takes the agent offline. SSH key injection via rescue mode failed on this tier — the
ssh_keysparameter was accepted (rescue enabled, rebooted) but the key was never authorized, despite the same procedure working on 8 other Hetzner boxes (CPX11, CPX21). Root cause unknown. The key IS registered in the project (ID 114709791) and will work if the server is ever rebuilt. A local ollama+Qwen2.5 7B fallback is installed on the netcup box for maintenance periods. Alternative injection method: use the rescueroot_passwordfromenable_rescueinstead of key injection.