summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--inc/hook.inc.php37
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php254
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/bootentryhook.inc.php91
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/execdata.inc.php162
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php2
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php2
-rw-r--r--modules-available/serversetup-bwlp-ipxe/install.inc.php78
-rw-r--r--modules-available/serversetup-bwlp-ipxe/lang/de/messages.json4
-rw-r--r--modules-available/serversetup-bwlp-ipxe/lang/de/template-tags.json5
-rw-r--r--modules-available/serversetup-bwlp-ipxe/lang/en/messages.json4
-rw-r--r--modules-available/serversetup-bwlp-ipxe/lang/en/template-tags.json5
-rw-r--r--modules-available/serversetup-bwlp-ipxe/page.inc.php140
-rw-r--r--modules-available/serversetup-bwlp-ipxe/templates/bootentry-list.html16
-rw-r--r--modules-available/serversetup-bwlp-ipxe/templates/ipxe-new-boot-entry.html89
-rw-r--r--modules-available/serversetup-bwlp-ipxe/templates/menu-edit.html4
15 files changed, 695 insertions, 198 deletions
diff --git a/inc/hook.inc.php b/inc/hook.inc.php
index bed81aeb..05078f72 100644
--- a/inc/hook.inc.php
+++ b/inc/hook.inc.php
@@ -27,6 +27,26 @@ class Hook
return $retval;
}
+ /**
+ * Load given hook for a specific module only.
+ *
+ * @param string $moduleName Module
+ * @param string $hookName Hook
+ * @param bool $filterBroken return false if the module has missing deps
+ * @return Hook|false hook instance, false on error or if module doesn't have given hook
+ */
+ public static function loadSingle($moduleName, $hookName, $filterBroken = true)
+ {
+ if (Module::get($moduleName) === false) // No such module
+ return false;
+ if ($filterBroken && !Module::isAvailable($moduleName)) // Broken
+ return false;
+ $file = 'modules/' . $moduleName . '/hooks/' . $hookName . '.inc.php';
+ if (!file_exists($file)) // No hook
+ return false;
+ return new Hook($moduleName, $file);
+ }
+
/*
*
*/
@@ -40,4 +60,21 @@ class Hook
$this->file = $hookFile;
}
+ /**
+ * Run the hook's code. The include is expected to return a
+ * value, which will in turn be the return value of this
+ * method.
+ *
+ * @return mixed The return value of the include file, or false on error
+ */
+ public function run()
+ {
+ try {
+ return (include $this->file);
+ } catch (Exception $e) {
+ error_log($e);
+ return false;
+ }
+ }
+
}
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php
index 130bb52b..e97d1389 100644
--- a/modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php
@@ -3,17 +3,6 @@
abstract class BootEntry
{
- public function __construct($data = false)
- {
- if (is_array($data)) {
- foreach ($data as $key => $value) {
- if (property_exists($this, $key)) {
- $this->{$key} = $value;
- }
- }
- }
- }
-
public abstract function supportsMode($mode);
public abstract function toScript($failLabel, $mode);
@@ -29,18 +18,31 @@ abstract class BootEntry
/**
* Return a BootEntry instance from the serialized data.
*
+ * @param string $module module this entry belongs to, or special values .script/.exec
* @param string $jsonString serialized entry data
* @return BootEntry|null instance representing boot entry, null on error
*/
- public static function fromJson($data)
+ public static function fromJson($module, $data)
{
+ if ($module{0} !== '.') {
+ // Hook from other module
+ $hook = Hook::loadSingle($module, 'ipxe-bootentry');
+ if ($hook === false) {
+ error_log('Module ' . $module . ' doesnt have an ipxe-bootentry hook');
+ return null;
+ }
+ $ret = $hook->run();
+ if (!($ret instanceof BootEntryHook))
+ return null;
+ return $ret->getBootEntry($data);
+ }
if (is_string($data)) {
$data = json_decode($data, true);
}
- if (isset($data['script'])) {
+ if ($module === '.script') {
return new CustomBootEntry($data);
}
- if (isset($data['executable'])) {
+ if ($module === '.exec') {
return new StandardBootEntry($data);
}
return null;
@@ -51,9 +53,9 @@ abstract class BootEntry
return new MenuBootEntry($menuId);
}
- public static function newStandardBootEntry($initData)
+ public static function newStandardBootEntry($initData, $efi = false, $arch = false)
{
- $ret = new StandardBootEntry($initData);
+ $ret = new StandardBootEntry($initData, $efi, $arch);
$list = [];
if ($ret->arch() !== StandardBootEntry::EFI) {
$list[] = StandardBootEntry::BIOS;
@@ -61,8 +63,9 @@ abstract class BootEntry
if ($ret->arch() === StandardBootEntry::EFI || $ret->arch() === StandardBootEntry::BOTH) {
$list[] = StandardBootEntry::EFI;
}
+ $data = $ret->toArray();
foreach ($list as $mode) {
- if (empty($ret->toArray()['executable'][$mode])) {
+ if (empty($data[$mode]['executable'])) {
error_log('Incomplete stdbot: ' . print_r($initData, true));
return null;
}
@@ -85,11 +88,11 @@ abstract class BootEntry
*/
public static function fromDatabaseId($id)
{
- $row = Database::queryFirst("SELECT data FROM serversetup_bootentry
+ $row = Database::queryFirst("SELECT module, data FROM serversetup_bootentry
WHERE entryid = :id LIMIT 1", ['id' => $id]);
if ($row === false)
return false;
- return self::fromJson($row['data']);
+ return self::fromJson($row['module'], $row['data']);
}
/**
@@ -103,7 +106,7 @@ abstract class BootEntry
$res = Database::simpleQuery("SELECT entryid, data FROM serversetup_bootentry");
$ret = [];
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $tmp = self::fromJson($row['data']);
+ $tmp = self::fromJson($row['module'], $row['data']);
if ($tmp === null)
continue;
$ret[$row['entryid']] = $tmp;
@@ -115,12 +118,15 @@ abstract class BootEntry
class StandardBootEntry extends BootEntry
{
- protected $executable;
- protected $initRd;
- protected $commandLine;
- protected $replace;
- protected $autoUnload;
- protected $resetConsole;
+ /**
+ * @var ExecData PCBIOS boot data
+ */
+ protected $pcbios;
+ /**
+ * @var ExecData same for EFI
+ */
+ protected $efi;
+
protected $arch; // Constants below
const BIOS = 'PCBIOS'; // Only valid for legacy BIOS boot
@@ -128,56 +134,104 @@ class StandardBootEntry extends BootEntry
const BOTH = 'PCBIOS-EFI'; // Supports both via distinct entry
const AGNOSTIC = 'agnostic'; // Supports both via same entry (PCBIOS entry)
- public function __construct($data = false)
+ const KEYS = ['executable', 'initRd', 'commandLine', 'replace', 'imageFree', 'autoUnload', 'resetConsole', 'dhcpOptions'];
+
+ public function __construct($data, $efi = false, $arch = false)
{
+ $this->pcbios = new ExecData();
+ $this->efi = new ExecData();
if ($data instanceof PxeSection) {
- // Gets arrayfied below
- $this->executable = $data->kernel;
- $this->initRd = [self::BIOS => $data->initrd];
- $this->commandLine = ' ' . str_replace('vga=current', '', $data->append) . ' ';
- $this->resetConsole = true;
- $this->replace = true;
- $this->autoUnload = true;
- if (strpos($this->commandLine, ' quiet ') !== false) {
- $this->commandLine .= ' loglevel=5 rd.systemd.show_status=auto';
+ // Import from PXELINUX menu
+ $this->fromPxeMenu($data);
+ } elseif ($data instanceof ExecData && is_string($arch)) {
+ if (!($efi instanceof ExecData)) {
+ $efi = new ExecData();
}
- if ($data->ipAppend & 1) {
- $this->commandLine .= ' ${ipappend1}';
+ $this->pcbios = $data;
+ $this->efi = $efi;
+ $this->arch = $arch;
+ } elseif (is_array($data)) {
+ // Serialized data
+ if (!isset($data['arch'])) {
+ error_log('Serialized data to StandardBootEntry doesnt contain arch: ' . json_encode($data));
+ } else {
+ $this->arch = $data['arch'];
}
- if ($data->ipAppend & 2) {
- $this->commandLine .= ' ${ipappend2}';
+ if (isset($data[self::BIOS]) || isset($data[self::EFI])) {
+ // Current format
+ $this->fromCurrentFormat($data);
+ } else {
+ // Convert legacy DB format
+ $this->fromLegacyFormat($data);
}
- if ($data->ipAppend & 4) {
- $this->commandLine .= ' SYSUUID=${uuid}';
- }
- $this->commandLine = trim(preg_replace('/\s+/', ' ', $this->commandLine));
} else {
- if (isset($data['initRd']) && is_array($data['initRd'])) {
- foreach ($data['initRd'] as &$initrd) {
- if (is_string($initrd)) {
- $initrd = preg_split('/\s*,\s*/', $initrd);
- }
- }
- unset($initrd);
+ error_log('Invalid StandardBootEntry constructor call');
+ }
+ if (!in_array($this->arch, [self::BIOS, self::EFI, self::BOTH, self::AGNOSTIC])) {
+ $this->arch = self::AGNOSTIC;
+ }
+ }
+
+ private function fromLegacyFormat($data)
+ {
+ $ok = false;
+ foreach (self::KEYS as $key) {
+ if (isset($data[$key][self::BIOS])) {
+ $this->pcbios->{$key} = $data[$key][self::BIOS];
+ $ok = true;
+ }
+ if (isset($data[$key][self::EFI])) {
+ $this->efi->{$key} = $data[$key][self::EFI];
+ $ok = true;
}
- parent::__construct($data);
}
- // Convert legacy DB format
- foreach (['executable', 'initRd', 'commandLine', 'replace', 'autoUnload', 'resetConsole'] as $key) {
- if (!is_array($this->{$key})) {
- $this->{$key} = [ self::BIOS => $this->{$key}, self::EFI => '' ];
+ if (!$ok) {
+ // Very old entry
+ foreach (self::KEYS as $key) {
+ if (isset($data[$key])) {
+ $this->pcbios->{$key} = $data[$key];
+ }
}
}
- foreach ($this->initRd as &$initrd) {
- if (!is_array($initrd)) {
- $initrd = [$initrd];
+ }
+
+ private function fromCurrentFormat($data)
+ {
+ foreach (self::KEYS as $key) {
+ if (isset($data[self::BIOS][$key])) {
+ $this->pcbios->{$key} = $data[self::BIOS][$key];
+ }
+ if (isset($data[self::EFI][$key])) {
+ $this->efi->{$key} = $data[self::EFI][$key];
}
- $initrd = array_filter($initrd, function($x) { return strlen(trim($x)) !== 0; });
}
- unset($initrd);
- if ($this->arch === null) {
- $this->arch = self::AGNOSTIC;
+ }
+
+ /**
+ * @param PxeSection $data
+ */
+ private function fromPxeMenu($data)
+ {
+ $bios = $this->pcbios;
+ $bios->executable = $data->kernel;
+ $bios->initRd = $data->initrd;
+ $bios->commandLine = ' ' . str_replace('vga=current', '', $data->append) . ' ';
+ $bios->resetConsole = true;
+ $bios->replace = true;
+ $bios->autoUnload = true;
+ if (strpos($bios->commandLine, ' quiet ') !== false) {
+ $bios->commandLine .= ' loglevel=5 rd.systemd.show_status=auto';
+ }
+ if ($data->ipAppend & 1) {
+ $bios->commandLine .= ' ${ipappend1}';
}
+ if ($data->ipAppend & 2) {
+ $bios->commandLine .= ' ${ipappend2}';
+ }
+ if ($data->ipAppend & 4) {
+ $bios->commandLine .= ' SYSUUID=${uuid}';
+ }
+ $bios->commandLine = trim(preg_replace('/\s+/', ' ', $bios->commandLine));
}
public function arch()
@@ -201,19 +255,37 @@ class StandardBootEntry extends BootEntry
if (!$this->supportsMode($mode)) {
return "prompt Entry doesn't have an executable for mode $mode\n";
}
- if ($this->arch === self::AGNOSTIC) {
- $mode = self::BIOS;
+ if ($this->arch === self::AGNOSTIC || $mode == self::BIOS) {
+ $entry = $this->pcbios;
+ } else {
+ $entry = $this->efi;
}
$script = '';
- if ($this->resetConsole[$mode]) {
+ if ($entry->resetConsole) {
$script .= "console ||\n";
}
- // TODO: Checkbox
- $script .= "imgfree ||\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($this->initRd[$mode])) {
- foreach (array_values($this->initRd[$mode]) as $i => $initrd) {
+ 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";
@@ -221,23 +293,23 @@ class StandardBootEntry extends BootEntry
}
}
$script .= "boot ";
- if ($this->autoUnload[$mode]) {
+ if ($entry->autoUnload) {
$script .= "-a ";
}
- if ($this->replace[$mode]) {
+ if ($entry->replace) {
$script .= "-r ";
}
- $script .= $this->executable[$mode];
+ $script .= $entry->executable;
if (empty($initrds)) {
$rdBase = '';
} else {
$rdBase = " initrd=" . implode(',', $initrds);
}
- if (!empty($this->commandLine[$mode])) {
- $script .= "$rdBase {$this->commandLine[$mode]}";
+ if (!empty($entry->commandLine)) {
+ $script .= "$rdBase {$entry->commandLine}";
}
$script .= " || goto $failLabel\n";
- if ($this->resetConsole[$mode]) {
+ if ($entry->resetConsole) {
$script .= "goto start ||\n";
}
return $script;
@@ -246,30 +318,16 @@ class StandardBootEntry extends BootEntry
public function addFormFields(&$array)
{
$array[$this->arch . '_selected'] = 'selected';
- foreach ([self::BIOS, self::EFI] as $mode) {
- $array['entries'][] = [
- 'is' . $mode => true,
- 'mode' => $mode,
- 'executable' => $this->executable[$mode],
- 'initRd' => implode(',', $this->initRd[$mode]),
- 'commandLine' => $this->commandLine[$mode],
- 'replace_checked' => $this->replace[$mode] ? 'checked' : '',
- 'autoUnload_checked' => $this->autoUnload[$mode] ? 'checked' : '',
- 'resetConsole_checked' => $this->resetConsole[$mode] ? 'checked' : '',
- ];
- }
+ $array['entries'][] = $this->pcbios->toFormFields(self::BIOS);
+ $array['entries'][] = $this->efi->toFormFields(self::EFI);
$array['exec_checked'] = 'checked';
}
public function toArray()
{
return [
- 'executable' => $this->executable,
- 'initRd' => $this->initRd,
- 'commandLine' => $this->commandLine,
- 'replace' => $this->replace,
- 'autoUnload' => $this->autoUnload,
- 'resetConsole' => $this->resetConsole,
+ self::BIOS => $this->pcbios->toArray(),
+ self::EFI => $this->efi->toArray(),
'arch' => $this->arch,
];
}
@@ -279,6 +337,13 @@ class CustomBootEntry extends BootEntry
{
protected $script;
+ public function __construct($data)
+ {
+ if (is_array($data) && isset($data['script'])) {
+ $this->script = $data['script'];
+ }
+ }
+
public function supportsMode($mode)
{
return true;
@@ -309,7 +374,6 @@ class MenuBootEntry extends BootEntry
public function __construct($menuId)
{
- parent::__construct(false);
$this->menuId = $menuId;
}
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/bootentryhook.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/bootentryhook.inc.php
new file mode 100644
index 00000000..2e2e5009
--- /dev/null
+++ b/modules-available/serversetup-bwlp-ipxe/inc/bootentryhook.inc.php
@@ -0,0 +1,91 @@
+<?php
+
+abstract class BootEntryHook
+{
+
+ /**
+ * @var string -- set by ipxe, not module implementing hook
+ */
+ public $moduleId;
+ /**
+ * @var string -- set by ipxe, not module implementing hook
+ */
+ public $checked;
+
+ private $selectedId;
+
+ public abstract function name();
+
+ /**
+ * @return HookEntryGroup[]
+ */
+ protected abstract function groupsInternal();
+
+ /**
+ * @param $id
+ * @return BootEntry|null the actual boot entry instance for given entry, null if invalid id
+ */
+ public abstract function getBootEntry($id);
+
+ /**
+ * @return HookEntryGroup[]
+ */
+ public final function groups()
+ {
+ $groups = $this->groupsInternal();
+ foreach ($groups as $group) {
+ foreach ($group->entries as $entry) {
+ if ($entry->id === $this->selectedId) {
+ $entry->selected = 'selected';
+ }
+ }
+ }
+ return $groups;
+ }
+
+ public function setSelected($id)
+ {
+ $this->selectedId = $id;
+ }
+
+}
+
+class HookEntryGroup
+{
+ /**
+ * @var string
+ */
+ public $groupName;
+ /**
+ * @var HookEntry[]
+ */
+ public $entries;
+
+ public function __construct($groupName, $entries)
+ {
+ $this->groupName = $groupName;
+ $this->entries = $entries;
+ }
+}
+
+class HookEntry
+{
+ /**
+ * @var string
+ */
+ public $id;
+ /**
+ * @var string
+ */
+ public $name;
+ /**
+ * @var string internal - to be set by ipxe module
+ */
+ public $selected;
+
+ public function __construct($id, $name)
+ {
+ $this->id = $id;
+ $this->name = $name;
+ }
+} \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/execdata.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/execdata.inc.php
new file mode 100644
index 00000000..b82ce2e7
--- /dev/null
+++ b/modules-available/serversetup-bwlp-ipxe/inc/execdata.inc.php
@@ -0,0 +1,162 @@
+<?php
+
+class ExecData
+{
+
+ /**
+ * @var string The binary to launch
+ */
+ public $executable = '';
+
+ /**
+ * @var string[] List of additional images to load (initrds)
+ */
+ public $initRd = [];
+
+ /**
+ * @var string Command line to pass to executable
+ */
+ public $commandLine = '';
+
+ /**
+ * @var bool Call imgfree before loading and executing this entry
+ */
+ public $imageFree = false;
+
+ /**
+ * @var bool Whether to completely replace the currently running iPXE stack
+ */
+ public $replace = false;
+
+ /**
+ * @var bool Whether to automatically unload the binary after execution
+ */
+ public $autoUnload = false;
+
+ /**
+ * @var bool Whether to reset the console before execution
+ */
+ public $resetConsole = false;
+
+ /**
+ * @var array DHCP options to override Maps OPTIONNUM -> Value
+ */
+ public $dhcpOptions = [];
+
+ /**
+ * Supported Options
+ */
+ const DHCP_OPTIONS = [
+ 17 => [
+ 'name' => 'Root Path',
+ 'type' => 'string',
+ ],
+ 43 => [
+ 'name' => 'Vendor Specific',
+ 'type' => 'string',
+ ],
+ 66 => [
+ 'name' => 'Next Server',
+ 'type' => 'string',
+ ],
+ 67 => [
+ 'name' => 'Boot File',
+ 'type' => 'string',
+ ],
+ 209 => [
+ 'name' => 'Configuration File',
+ 'type' => 'string',
+ ],
+ 210 => [
+ 'name' => 'Path Prefix',
+ 'type' => 'string',
+ ],
+ ];
+
+ private function sanitize()
+ {
+ settype($this->executable, 'string');
+ settype($this->initRd, 'array');
+ foreach ($this->initRd as &$entry) {
+ settype($entry, 'string');
+ }
+ settype($this->commandLine, 'string');
+ settype($this->imageFree, 'bool');
+ settype($this->replace, 'bool');
+ settype($this->autoUnload, 'bool');
+ settype($this->resetConsole, 'bool');
+ settype($this->dhcpOptions, 'array');
+ foreach (array_keys($this->dhcpOptions) as $key) {
+ $val =& $this->dhcpOptions[$key];
+ if (!empty($val['override'])) {
+ unset($val['override']);
+ $val['opt'] = $key;
+ if (isset($val['hex']) && isset($val['value'])) {
+ $val['value'] = preg_replace('/[^0-9a-f]/i', '', $val['value']);
+ $val['value'] = substr($val['value'], 0, floor(strlen($val['value']) / 2) * 2);
+ $val['value'] = strtolower($val['value']);
+ }
+ }
+ if (!isset($val['opt']) || !is_numeric($val['opt']) || $val['opt'] <= 0 || $val['opt'] >= 255) {
+ unset($this->dhcpOptions[$key]);
+ continue;
+ }
+ if (!array_key_exists($val['opt'], self::DHCP_OPTIONS))
+ continue; // Not known...
+ settype($val['value'], self::DHCP_OPTIONS[$val['opt']]['type']);
+ }
+ $this->dhcpOptions = array_values($this->dhcpOptions);
+ }
+
+ public function toArray()
+ {
+ $this->sanitize();
+ return [
+ 'executable' => $this->executable,
+ 'initRd' => $this->initRd,
+ 'commandLine' => $this->commandLine,
+ 'imageFree' => $this->imageFree,
+ 'replace' => $this->replace,
+ 'autoUnload' => $this->autoUnload,
+ 'resetConsole' => $this->resetConsole,
+ 'dhcpOptions' => $this->dhcpOptions,
+ ];
+ }
+
+ public function toFormFields($arch)
+ {
+ $this->sanitize();
+ $opts = [];
+ foreach (self::DHCP_OPTIONS as $opt => $val) {
+ $opts[$opt] = [
+ 'opt' => $opt,
+ 'name' => $val['name'],
+ ];
+ }
+ foreach ($this->dhcpOptions as $val) {
+ if (!isset($opts[$val['opt']])) {
+ $opts[$val['opt']] = [];
+ }
+ $opts[$val['opt']] += [
+ 'opt' => $val['opt'],
+ 'value' => $val['value'],
+ 'override_checked' => 'checked',
+ 'hex_checked' => empty($val['hex']) ? '' : 'checked',
+ ];
+ }
+ ksort($opts);
+ return [
+ 'is' . $arch => true,
+ 'mode' => $arch,
+ 'executable' => $this->executable,
+ 'initRd' => implode(',', $this->initRd),
+ 'commandLine' => $this->commandLine,
+ 'imageFree_checked' => $this->imageFree ? 'checked' : '',
+ 'replace_checked' => $this->replace ? 'checked' : '',
+ 'autoUnload_checked' => $this->autoUnload ? 'checked' : '',
+ 'resetConsole_checked' => $this->resetConsole ? 'checked' : '',
+ 'opts' => array_values($opts),
+ ];
+ }
+
+} \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php
index 0c20e839..f87d15c2 100644
--- a/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php
@@ -26,7 +26,7 @@ class IPxeMenu
$this->title = $menu['title'];
$this->defaultEntryId = $menu['defaultentryid'];
$res = Database::simpleQuery("SELECT e.menuentryid, e.entryid, e.refmenuid, e.hotkey, e.title, e.hidden, e.sortval, e.md5pass,
- b.data AS bootentry
+ b.module, b.data AS bootentry
FROM serversetup_menuentry e
LEFT JOIN serversetup_bootentry b USING (entryid)
WHERE e.menuid = :menuid
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php
index eff7f24e..a65e9f98 100644
--- a/modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php
@@ -48,7 +48,7 @@ class MenuEntry
}
$this->hotkey = self::getKeyCode($row['hotkey']);
if (!empty($row['bootentry'])) {
- $this->bootEntry = BootEntry::fromJson($row['bootentry']);
+ $this->bootEntry = BootEntry::fromJson($row['module'], $row['bootentry']);
} elseif ($row['refmenuid'] !== null) {
$this->bootEntry = BootEntry::forMenu($row['refmenuid']);
}
diff --git a/modules-available/serversetup-bwlp-ipxe/install.inc.php b/modules-available/serversetup-bwlp-ipxe/install.inc.php
index 35eeee37..37cfc085 100644
--- a/modules-available/serversetup-bwlp-ipxe/install.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/install.inc.php
@@ -1,17 +1,18 @@
<?php
-$res = array();
+$result = array();
-$res[] = tableCreate('serversetup_bootentry', "
+$result[] = tableCreate('serversetup_bootentry', "
`entryid` varchar(16) CHARACTER SET ascii NOT NULL,
`hotkey` varchar(8) CHARACTER SET ascii NOT NULL,
`title` varchar(100) NOT NULL,
`builtin` tinyint(1) NOT NULL,
+ `module` varchar(30) DEFAULT NULL,
`data` blob NOT NULL,
PRIMARY KEY (`entryid`)
");
-$res[] = tableCreate('serversetup_menu', "
+$result[] = tableCreate('serversetup_menu', "
`menuid` int(11) NOT NULL AUTO_INCREMENT,
`timeoutms` int(10) unsigned NOT NULL,
`title` varchar(100) NOT NULL COMMENT 'Escaped/Sanitized for iPXE!',
@@ -22,7 +23,7 @@ $res[] = tableCreate('serversetup_menu', "
KEY `isdefault` (`isdefault`)
");
-$res[] = tableCreate('serversetup_menuentry', "
+$result[] = tableCreate('serversetup_menuentry', "
`menuentryid` int(11) NOT NULL AUTO_INCREMENT,
`menuid` int(11) NOT NULL,
`entryid` varchar(16) CHARACTER SET ascii NULL COMMENT 'If NULL, entry is gap or another menu',
@@ -38,7 +39,7 @@ $res[] = tableCreate('serversetup_menuentry', "
KEY `entryid` (`entryid`)
");
-$res[] = tableCreate('serversetup_menu_location', '
+$result[] = tableCreate('serversetup_menu_location', '
`menuid` int(11) NOT NULL,
`locationid` int(11) NOT NULL,
`defaultentryid` int(11) DEFAULT NULL,
@@ -47,7 +48,7 @@ $res[] = tableCreate('serversetup_menu_location', '
KEY `defaultentryid` (`defaultentryid`)
');
-$res[] = tableCreate('serversetup_localboot', "
+$result[] = tableCreate('serversetup_localboot', "
`systemmodel` varchar(120) NOT NULL,
`pcbios` varchar(16) CHARACTER SET ascii DEFAULT NULL,
`efi` varchar(16) CHARACTER SET ascii DEFAULT NULL,
@@ -58,60 +59,79 @@ $res[] = tableCreate('serversetup_localboot', "
if (!tableHasColumn('serversetup_menu_location', 'defaultentryid')) {
if (Database::exec('ALTER TABLE serversetup_menu_location ADD COLUMN `defaultentryid` int(11) DEFAULT NULL,
ADD KEY `defaultentryid` (`defaultentryid`)') !== false) {
- $res[] = UPDATE_DONE;
+ $result[] = UPDATE_DONE;
} else {
- $res[] = UPDATE_FAILED;
+ $result[] = UPDATE_FAILED;
}
}
-$res[] = tableAddConstraint('serversetup_menu', 'defaultentryid', 'serversetup_menuentry', 'menuentryid',
+$result[] = tableAddConstraint('serversetup_menu', 'defaultentryid', 'serversetup_menuentry', 'menuentryid',
'ON DELETE SET NULL');
-$res[] = tableAddConstraint('serversetup_menuentry', 'entryid', 'serversetup_bootentry', 'entryid',
+$result[] = tableAddConstraint('serversetup_menuentry', 'entryid', 'serversetup_bootentry', 'entryid',
'ON UPDATE CASCADE ON DELETE CASCADE');
-$res[] = tableAddConstraint('serversetup_menuentry', 'menuid', 'serversetup_menu', 'menuid',
+$result[] = tableAddConstraint('serversetup_menuentry', 'menuid', 'serversetup_menu', 'menuid',
'ON UPDATE CASCADE ON DELETE CASCADE');
-$res[] = tableAddConstraint('serversetup_menu_location', 'menuid', 'serversetup_menu', 'menuid',
+$result[] = tableAddConstraint('serversetup_menu_location', 'menuid', 'serversetup_menu', 'menuid',
'ON UPDATE CASCADE ON DELETE CASCADE');
-$res[] = tableAddConstraint('serversetup_menu_location', 'defaultentryid', 'serversetup_menuentry', 'menuentryid',
+$result[] = tableAddConstraint('serversetup_menu_location', 'defaultentryid', 'serversetup_menuentry', 'menuentryid',
'ON UPDATE CASCADE ON DELETE SET NULL');
// 2019-03-19 Add refmenuid to have cascaded menus
if (!tableHasColumn('serversetup_menuentry', 'refmenuid')) {
- if (Database::exec("ALTER TABLE serversetup_menuentry ADD COLUMN `refmenuid` int(11) DEFAULT NULL COMMENT 'If entryid is NULL this can be a ref to another menu'") !== false) {
- $res[] = UPDATE_DONE;
- } else {
- $res[] = UPDATE_FAILED;
- }
+ if (Database::exec("ALTER TABLE serversetup_menuentry ADD COLUMN `refmenuid` int(11) DEFAULT NULL COMMENT 'If entryid is NULL this can be a ref to another menu'") !== false) {
+ $result[] = UPDATE_DONE;
+ } else {
+ $result[] = UPDATE_FAILED;
+ }
}
// 2019-03-26 Make localboot config distinct for efi and bios
if (!tableHasColumn('serversetup_localboot', 'pcbios')) {
- if (Database::exec("ALTER TABLE serversetup_localboot DROP COLUMN `bootmethod`,
- ADD COLUMN `pcbios` varchar(16) CHARACTER SET ascii DEFAULT NULL, ADD COLUMN `efi` varchar(16) CHARACTER SET ascii DEFAULT NULL") !== false) {
- $res[] = UPDATE_DONE;
- } else {
- $res[] = UPDATE_FAILED;
- }
+ if (Database::exec("ALTER TABLE serversetup_localboot DROP COLUMN `bootmethod`,
+ ADD COLUMN `pcbios` varchar(16) CHARACTER SET ascii DEFAULT NULL, ADD COLUMN `efi` varchar(16) CHARACTER SET ascii DEFAULT NULL") !== false) {
+ $result[] = UPDATE_DONE;
+ } else {
+ $result[] = UPDATE_FAILED;
+ }
}
-$res[] = tableAddConstraint('serversetup_menuentry', 'refmenuid', 'serversetup_menu', 'menuid',
- 'ON UPDATE CASCADE ON DELETE SET NULL');
+$result[] = tableAddConstraint('serversetup_menuentry', 'refmenuid', 'serversetup_menu', 'menuid',
+ 'ON UPDATE CASCADE ON DELETE SET NULL');
if (Module::get('location') !== false) {
if (!tableExists('location')) {
- $res[] = UPDATE_RETRY;
+ $result[] = UPDATE_RETRY;
} else {
- $res[] = tableAddConstraint('serversetup_menu_location', 'locationid', 'location', 'locationid',
+ $result[] = tableAddConstraint('serversetup_menu_location', 'locationid', 'location', 'locationid',
'ON UPDATE CASCADE ON DELETE CASCADE');
}
}
+// 2019-09-21 Add modue column to bootentry
+if (!tableHasColumn('serversetup_bootentry', 'module')) {
+ if (Database::exec("ALTER TABLE serversetup_bootentry
+ ADD COLUMN `module` varchar(30) CHARACTER SET ascii DEFAULT NULL AFTER `builtin`") !== false) {
+ $result[] = UPDATE_DONE;
+ $res = Database::simpleQuery('SELECT entryid, data FROM serversetup_bootentry WHERE module IS NULL');
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $json = json_decode($row['data'], true);
+ if (isset($json['script'])) {
+ Database::exec("UPDATE serversetup_bootentry SET module = '.script' WHERE entryid = :id", ['id' => $row['entryid']]);
+ } else {
+ Database::exec("UPDATE serversetup_bootentry SET module = '.exec' WHERE entryid = :id", ['id' => $row['entryid']]);
+ }
+ }
+ } else {
+ $result[] = UPDATE_FAILED;
+ }
+}
+
if (Module::isAvailable('serversetup')) {
IPxe::createDefaultEntries();
}
-responseFromArray($res);
+responseFromArray($result);
diff --git a/modules-available/serversetup-bwlp-ipxe/lang/de/messages.json b/modules-available/serversetup-bwlp-ipxe/lang/de/messages.json
index cc8af749..339296e7 100644
--- a/modules-available/serversetup-bwlp-ipxe/lang/de/messages.json
+++ b/modules-available/serversetup-bwlp-ipxe/lang/de/messages.json
@@ -7,6 +7,7 @@
"import-error": "Fehler beim Importieren",
"import-no-entries": "Nichts importiert: Men\u00fc scheint leer zu sein",
"invalid-boot-entry": "Ung\u00fcltiger Men\u00fceintrag: {{0}}",
+ "invalid-custom-entry-id": "Ung\u00fcltige Eintrags-ID {{1}} f\u00fcr Modul {{0}}",
"invalid-ip": "Kein Interface ist auf die Adresse {{0}} konfiguriert",
"invalid-menu-id": "Ung\u00fcltige Men\u00fc-ID: {{0}}",
"localboot-invalid-method": "Ung\u00fcltige localboot-Methode: {{0}}",
@@ -19,5 +20,6 @@
"missing-bootentry-data": "Fehlende Daten f\u00fcr den Men\u00fceintrag",
"no-ip-addr-set": "Bitte w\u00e4hlen Sie die prim\u00e4re IP-Adresse des Servers",
"no-ip-set": "Kann Import alter Konfiguration nicht ausf\u00fchren. Bitte zuerst die prim\u00e4re IP-Adresse des Servers festlegen.",
- "unknown-bootentry-type": "Unbekannter Eintrags-Typ: {{0}}"
+ "unknown-bootentry-type": "Unbekannter Eintrags-Typ: {{0}}",
+ "unknown-hook-module": "Unbekanntes Modul: {{0}}"
} \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp-ipxe/lang/de/template-tags.json b/modules-available/serversetup-bwlp-ipxe/lang/de/template-tags.json
index d2277546..07cf8fa3 100644
--- a/modules-available/serversetup-bwlp-ipxe/lang/de/template-tags.json
+++ b/modules-available/serversetup-bwlp-ipxe/lang/de/template-tags.json
@@ -15,11 +15,13 @@
"lang_bootentryHead": "Men\u00fceintr\u00e4ge",
"lang_bootentryIntro": "Hier k\u00f6nnen Sie Men\u00fceintr\u00e4ge definieren, die sich sp\u00e4ter einem Men\u00fc zuweisen lassen. Ein Men\u00fceintrag besteht entweder aus einem zu ladenden Kernel\/Image plus optional initrd, oder aus einem iPXE-Skript.",
"lang_bootentryTitle": "Men\u00fceintrag",
+ "lang_bootentryType": "Typ",
"lang_chooseIP": "Bitte w\u00e4hlen Sie die IP-Adresse, \u00fcber die der Server von den Clients zum Booten angesprochen werden soll.",
"lang_commandLine": "Command line",
"lang_copy": "Kopieren",
"lang_count": "Anzahl",
"lang_createUsbImage": "Bootbaren USB-Stick erstellen",
+ "lang_dhcpOverrides": "DHCP-Optionen \u00fcberschreiben",
"lang_downloadBootImage": "Boot-Image herunterladen",
"lang_downloadRufus": "Rufus herunterladen",
"lang_editBootEntryHead": "Men\u00fceintrag bearbeiten",
@@ -30,15 +32,18 @@
"lang_entryId": "ID",
"lang_entryTitle": "Bezeichnung",
"lang_execAutoUnload": "Nach Ausf\u00fchrung entladen (--autofree)",
+ "lang_execImageFree": "Andere geladene Images vor dem Ausf\u00fchren entladen (imgfree)",
"lang_execReplace": "Aktuellen iPXE-Stack erstzen (--replace)",
"lang_execResetConsole": "Konsole vor Ausf\u00fchrung zur\u00fccksetzen",
"lang_forceRecompile": "Jetzt neu kompilieren",
"lang_generationFailed": "Erzeugen des Bootmen\u00fcs fehlgeschlagen. Der Netzwerkboot von bwLehrpool wird wahrscheinlich nicht funktionieren. Wenn Sie den Fehler nicht selbst beheben k\u00f6nnen, melden Sie bitte die Logausgabe an das bwLehrpool-Projekt.",
+ "lang_hex": "Hex",
"lang_hotkey": "Hotkey",
"lang_idFormatHint": "(Max. 16 Zeichen, nur a-z 0-9 - _)",
"lang_imageToLoad": "Zu ladendes Image (z.B. Kernel)",
"lang_import": "Importieren",
"lang_initRd": "Zu ladendes initramfs",
+ "lang_ipxeSettings": "iPXE-spezifische Einstellungen",
"lang_ipxeWikiUrl": "im iPXE Wiki",
"lang_isDefault": "Standard",
"lang_listOfMenus": "Men\u00fcliste",
diff --git a/modules-available/serversetup-bwlp-ipxe/lang/en/messages.json b/modules-available/serversetup-bwlp-ipxe/lang/en/messages.json
index 9dafe62b..9e1c0b3e 100644
--- a/modules-available/serversetup-bwlp-ipxe/lang/en/messages.json
+++ b/modules-available/serversetup-bwlp-ipxe/lang/en/messages.json
@@ -7,6 +7,7 @@
"import-error": "Error importing menu",
"import-no-entries": "Nothing imported: Menu seems to be empty",
"invalid-boot-entry": "Invalid menu item: {{0}}",
+ "invalid-custom-entry-id": "Invalid entry id {{1}} for module {{0}}",
"invalid-ip": "No interface is configured with the address {{0}}",
"invalid-menu-id": "Invalid menu id: {{0}}",
"localboot-invalid-method": "Invalid localboot method: {{0}}",
@@ -19,5 +20,6 @@
"missing-bootentry-data": "Missing data for menu item",
"no-ip-addr-set": "Please set the server's primary IP address",
"no-ip-set": "Cannot import old configuration. Please set the primary IP address first.",
- "unknown-bootentry-type": "Unknown item type: {{0}}"
+ "unknown-bootentry-type": "Unknown item type: {{0}}",
+ "unknown-hook-module": "Unknown module: {{0}}"
} \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp-ipxe/lang/en/template-tags.json b/modules-available/serversetup-bwlp-ipxe/lang/en/template-tags.json
index 84c43dd7..29d99bf6 100644
--- a/modules-available/serversetup-bwlp-ipxe/lang/en/template-tags.json
+++ b/modules-available/serversetup-bwlp-ipxe/lang/en/template-tags.json
@@ -15,11 +15,13 @@
"lang_bootentryHead": "Menu items",
"lang_bootentryIntro": "This is where you can add, edit and remove menu items, which can be added to menus. A menu item is either a combination of a kernel\/image to load (and an optional initrd), or a custom iPXE-script.",
"lang_bootentryTitle": "Menu item",
+ "lang_bootentryType": "Type",
"lang_chooseIP": "Please select the IP address that the client server will use to boot.",
"lang_commandLine": "Command line",
"lang_copy": "Copy",
"lang_count": "Count",
"lang_createUsbImage": "Create bootable thumb drive",
+ "lang_dhcpOverrides": "Override DHCP options",
"lang_downloadBootImage": "Download boot-image",
"lang_downloadRufus": "Download Rufus",
"lang_editBootEntryHead": "Edit menu item",
@@ -30,15 +32,18 @@
"lang_entryId": "ID",
"lang_entryTitle": "Title",
"lang_execAutoUnload": "Unload after execution (--autofree)",
+ "lang_execImageFree": "Unload any other images before execution (imgfree)",
"lang_execReplace": "Replace current iPXE stack (--replace)",
"lang_execResetConsole": "Reset console before execution",
"lang_forceRecompile": "Force recompile",
"lang_generationFailed": "Could not generate boot menu. The bwLehrpool-System might not work properly. If you can't fix the problem, please report the error log below to the bwLehrpool project.",
+ "lang_hex": "Hex",
"lang_hotkey": "Hotkey",
"lang_idFormatHint": "(16 chars max, a-z 0-9 - _)",
"lang_imageToLoad": "Image to load (e.g. kernel)",
"lang_import": "Import",
"lang_initRd": "Optional initrd\/initramfs to load",
+ "lang_ipxeSettings": "iPXE-specific settings",
"lang_ipxeWikiUrl": "at the iPXE wiki",
"lang_isDefault": "Default",
"lang_listOfMenus": "Menulist",
diff --git a/modules-available/serversetup-bwlp-ipxe/page.inc.php b/modules-available/serversetup-bwlp-ipxe/page.inc.php
index 6a874775..40b75dd2 100644
--- a/modules-available/serversetup-bwlp-ipxe/page.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/page.inc.php
@@ -228,7 +228,7 @@ class Page_ServerSetup extends Page
if (is_file($file)) {
$base = basename($file);
$features = [];
- foreach (preg_split('/[\-\.\/]+/', $base, -1, PREG_SPLIT_NO_EMPTY) as $p) {
+ foreach (preg_split('/[\-.\/]+/', $base, -1, PREG_SPLIT_NO_EMPTY) as $p) {
if (array_key_exists($p, $strings)) {
$features += $strings[$p];
}
@@ -298,14 +298,11 @@ class Page_ServerSetup extends Page
{
$allowEdit = User::hasPermission('ipxe.bootentry.edit');
- $res = Database::simpleQuery("SELECT be.entryid, be.hotkey, be.title, be.builtin, Count(sm.menuid) AS refs FROM serversetup_bootentry be
+ $bootentryTable = Database::queryAll("SELECT be.entryid, be.hotkey, be.title, be.builtin, be.module, Count(sm.menuid) AS refs
+ FROM serversetup_bootentry be
LEFT JOIN serversetup_menuentry sm USING (entryid)
- GROUP BY be.entryid
+ GROUP BY be.entryid, be.title
ORDER BY be.title ASC");
- $bootentryTable = [];
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $bootentryTable[] = $row;
- }
if (empty($bootentryTable)) {
if (Property::getServerIp() === false || Property::getServerIp() === 'invalid') {
@@ -339,7 +336,7 @@ class Page_ServerSetup extends Page
FROM serversetup_menu m
LEFT JOIN serversetup_menu_location l USING (menuid)
LEFT JOIN location ll USING (locationid)
- GROUP BY menuid
+ GROUP BY menuid, title
ORDER BY title");
$menuTable = [];
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
@@ -420,31 +417,22 @@ class Page_ServerSetup extends Page
}
$menu['keys'] = array_map(function ($item) { return ['key' => $item]; }, MenuEntry::getKeyList());
$menu['entrylist'] = array_merge(
- Database::queryAll("SELECT entryid, title, hotkey, data FROM serversetup_bootentry ORDER BY title ASC"),
+ Database::queryAll("SELECT entryid, title, hotkey, module, data FROM serversetup_bootentry ORDER BY title ASC"),
// Add all menus, so we can link
Database::queryAll("SELECT Concat('menu:', menuid) AS entryid, title FROM serversetup_menu ORDER BY title ASC")
);
- class_exists('BootEntry'); // Leave this here for StandardBootEntry
foreach ($menu['entrylist'] as &$bootentry) {
- if (!isset($bootentry['data']))
+ if (!isset($bootentry['data']) || !isset($bootentry['module']) || $bootentry['module']{0} !== '.')
+ continue;
+ $entry = BootEntry::fromJson($bootentry['module'], $bootentry['data']);
+ if ($entry === null) {
+ error_log('WARNING: Ignoring NULL menu entry: ' . $bootentry['data']);
continue;
- $bootentry['data'] = json_decode($bootentry['data'], true);
+ }
+ $bootentry['data'] = $entry->toArray();
// Transform stuff suitable for mustache
if (!array_key_exists('arch', $bootentry['data']))
continue;
- // BIOS/EFI or both
- if ($bootentry['data']['arch'] === StandardBootEntry::BIOS
- || $bootentry['data']['arch'] === StandardBootEntry::BOTH) {
- $bootentry['data']['PCBIOS'] = array('executable' => $bootentry['data']['executable']['PCBIOS'],
- 'initRd' => $bootentry['data']['initRd']['PCBIOS'],
- 'commandLine' => $bootentry['data']['commandLine']['PCBIOS']);
- }
- if ($bootentry['data']['arch'] === StandardBootEntry::EFI
- || $bootentry['data']['arch'] === StandardBootEntry::BOTH) {
- $bootentry['data']['EFI'] = array('executable' => $bootentry['data']['executable']['EFI'],
- 'initRd' => $bootentry['data']['initRd']['EFI'],
- 'commandLine' => $bootentry['data']['commandLine']['EFI']);
- }
// Naming and agnostic
if ($bootentry['data']['arch'] === StandardBootEntry::BIOS) {
$bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_biosOnly', true);
@@ -453,16 +441,13 @@ class Page_ServerSetup extends Page
$bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_efiOnly', true);
unset($bootentry['data']['PCBIOS']);
} elseif ($bootentry['data']['arch'] === StandardBootEntry::AGNOSTIC) {
- $bootentry['data']['archAgnostic'] = array('executable' => $bootentry['data']['executable']['PCBIOS'],
- 'initRd' => $bootentry['data']['initRd']['PCBIOS'],
- 'commandLine' => $bootentry['data']['commandLine']['PCBIOS']);
$bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_archAgnostic', true);
unset($bootentry['data']['EFI']);
} else {
$bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_archBoth', true);
}
foreach ($bootentry['data'] as &$e) {
- if (isset($e['initRd']) && is_array($e['initRd'])) {
+ if (isset($e['initRd'])) {
$e['initRd'] = implode(',', $e['initRd']);
}
}
@@ -478,29 +463,57 @@ class Page_ServerSetup extends Page
private function showEditBootEntry()
{
- $params = [];
+ $params = ['hooks' => []];
+ foreach (Hook::load('ipxe-bootentry') as $hook) {
+ $var = $hook->run();
+ if ($var instanceof BootEntryHook) {
+ $var->moduleId = $hook->moduleId;
+ $params['hooks'][] = $var;
+ }
+ }
$id = Request::get('id', false, 'string');
if ($id === false) {
$params['exec_checked'] = 'checked';
$params['entryid'] = 'u-' . dechex(mt_rand(0x1000, 0xffff)) . '-' . dechex(time());
- $params['entries'] = [
- ['mode' => 'PCBIOS'],
- ['mode' => 'EFI'],
- ];
} else {
// Query existing entry
- $row = Database::queryFirst('SELECT entryid, title, builtin, data FROM serversetup_bootentry
+ $row = Database::queryFirst('SELECT entryid, title, builtin, module, data FROM serversetup_bootentry
WHERE entryid = :id LIMIT 1', ['id' => $id]);
if ($row === false) {
Message::addError('invalid-boot-entry', $id);
Util::redirect('?do=serversetup');
}
- $entry = BootEntry::fromJson($row['data']);
- if ($entry === null) {
- Message::addError('unknown-bootentry-type', $id);
- Util::redirect('?do=serversetup');
+ if ($row['module']{0} === '.') {
+ // either script or exec entry
+ $json = json_decode($row['data'], true);
+ if (!is_array($json)) {
+ Message::addError('unknown-bootentry-type', $id);
+ Util::redirect('?do=serversetup&show=bootentry');
+ }
+ $entry = BootEntry::fromJson($row['module'], $json);
+ if ($entry === null) {
+ Message::addError('unknown-bootentry-type', $id);
+ Util::redirect('?do=serversetup&show=bootentry');
+ }
+ $entry->addFormFields($params);
+ } else {
+ // Hook from another module
+ if (Module::get($row['module']) === false) {
+ Message::addError('unknown-hook-module', $row['module']);
+ } else {
+ foreach ($params['hooks'] as $he) {
+ /** @var BootEntryHook $he */
+ if ($he->moduleId === $row['module']) {
+ $he->setSelected($row['data']);
+ $he->checked = 'checked';
+ if ($he->getBootEntry($row['data']) === null) {
+ Message::addError('invalid-custom-entry-id', $row['module'], $row['data']);
+ }
+ break;
+ }
+ }
+ }
}
- $entry->addFormFields($params);
$params['title'] = $row['title'];
if (!Request::get('copy')) {
$params['oldentryid'] = $params['entryid'] = $row['entryid'];
@@ -787,34 +800,53 @@ class Page_ServerSetup extends Page
Message::addError('missing-bootentry-data');
return;
}
+ $module = false;
$type = Request::post('type', false, 'string');
- if ($type === 'exec') {
- $entry = BootEntry::newStandardBootEntry($data);
- } elseif ($type === 'script') {
- $entry = BootEntry::newCustomBootEntry($data);
+ if ($type{0} === '.') {
+ // Exec or script
+ if ($type === '.exec') {
+ $entry = BootEntry::newStandardBootEntry($data);
+ } elseif ($type === '.script') {
+ $entry = BootEntry::newCustomBootEntry($data);
+ }
+ if ($entry === null) {
+ Message::addError('main.empty-field');
+ Util::redirect('?do=serversetup&show=bootentry');
+ }
+ $entryData = json_encode($entry->toArray());
} else {
- Message::addError('unknown-bootentry-type', $type);
- return;
- }
- if ($entry === null) {
- Message::addError('main.empty-field');
- Util::redirect('?do=serversetup&show=bootentry');
+ // Module hook
+ $hook = Hook::loadSingle($type, 'ipxe-bootentry');
+ if ($hook === false) {
+ Message::addError('unknown-bootentry-type', $type);
+ return;
+ }
+ /** @var BootEntryHook $module */
+ $module = $hook->run();
+ $entryData = Request::post('selection-' . $type, false, 'string');
+ $entry = $module->getBootEntry($entryData);
+ if ($entry === null) {
+ Message::addError('invalid-custom-entry-id', $type, $entryData);
+ return;
+ }
}
$params = [
'entryid' => $newId,
'title' => Request::post('title', '', 'string'),
- 'data' => json_encode($entry->toArray()),
+ 'module' => $type,
+ 'data' => $entryData,
];
// New or update?
if (empty($oldEntryId)) {
// New entry
- Database::exec('INSERT INTO serversetup_bootentry (entryid, title, builtin, data)
- VALUES (:entryid, :title, 0, :data)', $params);
+ Database::exec('INSERT INTO serversetup_bootentry (entryid, title, builtin, module, data)
+ VALUES (:entryid, :title, 0, :module, :data)', $params);
Message::addSuccess('boot-entry-created', $newId);
} else {
// Edit existing entry
$params['oldid'] = $oldEntryId;
- Database::exec('UPDATE serversetup_bootentry SET entryid = If(builtin = 0, :entryid, entryid), title = :title, data = :data
+ Database::exec('UPDATE serversetup_bootentry SET
+ entryid = If(builtin = 0, :entryid, entryid), title = :title, module = :module, data = :data
WHERE entryid = :oldid', $params);
Message::addSuccess('boot-entry-updated', $newId);
}
diff --git a/modules-available/serversetup-bwlp-ipxe/templates/bootentry-list.html b/modules-available/serversetup-bwlp-ipxe/templates/bootentry-list.html
index dfc4e6a8..9eecc6f5 100644
--- a/modules-available/serversetup-bwlp-ipxe/templates/bootentry-list.html
+++ b/modules-available/serversetup-bwlp-ipxe/templates/bootentry-list.html
@@ -11,20 +11,24 @@
<thead>
<tr>
<th>{{lang_entryId}}</th>
+ <th>{{lang_bootentryType}}</th>
<th>{{lang_bootentryTitle}}</th>
- <th>{{lang_hotkey}}</th>
- <th class="slx-smallcol">{{lang_refCount}}</th>
- <th class="slx-smallcol">{{lang_edit}}</th>
- {{#allowEdit}}<th class="slx-smallcol">{{lang_copy}}</th>{{/allowEdit}}
- <th class="slx-smallcol">{{lang_delete}}</th>
+ <th class="small">{{lang_hotkey}}</th>
+ <th class="small slx-smallcol">{{lang_refCount}}</th>
+ <th class="small slx-smallcol">{{lang_edit}}</th>
+ {{#allowEdit}}<th class="small slx-smallcol">{{lang_copy}}</th>{{/allowEdit}}
+ <th class="small slx-smallcol">{{lang_delete}}</th>
</tr>
</thead>
<tbody>
{{#bootentryTable}}
<tr>
- <td>
+ <td class="small">
{{entryid}}
</td>
+ <td class="small">
+ {{module}}
+ </td>
<td>
{{title}}
</td>
diff --git a/modules-available/serversetup-bwlp-ipxe/templates/ipxe-new-boot-entry.html b/modules-available/serversetup-bwlp-ipxe/templates/ipxe-new-boot-entry.html
index b195394d..fd76c1d5 100644
--- a/modules-available/serversetup-bwlp-ipxe/templates/ipxe-new-boot-entry.html
+++ b/modules-available/serversetup-bwlp-ipxe/templates/ipxe-new-boot-entry.html
@@ -23,13 +23,19 @@
<div class="form-group">
<div class="radio">
- <input class="type-radio" type="radio" name="type" value="exec" id="type-exec" {{exec_checked}} {{disabled}}>
+ <input class="type-radio" type="radio" name="type" value=".exec" id="type-exec" {{exec_checked}} {{disabled}}>
<label for="type-exec">{{lang_typeExecEntry}}</label>
</div>
<div class="radio">
- <input class="type-radio" type="radio" name="type" value="script" id="type-script" {{script_checked}} {{disabled}}>
+ <input class="type-radio" type="radio" name="type" value=".script" id="type-script" {{script_checked}} {{disabled}}>
<label for="type-script">{{lang_typeScriptEntry}}</label>
</div>
+ {{#hooks}}
+ <div class="radio">
+ <input class="type-radio" type="radio" name="type" value="{{moduleId}}" id="type-{{moduleId}}" {{checked}} {{disabled}}>
+ <label for="type-{{moduleId}}">{{name}}</label>
+ </div>
+ {{/hooks}}
</div>
<div class="form-group">
@@ -68,42 +74,77 @@
<label for="input-ex-{{mode}}">
{{lang_imageToLoad}}
</label>
- <input id="input-ex-{{mode}}" class="form-control" name="entry[executable][{{mode}}]" value="{{executable}}" {{disabled}}>
+ <input id="input-ex-{{mode}}" class="form-control" name="entry[{{mode}}][executable]" value="{{executable}}" {{disabled}}>
</div>
<div class="form-group">
<label for="input-rd-{{mode}}">
{{lang_initRd}}
</label>
- <input id="input-rd-{{mode}}" class="form-control" name="entry[initRd][{{mode}}]" value="{{initRd}}" {{disabled}}>
+ <input id="input-rd-{{mode}}" class="form-control" name="entry[{{mode}}][initRd]" value="{{initRd}}" {{disabled}}>
</div>
<div class="form-group">
<label for="input-cmd-{{mode}}">
{{lang_commandLine}}
</label>
- <input id="input-cmd-{{mode}}" class="form-control" name="entry[commandLine][{{mode}}]"
+ <input id="input-cmd-{{mode}}" class="form-control" name="entry[{{mode}}][commandLine]"
value="{{commandLine}}" {{disabled}}>
</div>
+ <h4>{{lang_ipxeSettings}}</h4>
+ <div class="form-group">
+ <div class="checkbox checkbox-inline">
+ <input id="exec-imgfree-{{mode}}" class="form-control" type="checkbox"
+ name="entry[{{mode}}][imgfree]" {{imageFree_checked}} {{disabled}}>
+ <label for="exec-imgfree-{{mode}}">{{lang_execImageFree}}</label>
+ </div>
+ </div>
<div class="form-group">
<div class="checkbox checkbox-inline">
<input id="exec-replace-{{mode}}" class="form-control" type="checkbox"
- name="entry[replace][{{mode}}]" {{replace_checked}} {{disabled}}>
+ name="entry[{{mode}}][replace]" {{replace_checked}} {{disabled}}>
<label for="exec-replace-{{mode}}">{{lang_execReplace}}</label>
</div>
</div>
<div class="form-group">
<div class="checkbox checkbox-inline">
<input id="exec-au-{{mode}}" class="form-control" type="checkbox"
- name="entry[autoUnload][{{mode}}]" {{autoUnload_checked}} {{disabled}}>
+ name="entry[{{mode}}][autoUnload]" {{autoUnload_checked}} {{disabled}}>
<label for="exec-au-{{mode}}">{{lang_execAutoUnload}}</label>
</div>
</div>
<div class="form-group">
<div class="checkbox checkbox-inline">
<input id="exec-reset-{{mode}}" class="form-control" type="checkbox"
- name="entry[resetConsole][{{mode}}]" {{resetConsole_checked}} {{disabled}}>
+ name="entry[{{mode}}][resetConsole]" {{resetConsole_checked}} {{disabled}}>
<label for="exec-reset-{{mode}}">{{lang_execResetConsole}}</label>
</div>
</div>
+ <h4>{{lang_dhcpOverrides}}</h4>
+ {{#opts}}
+ <div class="form-group">
+ <div class="row">
+ <div class="col-sm-8">
+ <div class="checkbox">
+ <input type="checkbox" id="opt-{{mode}}-{{opt}}"
+ name="entry[{{mode}}][dhcpOptions][{{opt}}][override]" {{override_checked}} {{disabled}}>
+ <label for="opt-{{mode}}-{{opt}}">
+ {{name}} ({{opt}})
+ </label>
+ </div>
+ </div>
+ <div class="col-sm-4 text-right">
+ <div class="checkbox checkbox-inline">
+ <input class="hex-box" type="checkbox" id="opt-{{mode}}-{{opt}}-hex"
+ name="entry[{{mode}}][dhcpOptions][{{opt}}][hex]" {{hex_checked}} {{disabled}}>
+ <label for="opt-{{mode}}-{{opt}}-hex">
+ {{lang_hex}}
+ </label>
+ </div>
+ </div>
+ </div>
+ <input type="text" id="opt-{{mode}}-{{opt}}-value" class="form-control hex-value"
+ name="entry[{{mode}}][dhcpOptions][{{opt}}][value]" value="{{value}}" {{disabled}}>
+ </div>
+ {{/opts}}
</div>
</div>
</div>
@@ -121,6 +162,23 @@
</div>
</div>
+ {{#hooks}}
+ <div class="type-form" id="form-{{moduleId}}">
+ <label for="select-{{moduleId}}">{{name}}</label>
+ <div class="form-group">
+ <select id="select-{{moduleId}}" class="form-control" name="selection-{{moduleId}}">
+ {{#groups}}
+ <optgroup label="{{groupName}}">
+ {{#entries}}
+ <option value="{{id}}" {{selected}}>{{name}}</option>
+ {{/entries}}
+ </optgroup>
+ {{/groups}}
+ </select>
+ </div>
+ </div>
+ {{/hooks}}
+
{{#builtin}}
<div class="alert alert-warning">
{{lang_editBuiltinWarn}}
@@ -148,7 +206,8 @@
document.addEventListener('DOMContentLoaded', function () {
$('.type-radio').click(function () {
$('.type-form').hide();
- $('#form-' + $(this).val()).show();
+ var name = $(this).val().replace('.', '');
+ $('#form-' + name).show();
});
$('.type-radio[checked]').click();
var $as = $('#arch-selector');
@@ -167,5 +226,17 @@ document.addEventListener('DOMContentLoaded', function () {
$('#col-' + vs[i]).attr('class', 'mode-class col-md-' + cols).show();
}
}).change();
+ var colorize = function() {
+ var $t = $(this);
+ $t.css('color', ($t.data('hex') && !$t.val().match(/^[a-f0-9]*$/i)) ? 'red' : '');
+ };
+ var setHex = function() {
+ var n = '#' + this.id.replace(/-hex$/, '-value');
+ var $obj = $(n);
+ $obj.data('hex', this.checked);
+ colorize.call($obj[0]);
+ };
+ $('.hex-box').change(setHex).each(setHex);
+ $('.hex-value').change(colorize).keyup(colorize).each(colorize);
});
// --></script> \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp-ipxe/templates/menu-edit.html b/modules-available/serversetup-bwlp-ipxe/templates/menu-edit.html
index 358aa4d6..d2fa12be 100644
--- a/modules-available/serversetup-bwlp-ipxe/templates/menu-edit.html
+++ b/modules-available/serversetup-bwlp-ipxe/templates/menu-edit.html
@@ -150,10 +150,12 @@
</div>
{{/script}}
{{^script}}
+ {{#arch}}
<div class="form-group">
<label for="{{entryid}}-script">{{lang_archSelector}}</label>
- <pre id="{{entryid}}-arch">{{arch}}</pre>
+ <pre id="{{entryid}}-arch">{{.}}</pre>
</div>
+ {{/arch}}
{{#archAgnostic}}
<div class="form-group">
<label for="{{entryid}}-executable">{{lang_imageToLoad}}</label>