summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Rettberg2020-06-10 17:12:02 +0200
committerSimon Rettberg2020-06-10 17:12:02 +0200
commit3fb99a5e8c229885602198bea4dce26bcf0fcc4f (patch)
tree775501f868c5afe913ef6485dabb89feefea7fb0
parent[statistics] Fix querying location 0 (no location) (diff)
downloadslx-admin-3fb99a5e8c229885602198bea4dce26bcf0fcc4f.tar.gz
slx-admin-3fb99a5e8c229885602198bea4dce26bcf0fcc4f.tar.xz
slx-admin-3fb99a5e8c229885602198bea4dce26bcf0fcc4f.zip
[serversetup-bwlp-ipxe] Start refactoring ipxe script generator
This is WIP. Mostly restored all the old functionality. Boot entries are fetched when selected, not embedded in the main script, so password protection is a bit stronger. Hopefully allows for other script generators in the future.
-rw-r--r--modules-available/serversetup-bwlp-ipxe/api.inc.php277
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php149
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php10
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php86
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php82
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderbase.inc.php83
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderipxe.inc.php522
-rw-r--r--modules-available/serversetup-bwlp-ipxe/page.inc.php1
8 files changed, 749 insertions, 461 deletions
diff --git a/modules-available/serversetup-bwlp-ipxe/api.inc.php b/modules-available/serversetup-bwlp-ipxe/api.inc.php
index c3804a03..303f1560 100644
--- a/modules-available/serversetup-bwlp-ipxe/api.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/api.inc.php
@@ -1,273 +1,12 @@
<?php
-// Menu mode
-
-$serverIp = Property::getServerIp();
-
-// Check if required arguments are given; if not, spit out according script and chain to self
-$uuid = Request::any('uuid', false, 'string');
-// Get platform - EFI or PCBIOS
-$platform = Request::any('platform', false, 'string');
-$manuf = Request::any('manuf', false, 'string');
-$product = Request::any('product', false, 'string');
-$slxExtensions = Request::any('slx-extensions', false, 'int');
-
-if ($platform === false || ($uuid === false && $product === false) || $slxExtensions === false) {
- // Redirect to self with added parameters
- $url = parse_url($_SERVER['REQUEST_URI']);
- if (isset($_SERVER['SCRIPT_URI']) && preg_match('#^(\w+://[^/]+)#', $_SERVER['SCRIPT_URI'], $out)) {
- $urlbase = $out[1];
- } elseif (isset($_SERVER['REQUEST_SCHEME']) && isset($_SERVER['SERVER_NAME'])) {
- $urlbase = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['SERVER_NAME'];
- } elseif (isset($_SERVER['REQUEST_SCHEME']) && isset($_SERVER['SERVER_ADDR'])) {
- $urlbase = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['SERVER_ADDR'];
+(function() {
+ $builder = new ScriptBuilderIpxe();
+ $entryId = Request::get('entryid', false, 'int');
+ if ($entryId !== false) {
+ $data = $builder->getMenuEntry($entryId);
} else {
- $urlbase = 'http://' . $serverIp;
- }
- $urlbase .= $url['path'];
- if (empty($url['query'])) {
- $arr = [];
- } else {
- parse_str($url['query'], $arr);
- foreach ($arr as &$v) {
- $v = urlencode($v);
- }
- unset($v);
- }
- $arr['uuid'] = '${uuid}';
- $arr['mac'] = '${mac}';
- $arr['manuf'] = '${manufacturer:uristring}';
- $arr['product'] = '${product:uristring}';
- $arr['platform'] = '${platform:uristring}';
- $query = '?';
- foreach ($arr as $k => $v) {
- $query .= $k . '=' . $v . '&';
- }
- //$query = substr($query, 0, -1);
- echo <<<HERE
-#!ipxe
-set slxtest:string something ||
-iseq \${slxtest:md5} \${} && set slxext 0 || set slxext 1 ||
-clear slxtest ||
-set self {$urlbase}{$query}slx-extensions=\${slxext}
-:retry
-echo Chaining to \${self}
-chain -ar \${self} ||
-echo Chaining to self failed with \${errno}, retrying in a bit...
-sleep 5
-goto retry
-HERE;
- exit;
-}
-// ipxe has it lowercase, but we use uppercase
-$platform = strtoupper($platform);
-if ($platform !== 'PCBIOS' && $platform !== 'EFI') {
- $platform = 'PCBIOS'; // Just hope for the best?
-}
-
-$BOOT_METHODS = Localboot::BOOT_METHODS[$platform];
-
-$ip = $_SERVER['REMOTE_ADDR'];
-if (substr($ip, 0, 7) === '::ffff:') {
- $ip = substr($ip, 7);
-}
-$menu = Request::get('menuid', false, 'int');
-if ($menu !== false) {
- $menu = new IPxeMenu($menu);
- $initLabel = 'slx_menu';
-} else {
- $menu = IPxeMenu::forClient($ip, $uuid);
- $initLabel = 'init';
-}
-// If this is a menu with a single item, treat a timeout of 0 as "boot immediately" instead of "infinite"
-if ($menu->itemCount() === 1 && $menu->timeoutMs() === 0 && ($tmp = $menu->getDefaultScriptLabel()) !== false) {
- $directBoot = "goto $tmp ||";
- $initLabel = 'init';
-} else {
- $directBoot = '';
-}
-
-// Get preferred localboot method, depending on system model
-$localboot = false;
-$model = false;
-if ($uuid !== false && Module::get('statistics') !== false) {
- // If we have the machine table, we rather try to look up the system model from there, using the UUID
- $row = Database::queryFirst('SELECT systemmodel FROM machine WHERE machineuuid = :uuid', ['uuid' => $uuid]);
- if ($row !== false && !empty($row['systemmodel'])) {
- $model = $row['systemmodel'];
- }
-}
-if ($model === false) {
- // Otherwise use what iPXE sent us
- function modfilt($str)
- {
- if (empty($str) || preg_match('/product\s+name|be\s+filled|unknown|default\s+string|system\s+model|manufacturer/i', $str))
- return false;
- return trim(preg_replace('/\s+/', ' ', $str));
- }
- $manuf = modfilt($manuf);
- $product = modfilt($product);
- if (!empty($product)) {
- $model = $product;
- if (!empty($manuf)) {
- $model .= " ($manuf)";
- }
+ $data = $builder->fallback();
}
-}
-// Query
-if ($model !== false) {
- $e = strtolower($platform); // We made sure $platform is either PCBIOS or EFI, so no injection possible
- $row = Database::queryFirst("SELECT $e AS bootmethod FROM serversetup_localboot WHERE systemmodel = :model LIMIT 1",
- ['model' => $model]);
- if ($row !== false) {
- $localboot = $row['bootmethod'];
- }
-}
-if ($localboot === false || !isset($BOOT_METHODS[$localboot])) {
- $localboot = Localboot::getDefault()[$platform];
- if (!isset($BOOT_METHODS[$localboot])) {
- $localboot = array_keys($BOOT_METHODS)[0];
- }
-}
-// Convert to actual ipxe code
-if (isset($BOOT_METHODS[$localboot])) {
- $localboot = $BOOT_METHODS[$localboot];
-} else {
- $localboot = 'prompt Localboot not possible';
-}
-
-if ($slxExtensions) {
- $slxConsoleUpdate = '--update';
- $slxPasswordOnly = '--nouser';
-} else {
- $slxConsoleUpdate = '';
- $slxPasswordOnly = '';
-}
-
-$output = <<<HERE
-#!ipxe
-
-goto $initLabel || goto fail ||
-
-# functions
-
-# password check with gotos
-# set slx_hash to the expected hash
-# slx_salt to the salt to use
-# slx_pw_ok to the label to jump on success
-# slx_pw_fail to label for wrong pw
-:slx_pass_check
-login $slxPasswordOnly ||
-set slxtmp_pw \${password:md5}-\${slx_salt} || goto fail
-set slxtmp_pw \${slxtmp_pw:md5} || goto fail
-clear password ||
-iseq \${slxtmp_pw} \${slx_hash} || prompt Wrong password. Press a key. ||
-iseq \${slxtmp_pw} \${slx_hash} || goto \${slx_pw_fail} ||
-iseq \${slxtmp_pw} \${slx_hash} && goto \${slx_pw_ok} ||
-goto fail
-
-:slx_localboot
-imgfree ||
-console ||
-$localboot || goto fail
-
-# start
-:init
-
-set ipappend1 ip=\${ip}:{$serverIp}:\${gateway}:\${netmask}
-set ipappend2 BOOTIF=01-\${mac:hexhyp}
-set serverip $serverIp ||
-iseq \${idx} \${} && set idx:string X ||
-
-# Clean up in case we've been chained to
-imgfree ||
-
-$directBoot
-
-imgfetch --name bg-menu /tftp/pxe-menu.png ||
-
-:start
-
-console --left 55 --top 88 --right 63 --bottom 64 --keep --picture bg-menu ||
-
-colour --rgb 0xffffff 7
-colour --rgb 0xcccccc 5
-colour --rgb 0x000000 0
-colour --rgb 0xdddddd 6
-cpair --foreground 0 --background 4 1
-cpair --foreground 0 --background 5 2
-cpair --foreground 7 --background 9 0
-
-:slx_menu
-
-iseq \${serverip} \${} || goto ip_check_ok
-goto init
-:ip_check_ok
-
-console --left 55 --top 88 --right 63 --bottom 64 $slxConsoleUpdate --keep --picture bg-menu ||
-
-HERE;
-
-$output .= $menu->getMenuDefinition('target', $platform, $slxExtensions);
-
-$output .= <<<HERE
-
-console --left 60 --top 130 --right 67 --bottom 86 $slxConsoleUpdate ||
-goto \${target} ||
-echo Could not find menu entry in script.
-prompt Press any key to continue.
-goto start
-
-HERE;
-
-$output .= $menu->getItemsCode($platform);
-
-/*
-
-:i5
-chain -a /tftp/memtest.0 passes=1 onepass || goto membad
-prompt Memory OK. Press a key.
-goto init
-
-:i8
-set x:int32 0
-:again
-console --left 60 --top 130 --right 67 --bottom 96 --picture bg-load --keep ||
-console --left 55 --top 88 --right 63 --bottom 64 --picture bg-menu --keep ||
-inc x
-iseq \${x} 20 || goto again
-prompt DONE. Press dein Knie.
-goto slx_menu
-
-:membad
-iseq \${errno} 0x1 || goto memaborted
-params
-param scrot \${vram}
-imgfetch -a http://132.230.8.113/screen.php##params ||
-prompt Memory is bad. Press a key.
-goto init
-
-:memaborted
-params
-param scrot \${vram}
-imgfetch -a http://132.230.8.113/screen.php##params ||
-prompt Memory test aborted. Press a key.
-goto init
-
-*/
-
-$output .= <<<HERE
-:fail
-prompt Boot failed. Press any key to start.
-goto init
-HERE;
-
-setlocale(LC_ALL, 'de_DE.UTF-8', 'de_DE.utf-8', 'de_DE.utf8', 'de_DE', 'de', 'German', 'ge', 'en_US.UTF-8', 'en_US.utf-8');
-if ($platform === 'EFI') {
- $cs = 'ASCII';
-} else {
- $cs = 'IBM437';
-}
-Header('Content-Type: text/plain; charset=' . $cs);
-
-echo iconv('UTF-8', $cs . '//TRANSLIT//IGNORE', $output);
+ $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 e89380ce..7b8fb4b5 100644
--- a/modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php
@@ -14,7 +14,11 @@ abstract class BootEntry
public abstract function supportsMode($mode);
- public abstract function toScript($failLabel, $mode);
+ /**
+ * @param ScriptBuilderBase $builder
+ * @return string
+ */
+ public abstract function toScript($builder);
public abstract function toArray();
@@ -54,6 +58,9 @@ abstract class BootEntry
if ($module === '.exec') {
return new StandardBootEntry($data);
}
+ if ($module === '.special') {
+ return new SpecialBootEntry($data);
+ }
return null;
}
@@ -84,7 +91,7 @@ abstract class BootEntry
public static function newCustomBootEntry($initData)
{
- if (empty($initData['script']))
+ if (!is_array($initData) || empty($initData))
return null;
return new CustomBootEntry($initData);
}
@@ -135,8 +142,10 @@ class StandardBootEntry extends BootEntry
* @var ExecData same for EFI
*/
protected $efi;
-
- protected $arch; // Constants below
+ /**
+ * @var string BootEntry Constants above
+ */
+ protected $arch;
const KEYS = ['executable', 'initRd', 'commandLine', 'replace', 'imageFree', 'autoUnload', 'resetConsole', 'dhcpOptions'];
@@ -257,74 +266,13 @@ class StandardBootEntry extends BootEntry
return false;
}
- public function toScript($failLabel, $mode)
+ public function toScript($builder)
{
- if (!$this->supportsMode($mode)) {
- return "prompt Entry doesn't have an executable for mode $mode\n";
- }
- if ($this->arch === BootEntry::AGNOSTIC || $mode == BootEntry::BIOS) {
- $entry = $this->pcbios;
- } else {
- $entry = $this->efi;
- }
- $entry->sanitize();
-
- $script = '';
- if ($entry->resetConsole) {
- $script .= "console ||\n";
- }
- if ($entry->imageFree) {
- $script .= "imgfree ||\n";
- }
- foreach ($entry->dhcpOptions as $opt) {
- if (empty($opt['value'])) {
- $val = '${}';
- } else {
- if (empty($opt['hex'])) {
- $val = bin2hex($opt['value']);
- } else {
- $val = $opt['value'];
- }
- preg_match_all('/[0-9a-f]{2}/', $val, $out);
- $val = implode(':', $out[0]);
- }
- $script .= 'set net${idx}/' . $opt['opt'] . ':hex ' . $val
- . ' || prompt Cannot override DHCP server option ' . $opt['opt'] . ". Press any key to continue anyways.\n";
- }
- $initrds = [];
- if (!empty($entry->initRd)) {
- foreach (array_values($entry->initRd) as $i => $initrd) {
- if (empty($initrd))
- continue;
- $script .= "initrd --name initrd$i $initrd || goto $failLabel\n";
- $initrds[] = "initrd$i";
- }
- }
- $script .= "boot ";
- if ($entry->autoUnload) {
- $script .= "-a ";
- }
- if ($entry->replace) {
- $script .= "-r ";
- }
- $script .= $entry->executable;
- if (!empty($initrds)) {
- if ($mode === BootEntry::BIOS) {
- $script .= " initrd=" . implode(',', $initrds);
- } else {
- foreach ($initrds as $initrd) {
- $script .= " initrd=$initrd";
- }
- }
- }
- if (!empty($entry->commandLine)) {
- $script .= ' ' . $entry->commandLine;
- }
- $script .= " || goto $failLabel\n";
- if ($entry->resetConsole) {
- $script .= "goto start ||\n";
- }
- return $script;
+ if ($this->arch === BootEntry::AGNOSTIC) // Same as below, could construct fall-through but this is more clear
+ return $builder->execDataToScript($this->pcbios, null, null);
+ return $builder->execDataToScript(null,
+ $this->supportsMode(BootEntry::BIOS) ? $this->pcbios : null,
+ $this->supportsMode(BootEntry::EFI) ? $this->efi : null);
}
public function addFormFields(&$array)
@@ -347,12 +295,22 @@ class StandardBootEntry extends BootEntry
class CustomBootEntry extends BootEntry
{
- protected $script;
+ /**
+ * @var string iPXE
+ */
+ protected $ipxe;
+
+ protected $bash;
+
+ protected $grub;
public function __construct($data)
{
- if (is_array($data) && isset($data['script'])) {
- $this->script = $data['script'];
+ if (is_array($data)) {
+ $this->ipxe = $data['script'] ?? ''; // LEGACY
+ foreach (['bash', 'grub'] as $key) {
+ $this->{$key} = $data[$key] ?? '';
+ }
}
}
@@ -361,22 +319,24 @@ class CustomBootEntry extends BootEntry
return true;
}
- public function toScript($failLabel, $mode)
+ public function toScript($builder)
{
- return str_replace('%fail%', $failLabel, $this->script) . "\n";
+ if ($builder instanceof ScriptBuilderIpxe)
+ return $this->ipxe;
+ return '';
}
public function addFormFields(&$array)
{
$array['entry'] = [
- 'script' => $this->script,
+ 'script' => $this->ipxe,
];
$array['script_checked'] = 'checked';
}
public function toArray()
{
- return ['script' => $this->script];
+ return ['script' => $this->ipxe];
}
}
@@ -394,9 +354,10 @@ class MenuBootEntry extends BootEntry
return true;
}
- public function toScript($failLabel, $mode)
+ public function toScript($builder)
{
- return 'chain -ar ${self}&menuid=' . $this->menuId . ' || goto ' . $failLabel . "\n";
+ $menu = IPxeMenu::get($this->menuId);
+ return $builder->menuToScript($menu);
}
public function toArray()
@@ -409,3 +370,31 @@ class MenuBootEntry extends BootEntry
}
}
+class SpecialBootEntry extends BootEntry
+{
+
+ private $type;
+
+ public function __construct($type)
+ {
+ $this->type = $type['type'] ?? $type;
+ }
+
+ public function supportsMode($mode)
+ {
+ return true;
+ }
+
+ public function toScript($builder)
+ {
+ return $builder->getSpecial($this->type);
+ }
+
+ public function toArray()
+ {
+ return [];
+ }
+
+ public function addFormFields(&$array) { }
+
+} \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php
index 3ee33cae..29885588 100644
--- a/modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php
@@ -377,10 +377,8 @@ class IPxe
'entryid' => 'localboot',
'hotkey' => 'L',
'title' => 'Lokales System starten',
- 'module' => '.script',
- 'data' => json_encode([
- 'script' => 'goto slx_localboot || goto %fail% ||',
- ]),
+ 'module' => '.special',
+ 'data' => json_encode(['type' => 'localboot']),
]);
Database::exec($query,
[
@@ -389,7 +387,7 @@ class IPxe
'title' => 'Power off',
'module' => '.script',
'data' => json_encode([
- 'script' => 'poweroff || goto %fail% ||',
+ 'script' => 'poweroff || goto fail ||',
]),
]);
Database::exec($query,
@@ -399,7 +397,7 @@ class IPxe
'title' => 'Reboot',
'module' => '.script',
'data' => json_encode([
- 'script' => 'reboot || goto %fail% ||',
+ 'script' => 'reboot || goto fail ||',
]),
]);
}
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php
index f87d15c2..15766227 100644
--- a/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php
@@ -4,23 +4,35 @@ class IPxeMenu
{
protected $menuid;
- protected $timeoutMs;
- protected $title;
- protected $defaultEntryId;
+ public $timeoutMs;
+ public $title;
+ public $defaultEntryId;
/**
* @var MenuEntry[]
*/
- protected $items = [];
+ public $items = [];
+ /**
+ * @param int $menuId
+ */
+ public static function get($menuId, $emptyFallback = false)
+ {
+ $menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid FROM serversetup_menu
+ WHERE menuid = :menuid LIMIT 1", ['menuid' => $menuId]);
+ if ($menu !== false)
+ return new IPxeMenu($menu);
+ if (!$emptyFallback)
+ return null;
+ return new EmptyIPxeMenu();
+ }
+
+ /**
+ * IPxeMenu constructor.
+ *
+ * @param array $menu array for according menu row
+ */
public function __construct($menu)
{
- if (!is_array($menu)) {
- $menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid FROM serversetup_menu
- WHERE menuid = :menuid LIMIT 1", ['menuid' => $menu]);
- if (!is_array($menu)) {
- $menu = ['menuid' => 'foo', 'title' => 'Invalid Menu ID: ' . (int)$menu];
- }
- }
$this->menuid = (int)$menu['menuid'];
$this->timeoutMs = (int)$menu['timeoutms'];
$this->title = $menu['title'];
@@ -40,41 +52,6 @@ class IPxeMenu
}
}
- public function getMenuDefinition($targetVar, $mode, $slxExtensions)
- {
- $str = "menu -- {$this->title}\n";
- foreach ($this->items as $item) {
- $str .= $item->getMenuItemScript("m_{$this->menuid}", $this->defaultEntryId, $mode, $slxExtensions);
- }
- if ($this->defaultEntryId === null) {
- $defaultLabel = "mx_{$this->menuid}_poweroff";
- } else {
- $defaultLabel = "m_{$this->menuid}_{$this->defaultEntryId}";
- }
- $str .= "choose";
- if ($this->timeoutMs > 0) {
- $str .= " --timeout {$this->timeoutMs}";
- }
- $str .= " $targetVar || goto $defaultLabel || goto fail\n";
- if ($this->defaultEntryId === null) {
- $str .= "goto skip_{$defaultLabel}\n"
- . ":{$defaultLabel}\n"
- . "poweroff || goto fail\n"
- . ":skip_{$defaultLabel}\n";
- }
- return $str;
- }
-
- public function getItemsCode($mode)
- {
- $str = '';
- foreach ($this->items as $item) {
- $str .= $item->getBootEntryScript("m_{$this->menuid}", 'fail', $mode);
- $str .= "goto slx_menu\n";
- }
- return $str;
- }
-
public function title()
{
return $this->title;
@@ -94,13 +71,11 @@ class IPxeMenu
}
/**
- * @return string|false Return script label of default entry, false if not set
+ * @return string|null Return script label of default entry, null if not set
*/
- public function getDefaultScriptLabel()
+ public function getDefaultEntryId()
{
- if ($this->defaultEntryId !== null)
- return "m_{$this->menuid}_{$this->defaultEntryId}";
- return false;
+ return $this->defaultEntryId;
}
/**
@@ -126,10 +101,11 @@ class IPxeMenu
$chain = Location::getLocationRootChain($locationId);
}
if (!empty($chain)) {
- $res = Database::simpleQuery("SELECT m.menuid, m.timeoutms, m.title, IFNULL(ml.defaultentryid, m.defaultentryid) AS defaultentryid, ml.locationid
- FROM serversetup_menu m
- INNER JOIN serversetup_menu_location ml USING (menuid)
- WHERE ml.locationid IN (:chain)", ['chain' => $chain]);
+ $res = Database::simpleQuery("SELECT m.menuid, m.timeoutms, m.title,
+ IFNULL(ml.defaultentryid, m.defaultentryid) AS defaultentryid, ml.locationid
+ FROM serversetup_menu m
+ INNER JOIN serversetup_menu_location ml USING (menuid)
+ WHERE ml.locationid IN (:chain)", ['chain' => $chain]);
if ($res->rowCount() > 0) {
// Make the location id key, preserving order (closest location is first)
$chain = array_flip($chain);
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php
index a65e9f98..1e567448 100644
--- a/modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php
@@ -5,33 +5,51 @@ class MenuEntry
/**
* @var int id of entry, used for pw
*/
- private $menuentryid;
+ public $menuentryid;
/**
* @var false|string key code as expected by iPXE
*/
- private $hotkey;
+ public $hotkey;
/**
* @var string
*/
- private $title;
+ public $title;
/**
* @var bool
*/
- private $hidden;
+ public $hidden;
/**
* @var bool
*/
- private $gap;
+ public $gap;
/**
* @var int
*/
- private $sortval;
+ public $sortval;
/**
* @var BootEntry
*/
- private $bootEntry = null;
+ public $bootEntry = null;
- private $md5pass = null;
+ public $plainpass = null;
+
+ public $md5pass = null;
+
+ /**
+ * @param int $menuEntryId
+ * @return MenuEntry|null
+ */
+ public static function get($menuEntryId)
+ {
+ $row = Database::queryFirst("SELECT e.menuentryid, e.entryid, e.refmenuid, e.hotkey, e.title, e.hidden, e.sortval, e.plainpass, e.md5pass,
+ b.module, b.data AS bootentry
+ FROM serversetup_menuentry e
+ LEFT JOIN serversetup_bootentry b USING (entryid)
+ WHERE e.menuentryid = :id", ['id' => $menuEntryId]);
+ if ($row === false)
+ return null;
+ return new MenuEntry($row);
+ }
/**
* MenuEntry constructor.
@@ -46,10 +64,10 @@ class MenuEntry
$this->{$key} = $value;
}
}
- $this->hotkey = self::getKeyCode($row['hotkey']);
+ $this->hotkey = self::getKeyCode($row['hotkey'] ?? '');
if (!empty($row['bootentry'])) {
$this->bootEntry = BootEntry::fromJson($row['module'], $row['bootentry']);
- } elseif ($row['refmenuid'] !== null) {
+ } elseif (isset($row['refmenuid'])) {
$this->bootEntry = BootEntry::forMenu($row['refmenuid']);
}
$this->gap = (array_key_exists('entryid', $row) && $row['entryid'] === null && $row['refmenuid'] === null);
@@ -60,49 +78,11 @@ class MenuEntry
settype($this->menuentryid, 'int');
}
- public function getMenuItemScript($lblPrefix, $requestedDefaultId, $mode, $slxExtensions)
- {
- if ($this->bootEntry !== null && !$this->bootEntry->supportsMode($mode))
- return '';
- $str = 'item ';
- if ($this->gap) {
- $str .= '--gap -- ';
- } else {
- if ($this->hidden && $slxExtensions) {
- if ($this->hotkey === false)
- return ''; // Hidden entries without hotkey are illegal
- $str .= '--hidden ';
- }
- if ($this->hotkey !== false) {
- $str .= '--key ' . $this->hotkey . ' ';
- }
- if ($this->menuentryid == $requestedDefaultId) {
- $str .= '--default ';
- }
- $str .= "-- {$lblPrefix}_{$this->menuentryid} ";
- }
- if (empty($this->title)) {
- $str .= '${}';
- } else {
- $str .= $this->title;
- }
- return $str . " || prompt Could not create menu item for {$lblPrefix}_{$this->menuentryid}\n";
- }
-
- public function getBootEntryScript($lblPrefix, $failLabel, $mode)
+ public function getBootEntryScript($builder)
{
- if ($this->bootEntry === null || !$this->bootEntry->supportsMode($mode))
+ if ($this->bootEntry === null)
return '';
- $str = ":{$lblPrefix}_{$this->menuentryid}\n";
- if (!empty($this->md5pass)) {
- $str .= "set slx_hash {$this->md5pass} || goto $failLabel\n"
- . "set slx_salt {$this->menuentryid} || goto $failLabel\n"
- . "set slx_pw_ok {$lblPrefix}_ok || goto $failLabel\n"
- . "set slx_pw_fail slx_menu || goto $failLabel\n"
- . "goto slx_pass_check || goto $failLabel\n"
- . ":{$lblPrefix}_ok\n";
- }
- return $str . $this->bootEntry->toScript($failLabel, $mode);
+ return $this->bootEntry->toScript($builder);
}
public function menuEntryId()
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderbase.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderbase.inc.php
new file mode 100644
index 00000000..c6adc953
--- /dev/null
+++ b/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderbase.inc.php
@@ -0,0 +1,83 @@
+<?php
+
+abstract class ScriptBuilderBase
+{
+
+ private $lblId = 0;
+
+ protected $serverIp;
+
+ protected $platform = '';
+
+ protected $clientIp;
+
+ /**
+ * @var bool Running iPXE has slx-extensions
+ */
+ protected $hasExtension = false;
+
+ public function hasExtensions()
+ {
+ return $this->hasExtension;
+ }
+
+ public function platform()
+ {
+ return $this->platform;
+ }
+
+ public function getLabel()
+ {
+ return 'b' . mt_rand(100, 999) . 'x' . (++$this->lblId);
+ }
+
+ public function __construct($platform = null, $serverIp = null, $slxExtensions = null)
+ {
+ $this->clientIp = $_SERVER['REMOTE_ADDR'];
+ if (substr($this->clientIp, 0, 7) === '::ffff:') {
+ $this->clientIp = substr($this->clientIp, 7);
+ }
+ $this->serverIp = $serverIp ?? $_SERVER['SERVER_ADDR'] ?? Property::getServerIp();
+ $this->platform = $platform ?? Request::any('platform', false, 'string');
+ 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');
+ }
+
+ /**
+ * Output given string (script) to client, in a suitable encoding, headers, etc.
+ * @param string $string
+ */
+ public abstract function output($string);
+
+ public abstract function getMenu($menuId);
+
+ public abstract function getMenuEntry($menuEntryId);
+
+ public abstract function getSpecial($special);
+
+ public abstract function fallback();
+
+ /**
+ * @param IPxeMenu|null $menu
+ * @return string
+ */
+ public abstract function menuToScript($menu);
+
+ /**
+ * Pass EITHER only $agnostic, OR $bios and/or $efi
+ * If $agnostic is given, it should be used unconditionally,
+ * and $bios/$efi should be ignored.
+ * @param ExecData $agnostic
+ * @param ExecData $bios
+ * @param ExecData $efi
+ * @return string
+ */
+ public abstract function execDataToScript($agnostic, $bios, $efi);
+
+} \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderipxe.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderipxe.inc.php
new file mode 100644
index 00000000..23c4bd70
--- /dev/null
+++ b/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderipxe.inc.php
@@ -0,0 +1,522 @@
+<?php
+
+class ScriptBuilderIpxe extends ScriptBuilderBase
+{
+
+ private function getUrlBase()
+ {
+ if (isset($_SERVER['REQUEST_URI'])) {
+ $url = parse_url($_SERVER['REQUEST_URI']);
+ if (isset($_SERVER['SCRIPT_URI']) && preg_match('#^(\w+://[^/]+)#', $_SERVER['SCRIPT_URI'], $out)) {
+ $urlbase = $out[1];
+ } elseif (isset($_SERVER['REQUEST_SCHEME']) && isset($_SERVER['SERVER_NAME'])) {
+ $urlbase = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['SERVER_NAME'];
+ } elseif (isset($_SERVER['REQUEST_SCHEME']) && isset($_SERVER['SERVER_ADDR'])) {
+ $urlbase = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['SERVER_ADDR'];
+ } else {
+ $urlbase = 'http://' . $this->serverIp;
+ }
+ return $urlbase . $url['path'];
+ }
+ // Static fallback
+ return 'http://' . $this->serverIp . '/boot/ipxe';
+
+ }
+
+ private function getUrlFull(&$hasExt, $key = null, $value = null)
+ {
+ $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['menuid'], $fromQuery['entryid'], $fromQuery['special']);
+ if ($key !== null) {
+ $fromQuery[$key] = $value;
+ }
+ $hasExt = isset($fromQuery['slx-extensions']);
+ $required = [
+ 'uuid' => '${uuid}',
+ 'mac' => '${mac}',
+ 'manuf' => '${manufacturer:uristring}',
+ 'product' => '${product:uristring}',
+ 'platform' => '${platform:uristring}',
+ ];
+ $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($key = null, $value = null)
+ {
+ // Redirect to self with added parameters
+ $urlfull = $this->getUrlFull($hasExt, $key, $value);
+ if ($hasExt) {
+ $output = "#!ipxe\nset self {$urlfull} ||\n";
+ } else {
+ $output = <<<HERE
+#!ipxe
+set slxtest:string something ||
+iseq \${slxtest:md5} \${} && set slxext 0 || set slxext 1 ||
+clear slxtest ||
+set self {$urlfull}slx-extensions=\${slxext} ||
+
+HERE;
+ }
+ $output .= <<<HERE
+:retry
+echo Chaining to \${self}
+chain -ar \${self} ||
+echo Chaining to self failed with \${errno}, retrying in a bit...
+sleep 5
+goto retry
+
+HERE;
+ return $output;
+ }
+
+ public function fallback()
+ {
+ // 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 === '') {
+ // REQUIRED so we can hide incompatible entries
+ return $this->redirect();
+ }
+
+ $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 getMenu($menuId)
+ {
+ $menu = IPxeMenu::get($menuId, true);
+ $base = $this->getUrlFull($he);
+ return "#!ipxe\nset self {$base} ||\n" . $this->menuToScript($menu);
+ }
+
+
+ /**
+ * @param IPxeMenu $menu
+ */
+ public function menuToScript($menu)
+ {
+ if ($this->hasExtension) {
+ $slxConsoleUpdate = '--update';
+ $slxPasswordOnly = '--nouser';
+ } else {
+ $slxConsoleUpdate = '';
+ $slxPasswordOnly = '';
+ }
+
+ $serverIp = $this->serverIp;
+ $output = <<<HERE
+:start
+
+imgstat bg-menu || imgfetch --name bg-menu /tftp/pxe-menu.png ||
+console --left 55 --top 88 --right 63 --bottom 64 --keep --picture bg-menu ||
+
+colour --rgb 0xffffff 7
+colour --rgb 0xcccccc 5
+colour --rgb 0x000000 0
+colour --rgb 0xdddddd 6
+cpair --foreground 0 --background 4 1
+cpair --foreground 0 --background 5 2
+cpair --foreground 7 --background 9 0
+
+:slx_menu
+
+console --left 55 --top 88 --right 63 --bottom 64 $slxConsoleUpdate --keep --picture bg-menu ||
+
+menu -- {$menu->title} || prompt Error creating menu ||
+
+HERE;
+ foreach ($menu->items as $item) {
+ $output .= $this->getMenuItemScript($menu->defaultEntryId, $item);
+ }
+ if ($menu->defaultEntryId === null) {
+ $default = "poweroff || exit 1 ||";
+ } else {
+ $default = "chain -a \${self}&entryid={$menu->defaultEntryId} ||";
+ }
+ $output .= "choose";
+ if ($menu->timeoutMs > 0) {
+ $output .= " --timeout {$menu->timeoutMs}";
+ }
+ $output .= " selection || goto default || goto fail\n";
+ $output .= <<<HERE
+console --left 60 --top 130 --right 67 --bottom 86 $slxConsoleUpdate ||
+chain -a \${self}&entryid=\${selection} ||
+goto fail || goto start
+goto \${target} ||
+echo Could not find menu entry in script.
+prompt Press any key to continue.
+goto start
+:default
+$default
+:fail
+prompt Boot failed. Press any key to start.
+goto start
+
+HERE;
+
+ /*
+
+ :i5
+ chain -a /tftp/memtest.0 passes=1 onepass || goto membad
+ prompt Memory OK. Press a key.
+ goto init
+
+ :i8
+ set x:int32 0
+ :again
+ console --left 60 --top 130 --right 67 --bottom 96 --picture bg-load --keep ||
+ console --left 55 --top 88 --right 63 --bottom 64 --picture bg-menu --keep ||
+ inc x
+ iseq \${x} 20 || goto again
+ prompt DONE. Press dein Knie.
+ goto slx_menu
+
+ :membad
+ iseq \${errno} 0x1 || goto memaborted
+ params
+ param scrot \${vram}
+ imgfetch -a http://132.230.8.113/screen.php##params ||
+ prompt Memory is bad. Press a key.
+ goto init
+
+ :memaborted
+ params
+ param scrot \${vram}
+ imgfetch -a http://132.230.8.113/screen.php##params ||
+ prompt Memory test aborted. Press a key.
+ goto init
+
+ */
+ return $output;
+ }
+
+ /**
+ * @param $requestedDefaultId
+ * @param MenuEntry $entry
+ * @return string
+ */
+ private function getMenuItemScript($requestedDefaultId, $entry)
+ {
+ if ($entry->bootEntry === null || (!empty($this->platform) && !$entry->bootEntry->supportsMode($this->platform)))
+ return '';
+ $str = 'item ';
+ if ($entry->gap) {
+ $str .= '--gap -- ';
+ } else {
+ if ($entry->hidden && $this->hasExtension) {
+ if ($entry->hotkey === false)
+ return ''; // Hidden entries without hotkey are illegal
+ $str .= '--hidden ';
+ }
+ if ($entry->hotkey !== false) {
+ $str .= '--key ' . $entry->hotkey . ' ';
+ }
+ if ($entry->menuentryid == $requestedDefaultId) {
+ $str .= '--default ';
+ }
+ $str .= "-- {$entry->menuentryid} ";
+ }
+ if (empty($entry->title)) {
+ $str .= '${}';
+ } else {
+ $str .= $entry->title;
+ }
+ return $str . " || prompt Could not create menu item for {$entry->menuentryid}\n";
+ }
+
+ public function getSpecial($special)
+ {
+ if ($special === 'localboot') {
+ // Get preferred localboot method, depending on system model
+ // Check if required arguments are given; if not, spit out according script and chain to self
+ $uuid = Request::any('uuid', false, 'string');
+ // Get platform - EFI or PCBIOS
+ $manuf = Request::any('manuf', false, 'string');
+ $product = Request::any('product', false, 'string');
+ if ($uuid === false && $manuf === false && $product === false) {
+ return $this->redirect('special', 'localboot');
+ }
+ $BOOT_METHODS = Localboot::BOOT_METHODS[$this->platform];
+ $localboot = false;
+ $model = false;
+ if ($uuid !== false && Module::get('statistics') !== false) {
+ // If we have the machine table, we rather try to look up the system model from there, using the UUID
+ $row = Database::queryFirst('SELECT systemmodel FROM machine WHERE machineuuid = :uuid', ['uuid' => $uuid]);
+ if ($row !== false && !empty($row['systemmodel'])) {
+ $model = $row['systemmodel'];
+ }
+ }
+ if ($model === false) {
+ // Otherwise use what iPXE sent us
+ $manuf = $this->modfilt($manuf);
+ $product = $this->modfilt($product);
+ if (!empty($product)) {
+ $model = $product;
+ if (!empty($manuf)) {
+ $model .= " ($manuf)";
+ }
+ }
+ }
+ // Query
+ if ($model !== false) {
+ $e = strtolower($this->platform); // We made sure $this->platform is either PCBIOS or EFI, so no injection possible
+ $row = Database::queryFirst("SELECT $e AS bootmethod FROM serversetup_localboot WHERE systemmodel = :model LIMIT 1",
+ ['model' => $model]);
+ if ($row !== false) {
+ $localboot = $row['bootmethod'];
+ }
+ }
+ if ($localboot === false || !isset($BOOT_METHODS[$localboot])) {
+ $localboot = Localboot::getDefault()[$this->platform];
+ if (!isset($BOOT_METHODS[$localboot])) {
+ $localboot = array_keys($BOOT_METHODS)[0];
+ }
+ }
+ // Convert to actual ipxe code
+ if (isset($BOOT_METHODS[$localboot])) {
+ $localboot = $BOOT_METHODS[$localboot];
+ } else {
+ $localboot = 'prompt Localboot not possible';
+ }
+ $output = <<<BLA
+imgfree ||
+console ||
+$localboot || goto fail
+
+BLA;
+
+ //
+ } else {
+ $output = "prompt --timeout 5000 Unknown special command '$special' ||\nchain -ar \${self}\n";
+ }
+ return $output;
+ }
+
+ public function output($string)
+ {
+ setlocale(LC_ALL, 'de_DE.UTF-8', 'de_DE.utf-8', 'de_DE.utf8', 'de_DE', 'de', 'German', 'ge', 'en_US.UTF-8', 'en_US.utf-8');
+ if ($this->platform === 'EFI') {
+ $cs = 'ASCII';
+ } else {
+ $cs = 'IBM437';
+ }
+ Header('Content-Type: text/plain; charset=' . $cs);
+
+ echo iconv('UTF-8', $cs . '//TRANSLIT//IGNORE', $string);
+ }
+
+ public function modfilt($str)
+ {
+ if (empty($str) || preg_match('/product\s+name|be\s+filled|unknown|default\s+string|system\s+model|manufacturer/i', $str))
+ return false;
+ return trim(preg_replace('/\s+/', ' ', $str));
+ }
+
+ /**
+ * @param IPxeMenu $menu
+ */
+ private function menuCheckAutostart($menu)
+ {
+ // If this is a menu with a single item, treat a timeout of 0 as "boot immediately" instead of "infinite"
+ if ($menu->itemCount() === 1 && $menu->timeoutMs() === 0 && ($tmp = $menu->getDefaultEntryId()) !== null) {
+ if (empty($menu->items[0]->md5pass)) {
+ return $menu->items[0]->getBootEntryScript($this);
+ } else {
+ return $this->passwordDialog($menu->items[0]);
+ }
+ }
+ return '';
+ }
+
+ const PROP_PW_SALT = 'ipxe.salt.';
+
+ /**
+ * @param MenuEntry $menuEntryId
+ */
+ private function passwordDialog($entry)
+ {
+ if ($this->hasExtension) {
+ $salt = dechex(mt_rand(0x100000, 0xFFFFFF));
+ Property::addToList(self::PROP_PW_SALT . $this->clientIp, $salt, 5);
+ return <<<HERE
+set password \${} ||
+login --nouser ||
+set password \${password:md5}-{$entry->menuentryid}
+set password \${password:md5}$salt
+params
+param pwhash \${password:md5}
+chain -a \${self}&entryid={$entry->menuentryid}##params || goto fail ||
+
+HERE;
+ }
+ return <<<HERE
+set username PASSWORD ONLY ||
+login ||
+params
+param pwplain \${password}
+chain -a \${self}&entryid={$entry->menuentryid}##params || goto fail ||
+
+HERE;
+ }
+
+ public function getMenuEntry($menuEntryId)
+ {
+ $entry = MenuEntry::get($menuEntryId);
+ if ($entry === null)
+ return "#!ipxe\nprompt --timeout 10000 Invalid menu entry id: $menuEntryId\n";
+ $base = $this->getUrlBase();
+ // Make sure legacy variables are set; they might get used
+ $output = <<<HERE
+#!ipxe
+set ipappend1 ip=\${ip}:{$this->serverIp}:\${gateway}:\${netmask}
+set ipappend2 BOOTIF=01-\${mac:hexhyp}
+set serverip {$this->serverIp} ||
+iseq \${idx} \${} && set idx:string X ||
+iseq \${self} \${} && set self {$base}? ||
+
+HERE;
+ // Check for password
+ if (!empty($entry->md5pass)) { // TODO: This should be split out so we can unconditionally get entry code
+ $pwh = Request::post('pwhash', false, 'string');
+ $pwp = Request::post('pwplain', false, 'string');
+ if ($pwh === false && $pwp === false) {
+ return $output . $this->passwordDialog($entry);
+ }
+ $ok = false;
+ if ($pwh !== false) {
+ $list = Property::getList(self::PROP_PW_SALT . $this->clientIp);
+ foreach ($list as $salt) {
+ if ($pwh === md5($entry->md5pass . $salt)) {
+ $ok = true;
+ break;
+ }
+ }
+ }
+ if (!$ok && $pwp !== false && !empty($entry->plainpass)) {
+ $ok = ($pwp === $entry->plainpass);
+ }
+ if (!$ok) {
+ return $output . "prompt --timeout 10000 Wrong password ||\n";
+ }
+ }
+ // Output actual entry
+ $output .= str_replace('%fail%', 'fail', $entry->getBootEntryScript($this));
+ $output .= <<<HERE
+
+goto end
+:fail
+prompt --timeout 5000 Error launching selected boot entry ||
+:end
+
+HERE;
+ return $output;
+ }
+
+ public function execDataToScript($agnostic, $bios, $efi) : string
+ {
+ if ($agnostic !== null)
+ return $this->execDataToScriptInternal($agnostic) . "\ngoto fail\n";
+
+ if (empty($this->platform)) {
+ // output dynamic code that decides client-side
+ $biosLabel = $this->getLabel();
+ $output = 'iseq ${platform} efi || goto ' . $biosLabel . "\n";
+ // EFI
+ if ($efi !== null) {
+ $output .= $this->execDataToScriptInternal($efi) . "\n";
+ } else {
+ $output .= "echo EFI not supported\n";
+ }
+ $output .= "goto fail\n"
+ . ':' . $biosLabel . "\n";
+ if ($bios !== null) {
+ $output .= $this->execDataToScriptInternal($bios) . "\n";
+ } else {
+ $output .= "echo BIOS not supported\n";
+ }
+ return $output . "goto fail\n";
+ }
+ // static, we know in advance
+ return $this->execDataToScriptInternal($bios ?? $efi ?? new ExecData()) . "\ngoto fail\n";
+ }
+
+ private function execDataToScriptInternal(ExecData $entry) : string
+ {
+ $entry->sanitize();
+ $script = '';
+ if ($entry->resetConsole) {
+ $script .= "console ||\n";
+ }
+ if ($entry->imageFree) {
+ $script .= "imgfree ||\n";
+ }
+ foreach ($entry->dhcpOptions as $opt) {
+ if (empty($opt['value'])) {
+ $val = '${}';
+ } else {
+ if (empty($opt['hex'])) {
+ $val = bin2hex($opt['value']);
+ } else {
+ $val = $opt['value'];
+ }
+ preg_match_all('/[0-9a-f]{2}/', $val, $out);
+ $val = implode(':', $out[0]);
+ }
+ $script .= 'set net${idx}/' . $opt['opt'] . ':hex ' . $val
+ . ' || prompt Cannot override DHCP server option ' . $opt['opt'] . ". Press any key to continue anyways.\n";
+ }
+ $initrds = [];
+ if (!empty($entry->initRd)) {
+ foreach (array_values($entry->initRd) as $i => $initrd) {
+ if (empty($initrd))
+ continue;
+ $script .= "initrd --name initrd$i $initrd || goto fail\n";
+ $initrds[] = "initrd$i";
+ }
+ }
+ $script .= "boot ";
+ if ($entry->autoUnload) {
+ $script .= "-a ";
+ }
+ if ($entry->replace) {
+ $script .= "-r ";
+ }
+ $script .= $entry->executable;
+ if (!empty($initrds)) {
+ foreach ($initrds as $initrd) {
+ $script .= " initrd=$initrd";
+ }
+ }
+ if (!empty($entry->commandLine)) {
+ $script .= ' ' . $entry->commandLine;
+ }
+ $script .= " || goto fail\n";
+ if ($entry->resetConsole) {
+ $script .= "goto start ||\n";
+ }
+ return $script;
+ }
+
+}
diff --git a/modules-available/serversetup-bwlp-ipxe/page.inc.php b/modules-available/serversetup-bwlp-ipxe/page.inc.php
index 4845b2f8..4e3501fb 100644
--- a/modules-available/serversetup-bwlp-ipxe/page.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/page.inc.php
@@ -303,6 +303,7 @@ class Page_ServerSetup extends Page
FROM serversetup_bootentry be
LEFT JOIN serversetup_menuentry sme USING (entryid)
LEFT JOIN serversetup_menu sm USING (menuid)
+ WHERE be.module <> '.special'
GROUP BY be.entryid, be.title
ORDER BY be.title ASC");