host($run); if (! $run->context('reboot_issued')) { $this->keyLogin($this->shell, $host); // Tolerated, deliberately: an image that never had the // linux-image-amd64 metapackage installed makes apt exit non-zero // with nothing wrong. What matters is not whether the Debian kernel // went away but whether a Proxmox one can boot, and that is checked // below on its own terms rather than inferred from this line. $this->shell->run('export DEBIAN_FRONTEND=noninteractive; apt-get -y remove linux-image-amd64 || true'); if (($refusal = $this->refuseUnbootableReboot()) !== null) { return $refusal; } $run->mergeContext([ 'reboot_issued' => true, 'reboot_deadline' => now()->addMinutes(15)->toIso8601String(), ]); $this->shell->run('systemctl reboot || reboot'); return StepResult::poll(30, 'rebooting into the Proxmox kernel'); } try { $this->keyLogin($this->shell, $host); $uname = trim($this->shell->run('uname -r')->stdout); } catch (Throwable) { $uname = ''; } if (str_contains($uname, '-pve')) { return StepResult::advance(); } $deadline = $run->context('reboot_deadline'); if ($deadline !== null && now()->greaterThan(Carbon::parse($deadline))) { // Clear the reboot markers so a manual retry re-issues a fresh reboot // instead of immediately re-hitting the expired deadline. $run->forgetContext('reboot_issued'); $run->forgetContext('reboot_deadline'); return StepResult::fail('Host did not return on the Proxmox kernel before the deadline.'); } return StepResult::poll(15, 'waiting for the Proxmox kernel'); } /** * Everything that has to be true for this machine to come back, or a fail * naming what is not. * * The three checks are the three ways the old code could reboot into * nothing. It removed the Debian kernel with `|| true` and ignored * update-grub's exit status, so a /boot that had filled up — common enough * on a provider image with a small boot partition, and made likelier by the * full-upgrade the previous step runs — produced a truncated initramfs, a * half-written grub.cfg, and a reboot issued on the strength of neither. * * A fail rather than a retry: none of these three conditions fixes itself, * and an operator who is already watching the onboarding can clear /boot or * reinstall the kernel. Retrying would only spend the budget and then say * something vaguer. */ private function refuseUnbootableReboot(): ?StepResult { // A kernel image is not enough on its own — grub needs the initramfs // that goes with it, and a truncated or absent initrd is exactly what a // full /boot leaves behind. Both are listed so the message can say which // half is missing. $kernels = trim($this->shell->run('ls -1 /boot/vmlinuz-*-pve 2>/dev/null')->stdout); $initrds = trim($this->shell->run('ls -1 /boot/initrd.img-*-pve 2>/dev/null')->stdout); if ($kernels === '') { return StepResult::fail( 'Refusing to reboot: no Proxmox kernel image (/boot/vmlinuz-*-pve) is installed on this host, '. 'so it would come back on the Debian kernel at best and not at all if that one was removed. '. 'Check `apt-get -y install proxmox-ve` and the free space in /boot, then retry.' ); } if ($initrds === '') { return StepResult::fail( 'Refusing to reboot: a Proxmox kernel is installed ('.$this->firstLine($kernels).') but it has no '. 'initramfs (/boot/initrd.img-*-pve), which is what a full /boot leaves behind. Free space in /boot, '. 'run `update-initramfs -u -k all`, then retry.' ); } // Checked for its EXIT STATUS, which the old code discarded. update-grub // failing is the difference between a boot menu that lists the new // kernel and one that does not mention it. $grub = $this->shell->run('update-grub'); if (! $grub->ok()) { return StepResult::fail( 'Refusing to reboot: update-grub failed, so the boot menu may not list the Proxmox kernel '. '('.$this->firstLine($kernels).'). Output: '.$this->firstLine(trim($grub->stderr.' '.$grub->stdout)).'. '. 'Fix that on the host (usually free space in /boot), then retry.' ); } return null; } /** Keeps a multi-line `ls` or a long apt error from swamping the run's error field. */ private function firstLine(string $output): string { $line = trim((string) preg_split('/\R/', $output)[0]); return $line === '' ? '(no output)' : $line; } }