summaryrefslogtreecommitdiffstats
path: root/modules-available/serversetup-bwlp-ipxe
diff options
context:
space:
mode:
Diffstat (limited to 'modules-available/serversetup-bwlp-ipxe')
-rw-r--r--modules-available/serversetup-bwlp-ipxe/api.inc.php13
-rw-r--r--modules-available/serversetup-bwlp-ipxe/hooks/ipxe-update.inc.php20
-rw-r--r--modules-available/serversetup-bwlp-ipxe/hooks/locations-column.inc.php57
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php127
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/bootentryhook.inc.php57
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/execdata.inc.php7
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php154
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/ipxebuilder.inc.php80
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php113
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/localboot.inc.php37
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php58
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/pxelinux.inc.php174
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/pxemenu.inc.php59
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/pxesection.inc.php117
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderbase.inc.php49
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderbash.inc.php20
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/scriptbuildergrub.inc.php330
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderipxe.inc.php137
-rw-r--r--modules-available/serversetup-bwlp-ipxe/install.inc.php4
-rw-r--r--modules-available/serversetup-bwlp-ipxe/lang/de/messages.json2
-rw-r--r--modules-available/serversetup-bwlp-ipxe/lang/de/module.json3
-rw-r--r--modules-available/serversetup-bwlp-ipxe/lang/de/template-tags.json11
-rw-r--r--modules-available/serversetup-bwlp-ipxe/lang/en/messages.json2
-rw-r--r--modules-available/serversetup-bwlp-ipxe/lang/en/module.json3
-rw-r--r--modules-available/serversetup-bwlp-ipxe/lang/en/template-tags.json15
-rw-r--r--modules-available/serversetup-bwlp-ipxe/page.inc.php183
-rw-r--r--modules-available/serversetup-bwlp-ipxe/templates/download.html11
-rw-r--r--modules-available/serversetup-bwlp-ipxe/templates/git_task.html15
-rw-r--r--modules-available/serversetup-bwlp-ipxe/templates/ipaddress.html114
-rw-r--r--modules-available/serversetup-bwlp-ipxe/templates/ipxe_update.html28
30 files changed, 1323 insertions, 677 deletions
diff --git a/modules-available/serversetup-bwlp-ipxe/api.inc.php b/modules-available/serversetup-bwlp-ipxe/api.inc.php
index 4ca9fdec..dc78f481 100644
--- a/modules-available/serversetup-bwlp-ipxe/api.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/api.inc.php
@@ -1,9 +1,20 @@
<?php
(function() {
- $type = Request::any('type');
+ $type = Request::any('type', null, 'string');
+ if ($type === null && isset($_SERVER['HTTP_USER_AGENT'])) {
+ if (preg_match('/^iPXE/', $_SERVER['HTTP_USER_AGENT'])) {
+ $type = 'ipxe';
+ } elseif (preg_match('/^GRUB/', $_SERVER['HTTP_USER_AGENT'])) {
+ $type = 'grub';
+ } elseif (preg_match('/wget|curl/i', $_SERVER['HTTP_USER_AGENT'])) {
+ $type = 'bash';
+ }
+ }
if ($type === 'bash') {
$builder = new ScriptBuilderBash();
+ } elseif ($type === 'grub') {
+ $builder = new ScriptBuilderGrub();
} else {
$builder = new ScriptBuilderIpxe();
}
diff --git a/modules-available/serversetup-bwlp-ipxe/hooks/ipxe-update.inc.php b/modules-available/serversetup-bwlp-ipxe/hooks/ipxe-update.inc.php
index c58a64ae..76f8cfa2 100644
--- a/modules-available/serversetup-bwlp-ipxe/hooks/ipxe-update.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/hooks/ipxe-update.inc.php
@@ -1,12 +1,22 @@
<?php
+$get = Module::get('serversetup');
+if ($get === false)
+ return;
+
+$get->activate(1, false);
+
$data = [
- 'ipaddress' => Property::getServerIp()
+ 'ipaddress' => Property::getServerIp(),
+ 'parentTask' => $taskId,
];
if ($data['ipaddress'] === 'invalid')
- return false;
+ return null;
$task = Taskmanager::submit('CompileIPxeNew', $data);
-if (Taskmanager::isFailed($task))
- return false;
-Property::set('ipxe-task-id', $task['id'], 15);
+if (Taskmanager::isFailed($task)) {
+ error_log(print_r($task, true));
+ return null;
+}
+TaskmanagerCallback::addCallback($task, 'ipxeCompileDone');
+Property::set(IPxeBuilder::PROP_IPXE_COMPILE_TASKID, $task['id'], 15);
return $task['id']; \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp-ipxe/hooks/locations-column.inc.php b/modules-available/serversetup-bwlp-ipxe/hooks/locations-column.inc.php
new file mode 100644
index 00000000..d3290b62
--- /dev/null
+++ b/modules-available/serversetup-bwlp-ipxe/hooks/locations-column.inc.php
@@ -0,0 +1,57 @@
+<?php
+
+if (!User::hasPermission('.serversetup.ipxe.menu.assign')
+ || !Module::isAvailable('serversetup')
+ || !class_exists('IPxe')) {
+ return null;
+}
+
+class IpxeLocationColumn extends AbstractLocationColumn
+{
+
+ private $lookup = [];
+
+ public function __construct(array $allowedLocationIds)
+ {
+ $res = Database::simpleQuery("SELECT ml.locationid, m.title, ml.defaultentryid FROM serversetup_menu m
+ INNER JOIN serversetup_menu_location ml USING (menuid)
+ WHERE locationid IN (:allowedLocationIds) GROUP BY locationid", compact('allowedLocationIds'));
+ foreach ($res as $row) {
+ $lid = (int)$row['locationid'];
+ if ($row['defaultentryid'] !== null) {
+ $row['title'] .= '(*)';
+ }
+ $this->lookup[$lid] = $row['title'];
+ }
+ }
+
+ public function getColumnHtml(int $locationId): string
+ {
+ return htmlspecialchars($this->lookup[$locationId] ?? '');
+ }
+
+ public function getEditUrl(int $locationId): string
+ {
+ if (!User::hasPermission('.serversetup.ipxe.menu.assign', $locationId))
+ return '';
+ return '?do=serversetup&show=assignlocation&locationid=' . $locationId;
+ }
+
+ public function header(): string
+ {
+ return Dictionary::translateFileModule('serversetup', 'module', 'location-column-header');
+ }
+
+ public function priority(): int
+ {
+ return 3000;
+ }
+
+ public function propagateColumn(): bool
+ {
+ return true;
+ }
+
+}
+
+return new IpxeLocationColumn($allowedLocationIds); \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php
index 614f5ee4..5812c0cd 100644
--- a/modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php
@@ -12,17 +12,28 @@ abstract class BootEntry
/** Supports both via distinct entry */
const BOTH = 'PCBIOS-EFI';
- public abstract function supportsMode($mode);
-
/**
- * @param ScriptBuilderBase $builder
- * @return string
+ * @var string Internal ID - set to your liking, e.g. the MiniLinux version identifier
*/
- public abstract function toScript($builder);
+ protected $internalId;
+
+ public function __construct(string $internalId)
+ {
+ $this->internalId = $internalId;
+ }
+
+ public abstract function supportsMode(string $mode): bool;
- public abstract function toArray();
+ public abstract function toScript(ScriptBuilderBase $builder): string;
- public abstract function addFormFields(&$array);
+ public abstract function toArray(): array;
+
+ public abstract function addFormFields(array &$array): void;
+
+ public function internalId(): string
+ {
+ return $this->internalId;
+ }
/*
*
@@ -32,15 +43,15 @@ 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
+ * @param string $data serialized entry data
+ * @return ?BootEntry instance representing boot entry, null on error
*/
- public static function fromJson($module, $data)
+ public static function fromJson(string $module, string $data): ?BootEntry
{
- if ($module{0} !== '.') {
+ if ($module[0] !== '.') {
// Hook from other module
$hook = Hook::loadSingle($module, 'ipxe-bootentry');
- if ($hook === false) {
+ if ($hook === null) {
error_log('Module ' . $module . ' doesnt have an ipxe-bootentry hook');
return null;
}
@@ -49,9 +60,9 @@ abstract class BootEntry
return null;
return $ret->getBootEntry($data);
}
- if (is_string($data)) {
- $data = json_decode($data, true);
- }
+ $data = json_decode($data, true);
+ if (!is_array($data))
+ return null;
if ($module === '.script') {
return new CustomBootEntry($data);
}
@@ -64,14 +75,14 @@ abstract class BootEntry
return null;
}
- public static function forMenu($menuId)
+ public static function forMenu(int $menuId): MenuBootEntry
{
return new MenuBootEntry($menuId);
}
- public static function newStandardBootEntry($initData, $efi = false, $arch = false)
+ public static function newStandardBootEntry($initData, $efi = false, $arch = false, string $internalId = ''): ?StandardBootEntry
{
- $ret = new StandardBootEntry($initData, $efi, $arch);
+ $ret = new StandardBootEntry($initData, $efi, $arch, $internalId);
$list = [];
if ($ret->arch() !== self::EFI) {
$list[] = self::BIOS;
@@ -89,7 +100,7 @@ abstract class BootEntry
return $ret;
}
- public static function newCustomBootEntry($initData)
+ public static function newCustomBootEntry($initData): ?CustomBootEntry
{
if (!is_array($initData) || empty($initData))
return null;
@@ -99,15 +110,14 @@ abstract class BootEntry
/**
* Return a BootEntry instance from database with the given id.
*
- * @param string $id
- * @return BootEntry|null|false false == unknown id, null = unknown entry type, BootEntry instance on success
+ * @return ?BootEntry null = unknown entry type, BootEntry instance on success
*/
- public static function fromDatabaseId($id)
+ public static function fromDatabaseId(string $id): ?BootEntry
{
$row = Database::queryFirst("SELECT module, data FROM serversetup_bootentry
WHERE entryid = :id LIMIT 1", ['id' => $id]);
if ($row === false)
- return false;
+ return null;
return self::fromJson($row['module'], $row['data']);
}
@@ -117,11 +127,11 @@ abstract class BootEntry
*
* @return BootEntry[] all existing BootEntries
*/
- public static function getAll()
+ public static function getAll(): array
{
- $res = Database::simpleQuery("SELECT entryid, data FROM serversetup_bootentry");
+ $res = Database::simpleQuery("SELECT entryid, module, data FROM serversetup_bootentry");
$ret = [];
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
$tmp = self::fromJson($row['module'], $row['data']);
if ($tmp === null)
continue;
@@ -143,14 +153,15 @@ class StandardBootEntry extends BootEntry
*/
protected $efi;
/**
- * @var string BootEntry Constants above
+ * @var ?string BootEntry Constants above
*/
protected $arch;
const KEYS = ['executable', 'initRd', 'commandLine', 'replace', 'imageFree', 'autoUnload', 'resetConsole', 'dhcpOptions'];
- public function __construct($data, $efi = false, $arch = false)
+ public function __construct($data, $efi = false, ?string $arch = null, string $internalId = '')
{
+ parent::__construct($internalId);
$this->pcbios = new ExecData();
$this->efi = new ExecData();
if ($data instanceof PxeSection) {
@@ -223,10 +234,7 @@ class StandardBootEntry extends BootEntry
}
}
- /**
- * @param PxeSection $data
- */
- private function fromPxeMenu($data)
+ private function fromPxeMenu(PxeSection $data): void
{
$bios = $this->pcbios;
$bios->executable = $data->kernel;
@@ -250,12 +258,12 @@ class StandardBootEntry extends BootEntry
$bios->commandLine = trim(preg_replace('/\s+/', ' ', $bios->commandLine));
}
- public function arch()
+ public function arch(): ?string
{
return $this->arch;
}
- public function supportsMode($mode)
+ public function supportsMode(string $mode): bool
{
if ($mode === $this->arch || $this->arch === BootEntry::AGNOSTIC)
return true;
@@ -266,7 +274,7 @@ class StandardBootEntry extends BootEntry
return false;
}
- public function toScript($builder)
+ public function toScript(ScriptBuilderBase $builder): string
{
if ($this->arch === BootEntry::AGNOSTIC) // Same as below, could construct fall-through but this is more clear
return $builder->execDataToScript($this->pcbios, null, null);
@@ -275,7 +283,7 @@ class StandardBootEntry extends BootEntry
$this->supportsMode(BootEntry::EFI) ? $this->efi : null);
}
- public function addFormFields(&$array)
+ public function addFormFields(array &$array): void
{
$array[$this->arch . '_selected'] = 'selected';
$array['entries'][] = $this->pcbios->toFormFields(BootEntry::BIOS);
@@ -283,7 +291,10 @@ class StandardBootEntry extends BootEntry
$array['exec_checked'] = 'checked';
}
- public function toArray()
+ /**
+ * @return array{PCBIOS: array, EFI: array, arch: string}
+ */
+ public function toArray(): array
{
return [
BootEntry::BIOS => $this->pcbios->toArray(),
@@ -298,7 +309,7 @@ class CustomBootEntry extends BootEntry
/**
* @var string iPXE
*/
- protected $ipxe;
+ protected $ipxe = '';
protected $bash;
@@ -306,6 +317,7 @@ class CustomBootEntry extends BootEntry
public function __construct($data)
{
+ parent::__construct('custom');
if (is_array($data)) {
$this->ipxe = $data['script'] ?? ''; // LEGACY
foreach (['bash', 'grub'] as $key) {
@@ -314,21 +326,24 @@ class CustomBootEntry extends BootEntry
}
}
- public function supportsMode($mode)
+ public function supportsMode(string $mode): bool
{
return true;
}
- public function toScript($builder)
+ public function toScript(ScriptBuilderBase $builder): string
{
+ // TODO: A (very) simple translator for oneliners like "poweroff || goto fail" maybe?
if ($builder instanceof ScriptBuilderIpxe)
return $this->ipxe;
if ($builder instanceof ScriptBuilderBash)
return $this->bash;
+ if ($builder instanceof ScriptBuilderGrub)
+ return $this->grub;
return '';
}
- public function addFormFields(&$array)
+ public function addFormFields(array &$array): void
{
$array['entry'] = [
'script' => $this->ipxe,
@@ -336,7 +351,10 @@ class CustomBootEntry extends BootEntry
$array['script_checked'] = 'checked';
}
- public function toArray()
+ /**
+ * @return array{script: string}
+ */
+ public function toArray(): array
{
return ['script' => $this->ipxe];
}
@@ -344,30 +362,32 @@ class CustomBootEntry extends BootEntry
class MenuBootEntry extends BootEntry
{
+ /** @var int */
protected $menuId;
- public function __construct($menuId)
+ public function __construct(int $menuId)
{
+ parent::__construct('menu-' . $menuId);
$this->menuId = $menuId;
}
- public function supportsMode($mode)
+ public function supportsMode(string $mode): bool
{
return true;
}
- public function toScript($builder)
+ public function toScript(ScriptBuilderBase $builder): string
{
- $menu = IPxeMenu::get($this->menuId);
+ $menu = IPxeMenu::get($this->menuId, true);
return $builder->menuToScript($menu);
}
- public function toArray()
+ public function toArray(): array
{
return [];
}
- public function addFormFields(&$array)
+ public function addFormFields(array &$array): void
{
}
}
@@ -380,23 +400,24 @@ class SpecialBootEntry extends BootEntry
public function __construct($type)
{
$this->type = $type['type'] ?? $type;
+ parent::__construct('special-' . $this->type);
}
- public function supportsMode($mode)
+ public function supportsMode(string $mode): bool
{
return true;
}
- public function toScript($builder)
+ public function toScript(ScriptBuilderBase $builder): string
{
return $builder->getSpecial($this->type);
}
- public function toArray()
+ public function toArray(): array
{
return [];
}
- public function addFormFields(&$array) { }
+ public function addFormFields(array &$array): void { }
-} \ No newline at end of file
+}
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/bootentryhook.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/bootentryhook.inc.php
index 73611b0a..ab55c888 100644
--- a/modules-available/serversetup-bwlp-ipxe/inc/bootentryhook.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/inc/bootentryhook.inc.php
@@ -6,41 +6,37 @@ abstract class BootEntryHook
/**
* @var string -- set by ipxe, not module implementing hook
*/
- public $moduleId;
+ public $moduleId = '';
/**
* @var string -- set by ipxe, not module implementing hook
*/
- public $checked;
+ public $checked = '';
- private $selectedId;
+ private $selectedId = '';
private $data = [];
/**
* @return string
*/
- public abstract function name();
+ public abstract function name(): string;
/**
* @return HookExtraField[]
*/
- public abstract function extraFields();
+ public abstract function extraFields(): array;
- /**
- * @param string $id
- * @return bool
- */
- public abstract function isValidId($id);
+ public abstract function isValidId(string $id): bool;
/**
* @return HookEntryGroup[]
*/
- protected abstract function groupsInternal();
+ protected abstract function groupsInternal(): array;
/**
* @return HookEntryGroup[]
*/
- public final function groups()
+ public final function groups(): array
{
$groups = $this->groupsInternal();
foreach ($groups as $group) {
@@ -54,16 +50,13 @@ abstract class BootEntryHook
}
/**
- * @param $id
* @return BootEntry|null the actual boot entry instance for given entry, null if invalid id
*/
- public abstract function getBootEntryInternal($localData);
+ public abstract function getBootEntryInternal(array $localData): ?BootEntry;
- public final function getBootEntry($data)
+ public final function getBootEntry(string $jsonString): ?BootEntry
{
- if (!is_array($data)) {
- $data = json_decode($data, true);
- }
+ $data = json_decode($jsonString, true);
return $this->getBootEntryInternal($data);
}
@@ -71,7 +64,7 @@ abstract class BootEntryHook
* @param string $mixed either the plain ID if the entry to be marked as selected, or the JSON string representing
* the entire entry, which must have a key called 'id' that will be used as the ID then.
*/
- public function setSelected($mixed)
+ public function setSelected(string $mixed): void
{
$json = @json_decode($mixed, true);
if (is_array($json)) {
@@ -86,16 +79,19 @@ abstract class BootEntryHook
/**
* @return string ID of entry that was marked as selected by setSelected()
*/
- public function getSelected()
+ public function getSelected(): string
{
return $this->selectedId;
}
- public function renderExtraFields()
+ /**
+ * @return HookExtraField[]
+ */
+ public function renderExtraFields(): array
{
$list = $this->extraFields();
- foreach ($list as &$entry) {
- $entry->currentValue = isset($this->data[$entry->name]) ? $this->data[$entry->name] : $entry->default;
+ foreach ($list as $entry) {
+ $entry->currentValue = $this->data[$entry->name] ?? $entry->default;
$entry->hook = $this;
}
return $list;
@@ -144,14 +140,7 @@ class HookEntry
*/
public $selected;
- /**
- * HookEntry constructor.
- *
- * @param string $id
- * @param string $name
- * @param bool $valid
- */
- public function __construct($id, $name, $valid)
+ public function __construct(string $id, string $name, bool $valid)
{
$this->id = $id;
$this->name = $name;
@@ -182,7 +171,7 @@ class HookExtraField
*/
public $hook;
- public function __construct($name, $type, $default)
+ public function __construct(string $name, string $type, $default)
{
$this->name = $name;
$this->type = $type;
@@ -203,10 +192,10 @@ class HookExtraField
return $val;
}
- public function html()
+ public function html(): string
{
$fieldId = 'extra-' . $this->hook->moduleId . '-' . $this->name;
- $fieldText = htmlspecialchars(Dictionary::translateFileModule($this->hook->moduleId, 'module', 'ipxe-' . $this->name, true));
+ $fieldText = htmlspecialchars(Dictionary::translateFileModule($this->hook->moduleId, 'module', 'ipxe-' . $this->name));
if (is_array($this->type)) {
$out = '<label for="' . $fieldId . '">' . $fieldText . '</label><select class="form-control" name="' . $fieldId . '" id="' . $fieldId . '">';
foreach ($this->type as $entry) {
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/execdata.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/execdata.inc.php
index 1f6fa265..e4f7a1d7 100644
--- a/modules-available/serversetup-bwlp-ipxe/inc/execdata.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/inc/execdata.inc.php
@@ -108,7 +108,10 @@ class ExecData
$this->dhcpOptions = array_values($this->dhcpOptions);
}
- public function toArray()
+ /**
+ * @return array{executable: string, initRd: string[], commandLine: string, imageFree: bool, replace: bool, autoUnload: bool, resetConsole: bool, dhcpOptions: array}
+ */
+ public function toArray(): array
{
$this->sanitize();
return [
@@ -123,7 +126,7 @@ class ExecData
];
}
- public function toFormFields($arch)
+ public function toFormFields(string $arch): array
{
$this->sanitize();
$opts = [];
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php
index 29885588..5e0531ab 100644
--- a/modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php
@@ -12,13 +12,13 @@ class IPxe
* Import all IP-Range based pxe menus from the given directory.
*
* @param string $configPath The pxelinux.cfg path where to look for menu files in hexadecimal IP format.
- * @return Number of menus imported
+ * @return int Number of menus imported
*/
- public static function importSubnetPxeMenus($configPath)
+ public static function importSubnetPxeMenus(string $configPath): int
{
$res = Database::simpleQuery('SELECT menuid, entryid FROM serversetup_menuentry ORDER BY sortval ASC');
$menus = [];
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
if (!isset($menus[$row['menuid']])) {
$menus[(int)$row['menuid']] = [];
}
@@ -40,7 +40,7 @@ class IPxe
WHERE startaddr >= :start AND endaddr <= :end", compact('start', 'end'));
$locations = [];
// Iterate over result, eliminate those that are dominated by others
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
foreach ($locations as &$loc) {
if ($row['startaddr'] <= $loc['startaddr'] && $row['endaddr'] >= $loc['endaddr']) {
$loc = false;
@@ -52,6 +52,10 @@ class IPxe
$locations[] = $row;
}
$menu = PxeLinux::parsePxeLinux($content, true);
+ if ($menu === null) {
+ error_log("Skipping empty pxelinux menu file $file");
+ continue;
+ }
// Insert all entries first, so we can get the list of entry IDs
$entries = [];
self::importPxeMenuEntries($menu, $entries);
@@ -83,10 +87,10 @@ class IPxe
} else {
error_log('Imported menu ' . $menu->title . ' is NEW, using for ' . count($locations) . ' locations.');
// Insert new menu
- $menuId = self::insertMenu($menu, 'Auto Imported', false, 0, [], []);
- if ($menuId === false)
+ $menuId = self::insertMenu($menu, 'Auto Imported', null, 0, [], []);
+ if ($menuId === null)
continue;
- $menus[(int)$menuId] = $entries;
+ $menus[$menuId] = $entries;
$importCount++;
}
foreach ($locations as $loc) {
@@ -103,20 +107,20 @@ class IPxe
return $importCount;
}
- public static function importLegacyMenu($force = false)
+ public static function importLegacyMenu(bool $force = false): bool
{
// See if anything is there
if (!$force && false !== Database::queryFirst("SELECT menuentryid FROM serversetup_menuentry LIMIT 1"))
return false; // Already exists
// Now create the default entry
self::createDefaultEntries();
- $prepend = ['bwlp-default' => false, 'localboot' => false];
+ $prepend = ['bwlp-default' => null, 'localboot' => null];
$defaultLabel = 'bwlp-default';
$menuTitle = 'bwLehrpool Bootauswahl';
$pxeConfig = '';
$timeoutSecs = 60;
- // Try to import any customization
- $oldMenu = Property::getBootMenu();
+ // Try to import any customization of the legacy PXELinux menu (despite the property name hinting at iPXE)
+ $oldMenu = json_decode(Property::get('ipxe-menu'), true);
if (is_array($oldMenu)) {
//
if (isset($oldMenu['timeout'])) {
@@ -136,47 +140,45 @@ class IPxe
}
}
$append = [
- '',
- 'bwlp-default-dbg' => false,
- '',
- 'poweroff' => false,
+ new PxeSection(null),
+ 'bwlp-default-dbg' => null,
+ new PxeSection(null),
+ 'poweroff' => null,
];
- return self::insertMenu(PxeLinux::parsePxeLinux($pxeConfig, false), $menuTitle, $defaultLabel, $timeoutSecs, $prepend, $append);
+ self::insertMenu(PxeLinux::parsePxeLinux($pxeConfig, false), $menuTitle, $defaultLabel, $timeoutSecs, $prepend, $append);
+ return !empty($pxeConfig);
}
/**
- * @param PxeMenu $pxeMenu
- * @param string $menuTitle
- * @param string|false $defaultLabel Fallback for the default label, if PxeMenu doesn't set one
+ * @param ?string $defaultLabel Fallback for the default label, if PxeMenu doesn't set one
* @param int $defaultTimeoutSeconds Default timeout, if PxeMenu doesn't set one
- * @param array $prepend
- * @param array $append
- * @return int|false
+ * @param (?PxeSection)[] $prepend
+ * @param (?PxeSection)[] $append
+ * @return ?int ID of newly created menu, or null on error, e.g. if the menu is empty
*/
- public static function insertMenu($pxeMenu, $menuTitle, $defaultLabel, $defaultTimeoutSeconds, $prepend, $append)
+ public static function insertMenu(?PxeMenu $pxeMenu, string $menuTitle, ?string $defaultLabel, int $defaultTimeoutSeconds,
+ array $prepend, array $append): ?int
{
$timeoutMs = [];
$menuEntries = $prepend;
- settype($menuEntries, 'array');
- if (!empty($pxeMenu)) {
- $pxe =& $pxeMenu;
- if (!empty($pxe->title)) {
- $menuTitle = $pxe->title;
+ if ($pxeMenu !== null) {
+ if (!empty($pxeMenu->title)) {
+ $menuTitle = $pxeMenu->title;
}
- if ($pxe->timeoutLabel !== null && $pxe->hasLabel($pxe->timeoutLabel)) {
- $defaultLabel = $pxe->timeoutLabel;
- } elseif ($pxe->hasLabel($pxe->default)) {
- $defaultLabel = $pxe->default;
+ if ($pxeMenu->timeoutLabel !== null && $pxeMenu->hasLabel($pxeMenu->timeoutLabel)) {
+ $defaultLabel = $pxeMenu->timeoutLabel;
+ } elseif ($pxeMenu->hasLabel($pxeMenu->default)) {
+ $defaultLabel = $pxeMenu->default;
}
- $timeoutMs[] = $pxe->timeoutMs;
- $timeoutMs[] = $pxe->totalTimeoutMs;
- self::importPxeMenuEntries($pxe, $menuEntries);
+ $timeoutMs[] = $pxeMenu->timeoutMs;
+ $timeoutMs[] = $pxeMenu->totalTimeoutMs;
+ self::importPxeMenuEntries($pxeMenu, $menuEntries);
}
- if (is_array($append)) {
+ if (!empty($append)) {
$menuEntries += $append;
}
if (empty($menuEntries))
- return false;
+ return null;
// Make menu
$timeoutMs = array_filter($timeoutMs, function($x) { return is_int($x) && $x > 0; });
if (empty($timeoutMs)) {
@@ -195,25 +197,29 @@ class IPxe
// Figure out entryid for default label
// Fiddly diddly way of getting the mangled entryid for the wanted pxe menu label
$defaultEntryId = false;
+ $fallbackDefault = false;
foreach ($menuEntries as $entryId => $section) {
- if ($section instanceof PxeSection) {
- if ($section->isDefault) {
- $defaultEntryId = $entryId;
- break;
- }
- if ($section->label === $defaultLabel) {
- $defaultEntryId = $entryId;
- }
+ if ($section === null)
+ continue;
+ if ($section->isDefault) {
+ $defaultEntryId = $entryId;
+ break;
+ }
+ if ($section->label === $defaultLabel) {
+ $defaultEntryId = $entryId;
+ }
+ if ($fallbackDefault === false && !empty($entryId)) {
+ $fallbackDefault = $entryId;
}
}
if ($defaultEntryId === false) {
- $defaultEntryId = array_keys($menuEntries)[0];
+ $defaultEntryId = $fallbackDefault;
}
// Link boot entries to menu
$defaultMenuEntryId = null;
$order = 1000;
foreach ($menuEntries as $entryId => $entry) {
- if (is_string($entry)) {
+ if ($entry !== null && $entry->isTextOnly()) {
// Gap entry
Database::exec("INSERT INTO serversetup_menuentry
(menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass)
@@ -221,7 +227,7 @@ class IPxe
'menuid' => $menuId,
'entryid' => null,
'hotkey' => '',
- 'title' => self::sanitizeIpxeString($entry),
+ 'title' => self::sanitizeIpxeString($entry->title),
'hidden' => 0,
'sortval' => $order += 100,
]);
@@ -232,7 +238,7 @@ class IPxe
continue;
$data['pass'] = '';
$data['hidden'] = 0;
- if ($entry instanceof PxeSection) {
+ if ($entry !== null) {
$data['hidden'] = (int)$entry->isHidden;
// Prefer explicit data from this imported menu over the defaults
$title = self::sanitizeIpxeString($entry->title);
@@ -243,7 +249,7 @@ class IPxe
$data['hotkey'] = $entry->hotkey;
}
if (!empty($entry->passwd)) {
- // Most likely it's a hash so we cannot recover; ask people to reset
+ // Most likely it's a hash, so we cannot recover; ask people to reset
$data['pass'] ='please_reset';
}
}
@@ -268,36 +274,28 @@ class IPxe
/**
* Import only the bootentries from the given PXELinux menu
- * @param PxeMenu $pxe
- * @param array $menuEntries Where to append the generated menu items to
+ *
+ * @param PxeSection[] $menuEntries Where to append the generated menu items to
*/
- public static function importPxeMenuEntries($pxe, &$menuEntries)
+ public static function importPxeMenuEntries(PxeMenu $pxe, array &$menuEntries): void
{
if (self::$allEntries === false) {
self::$allEntries = BootEntry::getAll();
}
foreach ($pxe->sections as $section) {
- if ($section->localBoot !== false || preg_match('/chain\.c32$/i', $section->kernel)) {
+ if ($section->isLocalboot()) {
$menuEntries['localboot'] = $section;
continue;
}
- if ($section->label === null) {
- if (!$section->isHidden && !empty($section->title)) {
- $menuEntries[] = $section->title;
- }
- continue;
- }
- if (empty($section->kernel)) {
- if (!$section->isHidden && !empty($section->title)) {
- $menuEntries[] = $section->title;
- }
+ if ($section->isTextOnly()) {
+ $menuEntries[] = $section;
continue;
}
$label = self::cleanLabelFixLocal($section);
$entry = self::pxe2BootEntry($section);
if ($entry === null)
continue; // Error? Ignore
- if ($label !== false || ($label = array_search($entry, self::$allEntries))) {
+ if ($label !== false || ($label = array_search($entry, self::$allEntries)) !== false) {
// Exact Duplicate, Do Nothing
error_log('Ignoring duplicate boot entry ' . $section->label . ' (' . $section->kernel . ')');
} else {
@@ -321,7 +319,7 @@ class IPxe
Database::exec('INSERT IGNORE INTO serversetup_bootentry (entryid, module, hotkey, title, builtin, data)
VALUES (:label, :module, :hotkey, :title, 0, :data)', [
'label' => $label,
- 'module' => ($entry instanceof \StandardBootEntry) ? '.exec' : '.script',
+ 'module' => ($entry instanceof StandardBootEntry) ? '.exec' : '.script',
'hotkey' => $hotkey,
'title' => $title,
'data' => json_encode($data),
@@ -408,10 +406,9 @@ class IPxe
* Also it patches the entry if it's referencing the local bwlp install
* but with different options.
*
- * @param PxeSection $section
* @return string|false existing label if match, false otherwise
*/
- private static function cleanLabelFixLocal($section)
+ private static function cleanLabelFixLocal(PxeSection $section)
{
$myip = Property::getServerIp();
// Detect our "old" entry types
@@ -434,10 +431,9 @@ class IPxe
}
/**
- * @param PxeSection $section
* @return BootEntry|null The according boot entry, null if it's unparsable
*/
- private static function pxe2BootEntry($section)
+ private static function pxe2BootEntry(PxeSection $section): ?BootEntry
{
if (preg_match('/(pxechain\.com|pxechn\.c32)$/i', $section->kernel)) {
// Chaining -- create script
@@ -466,7 +462,7 @@ class IPxe
$script .= "set netX/{$opt}:{$type} {$args[$i]} || goto %fail%\n";
}
}
- } elseif ($arg{0} === '-') {
+ } elseif ($arg[0] === '-') {
continue;
} elseif ($file === false) {
$file = self::parseFile($arg);
@@ -496,11 +492,8 @@ class IPxe
/**
* Parse PXELINUX file notion. Basically, turn
* server::file into tftp://server/file.
- *
- * @param string $file
- * @return string
*/
- private static function parseFile($file)
+ private static function parseFile(string $file): string
{
if (preg_match(',^([^:/]+)::(.*)$,', $file, $out)) {
return 'tftp://' . $out[1] . '/' . $out[2];
@@ -508,12 +501,12 @@ class IPxe
return $file;
}
- public static function sanitizeIpxeString($string)
+ public static function sanitizeIpxeString(string $string): string
{
return str_replace(['&', '|', ';', '$', "\r", "\n"], ['+', '/', ':', 'S', ' ', ' '], $string);
}
- public static function makeMd5Pass($plainpass, $salt)
+ public static function makeMd5Pass(string $plainpass, string $salt): string
{
if (empty($plainpass))
return '';
@@ -528,15 +521,16 @@ class IPxe
* remove any occurrence of either "option" or "option=something". If the argument starts with a
* '+', it will be added to the command line after removing the '+'. If the argument starts with any
* other character, it will also be added to the command line.
+ *
* @param string $cmdLine command line to modify
- * @param string $modifier modification string of space separated arguments
+ * @param string $modifier modification string of space separated arguments
* @return string the modified command line
*/
- public static function modifyCommandLine($cmdLine, $modifier)
+ public static function modifyCommandLine(string $cmdLine, string $modifier): string
{
$items = preg_split('/\s+/', $modifier, -1, PREG_SPLIT_NO_EMPTY);
foreach ($items as $item) {
- if ($item{0} === '-') {
+ if ($item[0] === '-') {
$item = preg_quote(substr($item, 1), '/');
$cmdLine = preg_replace('/(^|\s)' . $item . '(=\S*)?($|\s)/', ' ', $cmdLine);
} else {
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/ipxebuilder.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/ipxebuilder.inc.php
new file mode 100644
index 00000000..a2b25f55
--- /dev/null
+++ b/modules-available/serversetup-bwlp-ipxe/inc/ipxebuilder.inc.php
@@ -0,0 +1,80 @@
+<?php
+
+class IPxeBuilder
+{
+
+ const PROP_IPXE_BUILDSTRING = 'ipxe.compile-time';
+ const PROP_IPXE_HASH = 'ipxe.commit-hash';
+ const PROP_IPXE_COMPILE_TASKID = 'ipxe-task-id';
+ const VERSION_LIST_TASK = 'ipxe-version-list-id';
+ const PROP_VERSION_SELECT_TASKID = 'ipxe-version-select-id';
+
+ /**
+ * Checkout given commit/ref of ipxe repo. Returns the according task-id, or null on error
+ *
+ * @param string $version version ref (commit, tag, ...)
+ * @param ?string $parent parent task id, if any
+ */
+ public static function setIpxeVersion(string $version, ?string $parent = null): ?string
+ {
+ $task = Taskmanager::submit('IpxeVersion', [
+ 'action' => 'CHECKOUT',
+ 'ref' => $version,
+ 'parentTask' => $parent,
+ ]);
+ if (!Taskmanager::isTask($task))
+ return null;
+ TaskmanagerCallback::addCallback($task, 'ipxeVersionSet');
+ Property::set(IPxeBuilder::PROP_VERSION_SELECT_TASKID, $task['id'], 2);
+ return $task['id'];
+ }
+
+ public static function getVersionTaskResult(): ?array
+ {
+ $task = Taskmanager::status(IPxeBuilder::VERSION_LIST_TASK);
+ if (!Taskmanager::isTask($task) || Taskmanager::isFailed($task)) {
+ $task = Taskmanager::submit('IpxeVersion',
+ ['id' => IPxeBuilder::VERSION_LIST_TASK, 'action' => 'LIST']);
+ }
+ $task = Taskmanager::waitComplete($task);
+ if (Taskmanager::isFinished($task) && !Taskmanager::isFailed($task)) {
+ return $task['data'];
+ }
+ return null;
+ }
+
+ /**
+ * Callback when compile Taskmanager job finished
+ */
+ public static function compileCompleteCallback(array $task): void
+ {
+ if (!Taskmanager::isFinished($task) || Taskmanager::isFailed($task))
+ return;
+ $version = 'Unknown';
+ if (isset($task['data']['hash'])) {
+ $hash = $task['data']['hash'];
+ Property::set(IPxeBuilder::PROP_IPXE_HASH, $hash);
+ $version = $hash;
+ $list = IPxeBuilder::getVersionTaskResult();
+ if (isset($list['versions'])) {
+ foreach ($list['versions'] as $v) {
+ if ($v['hash'] === $version) {
+ // Do NOT change (see below)
+ $version = date('Y-m-d H:i', $v['date']) . ' (' . substr($version, 0, 7) . ')';
+ break;
+ }
+ }
+ }
+ }
+ // Do NOT change the format of this string -- we depend on it in ScriptBuilderIpxe::output()
+ $buildString = date('d.m.Y H:i') . ', Version: ' . $version;
+ Property::set(IPxeBuilder::PROP_IPXE_BUILDSTRING, $buildString);
+ }
+
+ public static function setIPxeVersionCallback(array $task): void
+ {
+ if (!Taskmanager::isFinished($task) || Taskmanager::isFailed($task) || empty($task['data']['ref']))
+ return;
+ Property::set(IPxeBuilder::PROP_IPXE_HASH, $task['data']['ref']);
+ }
+} \ 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 b1e13e87..3ffecba1 100644
--- a/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php
@@ -3,19 +3,28 @@
class IPxeMenu
{
+ /**
+ * @var int ID of this menu, from DB
+ */
protected $menuid;
+ /**
+ * @var int 0 = disabled, otherwise, launch default option after timeout
+ */
public $timeoutMs;
+ /**
+ * @var string title to display above menu
+ */
public $title;
+ /**
+ * @var int menu entry id from DB
+ */
public $defaultEntryId;
/**
* @var MenuEntry[]
*/
public $items = [];
- /**
- * @param int $menuId
- */
- public static function get($menuId, $emptyFallback = false)
+ public static function get(int $menuId, bool $emptyFallback = false): ?IPxeMenu
{
$menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid FROM serversetup_menu
WHERE menuid = :menuid LIMIT 1", ['menuid' => $menuId]);
@@ -31,33 +40,34 @@ class IPxeMenu
*
* @param array $menu array for according menu row
*/
- public function __construct($menu)
+ public function __construct(array $menu)
{
$this->menuid = (int)$menu['menuid'];
$this->timeoutMs = (int)$menu['timeoutms'];
- $this->title = $menu['title'];
- $this->defaultEntryId = $menu['defaultentryid'];
+ $this->title = (string)$menu['title'];
+ $defaultEntryId = $menu['defaultentryid'];
$res = Database::simpleQuery("SELECT e.menuentryid, e.entryid, e.refmenuid, e.hotkey, e.title,
e.hidden, e.sortval, e.md5pass, b.module, b.data AS bootentry, b.title AS betitle
FROM serversetup_menuentry e
LEFT JOIN serversetup_bootentry b USING (entryid)
WHERE e.menuid = :menuid
ORDER BY e.sortval ASC, e.title ASC", ['menuid' => $menu['menuid']]);
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
$this->items[] = new MenuEntry($row);
}
// Make sure we have a default entry if the menu isn't empty
- if ($this->defaultEntryId === null && !empty($this->items)) {
- $this->defaultEntryId = $this->items[0]->menuEntryId();
+ if ($defaultEntryId === null && !empty($this->items)) {
+ $defaultEntryId = $this->items[0]->menuEntryId();
}
+ $this->defaultEntryId = (int)$defaultEntryId;
}
- public function title()
+ public function title(): string
{
return $this->title;
}
- public function timeoutMs()
+ public function timeoutMs(): int
{
return $this->timeoutMs;
}
@@ -65,36 +75,74 @@ class IPxeMenu
/**
* @return int Number of items in this menu
*/
- public function itemCount()
+ public function itemCount(): int
{
return count($this->items);
}
/**
- * @return string|null Return script label of default entry, null if not set
+ * @return MenuEntry|null Return preselected menu entry
*/
- public function getDefaultEntryId()
+ public function defaultEntry(): ?MenuEntry
{
- return $this->defaultEntryId;
+ foreach ($this->items as $item) {
+ if ($item->menuEntryId() === $this->defaultEntryId)
+ return $item;
+ }
+ return null;
+ }
+
+ private function maybeOverrideDefault(string $uuid)
+ {
+ $e = $this->defaultEntry();
+ // Shortcut - is already bwlp and timeout is reasonable (1-15s), do nothing
+ $defIsMl = $e !== null && substr($e->internalId(), 0, 3) === 'ml-';
+ $timeoutOk = $this->timeoutMs > 0 && $this->timeoutMs <= 15000;
+ if ($timeoutOk && $defIsMl)
+ return;
+ // No runmode module anyways
+ if (!Module::isAvailable('runmode'))
+ return;
+ $rm = RunMode::getRunMode($uuid);
+ // No runmode for this client, cannot be PVSmgr
+ if ($rm === false)
+ return;
+ // Is not pvsmgr
+ if ($rm['module'] !== 'roomplanner')
+ return;
+ // See if it's a dedicated station, if so make sure it boots into bwLehrpool
+ $data = json_decode($rm['modedata'], true);
+ if ($data['dedicatedmgr'] ?? false) {
+ if (!$defIsMl) {
+ $this->overrideDefaultToMinilinux();
+ }
+ if (!$timeoutOk) {
+ $this->timeoutMs = 5000;
+ }
+ }
}
/**
- * @return MenuEntry|null Return preselected menu entry
+ * Patch the menu to make sure bwLehrpool/"MiniLinux" is the default
+ * boot option, and set timeout to something reasonable. This is used
+ * for dedicated PVS managers, as they might not have a keyboard
+ * connected.
*/
- public function defaultEntry()
+ private function overrideDefaultToMinilinux()
{
foreach ($this->items as $item) {
- if ($item->menuEntryId() == $this->defaultEntryId)
- return $item;
+ if (substr($item->internalId(), 0, 3) === 'ml-') {
+ $this->defaultEntryId = $item->menuEntryId();
+ return;
+ }
}
- return null;
}
/*
*
*/
- public static function forLocation($locationId) : IPxeMenu
+ public static function forLocation(int $locationId): IPxeMenu
{
$chain = null;
if (Module::isAvailable('locations')) {
@@ -109,7 +157,7 @@ class IPxeMenu
if ($res->rowCount() > 0) {
// Make the location id key, preserving order (closest location is first)
$chain = array_flip($chain);
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
// Overwrite the value (numeric ascending values, useless) with menu array of according location
$chain[(int)$row['locationid']] = $row;
}
@@ -132,13 +180,19 @@ class IPxeMenu
return new IPxeMenu($menu);
}
- public static function forClient($ip, $uuid) : IPxeMenu
+ public static function forClient(string $ip, ?string $uuid): IPxeMenu
{
$locationId = 0;
if (Module::isAvailable('locations')) {
$locationId = Location::getFromIpAndUuid($ip, $uuid);
}
- return self::forLocation($locationId);
+ $menu = self::forLocation($locationId);
+ if ($uuid !== null) {
+ // Super specialcase hackery: If this is a dedicated PVS, force the default to
+ // be bwlp/"minilinux"
+ $menu->maybeOverrideDefault($uuid);
+ }
+ return $menu;
}
}
@@ -146,11 +200,14 @@ class IPxeMenu
class EmptyIPxeMenu extends IPxeMenu
{
- /** @noinspection PhpMissingParentConstructorInspection */
public function __construct()
{
- $this->title = 'No menu defined';
- $this->menuid = -1;
+ parent::__construct([
+ 'menuid' => -1,
+ 'timeoutms' => 120,
+ 'defaultentryid' => null,
+ 'title' => 'No menu defined',
+ ]);
$this->items[] = new MenuEntry([
'title' => 'Please create a menu in Server-Setup first'
]);
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/localboot.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/localboot.inc.php
index 4203f931..4d1a56c7 100644
--- a/modules-available/serversetup-bwlp-ipxe/inc/localboot.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/inc/localboot.inc.php
@@ -7,38 +7,49 @@ class Localboot
const BOOT_METHODS = [
'PCBIOS' => [
- 'EXIT' => 'exit 1',
+ 'EXIT' => 'set slx_exit 1 ||
+exit 1',
'COMBOOT' => 'set netX/209:string localboot.cfg ||
set netX/210:string http://${serverip}/tftp/sl-bios/ ||
chain -ar /tftp/sl-bios/lpxelinux.0',
'SANBOOT' => 'sanboot --no-describe',
],
'EFI' => [
- 'EXIT' => 'exit 1',
- 'COMBOOT' => 'set netX/209:string localboot.cfg ||
-set netX/210:string http://${serverip}/tftp/sl-efi64/ ||
-chain -ar /tftp/sl-efi64/syslinux.efi',
+ 'EXIT' => 'set slx_exit 1 ||
+exit 1',
+ 'SANBOOT' => 'imgfree ||
+console ||
+set filename \EFI\Boot\bootx64.efi ||
+set i:int32 0 ||
+:blubber
+sanboot --no-describe --drive ${i} --filename ${filename} ||
+inc i
+iseq ${i} 10 || goto blubber',
+ 'GRUB' => 'chain /tftp/grub-boot.img',
],
];
- public static function getDefault()
+ /**
+ * @return array{PCBIOS: string, EFI: string}
+ */
+ public static function getDefault(): array
{
- $ret = explode(',', Property::get(self::PROPERTY_KEY, 'SANBOOT,EXIT'));
+ $ret = explode(',', Property::get(self::PROPERTY_KEY, 'SANBOOT,GRUB'));
if (empty($ret)) {
- $ret = ['SANBOOT', 'EXIT'];
+ $ret = ['SANBOOT', 'GRUB'];
} elseif (count($ret) < 2) {
- $ret[] = 'EXIT';
+ $ret[] = 'SANBOOT';
}
- if (null === self::BOOT_METHODS['PCBIOS'][$ret[0]]) {
+ if (!isset(self::BOOT_METHODS['PCBIOS'][$ret[0]])) {
$ret[0] = 'SANBOOT';
}
- if (null === self::BOOT_METHODS['EFI'][$ret[1]]) {
- $ret[1] = 'EXIT';
+ if (!isset(self::BOOT_METHODS['EFI'][$ret[1]])) {
+ $ret[1] = 'GRUB';
}
return ['PCBIOS' => $ret[0], 'EFI' => $ret[1]];
}
- public static function setDefault($pcbios, $efi)
+ public static function setDefault(string $pcbios, string $efi)
{
Property::set(self::PROPERTY_KEY, "$pcbios,$efi");
}
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php
index eb4a98de..da94a16b 100644
--- a/modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php
@@ -27,7 +27,7 @@ class MenuEntry
*/
public $sortval;
/**
- * @var BootEntry
+ * @var ?BootEntry
*/
public $bootEntry = null;
@@ -35,11 +35,7 @@ class MenuEntry
public $md5pass = null;
- /**
- * @param int $menuEntryId
- * @return MenuEntry|null
- */
- public static function get($menuEntryId)
+ public static function get(int $menuEntryId): ?MenuEntry
{
$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, b.title AS betitle
@@ -56,48 +52,53 @@ class MenuEntry
*
* @param array $row row from database
*/
- public function __construct($row)
+ public function __construct(array $row)
{
- if (is_array($row)) {
- if (empty($row['title']) && !empty($row['betitle'])) {
- $row['title'] = $row['betitle'];
- }
- foreach ($row as $key => $value) {
- if (property_exists($this, $key)) {
- $this->{$key} = $value;
- }
- }
- $this->hotkey = self::getKeyCode($row['hotkey'] ?? '');
- if (!empty($row['bootentry'])) {
- $this->bootEntry = BootEntry::fromJson($row['module'], $row['bootentry']);
- } elseif (isset($row['refmenuid'])) {
- $this->bootEntry = BootEntry::forMenu($row['refmenuid']);
+ if (empty($row['title']) && !empty($row['betitle'])) {
+ $row['title'] = $row['betitle'];
+ }
+ foreach ($row as $key => $value) {
+ if (property_exists($this, $key)) {
+ $this->{$key} = $value;
}
- $this->gap = (array_key_exists('entryid', $row) && $row['entryid'] === null && $row['refmenuid'] === null);
}
+ $this->hotkey = self::getKeyCode($row['hotkey'] ?? '');
+ if (!empty($row['bootentry'])) {
+ $this->bootEntry = BootEntry::fromJson($row['module'], $row['bootentry']);
+ } elseif (isset($row['refmenuid'])) {
+ $this->bootEntry = BootEntry::forMenu($row['refmenuid']);
+ }
+ $this->gap = (array_key_exists('entryid', $row) && $row['entryid'] === null && $row['refmenuid'] === null);
settype($this->hidden, 'bool');
settype($this->gap, 'bool');
settype($this->sortval, 'int');
settype($this->menuentryid, 'int');
}
- public function getBootEntryScript($builder)
+ public function getBootEntryScript(ScriptBuilderBase $builder): string
{
if ($this->bootEntry === null)
return '';
return $this->bootEntry->toScript($builder);
}
- public function menuEntryId()
+ public function menuEntryId(): int
{
return $this->menuentryid;
}
- public function title()
+ public function title(): string
{
return $this->title;
}
+ public function internalId(): string
+ {
+ if ($this->bootEntry === null)
+ return '';
+ return $this->bootEntry->internalId();
+ }
+
/*
*
*/
@@ -137,7 +138,7 @@ class MenuEntry
*
* @return string[] list of known key names
*/
- public static function getKeyList()
+ public static function getKeyList(): array
{
return array_keys(self::getKeyArray());
}
@@ -146,10 +147,9 @@ class MenuEntry
* Get the key code ipxe expects for the given named
* key. Returns false if the key name is unknown.
*
- * @param string $keyName
* @return false|string Key code as hex string, or false if not found
*/
- public static function getKeyCode($keyName)
+ public static function getKeyCode(string $keyName)
{
$data = self::getKeyArray();
if (isset($data[$keyName]))
@@ -161,7 +161,7 @@ class MenuEntry
* @param string $keyName desired key name
* @return string $keyName if it's known, empty string otherwise
*/
- public static function filterKeyName($keyName)
+ public static function filterKeyName(string $keyName): string
{
$data = self::getKeyArray();
if (isset($data[$keyName]))
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/pxelinux.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/pxelinux.inc.php
index 3f406767..24b099dc 100644
--- a/modules-available/serversetup-bwlp-ipxe/inc/pxelinux.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/inc/pxelinux.inc.php
@@ -7,11 +7,14 @@ class PxeLinux
/**
* Takes a (partial) pxelinux menu and parses it into
* a PxeMenu object.
+ *
* @param string $input The pxelinux menu to parse
- * @return PxeMenu the parsed menu
+ * @return ?PxeMenu the parsed menu, or null if input is not a PXELinux menu
*/
- public static function parsePxeLinux($input, $isCp437)
+ public static function parsePxeLinux(string $input, bool $isCp437): ?PxeMenu
{
+ if (empty($input))
+ return null;
if ($isCp437) {
$input = iconv('IBM437', 'UTF8//TRANSLIT//IGNORE', $input);
}
@@ -76,12 +79,14 @@ class PxeLinux
}
$section->helpText = $text;
} elseif (self::handleKeyword($key, $val, $sectionPropMap, $section)) {
- continue;
+ //continue;
}
}
if ($section !== null) {
$menu->sections[] = $section;
}
+ if (empty($menu->sections))
+ return null; // Probably not a PXE menu but random text?
foreach ($menu->sections as $section) {
$section->mangle();
}
@@ -93,13 +98,14 @@ class PxeLinux
* to the given object. The map to look up the keyword has to be passed
* as well as the object to set the value in. Map and object should
* obviously match.
+ *
* @param string $key keyword of parsed line
* @param string $val raw value of currently parsed line (empty if not present)
* @param array $map Map in which $key is looked up as key
- * @param PxeMenu|PxeSection The object to set the parsed and sanitized value in
+ * @param PxeMenu|PxeSection $object The object to set the parsed and sanitized value in
* @return bool true if the value was found in the map (and set in the object), false otherwise
*/
- private static function handleKeyword($key, $val, $map, $object)
+ private static function handleKeyword(string $key, string $val, array $map, $object): bool
{
if (!isset($map[$key]))
return false;
@@ -122,161 +128,3 @@ class PxeLinux
}
-/**
- * Class representing a parsed pxelinux menu. Members
- * will be set to their annotated type if present or
- * be null otherwise, except for present-only boolean
- * options, which will default to false.
- */
-class PxeMenu
-{
-
- /**
- * @var string menu title, shown at the top of the menu
- */
- public $title;
- /**
- * @var int initial timeout after which $timeoutLabel would be executed
- */
- public $timeoutMs;
- /**
- * @var int if the user canceled the timeout by pressing a key, this timeout would still eventually
- * trigger and launch the $timeoutLabel section
- */
- public $totalTimeoutMs;
- /**
- * @var string label of section which will execute if the timeout expires
- */
- public $timeoutLabel;
- /**
- * @var bool hide menu and just show background after triggering an entry
- */
- public $menuClear = false;
- /**
- * @var bool boot the associated entry directly if its corresponding hotkey is pressed instead of just highlighting
- */
- public $immediateHotkeys = false;
- /**
- * @var PxeSection[] list of sections the menu contains
- */
- public $sections = [];
- /**
- * @var string The DEFAULT entry of the menu. Usually refers either to a
- * LABEL, or a loadable module (like vesamenu.c32)
- */
- public $default;
-
- /**
- * Check if any of the sections has the given label.
- */
- public function hasLabel($label)
- {
- foreach ($this->sections as $section) {
- if ($section->label === $label)
- return true;
- }
- return false;
- }
-
-}
-
-/**
- * Class representing a parsed pxelinux menu entry. Members
- * will be set to their annotated type if present or
- * be null otherwise, except for present-only boolean
- * options, which will default to false.
- */
-class PxeSection
-{
-
- /**
- * @var string label used internally in PXEMENU definition to address this entry
- */
- public $label;
- /**
- * @var string MENU LABEL of PXEMENU - title of entry displayed to the user
- */
- public $title;
- /**
- * @var int Number of spaces to prefix the title with
- */
- public $indent;
- /**
- * @var string help text to display when the entry is highlighted
- */
- public $helpText;
- /**
- * @var string Kernel to load
- */
- public $kernel;
- /**
- * @var string|string[] initrd to load for the kernel.
- * If mangle() has been called this will be an array,
- * otherwise it's a comma separated list.
- */
- public $initrd;
- /**
- * @var string command line options to pass to the kernel
- */
- public $append;
- /**
- * @var int IPAPPEND from PXEMENU. Bitmask of valid options 1 and 2.
- */
- public $ipAppend;
- /**
- * @var string Password protecting the entry. This is most likely in encrypted form.
- */
- public $passwd;
- /**
- * @var bool whether this section is marked as default (booted after timeout)
- */
- public $isDefault = false;
- /**
- * @var bool Menu entry is not visible (can only be triggered by timeout)
- */
- public $isHidden = false;
- /**
- * @var bool Disable this entry, making it unselectable
- */
- public $isDisabled = false;
- /**
- * @var int|false Value of the LOCALBOOT field, false if not set
- */
- public $localBoot = false;
- /**
- * @var string hotkey to trigger item. Only valid after calling mangle()
- */
- public $hotkey;
-
- public function __construct($label) { $this->label = $label; }
-
- public function mangle()
- {
- if (($i = strpos($this->title, '^')) !== false) {
- $this->hotkey = strtoupper($this->title{$i+1});
- $this->title = substr($this->title, 0, $i) . substr($this->title, $i + 1);
- }
- if (strpos($this->append, 'initrd=') !== false) {
- $parts = preg_split('/\s+/', $this->append);
- $this->append = '';
- for ($i = 0; $i < count($parts); ++$i) {
- if (preg_match('/^initrd=(.*)$/', $parts[$i], $out)) {
- if (!empty($this->initrd)) {
- $this->initrd .= ',';
- }
- $this->initrd .= $out[1];
- } else {
- $this->append .= ' ' . $parts[$i];
- }
- }
- $this->append = trim($this->append);
- }
- if (is_string($this->initrd)) {
- $this->initrd = explode(',', $this->initrd);
- } elseif (!is_array($this->initrd)) {
- $this->initrd = [];
- }
- }
-
-}
-
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/pxemenu.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/pxemenu.inc.php
new file mode 100644
index 00000000..7be57ef1
--- /dev/null
+++ b/modules-available/serversetup-bwlp-ipxe/inc/pxemenu.inc.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * Class representing a parsed pxelinux menu. Members
+ * will be set to their annotated type if present or
+ * be null otherwise, except for present-only boolean
+ * options, which will default to false.
+ */
+class PxeMenu
+{
+
+ /**
+ * @var string menu title, shown at the top of the menu
+ */
+ public $title;
+ /**
+ * @var int initial timeout after which $timeoutLabel would be executed
+ */
+ public $timeoutMs;
+ /**
+ * @var int if the user canceled the timeout by pressing a key, this timeout would still eventually
+ * trigger and launch the $timeoutLabel section
+ */
+ public $totalTimeoutMs;
+ /**
+ * @var string label of section which will execute if the timeout expires
+ */
+ public $timeoutLabel;
+ /**
+ * @var bool hide menu and just show background after triggering an entry
+ */
+ public $menuClear = false;
+ /**
+ * @var bool boot the associated entry directly if its corresponding hotkey is pressed instead of just highlighting
+ */
+ public $immediateHotkeys = false;
+ /**
+ * @var PxeSection[] list of sections the menu contains
+ */
+ public $sections = [];
+ /**
+ * @var string The DEFAULT entry of the menu. Usually refers either to a
+ * LABEL, or a loadable module (like vesamenu.c32)
+ */
+ public $default;
+
+ /**
+ * Check if any of the sections has the given label.
+ */
+ public function hasLabel(string $label): bool
+ {
+ foreach ($this->sections as $section) {
+ if ($section->label === $label)
+ return true;
+ }
+ return false;
+ }
+
+} \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/pxesection.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/pxesection.inc.php
new file mode 100644
index 00000000..2d9cd6ab
--- /dev/null
+++ b/modules-available/serversetup-bwlp-ipxe/inc/pxesection.inc.php
@@ -0,0 +1,117 @@
+<?php
+
+/**
+ * Class representing a parsed pxelinux menu entry. Members
+ * will be set to their annotated type if present or
+ * be null otherwise, except for present-only boolean
+ * options, which will default to false.
+ */
+class PxeSection
+{
+
+ /**
+ * @var ?string label used internally in PXEMENU definition to address this entry
+ */
+ public $label;
+ /**
+ * @var string MENU LABEL of PXEMENU - title of entry displayed to the user
+ */
+ public $title;
+ /**
+ * @var int Number of spaces to prefix the title with
+ */
+ public $indent;
+ /**
+ * @var string help text to display when the entry is highlighted
+ */
+ public $helpText;
+ /**
+ * @var string Kernel to load
+ */
+ public $kernel;
+ /**
+ * @var string|string[] initrd to load for the kernel.
+ * If mangle() has been called this will be an array,
+ * otherwise it's a comma separated list.
+ */
+ public $initrd;
+ /**
+ * @var string command line options to pass to the kernel
+ */
+ public $append;
+ /**
+ * @var int IPAPPEND from PXEMENU. Bitmask of valid options 1 and 2.
+ */
+ public $ipAppend;
+ /**
+ * @var string Password protecting the entry. This is most likely in encrypted form.
+ */
+ public $passwd;
+ /**
+ * @var bool whether this section is marked as default (booted after timeout)
+ */
+ public $isDefault = false;
+ /**
+ * @var bool Menu entry is not visible (can only be triggered by timeout)
+ */
+ public $isHidden = false;
+ /**
+ * @var bool Disable this entry, making it unselectable
+ */
+ public $isDisabled = false;
+ /**
+ * @var int|false Value of the LOCALBOOT field, false if not set
+ */
+ public $localBoot = false;
+ /**
+ * @var string hotkey to trigger item. Only valid after calling mangle()
+ */
+ public $hotkey;
+
+ public function __construct(?string $label) { $this->label = $label; }
+
+ public function mangle()
+ {
+ if (($i = strpos($this->title, '^')) !== false) {
+ $this->hotkey = strtoupper($this->title[$i + 1]);
+ $this->title = substr($this->title, 0, $i) . substr($this->title, $i + 1);
+ }
+ if (strpos($this->append, 'initrd=') !== false) {
+ $parts = preg_split('/\s+/', $this->append);
+ $this->append = '';
+ for ($i = 0; $i < count($parts); ++$i) {
+ if (preg_match('/^initrd=(.*)$/', $parts[$i], $out)) {
+ if (!empty($this->initrd)) {
+ $this->initrd .= ',';
+ }
+ $this->initrd .= $out[1];
+ } else {
+ $this->append .= ' ' . $parts[$i];
+ }
+ }
+ $this->append = trim($this->append);
+ }
+ if (is_string($this->initrd)) {
+ $this->initrd = explode(',', $this->initrd);
+ } elseif (!is_array($this->initrd)) {
+ $this->initrd = [];
+ }
+ }
+
+ /**
+ * Does this appear to be an entry that triggers localboot?
+ */
+ public function isLocalboot(): bool
+ {
+ return $this->localBoot !== false || preg_match('/chain\.c32$/i', $this->kernel);
+ }
+
+ /**
+ * Is this (most likely) a separating entry only that cannot be selected?
+ */
+ public function isTextOnly(): bool
+ {
+ return ($this->label === null || empty($this->kernel)) && !$this->isHidden && !empty($this->title);
+ }
+
+} \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderbase.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderbase.inc.php
index 84cfd7db..9cd07388 100644
--- a/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderbase.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderbase.inc.php
@@ -9,8 +9,10 @@ abstract class ScriptBuilderBase
protected $platform = '';
+ /** @var string */
protected $clientIp;
+ /** @var ?string */
protected $uuid;
/**
@@ -18,57 +20,57 @@ abstract class ScriptBuilderBase
*/
protected $hasExtension = false;
- public function hasExtensions()
+ public function hasExtensions(): bool
{
return $this->hasExtension;
}
- public function platform()
+ public function platform(): string
{
return $this->platform;
}
- public function uuid()
+ public function uuid(): ?string
{
return $this->uuid;
}
- public function clientIp()
+ public function clientIp(): string
{
return $this->clientIp;
}
- public function getLabel()
+ public function getLabel(): string
{
return 'b' . mt_rand(100, 999) . 'x' . (++$this->lblId);
}
- public function __construct($platform = null, $serverIp = null, $slxExtensions = null)
+ public function __construct(?string $platform = null, ?string $serverIp = null, ?bool $slxExtensions = null)
{
- $this->clientIp = $_SERVER['REMOTE_ADDR'];
+ $this->clientIp = (string)$_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 = $platform ?? Request::any('platform', null, 'string');
+ if ($this->platform !== null) {
$this->platform = strtoupper($this->platform);
}
if ($this->platform !== 'EFI' && $this->platform !== 'PCBIOS') {
$this->platform = '';
}
$this->hasExtension = $slxExtensions ?? (bool)Request::any('slx-extensions', false, 'int');
- $this->uuid = Request::any('uuid', false, 'string');
- if (!preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i', $this->uuid)) {
- $this->uuid = false;
+ $uuid = Request::any('uuid', null, 'string');
+ if ($uuid !== null
+ && preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i', $uuid)) {
+ $this->uuid = (string)$uuid;
}
}
/**
* Output given string (script) to client, in a suitable encoding, headers, etc.
- * @param string $string
*/
- public abstract function output($string);
+ public abstract function output(string $string): void;
public abstract function bootstrapLive();
@@ -79,31 +81,22 @@ abstract class ScriptBuilderBase
* @param bool $honorPassword Whether we should generate a password dialog if protected, or skip
* @return string generated script/code/...
*/
- public abstract function getMenuEntry($menuEntry, $honorPassword = true);
+ public abstract function getMenuEntry(?MenuEntry $entry, bool $honorPassword = true): string;
/**
* @param BootEntry|null|false $bootEntry
- * @return string
*/
- public abstract function getBootEntry($bootEntry);
+ public abstract function getBootEntry(?BootEntry $entry): string;
- public abstract function getSpecial($special);
+ public abstract function getSpecial(string $special);
- /**
- * @param IPxeMenu|null $menu
- * @return string
- */
- public abstract function menuToScript($menu);
+ public abstract function menuToScript(IPxeMenu $menu): string;
/**
* 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);
+ public abstract function execDataToScript(?ExecData $agnostic, ?ExecData $bios, ?ExecData $efi): string;
} \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderbash.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderbash.inc.php
index 86b2931f..d6b542ec 100644
--- a/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderbash.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderbash.inc.php
@@ -3,39 +3,39 @@
class ScriptBuilderBash extends ScriptBuilderBase
{
- public function output($string)
+ public function output(string $string): void
{
echo $string;
}
- public function bootstrapLive() { return false; }
+ public function bootstrapLive(): bool { return false; }
- public function getMenu(IPxeMenu $menu, bool $bootstrap)
+ public function getMenu(IPxeMenu $menu, bool $bootstrap): string
{
return $this->menuToScript($menu);
}
- public function getBootEntry($entry)
+ public function getBootEntry(?BootEntry $entry): string
{
- if (!$entry) {
+ if ($entry === null) {
return "echo 'Invalid boot entry id'\nread -n1 -r _\n";
}
return $entry->toScript($this);
}
- public function getMenuEntry($entry, $honorPassword = true)
+ public function getMenuEntry(?MenuEntry $entry, bool $honorPassword = true): string
{
if ($entry === null)
return "echo 'Invalid menu entry id - press any key to continue'\nread -n1 -r _\n";
return $entry->getBootEntryScript($this);
}
- public function getSpecial($special)
+ public function getSpecial(string $special): string
{
return ''; // We can't really do localboot here I guess
}
- public function menuToScript($menu)
+ public function menuToScript(IPxeMenu $menu): string
{
$output = "declare -A items_name items_gap hotkey_item\ndeclare menu_default menu_timeout menu_title\n";
foreach ($menu->items as $entry) {
@@ -59,7 +59,7 @@ class ScriptBuilderBash extends ScriptBuilderBase
. "\nmenu_title=" . $this->bashString($menu->title) . "\n";
}
- public function execDataToScript($agnostic, $bios, $efi) : string
+ public function execDataToScript(?ExecData $agnostic, ?ExecData $bios, ?ExecData $efi): string
{
if ($agnostic !== null)
return $this->execDataToScriptInternal($agnostic);
@@ -86,7 +86,7 @@ class ScriptBuilderBash extends ScriptBuilderBase
return $script;
}
- private function bashString($string)
+ private function bashString(string $string): string
{
if (strpos($string, "'") === false) {
return "'$string'";
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/scriptbuildergrub.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/scriptbuildergrub.inc.php
new file mode 100644
index 00000000..9dce5214
--- /dev/null
+++ b/modules-available/serversetup-bwlp-ipxe/inc/scriptbuildergrub.inc.php
@@ -0,0 +1,330 @@
+<?php
+
+class ScriptBuilderGrub extends ScriptBuilderBase
+{
+
+ /** @var bool */
+ private $confCodeEmited = false;
+
+ public function __construct(?string $platform = null, ?string $serverIp = null)
+ {
+ if (empty($platform)) {
+ $platform = Request::any('platform', null, 'string');
+ if ($platform === 'pc' || stripos($platform, 'bios') !== false) {
+ $platform = 'PCBIOS';
+ }
+ }
+ parent::__construct($platform, $serverIp, false);
+ }
+
+ private function getConfCode(): string
+ {
+ if ($this->confCodeEmited)
+ return '';
+ $this->confCodeEmited = true;
+ $str = '
+if ! [ "$uuid" ] ; then
+ smbios --type 1 --get-uuid 8 --set uuid
+fi
+set serverip="' . $this->getLocalIp() . '"
+';
+ foreach (['mac', 'ip', 'domain', 'hostname'] as $var) {
+ $str .= <<<EOF
+if ! [ "\$$var" ] ; then
+ set $var="\$net_default_$var"
+fi
+if ! [ "\$$var" ] ; then
+ set $var="\$net_efinet0_dhcp_$var"
+fi
+
+EOF;
+
+ }
+ return $str;
+ }
+
+ private function getLocalIp(): string
+ {
+ if (isset($_SERVER['SCRIPT_URI']) && preg_match('#^\w+://([^/]+)#', $_SERVER['SCRIPT_URI'], $out)) {
+ $host = $out[1];
+ } elseif (isset($_SERVER['SERVER_NAME'])) {
+ $host = $_SERVER['SERVER_NAME'];
+ } elseif (isset($_SERVER['SERVER_ADDR'])) {
+ $host = $_SERVER['SERVER_ADDR'];
+ } else {
+ $host = $this->serverIp;
+ }
+ return $host;
+ }
+
+ private function getGrubBase(): string
+ {
+ return '(http,' . $this->getLocalIp() . ')';
+ }
+
+ private function getUrlBase(): string
+ {
+ $host = $this->getGrubBase();
+ if (isset($_SERVER['REQUEST_URI'])) {
+ $url = parse_url($_SERVER['REQUEST_URI']);
+ $path = $url['path'];
+ } else {
+ // Static fallback
+ $path = '/boot/ipxe';
+ }
+ return $host . $path;
+
+ }
+
+ private function getUrlFull(?string $key = null, ?string $value = null): string
+ {
+ $url = parse_url($_SERVER['REQUEST_URI']);
+ $urlbase = $this->getUrlBase();
+ if (empty($url['query'])) {
+ $fromQuery = [];
+ } else {
+ parse_str($url['query'], $fromQuery);
+ foreach ($fromQuery as &$v) {
+ $v = urlencode($v);
+ }
+ unset($v);
+ }
+ unset($fromQuery['entryid'], $fromQuery['special'], $fromQuery['redir']);
+ if ($key !== null) {
+ $fromQuery[$key] = $value;
+ }
+ $required = [
+ 'type' => 'grub',
+ 'uuid' => '$uuid',
+ 'mac' => '$mac',
+ 'platform' => '$grub_platform',
+ ];
+ $fullQuery = '?';
+ foreach ($required + $fromQuery as $k => $v) { // Loop instead of http_build_query since we don't want escaping for the varnames!
+ $fullQuery .= $k . '=' . $v . '&';
+ }
+ return $urlbase . $fullQuery;
+ }
+
+ /**
+ * Redirect to same URL, but add our extended params
+ */
+ private function redirect(string $key = null, string $value = null): string
+ {
+ // Redirect to self with added parameters
+ $urlfull = $this->getUrlFull($key, $value);
+ return $this->getConfCode() . <<<HERE
+
+set self="${urlfull}"
+echo "Chaining to \$self..."
+configfile \${self}redir=1
+
+HERE;
+ }
+
+ /**
+ * Called when we handle a real client request, and don't just generate static data
+ * for whatever use-case that might have. In the latter case, it wouldn't make much sense
+ * to generate a redirect code snippet.
+ *
+ * @return string
+ */
+ public function bootstrapLive()
+ {
+ // Check if required arguments are given; if not, spit out according script and chain to self
+ if ($this->uuid === null || $this->platform === '') {
+ // REQUIRED so we can hide incompatible entries
+ // but avoid redirect cycle
+ if (Request::any('redir', '', 'string') === '') {
+ return $this->redirect();
+ }
+ }
+ return false;
+ }
+
+ public function getBootEntry(?BootEntry $entry): string
+ {
+ if ($entry === null) {
+ return "echo Invalid boot entry id\nsleep --interruptible --verbose 10\n";
+ }
+ return $entry->toScript($this);
+ }
+
+ public function getMenu(IPxeMenu $menu, bool $bootstrap): string
+ {
+ $base = $this->getUrlFull();
+ return $this->getConfCode()
+ . "set self=\"{$base}\"\n"
+ . $this->menuToScript($menu);
+ }
+
+ public function menuToScript(IPxeMenu $menu): string
+ {
+ if ($menu->defaultEntryId === null) {
+ $output = <<<EOF
+set timeout=0
+
+EOF;
+
+ } else {
+ $secs = (int)($menu->timeoutMs / 1000);
+ $output = <<<EOF
+set timeout={$secs}
+set default="id-{$menu->defaultEntryId}"
+
+EOF;
+ }
+ $output .= $this->getConfCode();
+ foreach ($menu->items as $item) {
+ $output .= $this->getMenuItemScript($item);
+ }
+ return $output;
+ }
+
+ private function getMenuItemScript(MenuEntry $entry): string
+ {
+ $str = "menuentry '" . str_replace("'", '', $entry->title) . "' --id 'id-" . $entry->menuentryid . "' {\n";
+ if ($entry->gap) {
+ $str .= "true\n"; // AFAICT, not possible in GRUB
+ } elseif ($entry->bootEntry === null || (!empty($this->platform) && !$entry->bootEntry->supportsMode($this->platform))) {
+ $str .= "echo Type mismatch\n";
+ } elseif ($entry->hidden && $entry->hotkey === false) {
+ $str .= "echo Hidden entries without hotkey are illegal\n"; // Hidden entries without hotkey are illegal
+ } else {
+ if ($entry->hotkey !== false) {
+ // Not supported by grub...
+ }
+ if ($entry->bootEntry instanceof MenuBootEntry) {
+ // Link
+ $str .= "configfile \${self}entryid={$entry->menuentryid}\n";
+ } else {
+ // Embed directly
+ // TODO: Password. Use read etc.; might need hashsum.mod, in that case, don't embed entry directly but use configfile...
+ $str .= $this->getMenuEntry($entry, true);
+ }
+ }
+ return $str . "}\n";
+ }
+
+ public function getSpecial(string $special): string
+ {
+ if ($special === 'localboot') {
+ // Sync this with setup-scripts/grub_localboot occasionally...
+ $output = <<<'EOF'
+insmod chain
+if [ "$grub_platform" = "pc" ] ; then
+ chainloader (hd0)+1
+ chainloader (hd1)+1
+ chainloader (hd2)+1
+fi
+insmod fat
+insmod part_gpt
+echo "Scanning, first pass..."
+for efi in (*,gpt*)/efi/grub/grubx64.efi (*,gpt*)/efi/boot/bootx64.efi (*,gpt*)/efi/*/*/bootmgfw.efi (*,gpt*)/efi/*/*.efi \
+ (*,msdos*)/efi/grub/grubx64.efi (*,msdos*)/efi/boot/bootx64.efi (*,msdos*)/efi/*/*/bootmgfw.efi (*,msdos*)/efi/*/*.efi; do
+ regexp --set=1:efi_device '^\((.*)\)/' "${efi}"
+done
+
+echo "Scanning, second pass..."
+for efi in (*,gpt*)/efi/grub/grubx64.efi (*,gpt*)/efi/boot/bootx64.efi (*,gpt*)/efi/*/*/bootmgfw.efi (*,gpt*)/efi/*/*.efi \
+ (*,msdos*)/efi/grub/grubx64.efi (*,msdos*)/efi/boot/bootx64.efi (*,msdos*)/efi/*/*/bootmgfw.efi (*,msdos*)/efi/*/*.efi; do
+ if [ -e "${efi}" ]; then
+ #regexp --set=1:efi_device '^\((.*)\)/' "${efi}"
+ regexp --set=1:root '^(\(.*\))/' "${efi}"
+ regexp --set=1:efi_path '^\(.*\)(/.*)$' "${efi}"
+ echo " >> Found operating system! <<"
+ echo " Path: '${efi}' on '${root}'"
+ echo " Fallback '${efi_path}'"
+ chainloader "${efi}"
+ boot
+ echo " That failed..."
+ fi
+done
+
+echo "No EFI known OS found. Exiting."
+exit
+EOF;
+
+ } else {
+ $output = <<<EOF
+echo "Unknown special command: $special"
+sleep --interruptible --verbose 10
+EOF;
+ }
+ return $output;
+ }
+
+ public function output(string $string): void
+ {
+ Header('Content-Type: text/plain; charset=UTF-8');
+ echo $string;
+ }
+
+ public function getMenuEntry(?MenuEntry $entry, bool $honorPassword = true): string
+ {
+ if ($entry === null)
+ return "echo Invalid menu entry id\nsleep --interruptible --verbose 10\n";
+ // TODO: Check for password
+ if ($honorPassword && !empty($entry->md5pass)) {
+ return "echo TODO: Implement password check...\nsleep --interruptible --verbose 10\n";
+ }
+ $meid = $entry->menuEntryId();
+ $output = $this->getConfCode() . "set menuentryid=$meid\n";
+ // Output actual entry
+ $output .= str_replace('%fail%', 'fail', $entry->getBootEntryScript($this));
+ return $output;
+ }
+
+ public function execDataToScript(?ExecData $agnostic, ?ExecData $bios, ?ExecData $efi): string
+ {
+ if ($agnostic !== null)
+ return $this->execDataToScriptInternal($agnostic);
+
+ if ($efi !== null && $this->platform === BootEntry::EFI)
+ return $this->execDataToScriptInternal($efi);
+ // Unknown or not EFI, should be BIOS at this point
+ return $this->execDataToScriptInternal($bios ?? $efi ?? new ExecData());
+ }
+
+ private function execDataToScriptInternal(ExecData $entry): string
+ {
+ $entry->sanitize();
+ $base = $this->getGrubBase();
+ $script = '';
+ // Overriding dhcpOpts probably not possible/necessary
+ $initrds = [];
+ if (!empty($entry->initRd)) {
+ foreach ($entry->initRd as $initrd) {
+ if (empty($initrd))
+ continue;
+ $initrds[] = $this->combineUrl($base, $initrd);
+ }
+ }
+ $file = $this->combineUrl($base, $entry->executable);
+ $script .= "linux $file {$entry->commandLine} slx.ipxe.id=\${menuentryid}\n";
+ if (!empty($initrds)) {
+ $script .= "initrd " . implode(' ', $initrds) . "\n";
+ }
+ return $script;
+ }
+
+ private function combineUrl(string $base, string $path): string
+ {
+ $url = parse_url($path);
+ if (isset($url['host'])) {
+ $scheme = $url['scheme'] ?? 'http';
+ $host = $url['host'];
+ $base = "($scheme,$host)";
+ $path = $url['path'] ?? '/';
+ if (isset($url['query'])) {
+ $path .= '?' . $url['query'];
+ }
+ } else {
+ if ($path[0] !== '/') {
+ $path = '/' . $path;
+ }
+ }
+ return $base . $path;
+ }
+
+}
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderipxe.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderipxe.inc.php
index 385cd15f..9421684f 100644
--- a/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderipxe.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/inc/scriptbuilderipxe.inc.php
@@ -3,7 +3,7 @@
class ScriptBuilderIpxe extends ScriptBuilderBase
{
- private function getUrlBase()
+ private function getUrlBase(): string
{
if (isset($_SERVER['REQUEST_URI'])) {
$url = parse_url($_SERVER['REQUEST_URI']);
@@ -23,7 +23,7 @@ class ScriptBuilderIpxe extends ScriptBuilderBase
}
- private function getUrlFull(&$hasExt, $key = null, $value = null)
+ private function getUrlFull(?bool &$hasExt = null, ?string $key = null, ?string $value = null): string
{
$url = parse_url($_SERVER['REQUEST_URI']);
$urlbase = $this->getUrlBase();
@@ -58,7 +58,7 @@ class ScriptBuilderIpxe extends ScriptBuilderBase
/**
* Redirect to same URL, but add our extended params
*/
- private function redirect($key = null, $value = null)
+ private function redirect(string $key = null, string $value = null): string
{
// Redirect to self with added parameters
$urlfull = $this->getUrlFull($hasExt, $key, $value);
@@ -102,27 +102,24 @@ HERE;
return false;
}
- public function getBootEntry($entry)
+ public function getBootEntry(?BootEntry $entry): string
{
- if (!$entry) {
+ if ($entry === null) {
return "#!ipxe\nprompt --timeout 5000 Invalid boot entry id\n";
}
return $entry->toScript($this);
}
- public function getMenu(IPxeMenu $menu, bool $bootstrap)
+ public function getMenu(IPxeMenu $menu, bool $bootstrap): string
{
if ($bootstrap) {
return "#!ipxe\nimgfree ||\n" . $this->menuToScript($menu);
}
- $base = $this->getUrlFull($he);
+ $base = $this->getUrlFull();
return "#!ipxe\nset self {$base} ||\n" . $this->menuToScript($menu);
}
- /**
- * @param IPxeMenu $menu
- */
- public function menuToScript($menu)
+ public function menuToScript(IPxeMenu $menu): string
{
if ($this->hasExtension) {
$slxConsoleUpdate = '--update';
@@ -136,13 +133,13 @@ HERE;
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
+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
@@ -166,7 +163,11 @@ HERE;
$output .= " selection || goto default || goto fail\n";
$output .= <<<HERE
console --left 60 --top 130 --right 67 --bottom 86 $slxConsoleUpdate ||
+set slx_exit \${} ||
chain -a \${self}&entryid=\${selection} ||
+iseq \${slx_exit} \${} || console ||
+iseq \${slx_exit} \${} || echo Exiting with code \${slx_exit} ||
+iseq \${slx_exit} \${} || exit \${slx_exit}
goto fail || goto start
goto \${target} ||
echo Could not find menu entry in script.
@@ -179,49 +180,10 @@ 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)
+ private function getMenuItemScript(int $requestedDefaultId, MenuEntry $entry): string
{
$str = 'item ';
if ($entry->gap) {
@@ -250,7 +212,7 @@ HERE;
return $str . " || prompt Could not create menu item for {$entry->menuentryid}\n";
}
- public function getSpecial($special)
+ public function getSpecial(string $special): string
{
if ($special === 'localboot') {
// Get preferred localboot method, depending on system model
@@ -299,11 +261,7 @@ HERE;
}
}
// Convert to actual ipxe code
- if (isset($BOOT_METHODS[$localboot])) {
- $localboot = $BOOT_METHODS[$localboot];
- } else {
- $localboot = 'prompt Localboot not possible';
- }
+ $localboot = $BOOT_METHODS[$localboot] ?? 'prompt Localboot not possible';
$output = <<<BLA
imgfree ||
console ||
@@ -317,17 +275,27 @@ BLA;
return $output;
}
- public function output($string)
+ public function output(string $string): void
{
- 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';
+ // iPXE introduced UTF-8 support at some point in 2022, and now expects all text/script files to be
+ // encoded as such. Since we still offer to use older versions, we need to detect that here and handle
+ // all non-ASCII chars differently.
+ // Use 'ipxe.compile-time' instead of const from IpxeBuilder to avoid pulling in another include
+ if (!preg_match('/Version: (\d{4})-\d{2}-\d{2}\b/', Property::get('ipxe.compile-time'), $out)
+ || (int)$out[1] >= 2022) {
+ Header('Content-Type: text/plain; charset=UTF-8');
+ echo $string;
} else {
- $cs = 'IBM437';
- }
- Header('Content-Type: text/plain; charset=' . $cs);
+ if ($this->platform === 'EFI') {
+ $cs = 'ASCII';
+ } else {
+ $cs = 'IBM437';
+ }
+ Header('Content-Type: text/plain; charset=' . $cs);
- echo iconv('UTF-8', $cs . '//TRANSLIT//IGNORE', $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');
+ echo iconv('UTF-8', $cs . '//TRANSLIT//IGNORE', $string);
+ }
}
public function modfilt($str)
@@ -337,28 +305,9 @@ BLA;
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)
+ private function passwordDialog(MenuEntry $entry): string
{
if ($this->hasExtension) {
$salt = dechex(mt_rand(0x100000, 0xFFFFFF));
@@ -384,7 +333,7 @@ chain -a \${self}&entryid={$entry->menuentryid}##params || goto fail ||
HERE;
}
- public function getMenuEntry($entry, $honorPassword = true)
+ public function getMenuEntry(?MenuEntry $entry, bool $honorPassword = true): string
{
if ($entry === null)
return "#!ipxe\nprompt --timeout 10000 Invalid menu entry id\n";
@@ -438,7 +387,7 @@ HERE;
return $output;
}
- public function execDataToScript($agnostic, $bios, $efi) : string
+ public function execDataToScript(?ExecData $agnostic, ?ExecData $bios, ?ExecData $efi) : string
{
if ($agnostic !== null)
return $this->execDataToScriptInternal($agnostic) . "\ngoto fail\n";
diff --git a/modules-available/serversetup-bwlp-ipxe/install.inc.php b/modules-available/serversetup-bwlp-ipxe/install.inc.php
index 983988bb..5af00493 100644
--- a/modules-available/serversetup-bwlp-ipxe/install.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/install.inc.php
@@ -102,7 +102,7 @@ if (!tableHasColumn('serversetup_localboot', 'pcbios')) {
$result[] = tableAddConstraint('serversetup_menuentry', 'refmenuid', 'serversetup_menu', 'menuid',
'ON UPDATE CASCADE ON DELETE SET NULL');
-if (Module::get('location') !== false) {
+if (Module::get('locations') !== false) {
if (!tableExists('location')) {
$result[] = UPDATE_RETRY;
} else {
@@ -117,7 +117,7 @@ if (!tableHasColumn('serversetup_bootentry', 'module')) {
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)) {
+ foreach ($res as $row) {
$json = json_decode($row['data'], true);
if (isset($json['script'])) {
Database::exec("UPDATE serversetup_bootentry SET module = '.script' WHERE entryid = :id", ['id' => $row['entryid']]);
diff --git a/modules-available/serversetup-bwlp-ipxe/lang/de/messages.json b/modules-available/serversetup-bwlp-ipxe/lang/de/messages.json
index 339296e7..5446130f 100644
--- a/modules-available/serversetup-bwlp-ipxe/lang/de/messages.json
+++ b/modules-available/serversetup-bwlp-ipxe/lang/de/messages.json
@@ -2,6 +2,7 @@
"boot-entry-created": "Men\u00fceintrag {{0}} erzeugt",
"boot-entry-updated": "Men\u00fceintrag {{0}} aktualisiert",
"bootentry-deleted": "Men\u00fceintrag gel\u00f6scht",
+ "cannot-edit-special": "Der Eintrag {{0}} kann nicht editiert werden",
"error-saving-entry": "Fehler beim Speichern des Eintrags {{0}}: {{1}}",
"image-not-found": "USB-Image nicht gefunden. Generieren Sie das Bootmen\u00fc neu.",
"import-error": "Fehler beim Importieren",
@@ -20,6 +21,7 @@
"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.",
+ "nothing-changed-or-protected": "{{0}}: Nichts ver\u00e4ndert, oder gesch\u00fctzter Eintrag",
"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/module.json b/modules-available/serversetup-bwlp-ipxe/lang/de/module.json
index f95573a2..559b84a5 100644
--- a/modules-available/serversetup-bwlp-ipxe/lang/de/module.json
+++ b/modules-available/serversetup-bwlp-ipxe/lang/de/module.json
@@ -9,9 +9,10 @@
"dl-usb": "USB-Image",
"dl-usbnic": "Mit USB Netzwerktreibern",
"dl-x86_64": "64\u2009Bit",
+ "location-column-header": "Bootmen\u00fc",
"module_name": "iPXE \/ Boot Menu",
"page_title": "PXE- und Boot-Einstellungen",
- "submenu_address": "Server-Adresse festlegen",
+ "submenu_address": "Boot-IP \/ iPXE-Version setzen",
"submenu_bootentry": "Men\u00fceintr\u00e4ge verwalten",
"submenu_download": "Downloads",
"submenu_import": "Importieren",
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 4335adc5..eba15cbe 100644
--- a/modules-available/serversetup-bwlp-ipxe/lang/de/template-tags.json
+++ b/modules-available/serversetup-bwlp-ipxe/lang/de/template-tags.json
@@ -36,8 +36,10 @@
"lang_execImageFree": "Andere geladene Images vor dem Ausf\u00fchren entladen (imgfree)",
"lang_execReplace": "Aktuellen iPXE-Stack ersetzen (--replace)",
"lang_execResetConsole": "Konsole vor Ausf\u00fchrung zur\u00fccksetzen",
+ "lang_fetchUpdate": "Updates laden",
"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_gitCheckout": "git-Ausgabe",
"lang_hex": "Hex",
"lang_hookExtraOptionHeading": "Weitere Angaben",
"lang_hookOfModule": "Eintrag von",
@@ -49,6 +51,7 @@
"lang_ipxeSettings": "iPXE-spezifische Einstellungen",
"lang_ipxeWikiUrl": "im iPXE Wiki",
"lang_isDefault": "Standard",
+ "lang_lastBuild": "Letzter erfolgreicher Bauvorgang",
"lang_listOfMenus": "Men\u00fcliste",
"lang_localBootDefault": "Standardm\u00e4\u00dfig verwendete Methode, um von Festplatte zu booten",
"lang_localBootExceptions": "Ausnahmen, pro Rechnermodell definierbar",
@@ -71,9 +74,12 @@
"lang_pxelinuxEntriesOnly": "Nur Eintr\u00e4ge importieren, kein Men\u00fc erzeugen",
"lang_pxelinuxImport": "PXE-Men\u00fc importieren",
"lang_pxelinuxImportIntro": "Hier k\u00f6nnen Sie ein PXE-Men\u00fc einf\u00fcgen und in entsprechende Men\u00fceintr\u00e4ge f\u00fcr iPXE umwandeln lassen.",
- "lang_recompileHint": "iPXE-Binaries jetzt neu kompilieren. Normalerweise wird dieser Vorgang bei \u00c4nderungen automatisch ausgef\u00fchrt. Sollten Bootprobleme auftreten, k\u00f6nnen Sie hier den Vorgang manuell ansto\u00dfen.",
+ "lang_reallyGitReset": "Wollen Sie wirklich alle \u00c4nderungen am iPXE-Sourcecode zur\u00fccksetzen?",
+ "lang_recompileHead": "iPXE-Binaries jetzt neu kompilieren",
+ "lang_recompileHint": "Normalerweise wird dieser Vorgang bei \u00c4nderungen automatisch ausgef\u00fchrt. Sollten Bootprobleme auftreten, k\u00f6nnen Sie hier den Vorgang manuell ansto\u00dfen. Au\u00dferdem k\u00f6nnen Sie hier explizit einen Versionsstand ausw\u00e4hlen. Durch \u00e4nderungen und Bugfixes am iPXE-Code kann es zu Regressionen kommen, d.h., dass bestimmte Hardware m\u00f6glicherweise mit neueren Versionen nicht mehr Bootet. In diesem Fall ist ein Wechsel auf eine \u00e4ltere Version eine vor\u00fcbergehende L\u00f6sung. Ansonsten ist es zu empfehlen, immer bei der aktuellen Version zu bleiben.",
"lang_refCount": "Referenzen",
"lang_referencingMenus": "Verkn\u00fcpfte Men\u00fcs",
+ "lang_resetWorkingTree": "git-Repo zur\u00fccksetzen",
"lang_saveAndReload": "Speichern und neu laden",
"lang_scriptContent": "Skript",
"lang_seconds": "Sekunden",
@@ -87,5 +93,6 @@
"lang_usbImgHelpLinux": "Nutzen Sie dd, um das Image auf einen USB-Stick zu schreiben. Das Image enth\u00e4lt bereits eine Partitionstabelle, achten Sie daher darauf, dass Sie das Image z.B. nach \/dev\/sdx schreiben, und nicht nach \/dev\/sdx1",
"lang_usbImgHelpWindows": "Unter Windows muss zun\u00e4chst ein Programm besorgt werden, mit dem sich Images direkt auf einen USB-Stick schreiben lassen. Es gibt gleich mehrere kostenlose und quelloffene Programme, eines davon ist Rufus. Rufus wurde mit dem bwLehrpool-Image gestetet. Nach dem Starten des Programms ist lediglich das heruntergeladene Image zu \u00f6ffnen, sowie in der Liste der Laufwerke der richtige USB-Stick auszuw\u00e4hlen (damit Sie nicht versehentlich Daten auf dem falschen Laufwerk \u00fcberschreiben!)",
"lang_useDefaultMenu": "\u00dcbergeordnetes Men\u00fc verwenden",
- "lang_useDefaultMenuEntry": "(Vorgabe des Men\u00fcs)"
+ "lang_useDefaultMenuEntry": "(Vorgabe des Men\u00fcs)",
+ "lang_versionSelect": "iPXE-Version ausw\u00e4hlen"
} \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp-ipxe/lang/en/messages.json b/modules-available/serversetup-bwlp-ipxe/lang/en/messages.json
index 9e1c0b3e..21fdfdc3 100644
--- a/modules-available/serversetup-bwlp-ipxe/lang/en/messages.json
+++ b/modules-available/serversetup-bwlp-ipxe/lang/en/messages.json
@@ -2,6 +2,7 @@
"boot-entry-created": "Created menu item {{0}}",
"boot-entry-updated": "Updated menu item {{0}}",
"bootentry-deleted": "Deleted menu item",
+ "cannot-edit-special": "Entry {{0}} cannot be edited",
"error-saving-entry": "Error saving item {{0}}: {{1}}",
"image-not-found": "USB image not found. Try regenerating the boot menu first.",
"import-error": "Error importing menu",
@@ -20,6 +21,7 @@
"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.",
+ "nothing-changed-or-protected": "{{0}}: Nothing changed, oder protected entry",
"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/module.json b/modules-available/serversetup-bwlp-ipxe/lang/en/module.json
index 30865907..ce660fc1 100644
--- a/modules-available/serversetup-bwlp-ipxe/lang/en/module.json
+++ b/modules-available/serversetup-bwlp-ipxe/lang/en/module.json
@@ -9,9 +9,10 @@
"dl-usb": "thumb drive image",
"dl-usbnic": "with USB NIC drivers",
"dl-x86_64": "64 bit",
+ "location-column-header": "Boot menu",
"module_name": "iPXE \/ Boot Menu",
"page_title": "iPXE and boot settings",
- "submenu_address": "Server address",
+ "submenu_address": "Set boot IP \/ iPXE version",
"submenu_bootentry": "Manage menu items",
"submenu_download": "Downloads",
"submenu_import": "Import",
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 91d48e5d..e4727ab7 100644
--- a/modules-available/serversetup-bwlp-ipxe/lang/en/template-tags.json
+++ b/modules-available/serversetup-bwlp-ipxe/lang/en/template-tags.json
@@ -3,7 +3,7 @@
"lang_add": "Add",
"lang_addBootentry": "Add Bootentry",
"lang_addMenu": "Add Menu",
- "lang_additionalInfoLink": "Read more",
+ "lang_additionalInfoLink": "More information",
"lang_archAgnostic": "Architecture-agnostic",
"lang_archBoth": "BIOS and EFI",
"lang_archSelector": "Select architecture",
@@ -36,8 +36,10 @@
"lang_execImageFree": "Unload any other images before execution (imgfree)",
"lang_execReplace": "Replace current iPXE stack (--replace)",
"lang_execResetConsole": "Reset console before execution",
+ "lang_fetchUpdate": "Fetch updates",
"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_gitCheckout": "git output",
"lang_hex": "Hex",
"lang_hookExtraOptionHeading": "Extra Options",
"lang_hookOfModule": "Entry of",
@@ -47,8 +49,9 @@
"lang_import": "Import",
"lang_initRd": "Optional initrd\/initramfs to load",
"lang_ipxeSettings": "iPXE-specific settings",
- "lang_ipxeWikiUrl": "at the iPXE wiki",
+ "lang_ipxeWikiUrl": "in the iPXE wiki",
"lang_isDefault": "Default",
+ "lang_lastBuild": "Last successful build",
"lang_listOfMenus": "Menulist",
"lang_localBootDefault": "Default method to use for booting from disk",
"lang_localBootExceptions": "Exceptions to the local boot method, defined per system model",
@@ -71,9 +74,12 @@
"lang_pxelinuxEntriesOnly": "Import menu items only, don't create menu",
"lang_pxelinuxImport": "Import PXELinux menu",
"lang_pxelinuxImportIntro": "Here you can paste a pxelinux menu to convert it to an iPXE menu.",
- "lang_recompileHint": "Recompile iPXE binaries now. Usually this happens automatically on changes, but if you suspect problems caused by outdated binaries, you can trigger recompilation here.",
+ "lang_reallyGitReset": "Do you really want to reset the iPXE source code and revert any local changes?",
+ "lang_recompileHead": "Recompile iPXE binaries now",
+ "lang_recompileHint": "Usually this happens automatically on changes, but if you suspect problems caused by outdated binaries, you can trigger recompilation here. You can also specify the iPXE version to use here. Although it is generally recommended to use the latest version, this might be useful if a regression occurs, i.e. some bugfix or other change in a newer version breaks booting specific hardware that was previously supported.",
"lang_refCount": "References",
"lang_referencingMenus": "Referencing menus",
+ "lang_resetWorkingTree": "Reset git repository",
"lang_saveAndReload": "Save and Reload",
"lang_scriptContent": "Script content",
"lang_seconds": "Seconds",
@@ -87,5 +93,6 @@
"lang_usbImgHelpLinux": "On Linux you can simply use dd to write the image to a usb stick. The image already contains a partition table, so make sure you write the image to the device itself and not to an already existing partition (e.g. to \/dev\/sdx not \/dev\/sdx1)",
"lang_usbImgHelpWindows": "On Windows you need to use a 3rd party tool that can directly write to usb sticks. There are several free and open source soltions, one of them being Rufus. Rufus has been tested with the bwLehrpool image and is very simple to use. After launching Rufus, just open the downloaded USB image, select the proper USB stick to write to (be careful not to overwrite the wrong drive!), and you're ready to go.",
"lang_useDefaultMenu": "Inherit from parent location",
- "lang_useDefaultMenuEntry": "(Menu default)"
+ "lang_useDefaultMenuEntry": "(Menu default)",
+ "lang_versionSelect": "Select iPXE version"
} \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp-ipxe/page.inc.php b/modules-available/serversetup-bwlp-ipxe/page.inc.php
index e31814d1..c9260687 100644
--- a/modules-available/serversetup-bwlp-ipxe/page.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/page.inc.php
@@ -6,14 +6,13 @@ class Page_ServerSetup extends Page
private $addrListTask;
private $compileTask = null;
private $currentAddress;
- private $currentMenu;
private $hasIpSet = false;
private function getCompileTask()
{
if ($this->compileTask !== null)
return $this->compileTask;
- $this->compileTask = Property::get('ipxe-task-id');
+ $this->compileTask = Property::get(IPxeBuilder::PROP_IPXE_COMPILE_TASKID);
if ($this->compileTask !== false) {
$this->compileTask = Taskmanager::status($this->compileTask);
if (!Taskmanager::isTask($this->compileTask) || Taskmanager::isFinished($this->compileTask)) {
@@ -37,7 +36,10 @@ class Page_ServerSetup extends Page
$this->handleGetImage();
}
- $this->currentMenu = Property::getBootMenu();
+ if (User::hasPermission('edit.address')) {
+ Taskmanager::submit('IpxeVersion',
+ ['id' => IPxeBuilder::VERSION_LIST_TASK, 'action' => 'LIST'], true);
+ }
$action = Request::post('action');
@@ -49,9 +51,20 @@ class Page_ServerSetup extends Page
if ($action === 'compile') {
User::assertPermission("edit.address");
if ($this->getCompileTask() === false) {
- Trigger::ipxe();
+ $taskId = IPxeBuilder::setIpxeVersion(Request::post('version', false, 'string'));
+ Trigger::ipxe($taskId);
}
- Util::redirect('?do=serversetup');
+ Util::redirect('?do=serversetup&show=address&sv=1');
+ }
+ if ($action === 'fetch' || $action === 'reset') {
+ User::assertPermission("edit.address");
+ if ($this->getCompileTask() === false) {
+ $task = Taskmanager::submit('IpxeVersion', ['action' => strtoupper($action)]);
+ if (Taskmanager::isTask($task)) {
+ Property::set(IPxeBuilder::PROP_VERSION_SELECT_TASKID, $task['id'], 2);
+ }
+ }
+ Util::redirect('?do=serversetup&show=address&sv=1');
}
if ($action === 'ip') {
@@ -110,32 +123,33 @@ class Page_ServerSetup extends Page
$addr = false;
if (User::hasPermission('ipxe.menu.view')) {
- Dashboard::addSubmenu('?do=serversetup&show=menu', Dictionary::translate('submenu_menu', true));
+ Dashboard::addSubmenu('?do=serversetup&show=menu', Dictionary::translate('submenu_menu'));
}
if (User::hasPermission('ipxe.bootentry.view')) {
- Dashboard::addSubmenu('?do=serversetup&show=bootentry', Dictionary::translate('submenu_bootentry', true));
+ Dashboard::addSubmenu('?do=serversetup&show=bootentry', Dictionary::translate('submenu_bootentry'));
}
if (User::hasPermission('edit.address')) {
- Dashboard::addSubmenu('?do=serversetup&show=address', Dictionary::translate('submenu_address', true));
+ Dashboard::addSubmenu('?do=serversetup&show=address', Dictionary::translate('submenu_address'));
$addr = true;
}
if (User::hasPermission('download')) {
- Dashboard::addSubmenu('?do=serversetup&show=download', Dictionary::translate('submenu_download', true));
+ Dashboard::addSubmenu('?do=serversetup&show=download', Dictionary::translate('submenu_download'));
}
if (User::hasPermission('ipxe.localboot.*')) {
- Dashboard::addSubmenu('?do=serversetup&show=localboot', Dictionary::translate('submenu_localboot', true));
+ Dashboard::addSubmenu('?do=serversetup&show=localboot', Dictionary::translate('submenu_localboot'));
}
if (User::hasPermission('ipxe.bootentry.edit')) {
- Dashboard::addSubmenu('?do=serversetup&show=import', Dictionary::translate('submenu_import', true));
+ Dashboard::addSubmenu('?do=serversetup&show=import', Dictionary::translate('submenu_import'));
}
if (Request::get('show') === false) {
$subs = Dashboard::getSubmenus();
+ $sv = Request::get('sv') ? '&sv=' . Request::get('sv') : '';
if (empty($subs)) {
User::assertPermission('download');
} elseif ($addr && !preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', Property::getServerIp())) {
- Util::redirect('?do=serversetup&show=address');
+ Util::redirect('?do=serversetup&show=address' . $sv);
} else {
- Util::redirect($subs[0]['url']);
+ Util::redirect($subs[0]['url'] . $sv);
}
}
}
@@ -147,15 +161,22 @@ class Page_ServerSetup extends Page
$show = Request::get('show');
if (in_array($show, ['menu', 'address', 'download'])) {
- $task = $this->getCompileTask();
- if ($task !== false) {
+ $selectTask = Taskmanager::status(Property::get(IPxeBuilder::PROP_VERSION_SELECT_TASKID));
+ $buildTask = $this->getCompileTask();
+ if ($buildTask !== false || Taskmanager::isRunning($selectTask) || Request::get('sv')) {
+ Render::addTemplate('git_task', [
+ 'selectTask' => Property::get(IPxeBuilder::PROP_VERSION_SELECT_TASKID),
+ 'reload' => Taskmanager::isRunning($selectTask),
+ ]);
+ }
+ if ($buildTask !== false) {
$files = [];
- if ($task['data'] && $task['data']['files']) {
- foreach ($task['data']['files'] as $k => $v) {
+ if ($buildTask['data'] && $buildTask['data']['files']) {
+ foreach ($buildTask['data']['files'] as $k => $v) {
$files[] = ['name' => $k, 'namehyphen' => str_replace(['/', '.'], '-', $k)];
}
}
- Render::addTemplate('ipxe_update', array('taskid' => $task['id'], 'files' => $files));
+ Render::addTemplate('ipxe_update', ['taskid' => $buildTask['id'], 'files' => $files]);
}
}
@@ -198,7 +219,6 @@ class Page_ServerSetup extends Page
break;
default:
Util::redirect('?do=serversetup');
- break;
}
}
@@ -210,20 +230,20 @@ class Page_ServerSetup extends Page
});
$files = [];
$strings = [
- 'efi' => [Dictionary::translate('dl-efi', true) => 50],
- 'pcbios' => [Dictionary::translate('dl-pcbios', true) => 51],
- 'usb' => [Dictionary::translate('dl-usb', true) => 80],
- 'hd' => [Dictionary::translate('dl-hd', true) => 81],
- 'lkrn' => [Dictionary::translate('dl-lkrn', true) => 82],
- 'i386' => [Dictionary::translate('dl-i386', true) => 10],
- 'x86_64' => [Dictionary::translate('dl-x86_64', true) => 11],
- 'ecm' => [Dictionary::translate('dl-usbnic', true) => 60],
- 'ncm' => [Dictionary::translate('dl-usbnic', true) => 61],
- 'ipxe' => [Dictionary::translate('dl-pcinic', true) => 62],
- 'snp' => [Dictionary::translate('dl-snp', true) => 63],
+ 'efi' => [Dictionary::translate('dl-efi') => 50],
+ 'pcbios' => [Dictionary::translate('dl-pcbios') => 51],
+ 'usb' => [Dictionary::translate('dl-usb') => 80],
+ 'hd' => [Dictionary::translate('dl-hd') => 81],
+ 'lkrn' => [Dictionary::translate('dl-lkrn') => 82],
+ 'i386' => [Dictionary::translate('dl-i386') => 10],
+ 'x86_64' => [Dictionary::translate('dl-x86_64') => 11],
+ 'ecm' => [Dictionary::translate('dl-usbnic') => 60],
+ 'ncm' => [Dictionary::translate('dl-usbnic') => 61],
+ 'ipxe' => [Dictionary::translate('dl-pcinic') => 62],
+ 'snp' => [Dictionary::translate('dl-snp') => 63],
];
foreach ($list as $file) {
- if ($file{0} === '.')
+ if ($file[0] === '.')
continue;
if (is_file($file)) {
$base = basename($file);
@@ -246,7 +266,10 @@ class Page_ServerSetup extends Page
Render::addTemplate('download', ['files' => $files]);
}
- private function makeSelectArray($list, $defaults)
+ /**
+ * @return array{EFI: array, PCBIOS: array}
+ */
+ private function makeSelectArray(array $list, array $defaults): array
{
$ret = ['EFI' => [], 'PCBIOS' => []];
foreach (['PCBIOS', 'EFI'] as $m) {
@@ -273,7 +296,7 @@ class Page_ServerSetup extends Page
) m
LEFT JOIN serversetup_localboot sl USING (systemmodel)
ORDER BY systemmodel');
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
$row['modelesc'] = urlencode($row['systemmodel']);
$row['options'] = $this->makeSelectArray(Localboot::BOOT_METHODS, $row);
$models[] = $row;
@@ -305,7 +328,7 @@ class Page_ServerSetup extends Page
GROUP BY be.entryid, be.title");
if (empty($bootentryTable)) {
- if (Property::getServerIp() === false || Property::getServerIp() === 'invalid') {
+ if (Property::getServerIp() === 'invalid') {
Message::addError('no-ip-set');
Util::redirect('?do=serversetup&show=address');
}
@@ -340,7 +363,7 @@ class Page_ServerSetup extends Page
GROUP BY menuid, title
ORDER BY title");
$menuTable = [];
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
if (empty($row['locations'])) {
$locations = [];
$row['allowEdit'] = in_array(0, $allowedEdit);
@@ -358,7 +381,7 @@ class Page_ServerSetup extends Page
));
}
- private function hasMenuPermission($menuid, $permission)
+ private function hasMenuPermission(int $menuid, string $permission): bool
{
$allowedEditLocations = User::getAllowedLocations($permission);
$allowEdit = in_array(0, $allowedEditLocations);
@@ -412,7 +435,7 @@ class Page_ServerSetup extends Page
LEFT JOIN serversetup_bootentry be USING (entryid)
WHERE menuid = :id
ORDER BY sortval ASC", compact('id'));
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
if ($row['entryid'] === null && $row['refmenuid'] !== null) {
$row['entryid'] = 'menu:' . $row['refmenuid'];
}
@@ -430,7 +453,7 @@ class Page_ServerSetup extends Page
foreach ($menu['entrylist'] as &$bootentry) {
if (!isset($bootentry['data']) || !isset($bootentry['module']))
continue;
- if ($bootentry['module']{0} !== '.') {
+ if ($bootentry['module'][0] !== '.') {
// Hook from other module
$bootentry['moduleName'] = Dictionary::translateFileModule($bootentry['module'], 'module', 'module_name');
if (!$bootentry['moduleName']) {
@@ -445,7 +468,7 @@ class Page_ServerSetup extends Page
if ($k === 'id')
continue;
$bootentry['otherFields'][] = [
- 'key' => Dictionary::translateFileModule($bootentry['module'], 'module', 'ipxe-' . $k, true),
+ 'key' => Dictionary::translateFileModule($bootentry['module'], 'module', 'ipxe-' . $k),
'value' => is_bool($v) ? Util::boolToString($v) : $v,
];
}
@@ -462,16 +485,16 @@ class Page_ServerSetup extends Page
continue;
// Naming and agnostic
if ($bootentry['data']['arch'] === BootEntry::BIOS) {
- $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_biosOnly', true);
+ $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_biosOnly');
unset($bootentry['data']['EFI']);
} elseif ($bootentry['data']['arch'] === BootEntry::EFI) {
- $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_efiOnly', true);
+ $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_efiOnly');
unset($bootentry['data']['PCBIOS']);
} elseif ($bootentry['data']['arch'] === BootEntry::AGNOSTIC) {
- $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_archAgnostic', true);
+ $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_archAgnostic');
unset($bootentry['data']['EFI']);
} else {
- $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_archBoth', true);
+ $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_archBoth');
}
foreach ($bootentry['data'] as &$e) {
if (isset($e['initRd'])) {
@@ -510,14 +533,13 @@ class Page_ServerSetup extends Page
Message::addError('invalid-boot-entry', $id);
Util::redirect('?do=serversetup');
}
- if ($row['module']{0} === '.') {
+ if ($row['module'] === '.special') {
+ Message::addError('cannot-edit-special', $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);
+ $entry = BootEntry::fromJson($row['module'], $row['data']);
if ($entry === null) {
Message::addError('unknown-bootentry-type', $id);
Util::redirect('?do=serversetup&show=bootentry');
@@ -572,22 +594,42 @@ class Page_ServerSetup extends Page
private function showEditAddress()
{
+ $status = IPxeBuilder::getVersionTaskResult();
+ $versions = false;
+ if ($status === null) {
+ $error = 'Taskmanager down';
+ } elseif (!empty($status['versions'])) {
+ $versions = $status['versions'];
+ foreach ($versions as &$version) {
+ if ($version['hash'] === Property::get(IPxeBuilder::PROP_IPXE_HASH)) {
+ $version['hash_selected'] = 'selected';
+ }
+ $version['date_s'] = date('Y-m-d H:i', $version['date']);
+ $version['hash_s'] = substr($version['hash'], 0, 7);
+ }
+ $error = false;
+ } else {
+ $error = $status['error'] ?? 'Unknown error';
+ }
Render::addTemplate('ipaddress', array(
- 'ips' => $this->addrListTask['data']['addresses'],
+ 'ips' => $this->addrListTask['data']['addresses'] ?? [],
'chooseHintClass' => $this->hasIpSet ? '' : 'alert alert-danger',
'disabled' => ($this->getCompileTask() === false) ? '' : 'disabled',
+ 'versions' => $versions,
+ 'error' => $error,
+ 'lastBuild' => Property::get(IPxeBuilder::PROP_IPXE_BUILDSTRING),
));
}
// -----------------------------------------------------------------------------------------------
- private function getLocalAddresses()
+ private function getLocalAddresses(): void
{
$this->addrListTask = Taskmanager::submit('LocalAddressesList', array());
if ($this->addrListTask === false) {
$this->addrListTask['data']['addresses'] = false;
- return false;
+ return;
}
if (!Taskmanager::isFinished($this->addrListTask)) { // TODO: Async if just displaying
@@ -596,12 +638,12 @@ class Page_ServerSetup extends Page
if (Taskmanager::isFailed($this->addrListTask) || !isset($this->addrListTask['data']['addresses'])) {
$this->addrListTask['data']['addresses'] = false;
- return false;
+ return;
}
$sortIp = array();
foreach (array_keys($this->addrListTask['data']['addresses']) as $key) {
- $item = & $this->addrListTask['data']['addresses'][$key];
+ $item =& $this->addrListTask['data']['addresses'][$key];
if (!isset($item['ip']) || !preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $item['ip']) || substr($item['ip'], 0, 4) === '127.') {
unset($this->addrListTask['data']['addresses'][$key]);
continue;
@@ -614,7 +656,6 @@ class Page_ServerSetup extends Page
}
unset($item);
array_multisort($sortIp, SORT_STRING, $this->addrListTask['data']['addresses']);
- return true;
}
private function deleteBootEntry()
@@ -757,7 +798,7 @@ class Page_ServerSetup extends Page
|| $defaultEntryId === null)) { // if still null, use whatever as fallback, in case user didn't select any
$defaultEntryId = $newKey;
}
- $keepIds[] = (int)$newKey;
+ $keepIds[] = $newKey;
if (!empty($entry['plainpass'])) {
Database::exec('UPDATE serversetup_menuentry SET md5pass = :md5pass WHERE menuentryid = :id', [
'md5pass' => IPxe::makeMd5Pass($entry['plainpass'], $newKey),
@@ -791,7 +832,7 @@ class Page_ServerSetup extends Page
{
$newAddress = Request::post('ip', 'none', 'string');
$valid = false;
- foreach ($this->addrListTask['data']['addresses'] as $item) {
+ foreach ($this->addrListTask['data']['addresses'] ?? [] as $item) {
if ($item['ip'] !== $newAddress)
continue;
$valid = true;
@@ -832,14 +873,15 @@ class Page_ServerSetup extends Page
Message::addError('missing-bootentry-data');
return;
}
- $module = false;
$type = Request::post('type', false, 'string');
- if ($type{0} === '.') {
+ if ($type[0] === '.') {
// Exec or script
if ($type === '.exec') {
$entry = BootEntry::newStandardBootEntry($data);
} elseif ($type === '.script') {
$entry = BootEntry::newCustomBootEntry($data);
+ } else {
+ $entry = null;
}
if ($entry === null) {
Message::addError('main.empty-field');
@@ -849,7 +891,7 @@ class Page_ServerSetup extends Page
} else {
// Module hook
$hook = Hook::loadSingle($type, 'ipxe-bootentry');
- if ($hook === false) {
+ if ($hook === null) {
Message::addError('unknown-bootentry-type', $type);
return;
}
@@ -883,10 +925,15 @@ class Page_ServerSetup extends Page
} else {
// Edit existing entry
$params['oldid'] = $oldEntryId;
- Database::exec('UPDATE serversetup_bootentry SET
+ // Ignore .special, must never update
+ $aff = 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);
+ WHERE entryid = :oldid AND module <> '.special'", $params);
+ if ($aff > 0) {
+ Message::addSuccess('boot-entry-updated', $newId);
+ } else {
+ Message::addWarning('nothing-changed-or-protected', $newId);
+ }
}
if (Request::post('next') === 'reload') {
Util::redirect('?do=serversetup&show=editbootentry&id=' . $newId);
@@ -924,7 +971,7 @@ class Page_ServerSetup extends Page
ORDER BY m.title ASC', ['locationid' => $locationId]);
$menus = [];
$hasDefault = false;
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
$eids = explode(',', $row['entries']);
$row['default_entry_title'] = $menuEntries[$row['menu_default']] ?? '';
$row['entries'] = [];
@@ -1023,7 +1070,7 @@ class Page_ServerSetup extends Page
Util::redirect('?do=serversetup&show=import');
}
$menu = PxeLinux::parsePxeLinux($content, false);
- if (empty($menu->sections)) {
+ if ($menu === null) {
Message::addWarning('import-no-entries');
Util::redirect('?do=serversetup&show=import');
}
@@ -1032,8 +1079,8 @@ class Page_ServerSetup extends Page
IPxe::importPxeMenuEntries($menu, $foo);
Util::redirect('?do=serversetup&show=bootentry');
} else {
- $id = IPxe::insertMenu($menu, 'Imported Menu', false, 0, [], []);
- if ($id === false) {
+ $id = IPxe::insertMenu($menu, 'Imported Menu', null, 0, [], []);
+ if ($id === null) {
Message::addError('import-error');
Util::redirect('?do=serversetup&show=import');
} else {
diff --git a/modules-available/serversetup-bwlp-ipxe/templates/download.html b/modules-available/serversetup-bwlp-ipxe/templates/download.html
index c4025d70..ff4e4216 100644
--- a/modules-available/serversetup-bwlp-ipxe/templates/download.html
+++ b/modules-available/serversetup-bwlp-ipxe/templates/download.html
@@ -15,7 +15,11 @@
</table>
<br>
<p>
- {{lang_additionalInfoLink}} <a href="https://ipxe.org/appnote/buildtargets" target="_blank">{{lang_ipxeWikiUrl}}</a>
+ {{lang_additionalInfoLink}}
+ <a href="https://ipxe.org/appnote/buildtargets" target="_blank">
+ {{lang_ipxeWikiUrl}}
+ <span class="glyphicon glyphicon-new-window"></span>
+ </a>
</p>
</div>
</div>
@@ -38,7 +42,10 @@
{{lang_usbImgHelpWindows}}
</p>
<p>
- <a href="https://rufus.akeo.ie/#download" target="_blank">{{lang_downloadRufus}}</a>
+ <a href="https://rufus.akeo.ie/#download" target="_blank">
+ {{lang_downloadRufus}}
+ <span class="glyphicon glyphicon-new-window"></span>
+ </a>
</p>
</div>
</div> \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp-ipxe/templates/git_task.html b/modules-available/serversetup-bwlp-ipxe/templates/git_task.html
new file mode 100644
index 00000000..7f199256
--- /dev/null
+++ b/modules-available/serversetup-bwlp-ipxe/templates/git_task.html
@@ -0,0 +1,15 @@
+<div id="tm-select-div" data-tm-id="{{selectTask}}" data-tm-log="error" data-tm-callback="ipxeSelVersionCb">
+ {{lang_gitCheckout}}
+</div>
+<script type="text/javascript">
+ function ipxeSelVersionCb(task) {
+ {{#reload}}
+ if (!task || !task.statusCode)
+ return;
+
+ if (task.statusCode === 'TASK_FINISHED') {
+ window.location.href = '?do=serversetup&show=address&sv=0';
+ }
+ {{/reload}}
+ }
+</script> \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp-ipxe/templates/ipaddress.html b/modules-available/serversetup-bwlp-ipxe/templates/ipaddress.html
index 74affb9f..f5a49beb 100644
--- a/modules-available/serversetup-bwlp-ipxe/templates/ipaddress.html
+++ b/modules-available/serversetup-bwlp-ipxe/templates/ipaddress.html
@@ -1,46 +1,72 @@
-<div class="panel panel-default">
- <div class="panel-heading">
- {{lang_bootAddress}}
+<h3>
+ {{lang_bootAddress}}
+</h3>
+<div class="{{chooseHintClass}}">
+ {{lang_chooseIP}}
+</div>
+<form method="post" action="?do=ServerSetup">
+ <input type="hidden" name="action" value="ip">
+ <input type="hidden" name="token" value="{{token}}">
+ <table class="slx-table">
+ {{#ips}}
+ <tr>
+ <td>{{ip}}</td>
+ {{#default}}
+ <td>
+ <span class="btn btn-success btn-xs"><span class="glyphicon glyphicon-ok"></span> {{lang_active}}</span>
+ </td>
+ {{/default}}
+ {{^default}}
+ <td>
+ <button class="btn btn-primary btn-xs" name="ip" value="{{ip}}" {{disabled}}>
+ <span class="glyphicon glyphicon-flag"></span>
+ {{lang_set}}
+ </button>
+ </td>
+ {{/default}}
+ </tr>
+ {{/ips}}
+ </table>
+</form>
+<hr>
+
+<h3>
+ {{lang_recompileHead}}
+</h3>
+<p>
+ {{lang_recompileHint}}
+</p>
+{{#error}}
+<div class="alert alert-danger">{{error}}</div>
+{{/error}}
+<form method="post" action="?do=ServerSetup">
+ <input type="hidden" name="token" value="{{token}}">
+ <div class="form-group">
+ <label>
+ {{lang_versionSelect}}
+ <select class="form-control" name="version">
+ {{#versions}}
+ <option value="{{hash}}" {{hash_selected}}>{{date_s}} ({{hash_s}})</option>
+ {{/versions}}
+ </select>
+ </label>
</div>
- <div class="panel-body">
- <div class="{{chooseHintClass}}">
- {{lang_chooseIP}}
- </div>
- <form method="post" action="?do=ServerSetup">
- <input type="hidden" name="action" value="ip">
- <input type="hidden" name="token" value="{{token}}">
- <table class="slx-table">
- {{#ips}}
- <tr>
- <td>{{ip}}</td>
- {{#default}}
- <td>
- <span class="btn btn-success btn-xs"><span class="glyphicon glyphicon-ok"></span> {{lang_active}}</span>
- </td>
- {{/default}}
- {{^default}}
- <td>
- <button class="btn btn-primary btn-xs" name="ip" value="{{ip}}" {{disabled}}>
- <span class="glyphicon glyphicon-flag"></span>
- {{lang_set}}
- </button>
- </td>
- {{/default}}
- </tr>
- {{/ips}}
- </table>
- </form>
+ <div class="buttonbar">
+ <button class="btn btn-default" name="action" value="compile" {{disabled}}>
+ <span class="glyphicon glyphicon-refresh"></span>
+ {{lang_forceRecompile}}
+ </button>
+ <button class="btn btn-default" name="action" value="fetch" {{disabled}}>
+ <span class="glyphicon glyphicon-arrow-down"></span>
+ {{lang_fetchUpdate}}
+ </button>
+ <button class="btn btn-danger" name="action" value="reset" {{disabled}} data-confirm="{{lang_reallyGitReset}}">
+ <span class="glyphicon glyphicon-trash"></span>
+ {{lang_resetWorkingTree}}
+ </button>
</div>
- <div class="panel-body">
- <p>
- {{lang_recompileHint}}
- </p>
- <form method="post" action="?do=ServerSetup">
- <input type="hidden" name="token" value="{{token}}">
- <button class="btn btn-default" name="action" value="compile" {{disabled}}>
- <span class="glyphicon glyphicon-refresh"></span>
- {{lang_forceRecompile}}
- </button>
- </form>
- </div>
-</div> \ No newline at end of file
+</form>
+<hr>
+
+<h3>{{lang_lastBuild}}</h3>
+{{lastBuild}}
diff --git a/modules-available/serversetup-bwlp-ipxe/templates/ipxe_update.html b/modules-available/serversetup-bwlp-ipxe/templates/ipxe_update.html
index 344d3905..328b61f6 100644
--- a/modules-available/serversetup-bwlp-ipxe/templates/ipxe_update.html
+++ b/modules-available/serversetup-bwlp-ipxe/templates/ipxe_update.html
@@ -19,8 +19,9 @@
</div>
<script type="text/javascript">
+ var $slxFileList;
document.addEventListener('DOMContentLoaded', function() {
- var slxFileList = $('#file-list').find('.glyphicon');
+ $slxFileList = $('#file-list').find('.glyphicon');
});
function ipxeGenCb(task)
@@ -30,25 +31,26 @@
if (task.statusCode === 'TASK_FINISHED') {
$('#tm-compile-div').find('pre').hide();
+ window.location.href = '?do=serversetup&show=address&sv=0';
}
+ // Working or finished
+ if (task.data && task.data.files && task.data.files) {
+ for (var k in task.data.files) {
+ if (!task.data.files[k])
+ continue;
+ var f = '#built-' + k.replace('/', '-').replace('.', '-');
+ var $e = $(f);
+ $e.find('.glyphicon-question-sign').removeClass('glyphicon-question-sign').addClass('glyphicon-ok text-success');
+ }
+ }
+ // On failure, change non-built targets to X
if (task.statusCode === 'TASK_ERROR') {
var $gf = $('#genfailed');
if (task.data && task.data.errors) {
$gf.append($('<pre>').text(task.data.errors));
}
$gf.show('slow');
- slxFileList.find('.glyphicon-question-sign').removeClass('glyphicon-question-sign').addClass('glyphicon-stop');
- } else {
- // Working or finished
- if (task.data && task.data.files && task.data.files) {
- for (var k in task.data.files) {
- if (!task.data.files[k])
- continue;
- var f = '#built-' + k.replace('/', '-').replace('.', '-');
- var $e = $(f);
- $e.find('.glyphicon-question-sign').removeClass('glyphicon-question-sign').addClass('glyphicon-ok text-success');
- }
- }
+ $slxFileList.find('.glyphicon-question-sign').removeClass('glyphicon-question-sign').addClass('glyphicon-stop');
}
}
</script>