From 8cdccaeb71a167c6a16b715e65967da3be8a040a Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Fri, 10 Jul 2020 16:40:06 +0200 Subject: [serversetup-bwlp-ipxe] Progress on new structure (+bash) --- .../minilinux/inc/linuxbootentryhook.inc.php | 4 +- .../serversetup-bwlp-ipxe/api.inc.php | 22 ++++- .../serversetup-bwlp-ipxe/inc/bootentry.inc.php | 2 + .../serversetup-bwlp-ipxe/inc/ipxemenu.inc.php | 4 +- .../inc/scriptbuilderbase.inc.php | 33 ++++++-- .../inc/scriptbuilderbash.inc.php | 97 ++++++++++++++++++++++ .../inc/scriptbuilderipxe.inc.php | 50 ++++++----- 7 files changed, 180 insertions(+), 32 deletions(-) create mode 100644 modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderbash.inc.php diff --git a/modules-available/minilinux/inc/linuxbootentryhook.inc.php b/modules-available/minilinux/inc/linuxbootentryhook.inc.php index 324ffc7e..19acedd9 100644 --- a/modules-available/minilinux/inc/linuxbootentryhook.inc.php +++ b/modules-available/minilinux/inc/linuxbootentryhook.inc.php @@ -117,7 +117,9 @@ class LinuxBootEntryHook extends BootEntryHook $exec->executable = 'kernel'; $exec->initRd = ['initramfs-stage31']; $exec->imageFree = true; - $exec->commandLine = 'slxbase=boot/%ID% slxsrv=${serverip} quiet splash ${ipappend1} ${ipappend2}'; + $exec->commandLine = 'slxbase=boot/%ID% slxsrv=${serverip} quiet splash ${ipappend1} ${ipappend2}' + . ' ipv4.ip=${ip} ipv4.router=${gateway} ipv4.dns=${dns} ipv4.hostname=${hostname} ipv4.domain=${domain} ipv4.search=${dnssl}' + . ' ipv4.if=${mac} ipv4.ntpsrv=${ntpsrv} ipv4.subnet=${netmask}'; // Overrides foreach (['executable', 'commandLine', 'initRd', 'imageFree'] as $key) { if (isset($remoteData[$key])) { diff --git a/modules-available/serversetup-bwlp-ipxe/api.inc.php b/modules-available/serversetup-bwlp-ipxe/api.inc.php index 303f1560..dd559efa 100644 --- a/modules-available/serversetup-bwlp-ipxe/api.inc.php +++ b/modules-available/serversetup-bwlp-ipxe/api.inc.php @@ -1,12 +1,26 @@ getMenuEntry($entryId); + if ($bootEntryId !== false) { + $entry = BootEntry::fromDatabaseId($bootEntryId); + $data = $builder->getBootEntry($entry); + } elseif ($entryId !== false) { + $entry = MenuEntry::get($entryId); + $data = $builder->getMenuEntry($entry); } else { - $data = $builder->fallback(); + $data = $builder->bootstrapLive(); + if ($data === false) { + $menu = IPxeMenu::forClient($builder->clientIp(), $builder->uuid()); + $data = $builder->getMenu($menu, true); + } } $builder->output($data); })(); diff --git a/modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php index 7b8fb4b5..614f5ee4 100644 --- a/modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php +++ b/modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php @@ -323,6 +323,8 @@ class CustomBootEntry extends BootEntry { if ($builder instanceof ScriptBuilderIpxe) return $this->ipxe; + if ($builder instanceof ScriptBuilderBash) + return $this->bash; return ''; } diff --git a/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php index 15766227..d4705be5 100644 --- a/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php +++ b/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php @@ -94,7 +94,7 @@ class IPxeMenu * */ - public static function forLocation($locationId) + public static function forLocation($locationId) : IPxeMenu { $chain = null; if (Module::isAvailable('locations')) { @@ -132,7 +132,7 @@ class IPxeMenu return new IPxeMenu($menu); } - public static function forClient($ip, $uuid) + public static function forClient($ip, $uuid) : IPxeMenu { $locationId = 0; if (Module::isAvailable('locations')) { diff --git a/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderbase.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderbase.inc.php index c6adc953..b9867ed9 100644 --- a/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderbase.inc.php +++ b/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderbase.inc.php @@ -11,6 +11,8 @@ abstract class ScriptBuilderBase protected $clientIp; + protected $uuid; + /** * @var bool Running iPXE has slx-extensions */ @@ -26,6 +28,16 @@ abstract class ScriptBuilderBase return $this->platform; } + public function uuid() + { + return $this->uuid; + } + + public function clientIp() + { + return $this->clientIp; + } + public function getLabel() { return 'b' . mt_rand(100, 999) . 'x' . (++$this->lblId); @@ -42,11 +54,11 @@ abstract class ScriptBuilderBase if ($this->platform !== false) { $this->platform = strtoupper($this->platform); } - Header('X-Popo: ' . $this->platform); if ($this->platform !== 'EFI' && $this->platform !== 'PCBIOS') { $this->platform = ''; } $this->hasExtension = $slxExtensions ?? (bool)Request::any('slx-extensions', false, 'int'); + $this->uuid = Request::any('uuid', false, 'string'); } /** @@ -55,13 +67,24 @@ abstract class ScriptBuilderBase */ public abstract function output($string); - public abstract function getMenu($menuId); + public abstract function bootstrapLive(); - public abstract function getMenuEntry($menuEntryId); + public abstract function getMenu(IPxeMenu $menu, bool $bootstrap); - public abstract function getSpecial($special); + /** + * @param MenuEntry|null $menuEntry The according menu entry, or null if invalid. + * @param bool $honorPassword Whether we should generate a password dialog if protected, or skip + * @return string generated script/code/... + */ + public abstract function getMenuEntry($menuEntry, $honorPassword = true); - public abstract function fallback(); + /** + * @param BootEntry|null|false $bootEntry + * @return string + */ + public abstract function getBootEntry($bootEntry); + + public abstract function getSpecial($special); /** * @param IPxeMenu|null $menu diff --git a/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderbash.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderbash.inc.php new file mode 100644 index 00000000..86b2931f --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderbash.inc.php @@ -0,0 +1,97 @@ +menuToScript($menu); + } + + public function getBootEntry($entry) + { + if (!$entry) { + return "echo 'Invalid boot entry id'\nread -n1 -r _\n"; + } + return $entry->toScript($this); + } + + public function getMenuEntry($entry, $honorPassword = true) + { + if ($entry === null) + return "echo 'Invalid menu entry id - press any key to continue'\nread -n1 -r _\n"; + return $entry->getBootEntryScript($this); + } + + public function getSpecial($special) + { + return ''; // We can't really do localboot here I guess + } + + public function menuToScript($menu) + { + $output = "declare -A items_name items_gap hotkey_item\ndeclare menu_default menu_timeout menu_title\n"; + foreach ($menu->items as $entry) { + $id = $entry->menuentryid; + if ($entry->bootEntry === null || (!empty($this->platform) && !$entry->bootEntry->supportsMode($this->platform))) + continue; + if (!$entry->hidden) { + $output .= 'items_name[' . $id . ']=' . $this->bashString($entry->title) . "\n"; + if ($entry->gap) { + $output .= 'items_gap[' . $id . "]=1\n"; + } + } + if ($entry->hotkey !== false) { + $output .= 'hotkey_item[' . $entry->hotkey . ']=' . $id . "\n"; + } + if ($id == $menu->defaultEntryId) { + $output .= "menu_default={$id}\n"; + } + } + return $output . "menu_timeout=" . $menu->timeoutMs + . "\nmenu_title=" . $this->bashString($menu->title) . "\n"; + } + + public function execDataToScript($agnostic, $bios, $efi) : string + { + if ($agnostic !== null) + return $this->execDataToScriptInternal($agnostic); + if ($bios !== null && $this->platform === BootEntry::BIOS) + return $this->execDataToScriptInternal($bios); + if ($efi !== null && $this->platform === BootEntry::EFI) + return $this->execDataToScriptInternal($efi); + return $this->execDataToScriptInternal($bios ?? $efi ?? new ExecData()); + } + + private function execDataToScriptInternal(ExecData $entry) : string + { + $entry->sanitize(); + $script = "declare -a initrd\ndeclare kernel kcl\n"; + if (!empty($entry->initRd)) { + foreach ($entry->initRd as $initrd) { + if (empty($initrd)) + continue; + $script .= 'initrd+=( ' . $this->bashString($initrd) . " )\n"; + } + } + $script .= 'kernel=' . $this->bashString($entry->executable) . "\n"; + $script .= 'kcl="' . str_replace('"', '"\\""', $entry->commandLine) . "\"\n"; // Allow expansion + return $script; + } + + private function bashString($string) + { + if (strpos($string, "'") === false) { + return "'$string'"; + } + return "'" . str_replace("'", "'\\''", $string) . "'"; + } + +} diff --git a/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderipxe.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderipxe.inc.php index 23c4bd70..1b33a1ac 100644 --- a/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderipxe.inc.php +++ b/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderipxe.inc.php @@ -86,32 +86,39 @@ HERE; return $output; } - public function fallback() + /** + * Called when we handle a real client request, and don't just generate static data + * for whatever use-case that might have. In the latter case, it wouldn't make much sense + * to generate a redirect code snippet. + * @return string + */ + public function bootstrapLive() { // Check if required arguments are given; if not, spit out according script and chain to self - $uuid = Request::any('uuid', false, 'string'); - error_log("Got UUID='$uuid' PLATF='" . $this->platform . "'"); - if ($uuid === false || $this->platform === '') { + if ($this->uuid === false || $this->platform === '') { // REQUIRED so we can hide incompatible entries return $this->redirect(); } + return false; + } - $menu = IPxeMenu::forClient($this->clientIp, $uuid); - $out = $this->menuCheckAutostart($menu); - if (!empty($out)) - return "#!ipxe\nimgfree ||\n" . $out; - - return "#!ipxe\nimgfree ||\n" . $this->menuToScript($menu); + public function getBootEntry($entry) + { + if (!$entry) { + return "#!ipxe\nprompt --timeout 5000 Invalid boot entry id\n"; + } + return $entry->toScript($this); } - public function getMenu($menuId) + public function getMenu(IPxeMenu $menu, bool $bootstrap) { - $menu = IPxeMenu::get($menuId, true); + if ($bootstrap) { + return "#!ipxe\nimgfree ||\n" . $this->menuToScript($menu); + } $base = $this->getUrlFull($he); return "#!ipxe\nset self {$base} ||\n" . $this->menuToScript($menu); } - /** * @param IPxeMenu $menu */ @@ -144,7 +151,7 @@ cpair --foreground 7 --background 9 0 console --left 55 --top 88 --right 63 --bottom 64 $slxConsoleUpdate --keep --picture bg-menu || -menu -- {$menu->title} || prompt Error creating menu || +menu -- {$menu->title} || prompt --timeout 5000 Error creating menu || HERE; foreach ($menu->items as $item) { @@ -306,7 +313,6 @@ console || $localboot || goto fail BLA; - // } else { $output = "prompt --timeout 5000 Unknown special command '$special' ||\nchain -ar \${self}\n"; @@ -381,12 +387,12 @@ chain -a \${self}&entryid={$entry->menuentryid}##params || goto fail || HERE; } - public function getMenuEntry($menuEntryId) + public function getMenuEntry($entry, $honorPassword = true) { - $entry = MenuEntry::get($menuEntryId); if ($entry === null) - return "#!ipxe\nprompt --timeout 10000 Invalid menu entry id: $menuEntryId\n"; + return "#!ipxe\nprompt --timeout 10000 Invalid menu entry id\n"; $base = $this->getUrlBase(); + $meid = $entry->menuEntryId(); // Make sure legacy variables are set; they might get used $output = <<serverIp} || iseq \${idx} \${} && set idx:string X || iseq \${self} \${} && set self {$base}? || +set menuentryid $meid || HERE; // Check for password - if (!empty($entry->md5pass)) { // TODO: This should be split out so we can unconditionally get entry code + if ($honorPassword && !empty($entry->md5pass)) { $pwh = Request::post('pwhash', false, 'string'); $pwp = Request::post('pwplain', false, 'string'); if ($pwh === false && $pwp === false) { @@ -459,6 +466,9 @@ HERE; return $output . "goto fail\n"; } // static, we know in advance + if ($efi !== null && $this->platform === BootEntry::EFI) + return $this->execDataToScriptInternal($efi) . "\ngoto fail\n"; + // Should be BIOS at this point return $this->execDataToScriptInternal($bios ?? $efi ?? new ExecData()) . "\ngoto fail\n"; } @@ -510,7 +520,7 @@ HERE; } } if (!empty($entry->commandLine)) { - $script .= ' ' . $entry->commandLine; + $script .= ' ' . $entry->commandLine . ' slx.ipxe.id=${menuentryid}'; } $script .= " || goto fail\n"; if ($entry->resetConsole) { -- cgit v1.2.3-55-g7522