confCodeEmited) return ''; $this->confCodeEmited = true; $str = ' if ! [ "$uuid" ] ; then smbios --type 1 --get-uuid 8 --set uuid fi set serverip="' . $this->getLocalIp() . '" '; foreach (['mac', 'ip', 'domain', 'hostname'] as $var) { $str .= <<serverIp; } return $host; } private function getGrubBase(): string { return '(http,' . $this->getLocalIp() . ')'; } private function getUrlBase(): string { $host = $this->getGrubBase(); if (isset($_SERVER['REQUEST_URI'])) { $url = parse_url($_SERVER['REQUEST_URI']); $path = $url['path']; } else { // Static fallback $path = '/boot/ipxe'; } return $host . $path; } private function getUrlFull(?string $key = null, ?string $value = null): string { $url = parse_url($_SERVER['REQUEST_URI']); $urlbase = $this->getUrlBase(); if (empty($url['query'])) { $fromQuery = []; } else { parse_str($url['query'], $fromQuery); foreach ($fromQuery as &$v) { $v = urlencode($v); } unset($v); } unset($fromQuery['entryid'], $fromQuery['special'], $fromQuery['redir']); if ($key !== null) { $fromQuery[$key] = $value; } $required = [ 'type' => 'grub', 'uuid' => '$uuid', 'mac' => '$mac', 'platform' => '$grub_platform', ]; $fullQuery = '?'; foreach ($required + $fromQuery as $k => $v) { // Loop instead of http_build_query since we don't want escaping for the varnames! $fullQuery .= $k . '=' . $v . '&'; } return $urlbase . $fullQuery; } /** * Redirect to same URL, but add our extended params */ private function redirect(string $key = null, string $value = null): string { // Redirect to self with added parameters $urlfull = $this->getUrlFull($key, $value); return $this->getConfCode() . <<uuid === null || $this->platform === '') { // REQUIRED so we can hide incompatible entries // but avoid redirect cycle if (Request::any('redir', '', 'string') === '') { return $this->redirect(); } } return false; } public function getBootEntry(?BootEntry $entry): string { if ($entry === null) { return "echo Invalid boot entry id\nsleep --interruptible --verbose 10\n"; } return $entry->toScript($this); } public function getMenu(IPxeMenu $menu, bool $bootstrap): string { $base = $this->getUrlFull(); return $this->getConfCode() . "set self=\"{$base}\"\n" . $this->menuToScript($menu); } public function menuToScript(IPxeMenu $menu): string { if ($menu->defaultEntryId === null) { $output = <<timeoutMs / 1000); $output = <<getConfCode(); foreach ($menu->items as $item) { $output .= $this->getMenuItemScript($item); } return $output; } private function getMenuItemScript(MenuEntry $entry): string { if (!empty($entry->md5pass) || $entry->hidden) return ''; // TODO $str = "menuentry '" . str_replace("'", '', $entry->title) . "' --id 'id-" . $entry->menuentryid . "' {\n"; if ($entry->gap) { $str .= "true\n"; // AFAICT, not possible in GRUB } elseif ($entry->bootEntry === null || (!empty($this->platform) && !$entry->bootEntry->supportsMode($this->platform))) { $str .= "echo Type mismatch\n"; } elseif ($entry->hidden && $entry->hotkey === false) { $str .= "echo Hidden entries without hotkey are illegal\n"; // Hidden entries without hotkey are illegal } else { if ($entry->hotkey !== false) { // Not supported by grub... } if ($entry->bootEntry instanceof MenuBootEntry) { // Link $str .= "configfile \${self}entryid={$entry->menuentryid}\n"; } else { // Embed directly // TODO: Password. Use read etc.; might need hashsum.mod, in that case, don't embed entry directly but use configfile... $str .= $this->getMenuEntry($entry, true); } } return $str . "}\n"; } public function getSpecial(string $special): string { if ($special === 'localboot') { // Sync this with setup-scripts/grub_localboot occasionally... $output = <<<'EOF' insmod chain if [ "$grub_platform" = "pc" ] ; then chainloader (hd0)+1 chainloader (hd1)+1 chainloader (hd2)+1 fi insmod fat insmod part_gpt echo "Scanning, first pass..." for efi in (*,gpt*)/efi/grub/grubx64.efi (*,gpt*)/efi/boot/bootx64.efi (*,gpt*)/efi/*/*/bootmgfw.efi (*,gpt*)/efi/*/*.efi \ (*,msdos*)/efi/grub/grubx64.efi (*,msdos*)/efi/boot/bootx64.efi (*,msdos*)/efi/*/*/bootmgfw.efi (*,msdos*)/efi/*/*.efi; do regexp --set=1:efi_device '^\((.*)\)/' "${efi}" done echo "Scanning, second pass..." for efi in (*,gpt*)/efi/grub/grubx64.efi (*,gpt*)/efi/boot/bootx64.efi (*,gpt*)/efi/*/*/bootmgfw.efi (*,gpt*)/efi/*/*.efi \ (*,msdos*)/efi/grub/grubx64.efi (*,msdos*)/efi/boot/bootx64.efi (*,msdos*)/efi/*/*/bootmgfw.efi (*,msdos*)/efi/*/*.efi; do if [ -e "${efi}" ]; then #regexp --set=1:efi_device '^\((.*)\)/' "${efi}" regexp --set=1:root '^(\(.*\))/' "${efi}" regexp --set=1:efi_path '^\(.*\)(/.*)$' "${efi}" echo " >> Found operating system! <<" echo " Path: '${efi}' on '${root}'" echo " Fallback '${efi_path}'" chainloader "${efi}" boot echo " That failed..." fi done echo "No EFI known OS found. Exiting." exit EOF; } else { $output = <<md5pass)) { return "echo TODO: Implement password check...\nsleep --interruptible --verbose 10\n"; } $meid = $entry->menuEntryId(); $output = $this->getConfCode() . "set menuentryid=$meid\n"; // Output actual entry $output .= str_replace('%fail%', 'fail', $entry->getBootEntryScript($this)); return $output; } public function execDataToScript(?ExecData $agnostic, ?ExecData $bios, ?ExecData $efi): string { if ($agnostic !== null) return $this->execDataToScriptInternal($agnostic); if ($efi !== null && $this->platform === BootEntry::EFI) return $this->execDataToScriptInternal($efi); // Unknown or not EFI, should be BIOS at this point return $this->execDataToScriptInternal($bios ?? $efi ?? new ExecData()); } private function execDataToScriptInternal(ExecData $entry): string { $entry->sanitize(); $base = $this->getGrubBase(); $script = ''; // Overriding dhcpOpts probably not possible/necessary $initrds = []; if (!empty($entry->initRd)) { foreach ($entry->initRd as $initrd) { if (empty($initrd)) continue; $initrds[] = $this->combineUrl($base, $initrd); } } $file = $this->combineUrl($base, $entry->executable); $script .= "linux $file {$entry->commandLine} slx.ipxe.id=\${menuentryid}\n"; if (!empty($initrds)) { $script .= "initrd " . implode(' ', $initrds) . "\n"; } return $script; } private function combineUrl(string $base, string $path): string { $url = parse_url($path); if (isset($url['host'])) { $scheme = $url['scheme'] ?? 'http'; $host = $url['host']; $base = "($scheme,$host)"; $path = $url['path'] ?? '/'; if (isset($url['query'])) { $path .= '?' . $url['query']; } } else { if ($path[0] !== '/') { $path = '/' . $path; } } return $base . $path; } }