Spring til indhold
DAfield-notesboot-recovery

Kassen vil ikke boote — og det er min egen skyld

En OOM-kaskade rebootede maskinen. Den kom aldrig op igen. Fire timer, et hav af blindspor og en KVM-konsol senere: GRUB blev aldrig loadet — fordi jeg fem dage forinden havde slettet boot-koden fra den forkerte disk.

Det her er fortsættelsen på »Jeg troede jeg var blevet hacket«. I det forrige indlæg ryddede jeg op på kassen, reclaimede 13 TB lager — og plantede uden at vide det en tidsindstillet bombe i boot-konfigurationen. Her går den af.

  1. maj 2026. Min Hetzner-dedikerede kasse har kørt min agentiske AI-stack — Coolify, Docker-in-Docker-devcontainere, det hele — i syv dage i træk. Kl. 21:43 dør den i flammer: kernel page allocation failure af order:10 (en 4 MB sammenhængende chunk), tre OOM-killer-invokeringer mod løbske Next.js dev-servere (28 GB virtuel ×2) og Node-processer (55 GB virtuel), og en reboot.

Den del regnede jeg med at komme over på minutter. Hvad der faktisk skete derefter åd resten af min nat — og årsagen var en ændring, jeg havde lavet bevidst, med omhu, fem dage forinden.

Fem dage forinden: partitions-beslutningen

En uge før crashet havde jeg opdaget, at Hetzners installimage havde sat alle tre diske (én ~954 GB NVMe og to 7,3 TB HDD'er) op som ét RAID5 — hvilket begrænsede brugbart lager til ~1,9 TB og efterlod cirka 13 TB HDD-kapacitet uadresseret. Så jeg gjorde arbejdet for at få den kapacitet igen: konvertér HDD'erne fra MBR til GPT in-place, byg et nyt RAID1-array, montér som /mnt/bulk.

Jeg gjorde det omhyggeligt. Fuld block-level-backup til en Synology-NAS via Tailscale først. Jeg tjekkede boot-mode (Legacy BIOS, ikke UEFI). Og jeg ræsonnerede, korrekt så langt det rakte, at MBR→GPT-konverteringen ville ødelægge GRUB på sda og sdb (GPT-strukturen overlapper med MBR-boot-koden), men at NVMe'en (som jeg ikke rørte) stadig ville boote, og at BIOS ville falde igennem til den, efter sda/sdb fejlede.

Konverteringen gik glat. RAID forblev [UUU]. Kassen kørte videre i fem dage.

22. maj: den døde

OOM-kaskaden ovenfor. Det interessante er, hvad der kom bagefter.

Hardware-reset fra Hetzner Robot-panelet. Vent tre minutter. SSH:

$ ssh root@projects.si14agents.com
ssh: connect to host projects.si14agents.com port 22: Connection timed out

OK, nogle gange er RAID-assembly langsom efter en uren nedlukning. Vent fem minutter. Det samme. Ti. Det samme.

Aktivér Hetzners Rescue-system (et lille Debian, PXE-bootet af deres netværk). Det kommer fint op — bekræfter, at kassen er i live, bare ikke booter fra disk. Fra rescue mounter jeg diskene. RAID sundt. Filsystemer rene. Kerner og initramfs til stede i /boot. grub.cfg ser fin ud.

Hypotesen jeg gravitiserede mod: et par uger forinden havde apt installeret en ny kerne, som boxen aldrig var rebootet ind i, plus en Synology-modificeret initramfs. Nem historie: ny kerne + modificeret initramfs = boot-fejl. Jeg håndredigerede grub.cfg til den gamle kerne. Rebootede.

Intet.

Timer med mass-masking

Overbevist om at booten hang på en stuck service, gik jeg tilbage i rescue og maskede alt — docker, containerd, Synology, tailscale, fail2ban, coolify-runneren. Strippede booten ned til næsten-vanilla Ubuntu. Rebootede. Stadig intet.

Det, der endelig afslørede det egentlige problem, var journalctl --list-boots fra den mounted disk. Den persistente journal viste præcis tre boot-ID'er: den oprindelige gode fra 15. maj, den forrige gode der endte ved OOM-crashet 22. maj 21:43, og… intet andet. Ingen af mine reboot-forsøg havde nået systemd-journald.

Det er ikke en stuck service. Booten når ikke engang frem til systemd. Hvad end der fejler, sker tidligere — ved kernen, eller under.

KVM afslører det endelig

Jeg havde nægtet at eskalere til Hetzner for KVM, fordi kassen var »levende nok« til at PXE-rescue. Efter timers blindspor gav jeg op og bestilte en manuel power-cycle — som Hetzner heller ikke kunne få til at virke, så de koblede en gratis KVM-konsol på i tre timer.

Inden for minutter så jeg den faktiske skærm under boot. Efter BIOS-POST, fem-sekunders nedtælling, valg af »Boot from first hard disk«, og så:

Type 'exit' to get the menu back
iPXE>

Den faldt direkte tilbage til iPXE-shellen. Hvilket betød: BIOS var sat op med netværk som Boot Option #1. NIC'ens PXE-ROM gav stafetten til Hetzners iPXE. iPXE's »boot fra første harddisk« forsøgte at chainloade fra sda — fandt ingen MBR-boot-kode (fordi jeg havde slettet den fem dage forinden, da jeg konverterede sda til GPT) — og dumpede mig i iPXE-shellen.

iPXE falder ikke videre til næste disk af sig selv. Det var den del, jeg havde misset i mit »BIOS falder igennem til NVMe«-ræsonnement. Native BIOS ville have. Hetzners iPXE-chainloader prøver den første disk og giver op.

Systemet havde kun »virket« i fem dage, fordi intet havde trigget boot-stien i den tid. OOM-crashet var den første reboot siden konverteringen — og første gang iPXE fik chancen for at fejle.

Den faktiske fiks

Fra rescue: mount disken, chroot, geninstallér GRUB på den disk, der stadig er i god form (grub-install --target=i386-pc /dev/nvme0n1 virker; sda/sdb fejler, fordi de nu er GPT uden en BIOS Boot Partition — en fix-it-later-TODO). Men GRUB på NVMe'en er ikke nok i sig selv — BIOS skal faktisk prøve NVMe'en før netværket. Så: KVM ind i boot-menuen (F11), vælg NVMe manuelt, se Ubuntu boote. Så ind i BIOS Setup, skift rækkefølgen til NVMe først og deaktivér LAN PXE Boot.

Gem, exit, reboot. Booter rent. Cirka fire timer fra OOM-crash til »alt grønt igen«.

Læringen

Bomben jeg plantede sad i ro i fem dage, indtil en OOM tvang den første reboot — og valgte tidspunktet for mig. Det er den dybe lektie: en ændrings blast radius kan overleve det vindue, hvor du laver ændringen.

Vis verificeringen, mass-masking og BIOS-rækkefølgen

Fem dage forinden: jeg tjekkede at alle tre diske var bootbare

$ dd if=/dev/nvme0n1 bs=512 count=1 2>/dev/null | strings | head
GRUB
$ dd if=/dev/sda bs=512 count=1 2>/dev/null | strings | head
GRUB     # ← også her
$ dd if=/dev/sdb bs=512 count=1 2>/dev/null | strings | head
GRUB     # ← og her

Alle tre havde GRUB stage 1 i deres MBR — standard Hetzner-RAID-install, GRUB på hver member, så systemet overlever et enkelt disk-svigt. Jeg vidste, at konverteringen ville fjerne den fra sda/sdb. Jeg vidste bare ikke, at iPXE ikke ville falde videre til NVMe'en.

Mass-masking fra rescue

systemctl --root=/mnt/root mask docker.service docker.socket containerd.service \
  synology-active-backup-business-linux-service.service \
  tailscaled.service fail2ban.service unattended-upgrades.service \
  'actions.runner.pksorensen-agentic-live-www.coolify-runner.service'

echo 'blacklist synosnap' > /mnt/root/etc/modprobe.d/blacklist-synosnap.conf
sed -i '/\/mnt\/bulk/s/^/#/' /mnt/root/etc/fstab

Alt sammen spildt arbejde — fejlen var under systemd, ikke i userspace. Bagefter unmaskede jeg det hele igen.

BIOS-boot-rækkefølgen, før og efter

Boot Option #1: SAMSUNG MZVL21T0HCLR-00B00    (var: IBA CL Slot 00FE = netværk)
Boot Option #2: P0: HGST HUH721008ALE600
Boot Option #3: P1: HGST HUH721008ALE600
LAN PXE Boot Option ROM: Disabled              (var: Enabled)

Hvad jeg tog med

  1. En ændrings blast radius kan overleve ændringsvinduet. Jeg ræsonnerede korrekt, at GPT-konverteringen ville fjerne GRUB fra sda/sdb. Det jeg ikke tog højde for, var kæden af hvad der sker, når BIOS er netværks-først og Hetzners iPXE er den, der prøver at chainloade — det lå uden for det system, jeg ændrede. Test dine antagelser ved faktisk at reboote — helst lige efter en ændring, på et tidspunkt du selv vælger, ikke når en OOM vælger det for dig.
  2. Diagnosticér ikke nedstrøms, når problemet er opstrøms. Jeg brugte timer på initramfs, services, kernel-moduler — alt sammen ting der kører efter GRUB. Den faktiske fejl var, at GRUB aldrig blev loadet. journalctl --list-boots uden poster efter crashet ville have udelukket userspace en time tidligere.
  3. En remote grafisk konsol er guld værd. Jeg undgik at eskalere til KVM, fordi kassen var levende nok til PXE-rescue. Det var den forkerte ramme: et system, der kan disk-rescue men ikke disk-boote, er præcis hvornår KVM er mest værdifuld. Du skal kunne se, hvad firmwaren laver.
  4. GPT-diske kræver en BIOS Boot Partition for at være BIOS-bootbare. En lille (~1 MB) partition med bios_grub-flaget er nok. Jeg burde have lavet en på hver HDD som del af GPT-konverteringen. (Ægte TODO.)
  5. Verificér at den kørende kerne matcher boot-defaulten. *** System restart required ***-noten betyder, at din kørende kerne og din boot-default er divergeret. Det er en mine, der kun udløses ved reboot — det værst tænkelige tidspunkt at opdage en utestet kombination. Periodiske bevidste reboots fanger det, før en OOM gør det for dig.

Kassen har været stabil siden denne recovery — og holdt stabil i 22 dage, før den næste katastrofe, som er en helt anden historie.