From c2a6dfb649107ce3ad64162e2bb2c3c7275650fb Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 12 Feb 2019 14:55:12 +0100 Subject: [serversetup*] PXELinux and iPXE side-by-side --- .../serversetup-bwlp-ipxe/api.inc.php | 254 +++++++ .../serversetup-bwlp-ipxe/config.json | 8 + .../serversetup-bwlp-ipxe/hooks/bootup.inc.php | 11 + .../hooks/ipxe-update.inc.php | 10 + .../hooks/main-warning.inc.php | 6 + .../serversetup-bwlp-ipxe/inc/bootentry.inc.php | 258 +++++++ .../serversetup-bwlp-ipxe/inc/ipxe.inc.php | 453 +++++++++++ .../serversetup-bwlp-ipxe/inc/ipxemenu.inc.php | 142 ++++ .../serversetup-bwlp-ipxe/inc/localboot.inc.php | 15 + .../serversetup-bwlp-ipxe/inc/menuentry.inc.php | 177 +++++ .../serversetup-bwlp-ipxe/inc/pxelinux.inc.php | 302 ++++++++ .../serversetup-bwlp-ipxe/install.inc.php | 89 +++ .../serversetup-bwlp-ipxe/lang/de/messages.json | 21 + .../serversetup-bwlp-ipxe/lang/de/module.json | 19 + .../serversetup-bwlp-ipxe/lang/de/permissions.json | 12 + .../lang/de/template-tags.json | 94 +++ .../serversetup-bwlp-ipxe/lang/en/messages.json | 5 + .../serversetup-bwlp-ipxe/lang/en/module.json | 3 + .../serversetup-bwlp-ipxe/lang/en/permissions.json | 6 + .../lang/en/template-tags.json | 42 ++ .../serversetup-bwlp-ipxe/lang/pt/messages.json | 3 + .../serversetup-bwlp-ipxe/lang/pt/module.json | 3 + .../lang/pt/template-tags.json | 38 + .../serversetup-bwlp-ipxe/page.inc.php | 829 +++++++++++++++++++++ .../permissions/permissions.json | 29 + .../templates/bootentry-list.html | 83 +++ .../serversetup-bwlp-ipxe/templates/download.html | 53 ++ .../serversetup-bwlp-ipxe/templates/heading.html | 3 + .../serversetup-bwlp-ipxe/templates/ipaddress.html | 44 ++ .../templates/ipxe-new-boot-entry.html | 165 ++++ .../templates/ipxe_update.html | 54 ++ .../serversetup-bwlp-ipxe/templates/localboot.html | 59 ++ .../templates/menu-assign-location.html | 69 ++ .../serversetup-bwlp-ipxe/templates/menu-edit.html | 368 +++++++++ .../serversetup-bwlp-ipxe/templates/menu-list.html | 100 +++ .../serversetup-bwlp-pxelinux/config.json | 3 + .../hooks/ipxe-update.inc.php | 9 + .../hooks/main-warning.inc.php | 6 + .../serversetup-bwlp-pxelinux/inc/ipxe.inc.php | 224 ++++++ .../lang/de/messages.json | 5 + .../serversetup-bwlp-pxelinux/lang/de/module.json | 4 + .../lang/de/permissions.json | 6 + .../lang/de/template-tags.json | 33 + .../lang/en/messages.json | 5 + .../serversetup-bwlp-pxelinux/lang/en/module.json | 3 + .../lang/en/permissions.json | 6 + .../lang/en/template-tags.json | 33 + .../lang/pt/messages.json | 3 + .../serversetup-bwlp-pxelinux/lang/pt/module.json | 3 + .../lang/pt/template-tags.json | 38 + .../serversetup-bwlp-pxelinux/page.inc.php | 187 +++++ .../permissions/permissions.json | 14 + .../templates/heading.html | 1 + .../templates/ipaddress.html | 37 + .../serversetup-bwlp-pxelinux/templates/ipxe.html | 117 +++ .../templates/ipxe_update.html | 38 + modules-available/serversetup-bwlp/api.inc.php | 254 ------- modules-available/serversetup-bwlp/config.json | 8 - .../serversetup-bwlp/hooks/bootup.inc.php | 11 - .../serversetup-bwlp/hooks/ipxe-update.inc.php | 10 - .../serversetup-bwlp/hooks/main-warning.inc.php | 6 - .../serversetup-bwlp/inc/bootentry.inc.php | 258 ------- .../serversetup-bwlp/inc/ipxe.inc.php | 453 ----------- .../serversetup-bwlp/inc/ipxemenu.inc.php | 142 ---- .../serversetup-bwlp/inc/localboot.inc.php | 15 - .../serversetup-bwlp/inc/menuentry.inc.php | 177 ----- .../serversetup-bwlp/inc/pxelinux.inc.php | 302 -------- modules-available/serversetup-bwlp/install.inc.php | 89 --- .../serversetup-bwlp/lang/de/messages.json | 21 - .../serversetup-bwlp/lang/de/module.json | 19 - .../serversetup-bwlp/lang/de/permissions.json | 12 - .../serversetup-bwlp/lang/de/template-tags.json | 94 --- .../serversetup-bwlp/lang/en/messages.json | 5 - .../serversetup-bwlp/lang/en/module.json | 3 - .../serversetup-bwlp/lang/en/permissions.json | 6 - .../serversetup-bwlp/lang/en/template-tags.json | 42 -- .../serversetup-bwlp/lang/pt/messages.json | 3 - .../serversetup-bwlp/lang/pt/module.json | 3 - .../serversetup-bwlp/lang/pt/template-tags.json | 38 - modules-available/serversetup-bwlp/page.inc.php | 829 --------------------- .../serversetup-bwlp/permissions/permissions.json | 29 - .../serversetup-bwlp/templates/bootentry-list.html | 83 --- .../serversetup-bwlp/templates/download.html | 53 -- .../serversetup-bwlp/templates/heading.html | 3 - .../serversetup-bwlp/templates/ipaddress.html | 44 -- .../templates/ipxe-new-boot-entry.html | 165 ---- .../serversetup-bwlp/templates/ipxe_update.html | 54 -- .../serversetup-bwlp/templates/localboot.html | 59 -- .../templates/menu-assign-location.html | 69 -- .../serversetup-bwlp/templates/menu-edit.html | 368 --------- .../serversetup-bwlp/templates/menu-list.html | 100 --- 91 files changed, 4602 insertions(+), 3827 deletions(-) create mode 100644 modules-available/serversetup-bwlp-ipxe/api.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/config.json create mode 100644 modules-available/serversetup-bwlp-ipxe/hooks/bootup.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/hooks/ipxe-update.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/hooks/main-warning.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/inc/localboot.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/inc/pxelinux.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/install.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/de/messages.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/de/module.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/de/permissions.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/de/template-tags.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/en/messages.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/en/module.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/en/permissions.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/en/template-tags.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/pt/messages.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/pt/module.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/pt/template-tags.json create mode 100644 modules-available/serversetup-bwlp-ipxe/page.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/permissions/permissions.json create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/bootentry-list.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/download.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/heading.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/ipaddress.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/ipxe-new-boot-entry.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/ipxe_update.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/localboot.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/menu-assign-location.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/menu-edit.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/menu-list.html create mode 100644 modules-available/serversetup-bwlp-pxelinux/config.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/hooks/ipxe-update.inc.php create mode 100644 modules-available/serversetup-bwlp-pxelinux/hooks/main-warning.inc.php create mode 100644 modules-available/serversetup-bwlp-pxelinux/inc/ipxe.inc.php create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/de/messages.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/de/module.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/de/permissions.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/de/template-tags.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/en/messages.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/en/module.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/en/permissions.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/en/template-tags.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/pt/messages.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/pt/module.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/pt/template-tags.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/page.inc.php create mode 100644 modules-available/serversetup-bwlp-pxelinux/permissions/permissions.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/templates/heading.html create mode 100644 modules-available/serversetup-bwlp-pxelinux/templates/ipaddress.html create mode 100644 modules-available/serversetup-bwlp-pxelinux/templates/ipxe.html create mode 100644 modules-available/serversetup-bwlp-pxelinux/templates/ipxe_update.html delete mode 100644 modules-available/serversetup-bwlp/api.inc.php delete mode 100644 modules-available/serversetup-bwlp/config.json delete mode 100644 modules-available/serversetup-bwlp/hooks/bootup.inc.php delete mode 100644 modules-available/serversetup-bwlp/hooks/ipxe-update.inc.php delete mode 100644 modules-available/serversetup-bwlp/hooks/main-warning.inc.php delete mode 100644 modules-available/serversetup-bwlp/inc/bootentry.inc.php delete mode 100644 modules-available/serversetup-bwlp/inc/ipxe.inc.php delete mode 100644 modules-available/serversetup-bwlp/inc/ipxemenu.inc.php delete mode 100644 modules-available/serversetup-bwlp/inc/localboot.inc.php delete mode 100644 modules-available/serversetup-bwlp/inc/menuentry.inc.php delete mode 100644 modules-available/serversetup-bwlp/inc/pxelinux.inc.php delete mode 100644 modules-available/serversetup-bwlp/install.inc.php delete mode 100644 modules-available/serversetup-bwlp/lang/de/messages.json delete mode 100644 modules-available/serversetup-bwlp/lang/de/module.json delete mode 100644 modules-available/serversetup-bwlp/lang/de/permissions.json delete mode 100644 modules-available/serversetup-bwlp/lang/de/template-tags.json delete mode 100644 modules-available/serversetup-bwlp/lang/en/messages.json delete mode 100644 modules-available/serversetup-bwlp/lang/en/module.json delete mode 100644 modules-available/serversetup-bwlp/lang/en/permissions.json delete mode 100644 modules-available/serversetup-bwlp/lang/en/template-tags.json delete mode 100644 modules-available/serversetup-bwlp/lang/pt/messages.json delete mode 100644 modules-available/serversetup-bwlp/lang/pt/module.json delete mode 100644 modules-available/serversetup-bwlp/lang/pt/template-tags.json delete mode 100644 modules-available/serversetup-bwlp/page.inc.php delete mode 100644 modules-available/serversetup-bwlp/permissions/permissions.json delete mode 100644 modules-available/serversetup-bwlp/templates/bootentry-list.html delete mode 100644 modules-available/serversetup-bwlp/templates/download.html delete mode 100644 modules-available/serversetup-bwlp/templates/heading.html delete mode 100644 modules-available/serversetup-bwlp/templates/ipaddress.html delete mode 100644 modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html delete mode 100644 modules-available/serversetup-bwlp/templates/ipxe_update.html delete mode 100644 modules-available/serversetup-bwlp/templates/localboot.html delete mode 100644 modules-available/serversetup-bwlp/templates/menu-assign-location.html delete mode 100644 modules-available/serversetup-bwlp/templates/menu-edit.html delete mode 100644 modules-available/serversetup-bwlp/templates/menu-list.html diff --git a/modules-available/serversetup-bwlp-ipxe/api.inc.php b/modules-available/serversetup-bwlp-ipxe/api.inc.php new file mode 100644 index 00000000..1df0e6e7 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/api.inc.php @@ -0,0 +1,254 @@ + $v) { + $query .= $k . '=' . $v . '&'; + } + //$query = substr($query, 0, -1); + echo << $uuid]); + if ($row !== false && !empty($row['systemmodel'])) { + $model = $row['systemmodel']; + } +} +if ($model === false) { + // Otherwise use what iPXE sent us + function modfilt($str) + { + if (empty($str) || preg_match('/product\s+name|be\s+filled|unknown|default\s+string/i', $str)) + return false; + return trim(preg_replace('/\s+/', ' ', $str)); + } + $manuf = modfilt($manuf); + $product = modfilt($product); + if (!empty($product)) { + $model = $product; + if (!empty($manuf)) { + $model .= " ($manuf)"; + } + } +} +// Query +if ($model !== false) { + $row = Database::queryFirst("SELECT bootmethod FROM serversetup_localboot WHERE systemmodel = :model LIMIT 1", + ['model' => $model]); + if ($row !== false) { + $localboot = $row['bootmethod']; + } +} +if ($localboot === false || !isset($BOOT_METHODS[$localboot])) { + $localboot = Property::get(Localboot::PROPERTY_KEY, 'AUTO'); + if (!isset($BOOT_METHODS[$localboot])) { + $localboot = 'AUTO'; + } +} +if (isset($BOOT_METHODS[$localboot])) { + // Move preferred method first + $BOOT_METHODS[] = $BOOT_METHODS[$localboot]; + unset($BOOT_METHODS[$localboot]); + $BOOT_METHODS = array_reverse($BOOT_METHODS); +} + +if ($slxExtensions) { + $slxConsoleUpdate = '--update'; +} else { + $slxConsoleUpdate = ''; +} + +$output = <<getMenuDefinition('target', $platform, $slxExtensions); + +$output .= <<getItemsCode($platform); + +/* + +:i5 +chain -a /tftp/memtest.0 passes=1 onepass || goto membad +prompt Memory OK. Press a key. +goto init + +:i8 +set x:int32 0 +:again +console --left 60 --top 130 --right 67 --bottom 96 --picture bg-load --keep || +console --left 55 --top 88 --right 63 --bottom 64 --picture bg-menu --keep || +inc x +iseq \${x} 20 || goto again +prompt DONE. Press dein Knie. +goto slx_menu + +:membad +iseq \${errno} 0x1 || goto memaborted +params +param scrot \${vram} +imgfetch -a http://132.230.8.113/screen.php##params || +prompt Memory is bad. Press a key. +goto init + +:memaborted +params +param scrot \${vram} +imgfetch -a http://132.230.8.113/screen.php##params || +prompt Memory test aborted. Press a key. +goto init + +*/ + +$output .= << 0) { + EventLog::info('Imported old PXELinux menu, with ' . $num . ' additional IP-range based menus.'); + } else { + EventLog::info('Imported old PXELinux menu.'); + } +} diff --git a/modules-available/serversetup-bwlp-ipxe/hooks/ipxe-update.inc.php b/modules-available/serversetup-bwlp-ipxe/hooks/ipxe-update.inc.php new file mode 100644 index 00000000..583c5a2b --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/hooks/ipxe-update.inc.php @@ -0,0 +1,10 @@ + Property::getServerIp() +]; +$task = Taskmanager::submit('CompileIPxeNew', $data); +if (!isset($task['id'])) + return false; +Property::set('ipxe-task-id', $task['id'], 15); +return $task['id']; \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/hooks/main-warning.inc.php b/modules-available/serversetup-bwlp-ipxe/hooks/main-warning.inc.php new file mode 100644 index 00000000..a2eba6ff --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/hooks/main-warning.inc.php @@ -0,0 +1,6 @@ + $value) { + if (property_exists($this, $key)) { + $this->{$key} = $value; + } + } + } + } + + public abstract function supportsMode($mode); + + public abstract function toScript($failLabel, $mode); + + public abstract function toArray(); + + public abstract function addFormFields(&$array); + + /* + * + */ + + /** + * Return a BootEntry instance from the serialized data. + * + * @param string $jsonString serialized entry data + * @return BootEntry|null instance representing boot entry, null on error + */ + public static function fromJson($data) + { + if (is_string($data)) { + $data = json_decode($data, true); + } + if (isset($data['script'])) { + return new CustomBootEntry($data); + } + if (isset($data['executable'])) { + return new StandardBootEntry($data); + } + return null; + } + + public static function newStandardBootEntry($initData) + { + $ret = new StandardBootEntry($initData); + $list = []; + if ($ret->arch() !== StandardBootEntry::EFI) { + $list[] = StandardBootEntry::BIOS; + } + if ($ret->arch() === StandardBootEntry::EFI || $ret->arch() === StandardBootEntry::BOTH) { + $list[] = StandardBootEntry::EFI; + } + foreach ($list as $mode) { + if (empty($initData['executable'][$mode])) + return null; + } + return $ret; + } + + public static function newCustomBootEntry($initData) + { + if (empty($initData['script'])) + return null; + return new CustomBootEntry($initData); + } + + /** + * 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 + */ + public static function fromDatabaseId($id) + { + $row = Database::queryFirst("SELECT data FROM serversetup_bootentry + WHERE entryid = :id LIMIT 1", ['id' => $id]); + if ($row === false) + return false; + return self::fromJson($row['data']); + } + +} + +class StandardBootEntry extends BootEntry +{ + protected $executable; + protected $initRd; + protected $commandLine; + protected $replace; + protected $autoUnload; + protected $resetConsole; + protected $arch; // Constants below + + const BIOS = 'PCBIOS'; // Only valid for legacy BIOS boot + const EFI = 'EFI'; // Only valid for EFI boot + const BOTH = 'PCBIOS-EFI'; // Supports both via distinct entry + const AGNOSTIC = 'agnostic'; // Supports both via same entry (PCBIOS entry) + + public function __construct($data = false) + { + if ($data instanceof PxeSection) { + // Gets arrayfied below + $this->executable = $data->kernel; + $this->initRd = $data->initrd; + $this->commandLine = ' ' . str_replace('vga=current', '', $data->append) . ' '; + $this->resetConsole = true; + $this->replace = true; + $this->autoUnload = true; + if (strpos($this->commandLine, ' quiet ') !== false) { + $this->commandLine .= ' loglevel=5 rd.systemd.show_status=auto'; + } + if ($data->ipAppend & 1) { + $this->commandLine .= ' ${ipappend1}'; + } + if ($data->ipAppend & 2) { + $this->commandLine .= ' ${ipappend2}'; + } + if ($data->ipAppend & 4) { + $this->commandLine .= ' SYSUUID=${uuid}'; + } + $this->commandLine = trim(preg_replace('/\s+/', ' ', $this->commandLine)); + } else { + parent::__construct($data); + } + // Convert legacy DB format + foreach (['executable', 'initRd', 'commandLine', 'replace', 'autoUnload', 'resetConsole'] as $key) { + if (!is_array($this->{$key})) { + $this->{$key} = [ 'PCBIOS' => $this->{$key}, 'EFI' => '' ]; + } + } + if ($this->arch === null) { + $this->arch = self::AGNOSTIC; + } + } + + public function arch() + { + return $this->arch; + } + + public function supportsMode($mode) + { + if ($mode === $this->arch || $this->arch === self::AGNOSTIC) + return true; + if ($mode === self::BIOS || $mode === self::EFI) { + return $this->arch === self::BOTH; + } + error_log('Unknown iPXE platform: ' . $mode); + return false; + } + + public function toScript($failLabel, $mode) + { + if (!$this->supportsMode($mode)) { + return "prompt Entry doesn't have an executable for mode $mode\n"; + } + if ($this->arch === self::AGNOSTIC) { + $mode = self::BIOS; + } + + $script = ''; + if ($this->resetConsole[$mode]) { + $script .= "console ||\n"; + } + if (!empty($this->initRd[$mode])) { + $script .= "imgfree ||\n"; + if (!is_array($this->initRd[$mode])) { + $script .= "initrd {$this->initRd[$mode]} || goto $failLabel\n"; + } else { + foreach ($this->initRd[$mode] as $initrd) { + $script .= "initrd $initrd || goto $failLabel\n"; + } + } + } + $script .= "boot "; + if ($this->autoUnload[$mode]) { + $script .= "-a "; + } + if ($this->replace[$mode]) { + $script .= "-r "; + } + $script .= $this->executable[$mode]; + $rdBase = basename($this->initRd[$mode]); + if (!empty($this->commandLine[$mode])) { + $script .= " initrd=$rdBase {$this->commandLine[$mode]}"; + } + $script .= " || goto $failLabel\n"; + if ($this->resetConsole[$mode]) { + $script .= "goto start ||\n"; + } + return $script; + } + + public function addFormFields(&$array) + { + $array[$this->arch . '_selected'] = 'selected'; + foreach ([self::BIOS, self::EFI] as $mode) { + $array['entries'][] = [ + 'is' . $mode => true, + 'mode' => $mode, + 'executable' => $this->executable[$mode], + 'initRd' => $this->initRd[$mode], + 'commandLine' => $this->commandLine[$mode], + 'replace_checked' => $this->replace[$mode] ? 'checked' : '', + 'autoUnload_checked' => $this->autoUnload[$mode] ? 'checked' : '', + 'resetConsole_checked' => $this->resetConsole[$mode] ? 'checked' : '', + ]; + } + $array['exec_checked'] = 'checked'; + } + + public function toArray() + { + return [ + 'executable' => $this->executable, + 'initRd' => $this->initRd, + 'commandLine' => $this->commandLine, + 'replace' => $this->replace, + 'autoUnload' => $this->autoUnload, + 'resetConsole' => $this->resetConsole, + 'arch' => $this->arch, + ]; + } +} + +class CustomBootEntry extends BootEntry +{ + protected $script; + + public function supportsMode($mode) + { + return true; + } + + public function toScript($failLabel, $mode) + { + return str_replace('%fail%', $failLabel, $this->script) . "\n"; + } + + public function addFormFields(&$array) + { + $array['entry'] = [ + 'script' => $this->script, + ]; + $array['script_checked'] = 'checked'; + } + + public function toArray() + { + return ['script' => $this->script]; + } +} diff --git a/modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php new file mode 100644 index 00000000..d34839f0 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php @@ -0,0 +1,453 @@ += :start AND endaddr <= :end", compact('start', 'end')); + $locations = []; + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + foreach ($locations as &$loc) { + if ($row['startaddr'] <= $loc['startaddr'] && $row['endaddr'] >= $loc['endaddr']) { + $loc = false; + } elseif ($row['startaddr'] >= $loc['startaddr'] && $row['endaddr'] <= $loc['endaddr']) { + continue 2; + } + } + unset($loc); + $locations[] = $row; + } + $menu = PxeLinux::parsePxeLinux($content); + $key = $menu->hash(true); + if (isset($menus[$key])) { + $menuId = $menus[$key]; + $defId = null; + // Figure out the default label, get it's label name + foreach ($menu->sections as $section) { + if ($section->isDefault) { + $defId = $section; + } elseif ($defId === null && $section->label === $menu->timeoutLabel) { + $defId = $section; + } + } + if ($defId !== null) { + $defId = self::cleanLabelFixLocal($defId); + // Confirm it actually exists (it should since the menu seems identical) and get menuEntryId + $me = Database::queryFirst('SELECT m.defaultentryid, me.menuentryid FROM serversetup_bootentry be + INNER JOIN serversetup_menuentry me ON (be.entryid = me.entryid) + INNER JOIN serversetup_menu m ON (m.menuid = me.menuid) + WHERE be.entryid = :id AND me.menuid = :menuid', + ['id' => $defId, 'menuid' => $menuId]); + if ($me === false || $me['defaultentryid'] == $me['menuentryid']) { + $defId = null; // Not found, or is already default - don't override if it's the same + } else { + $defId = $me['menuentryid']; + } + } + } else { + $menuId = self::insertMenu($menu, 'Imported', false, 0, [], []); + $menus[$key] = $menuId; + $defId = null; + $importCount++; + } + if ($menuId === false) + continue; + foreach ($locations as $loc) { + if ($loc === false) + continue; + Database::exec('INSERT IGNORE INTO serversetup_menu_location (menuid, locationid, defaultentryid) + VALUES (:menuid, :locationid, :def)', [ + 'menuid' => $menuId, + 'locationid' => $loc['locationid'], + 'def' => $defId, + ]); + } + } + return $importCount; + } + + public static function importLegacyMenu($force = false) + { + if (!$force && false !== Database::queryFirst("SELECT entryid FROM serversetup_bootentry WHERE entryid = 'bwlp-default'")) + return false; // Already exists + // Now create the default entry + self::createDefaultEntries(); + $prepend = ['bwlp-default' => false, 'localboot' => false]; + $defaultLabel = 'bwlp-default'; + $menuTitle = 'bwLehrpool Bootauswahl'; + $pxeConfig = ''; + $timeoutSecs = 60; + // Try to import any customization + $oldMenu = Property::getBootMenu(); + if (is_array($oldMenu)) { + // + if (isset($oldMenu['timeout'])) { + $timeoutSecs = (int)$oldMenu['timeout']; + } + if (isset($oldMenu['defaultentry'])) { + if ($oldMenu['defaultentry'] === 'net') { + $defaultLabel = 'bwlp-default'; + } elseif ($oldMenu['defaultentry'] === 'hdd') { + $defaultLabel = 'localboot'; + } elseif ($oldMenu['defaultentry'] === 'custom') { + $defaultLabel = 'custom'; + } + } + if (!empty($oldMenu['custom'])) { + $pxeConfig = $oldMenu['custom']; + } + } + $append = [ + '', + 'bwlp-default-dbg' => false, + '', + 'poweroff' => false, + ]; + return self::insertMenu(PxeLinux::parsePxeLinux($pxeConfig), $menuTitle, $defaultLabel, $timeoutSecs, $prepend, $append); + } + + /** + * @param PxeMenu $pxeMenu + * @param string $menuTitle + * @param string|false $defaultLabel + * @param $defaultTimeoutSeconds + * @param $prepend + * @param $append + * @return bool|int + */ + private static function insertMenu($pxeMenu, $menuTitle, $defaultLabel, $defaultTimeoutSeconds, $prepend, $append) + { + $timeoutMs = []; + $menuEntries = $prepend; + settype($menuEntries, 'array'); + if (!empty($pxeMenu)) { + $pxe =& $pxeMenu; + if (!empty($pxe->title)) { + $menuTitle = $pxe->title; + } + if ($pxe->timeoutLabel !== null) { + $defaultLabel = $pxe->timeoutLabel; + } + $timeoutMs[] = $pxe->timeoutMs; + $timeoutMs[] = $pxe->totalTimeoutMs; + foreach ($pxe->sections as $section) { + if ($section->localBoot || preg_match('/chain\.c32$/i', $section->kernel)) { + $menuEntries['localboot'] = 'localboot'; + 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; + } + continue; + } + $entry = self::pxe2BootEntry($section); + if ($entry === null) + continue; + $label = self::cleanLabelFixLocal($section); + if ($defaultLabel === $section->label) { + $defaultLabel = $label; + } + $hotkey = MenuEntry::filterKeyName($section->hotkey); + // Create boot entry + $data = $entry->toArray(); + Database::exec('INSERT IGNORE INTO serversetup_bootentry (entryid, hotkey, title, builtin, data) + VALUES (:label, :hotkey, :title, 0, :data)', [ + 'label' => $label, + 'hotkey' => $hotkey, + 'title' => self::sanitizeIpxeString($section->title), + 'data' => json_encode($data), + ]); + $menuEntries[$label] = $section; + } + } + if (is_array($append)) { + $menuEntries += $append; + } + if (empty($menuEntries)) + return false; + // Make menu + $timeoutMs = array_filter($timeoutMs, 'is_int'); + if (empty($timeoutMs)) { + $timeoutMs = (int)($defaultTimeoutSeconds * 1000); + } else { + $timeoutMs = min($timeoutMs); + } + $isDefault = (int)(Database::queryFirst('SELECT menuid FROM serversetup_menu WHERE isdefault = 1') === false); + Database::exec("INSERT INTO serversetup_menu (timeoutms, title, defaultentryid, isdefault) + VALUES (:timeoutms, :title, NULL, :isdefault)", [ + 'title' => self::sanitizeIpxeString($menuTitle), + 'timeoutms' => $timeoutMs, + 'isdefault' => $isDefault, + ]); + $menuId = Database::lastInsertId(); + if (!array_key_exists($defaultLabel, $menuEntries) && $timeoutMs > 0) { + $defaultLabel = array_keys($menuEntries)[0]; + } + // Link boot entries to menu + $defaultEntryId = null; + $order = 1000; + foreach ($menuEntries as $label => $entry) { + if (is_string($entry)) { + // Gap entry + Database::exec("INSERT INTO serversetup_menuentry + (menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass) + VALUES (:menuid, :entryid, :hotkey, :title, :hidden, :sortval, '', '')", [ + 'menuid' => $menuId, + 'entryid' => null, + 'hotkey' => '', + 'title' => self::sanitizeIpxeString($entry), + 'hidden' => 0, + 'sortval' => $order += 100, + ]); + continue; + } + $data = Database::queryFirst("SELECT entryid, hotkey, title FROM serversetup_bootentry WHERE entryid = :entryid", ['entryid' => $label]); + if ($data === false) + continue; + $data['pass'] = ''; + $data['hidden'] = 0; + if ($entry instanceof PxeSection) { + $data['hidden'] = (int)$entry->isHidden; + // Prefer explicit data from this imported menu over the defaults + $data['title'] = self::sanitizeIpxeString($entry->title); + if (MenuEntry::getKeyCode($entry->hotkey) !== false) { + $data['hotkey'] = $entry->hotkey; + } + if (!empty($entry->passwd)) { + // Most likely it's a hash so we cannot recover; ask people to reset + $data['pass'] ='please_reset'; + } + } + $data['menuid'] = $menuId; + $data['sortval'] = $order += 100; + $res = Database::exec("INSERT INTO serversetup_menuentry + (menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass) + VALUES (:menuid, :entryid, :hotkey, :title, :hidden, :sortval, :pass, :pass)", $data); + if ($res !== false && $label === $defaultLabel) { + $defaultEntryId = Database::lastInsertId(); + } + } + // Now we can set default entry + if (!empty($defaultEntryId)) { + Database::exec("UPDATE serversetup_menu SET defaultentryid = :entryid WHERE menuid = :menuid", + ['menuid' => $menuId, 'entryid' => $defaultEntryId]); + } + // TODO: masterpw? rather pointless.... + //$oldMenu['masterpasswordclear']; + return $menuId; + } + + private static function createDefaultEntries() + { + $query = 'INSERT IGNORE INTO serversetup_bootentry (entryid, hotkey, title, builtin, data) + VALUES (:entryid, :hotkey, :title, 1, :data)'; + Database::exec($query, + [ + 'entryid' => 'bwlp-default', + 'hotkey' => 'B', + 'title' => 'bwLehrpool-Umgebung starten', + 'data' => json_encode([ + 'executable' => '/boot/default/kernel', + 'initRd' => '/boot/default/initramfs-stage31', + 'commandLine' => 'slxbase=boot/default quiet splash loglevel=5 rd.systemd.show_status=auto intel_iommu=igfx_off ${ipappend1} ${ipappend2}', + 'replace' => true, + 'autoUnload' => true, + 'resetConsole' => true, + ]), + ]); + Database::exec($query, + [ + 'entryid' => 'bwlp-default-dbg', + 'hotkey' => '', + 'title' => 'bwLehrpool-Umgebung starten (nosplash, debug)', + 'data' => json_encode([ + 'executable' => '/boot/default/kernel', + 'initRd' => '/boot/default/initramfs-stage31', + 'commandLine' => 'slxbase=boot/default loglevel=7 intel_iommu=igfx_off ${ipappend1} ${ipappend2}', + 'replace' => true, + 'autoUnload' => true, + 'resetConsole' => true, + ]), + ]); + Database::exec($query, + [ + 'entryid' => 'localboot', + 'hotkey' => 'L', + 'title' => 'Lokales System starten', + 'data' => json_encode([ + 'script' => 'goto slx_localboot || goto %fail% ||', + ]), + ]); + Database::exec($query, + [ + 'entryid' => 'poweroff', + 'hotkey' => 'P', + 'title' => 'Power off', + 'data' => json_encode([ + 'script' => 'poweroff || goto %fail% ||', + ]), + ]); + Database::exec($query, + [ + 'entryid' => 'reboot', + 'hotkey' => 'R', + 'title' => 'Reboot', + 'data' => json_encode([ + 'script' => 'reboot || goto %fail% ||', + ]), + ]); + } + + /** + * Create unique label for a boot entry. It will try to figure out whether + * this is one of our default entries and if not, create a unique label + * representing the menu entry contents. + * Also it patches the entry if it's referencing the local bwlp install + * because side effects. + * + * @param PxeSection $section + * @return string + */ + private static function cleanLabelFixLocal($section) + { + $myip = Property::getServerIp(); + // Detect our "old" entry types + if (count($section->initrd) === 1 && preg_match(",$myip/boot/default/kernel\$,", $section->kernel) + && preg_match(",$myip/boot/default/initramfs-stage31\$,", $section->initrd[0])) { + // Kernel and initrd match, examine KCL + if ($section->append === 'slxbase=boot/default vga=current quiet splash') { + // Normal + return 'bwlp-default'; + } elseif ($section->append === 'slxbase=boot/default') { + // Debug output + return 'bwlp-default-dbg'; + } else { + // Transform to relative URL, leave KCL, fall through to generic label gen + $section->kernel = '/boot/default/kernel'; + $section->initrd = ['/boot/default/initramfs-stage31']; + } + } + // Generic -- "smart" hash of kernel, initrd and command line + $str = $section->kernel . ' ' . implode(',', $section->initrd); + $array = preg_split('/\s+/', $section->append, -1, PREG_SPLIT_NO_EMPTY); + sort($array); + $str .= ' ' . implode(' ', $array); + + return 'i-' . substr(md5($str), 0, 12); + } + + /** + * @param PxeSection $section + * @return BootEntry|null The according boot entry, null if it's unparsable + */ + private static function pxe2BootEntry($section) + { + if (preg_match('/(pxechain\.com|pxechn\.c32)$/i', $section->kernel)) { + // Chaining -- create script + $args = preg_split('/\s+/', $section->append); + $script = ''; + $file = false; + for ($i = 0; $i < count($args); ++$i) { + $arg = $args[$i]; + if ($arg === '-c') { // PXELINUX config file option + ++$i; + $script .= "set 209:string {$args[$i]} || goto %fail%\n"; + } elseif ($arg === '-p') { // PXELINUX prefix path option + ++$i; + $script .= "set 210:string {$args[$i]} || goto %fail%\n"; + } elseif ($arg === '-t') { // PXELINUX timeout option + ++$i; + $script .= "set 211:int32 {$args[$i]} || goto %fail%\n"; + } elseif ($arg === '-o') { // Overriding various DHCP options + ++$i; + if (preg_match('/^((?:0x)?[a-f0-9]{1,4})\.([bwlsh])=(.*)$/i', $args[$i], $out)) { + // TODO: 'q' (8byte) unsupported for now + $opt = intval($out[1], 0); + if ($opt > 0 && $opt < 255) { + static $optType = ['b' => 'uint8', 'w' => 'uint16', 'l' => 'int32', 's' => 'string', 'h' => 'hex']; + $type = $optType[$out[2]]; + $script .= "set {$opt}:{$type} {$args[$i]} || goto %fail%\n"; + } + } + } elseif ($arg{0} === '-') { + continue; + } elseif ($file === false) { + $file = self::parseFile($arg); + } + } + if ($file !== false) { + $url = parse_url($file); + if (isset($url['host'])) { + $script .= "set next-server {$url['host']} || goto %fail%\n"; + } + if (isset($url['path'])) { + $script .= "set filename {$url['path']} || goto %fail%\n"; + } + $script .= "chain -ar {$file} || goto %fail%\n"; + return new CustomBootEntry(['script' => $script]); + } + return null; + } + // "Normal" entry that should be convertible into a StandardBootEntry + $section->kernel = self::parseFile($section->kernel); + foreach ($section->initrd as &$initrd) { + $initrd = self::parseFile($initrd); + } + return BootEntry::newStandardBootEntry($section); + } + + /** + * Parse PXELINUX file notion. Basically, turn + * server::file into tftp://server/file. + * + * @param string $file + * @return string + */ + private static function parseFile($file) + { + if (preg_match(',^([^:/]+)::(.*)$,', $file, $out)) { + return 'tftp://' . $out[1] . '/' . $out[2]; + } + return $file; + } + + public static function sanitizeIpxeString($string) + { + return str_replace(['&', '|', ';', '$', "\r", "\n"], ['+', '/', ':', 'S', ' ', ' '], $string); + } + + public static function makeMd5Pass($plainpass, $salt) + { + if (empty($plainpass)) + return ''; + return md5(md5($plainpass) . '-' . $salt); + } + +} diff --git a/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php new file mode 100644 index 00000000..5c1a87d5 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php @@ -0,0 +1,142 @@ + $menu]); + if (!is_array($menu)) { + $menu = ['menuid' => 'foo', 'title' => 'Invalid Menu ID: ' . (int)$menu]; + } + } + $this->menuid = (int)$menu['menuid']; + $this->timeoutMs = (int)$menu['timeoutms']; + $this->title = $menu['title']; + $this->defaultEntryId = $menu['defaultentryid']; + $res = Database::simpleQuery("SELECT e.menuentryid, e.entryid, e.hotkey, e.title, e.hidden, e.sortval, e.md5pass, + b.data AS bootentry + 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)) { + $this->items[] = new MenuEntry($row); + } + } + + public function getMenuDefinition($targetVar, $mode, $slxExtensions) + { + $str = "menu -- {$this->title}\n"; + foreach ($this->items as $item) { + $str .= $item->getMenuItemScript("m_{$this->menuid}", $this->defaultEntryId, $mode, $slxExtensions); + } + if ($this->defaultEntryId === null) { + $defaultLabel = "mx_{$this->menuid}_poweroff"; + } else { + $defaultLabel = "m_{$this->menuid}_{$this->defaultEntryId}"; + } + $str .= "choose"; + if ($this->timeoutMs > 0) { + $str .= " --timeout {$this->timeoutMs}"; + } + $str .= " $targetVar || goto $defaultLabel || goto fail\n"; + if ($this->defaultEntryId === null) { + $str .= "goto skip_{$defaultLabel}\n" + . ":{$defaultLabel}\n" + . "poweroff || goto fail\n" + . ":skip_{$defaultLabel}\n"; + } + return $str; + } + + public function getItemsCode($mode) + { + $str = ''; + foreach ($this->items as $item) { + $str .= $item->getBootEntryScript("m_{$this->menuid}", 'fail', $mode); + $str .= "goto slx_menu\n"; + } + return $str; + } + + /* + * + */ + + public static function forLocation($locationId) + { + $chain = null; + if (Module::isAvailable('location')) { + $chain = Location::getLocationRootChain($locationId); + } + if (!empty($chain)) { + $res = Database::simpleQuery("SELECT m.menuid, m.timeoutms, m.title, IFNULL(ml.defaultentryid, m.defaultentryid) AS defaultentryid, ml.locationid + FROM serversetup_menu m + INNER JOIN serversetup_menu_location ml USING (menuid) + WHERE ml.locationid IN (:chain)", ['chain' => $chain]); + 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)) { + // Overwrite the value (numeric ascending values, useless) with menu array of according location + $chain[(int)$row['locationid']] = $row; + } + // Use first one that was found + foreach ($chain as $menu) { + if (is_array($menu)) { + return new IPxeMenu($menu); + } + } + // Should never end up here, but we'd just fall through and use the default + } + } + // We're here, no specific menu, use default + $menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid + FROM serversetup_menu + ORDER BY isdefault DESC LIMIT 1"); + if ($menu === false) { + return new EmptyIPxeMenu; + } + return new IPxeMenu($menu); + } + + public static function forClient($ip, $uuid) + { + $locationId = 0; + if (Module::isAvailable('location')) { + $locationId = Location::getFromIpAndUuid($ip, $uuid); + } + return self::forLocation($locationId); + } + +} + +class EmptyIPxeMenu extends IPxeMenu +{ + + /** @noinspection PhpMissingParentConstructorInspection */ + public function __construct() + { + $this->title = 'No menu defined'; + $this->menuid = -1; + $this->items[] = new MenuEntry([ + 'title' => 'Please create a menu in Server-Setup first' + ]); + $this->items[] = new MenuEntry([ + 'title' => 'Bitte erstellen Sie zunächst ein Menü' + ]); + } + +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/inc/localboot.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/localboot.inc.php new file mode 100644 index 00000000..3ab81862 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/inc/localboot.inc.php @@ -0,0 +1,15 @@ + 'iseq efi ${platform} && exit 1 || sanboot --no-describe', + 'EXIT' => 'exit 1', + 'COMBOOT' => 'chain /tftp/chain.c32 hd0', + 'SANBOOT' => 'sanboot --no-describe', + ]; + +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php new file mode 100644 index 00000000..d243fd23 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php @@ -0,0 +1,177 @@ + $value) { + if (property_exists($this, $key)) { + $this->{$key} = $value; + } + } + $this->hotkey = self::getKeyCode($row['hotkey']); + if (!empty($row['bootentry'])) { + $this->bootEntry = BootEntry::fromJson($row['bootentry']); + } + $this->gap = (array_key_exists('entryid', $row) && $row['entryid'] === null); + } + settype($this->hidden, 'bool'); + settype($this->gap, 'bool'); + settype($this->sortval, 'int'); + settype($this->menuentryid, 'int'); + } + + public function getMenuItemScript($lblPrefix, $requestedDefaultId, $mode, $slxExtensions) + { + if ($this->bootEntry !== null && !$this->bootEntry->supportsMode($mode)) + return ''; + $str = 'item '; + if ($this->gap) { + $str .= '--gap '; + } else { + if ($this->hidden && $slxExtensions) { + if ($this->hotkey === false) + return ''; // Hidden entries without hotkey are illegal + $str .= '--hidden '; + } + if ($this->hotkey !== false) { + $str .= '--key ' . $this->hotkey . ' '; + } + if ($this->menuentryid == $requestedDefaultId) { + $str .= '--default '; + } + $str .= "{$lblPrefix}_{$this->menuentryid} "; + } + if (empty($this->title)) { + $str .= '${}'; + } else { + $str .= $this->title; + } + return $str . " || prompt Could not create menu item for {$lblPrefix}_{$this->menuentryid}\n"; + } + + public function getBootEntryScript($lblPrefix, $failLabel, $mode) + { + if ($this->bootEntry === null || !$this->bootEntry->supportsMode($mode)) + return ''; + $str = ":{$lblPrefix}_{$this->menuentryid}\n"; + if (!empty($this->md5pass)) { + $str .= "set slx_hash {$this->md5pass} || goto $failLabel\n" + . "set slx_salt {$this->menuentryid} || goto $failLabel\n" + . "set slx_pw_ok {$lblPrefix}_ok || goto $failLabel\n" + . "set slx_pw_fail slx_menu || goto $failLabel\n" + . "goto slx_pass_check || goto $failLabel\n" + . ":{$lblPrefix}_ok\n"; + } + return $str . $this->bootEntry->toScript($failLabel, $mode); + } + + /* + * + */ + + private static function getKeyArray() + { + static $data = false; + if ($data === false) { + $data = [ + 'F5' => 0x107e, + 'F6' => 0x127e, + 'F7' => 0x137e, + 'F8' => 0x147e, + 'F9' => 0x157e, + 'F10' => 0x167e, + 'F11' => 0x187e, + 'F12' => 0x197e, + ]; + for ($i = 1; $i <= 26; ++$i) { + $letter = chr(0x40 + $i); + $data['SHIFT_' . $letter] = 0x40 + $i; + if ($letter !== 'C') { + $data['CTRL_' . $letter] = $i; + } + $data[$letter] = 0x60 + $i; + } + for ($i = 0; $i <= 9; ++$i) { + $data[chr(0x30 + $i)] = 0x30 + $i; + } + asort($data, SORT_NUMERIC); + } + return $data; + } + + /** + * Get all the known/supported keys, usable for menu items. + * + * @return string[] list of known key names + */ + public static function getKeyList() + { + return array_keys(self::getKeyArray()); + } + + /** + * 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) + { + $data = self::getKeyArray(); + if (isset($data[$keyName])) + return '0x' . dechex($data[$keyName]); + return false; + } + + /** + * @param string $keyName desired key name + * @return string $keyName if it's known, empty string otherwise + */ + public static function filterKeyName($keyName) + { + $data = self::getKeyArray(); + if (isset($data[$keyName])) + return $keyName; + return ''; + } + +} diff --git a/modules-available/serversetup-bwlp-ipxe/inc/pxelinux.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/pxelinux.inc.php new file mode 100644 index 00000000..1d022fef --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/inc/pxelinux.inc.php @@ -0,0 +1,302 @@ + ['string', 'title'], + 'menu default' => ['true', 'isDefault'], + 'menu hide' => ['true', 'isHidden'], + 'menu disabled' => ['true', 'isDisabled'], + 'menu indent' => ['int', 'indent'], + 'kernel' => ['string', 'kernel'], + 'com32' => ['string', 'kernel'], + 'pxe' => ['string', 'kernel'], + 'initrd' => ['string', 'initrd'], + 'append' => ['string', 'append'], + 'ipappend' => ['int', 'ipAppend'], + 'sysappend' => ['int', 'ipAppend'], + 'localboot' => ['int', 'localBoot'], + ]; + $globalPropMap = [ + 'timeout' => ['int', 'timeoutMs', 100], + 'totaltimeout' => ['int', 'totalTimeoutMs', 100], + 'menu title' => ['string', 'title'], + 'menu clear' => ['true', 'menuClear'], + 'menu immediate' => ['true', 'immediateHotkeys'], + 'ontimeout' => ['string', 'timeoutLabel'], + ]; + $lines = preg_split('/[\r\n]+/', $input); + $section = null; + $count = count($lines); + for ($li = 0; $li < $count; ++$li) { + $line =& $lines[$li]; + if (!preg_match('/^\s*([^m]\S*|menu\s+\S+)(\s+.*?|)\s*$/i', $line, $out)) + continue; + $val = trim($out[2]); + $key = trim($out[1]); + $key = strtolower($key); + $key = preg_replace('/\s+/', ' ', $key); + if ($key === 'label') { + if ($section !== null) { + $menu->sections[] = $section; + } + $section = new PxeSection($val); + } elseif ($key === 'menu separator') { + if ($section !== null) { + $menu->sections[] = $section; + $section = null; + } + $menu->sections[] = new PxeSection(null); + } elseif (self::handleKeyword($key, $val, $globalPropMap, $menu)) { + continue; + } elseif ($section === null) { + continue; + } elseif ($key === 'text' && strtolower($val) === 'help') { + $text = ''; + while (++$li < $count) { + $line =& $lines[$li]; + if (strtolower(trim($line)) === 'endtext') + break; + $text .= $line . "\n"; + } + $section->helpText = $text; + } elseif (self::handleKeyword($key, $val, $sectionPropMap, $section)) { + continue; + } + } + if ($section !== null) { + $menu->sections[] = $section; + } + foreach ($menu->sections as $section) { + $section->mangle(); + } + return $menu; + } + + /** + * Check if keyword is valid and if so, add its interpreted value + * 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 + * @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) + { + if (!isset($map[$key])) + return false; + $opt = $map[$key]; + // opt[0] is the type the value should be cast to; special case "true" means + // this is a bool option that will be set as soon as the keyword is present, + // as it doesn't have any parameters + if ($opt[0] === 'true') { + $val = true; + } else { + settype($val, $opt[0]); + } + // If opt[2] is present it's a multiplier for the value + if (isset($opt[2])) { + $val *= $opt[2]; + } + $object->{$opt[1]} = $val; + return true; + } + +} + +/** + * 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 = []; + + public function hash($fuzzy) + { + $ctx = hash_init('md5'); + if (!$fuzzy) { + hash_update($ctx, $this->title); + hash_update($ctx, $this->timeoutLabel); + } + hash_update($ctx, $this->timeoutMs); + foreach ($this->sections as $section) { + if ($fuzzy) { + hash_update($ctx, mb_strtolower(preg_replace('/[^a-zA-Z0-9]/', '', $section->title))); + } else { + hash_update($ctx, $section->label); + hash_update($ctx, $section->title); + hash_update($ctx, $section->indent); + hash_update($ctx, $section->helpText); + hash_update($ctx, $section->isDefault); + hash_update($ctx, $section->hotkey); + } + hash_update($ctx, $section->kernel); + hash_update($ctx, $section->append); + hash_update($ctx, $section->ipAppend); + hash_update($ctx, $section->passwd); + hash_update($ctx, $section->isHidden); + hash_update($ctx, $section->isDisabled); + hash_update($ctx, $section->localBoot); + foreach ($section->initrd as $initrd) { + hash_update($ctx, $initrd); + } + } + return hash_final($ctx, 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 crypted 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 Value of the LOCALBOOT field + */ + public $localBoot; + /** + * @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/install.inc.php b/modules-available/serversetup-bwlp-ipxe/install.inc.php new file mode 100644 index 00000000..25579c13 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/install.inc.php @@ -0,0 +1,89 @@ +compileTask !== null) + return $this->compileTask; + $this->compileTask = Property::get('ipxe-task-id'); + if ($this->compileTask !== false) { + $this->compileTask = Taskmanager::status($this->compileTask); + if (!Taskmanager::isTask($this->compileTask) || Taskmanager::isFinished($this->compileTask)) { + $this->compileTask = false; + } + } + return $this->compileTask; + } + + protected function doPreprocess() + { + User::load(); + + if (!User::isLoggedIn()) { + Message::addError('main.no-permission'); + Util::redirect('?do=Main'); + } + + if (Request::any('action') === 'getimage') { + User::assertPermission("download"); + $this->handleGetImage(); + } + + $this->currentMenu = Property::getBootMenu(); + + $action = Request::post('action'); + + if ($action === false) { + $this->currentAddress = Property::getServerIp(); + $this->getLocalAddresses(); + } + + if ($action === 'compile') { + User::assertPermission("edit.address"); + if ($this->getCompileTask() === false) { + Trigger::ipxe(); + } + Util::redirect('?do=serversetup'); + } + + if ($action === 'ip') { + User::assertPermission("edit.address"); + // New address is to be set + $this->getLocalAddresses(); + $this->updateLocalAddress(); + } + + if ($action === 'savebootentry') { + User::assertPermission('ipxe.bootentry.edit'); + $this->saveBootEntry(); + } + + if ($action === 'deleteBootentry') { + User::assertPermission('ipxe.bootentry.edit'); + $this->deleteBootEntry(); + } + + if ($action === 'savemenu') { + User::assertPermission('ipxe.menu.edit'); + $this->saveMenu(); + } + + if ($action === 'savelocation') { + // Permcheck in function + $this->saveLocationMenu(); + Util::redirect('?do=locations'); + } + + if ($action === 'savelocalboot') { + User::assertPermission('ipxe.localboot.edit'); + $this->saveLocalboot(); + } + + if ($action === 'deleteMenu') { + // Permcheck in function + $this->deleteMenu(); + } + + if ($action === 'setDefaultMenu') { + User::assertPermission('ipxe.menu.edit', 0); + $this->setDefaultMenu(); + } + + if (Request::isPost()) { + Util::redirect('?do=serversetup'); + } + + User::assertPermission('access-page'); + + if (User::hasPermission('ipxe.*')) { + Dashboard::addSubmenu('?do=serversetup&show=menu', Dictionary::translate('submenu_menu', true)); + Dashboard::addSubmenu('?do=serversetup&show=bootentry', Dictionary::translate('submenu_bootentry', true)); + } + if (User::hasPermission('edit.address')) { + Dashboard::addSubmenu('?do=serversetup&show=address', Dictionary::translate('submenu_address', true)); + } + if (User::hasPermission('download')) { + Dashboard::addSubmenu('?do=serversetup&show=download', Dictionary::translate('submenu_download', true)); + } + if (User::hasPermission('ipxe.localboot.*')) { + Dashboard::addSubmenu('?do=serversetup&show=localboot', Dictionary::translate('submenu_localboot', true)); + } + if (Request::get('show') === false) { + $subs = Dashboard::getSubmenus(); + if (empty($subs)) { + User::assertPermission('download'); + } else { + Util::redirect($subs[0]['url']); + } + } + } + + protected function doRender() + { + Render::addTemplate("heading"); + + $task = $this->getCompileTask(); + if ($task !== false) { + $files = []; + if ($task['data'] && $task['data']['files']) { + foreach ($task['data']['files'] as $k => $v) { + $files[] = ['name' => $k, 'namehyphen' => str_replace(['/', '.'], '-', $k)]; + } + } + Render::addTemplate('ipxe_update', array('taskid' => $task['id'], 'files' => $files)); + } + + switch (Request::get('show')) { + case 'editbootentry': + User::assertPermission('ipxe.bootentry.edit'); + $this->showEditBootEntry(); + break; + case 'editmenu': + User::assertPermission('ipxe.menu.view'); + $this->showEditMenu(); + break; + case 'download': + User::assertPermission('download'); + $this->showDownload(); + break; + case 'menu': + User::assertPermission('ipxe.menu.view'); + $this->showMenuList(); + break; + case 'bootentry': + User::assertPermission('ipxe.bootentry.view'); + $this->showBootentryList(); + break; + case 'address': + User::assertPermission('edit.address'); + $this->showEditAddress(); + break; + case 'assignlocation': + // Permcheck in function + $this->showEditLocation(); + break; + case 'localboot': + User::assertPermission('ipxe.localboot.*'); + $this->showLocalbootConfig(); + break; + default: + Util::redirect('?do=serversetup'); + break; + } + } + + private function showDownload() + { + $list = glob('/srv/openslx/www/boot/download/*', GLOB_NOSORT); + usort($list, function ($a, $b) { + return strcmp(substr($a, -4), substr($b, -4)) * 100 + strcmp($a, $b); + }); + $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], + ]; + foreach ($list as $file) { + if ($file{0} === '.') + continue; + if (is_file($file)) { + $base = basename($file); + $features = []; + foreach (preg_split('/[\-\.\/]+/', $base, -1, PREG_SPLIT_NO_EMPTY) as $p) { + if (array_key_exists($p, $strings)) { + $features += $strings[$p]; + } + } + asort($features); + $files[] = [ + 'name' => $base, + 'size' => Util::readableFileSize(filesize($file)), + 'modified' => Util::prettyTime(filemtime($file)), + 'class' => substr($base, -4) === '.usb' ? 'slx-bold' : '', + 'features' => implode(', ', array_keys($features)), + ]; + } + } + Render::addTemplate('download', ['files' => $files]); + } + + private function makeSelectArray($list, $default) + { + $ret = []; + foreach (array_keys($list) as $k) { + $ret[] = [ + 'key' => $k, + 'selected' => ($k === $default ? 'selected' : ''), + ]; + } + return $ret; + } + + private function showLocalbootConfig() + { + // Default setting + $default = Property::get(Localboot::PROPERTY_KEY, 'AUTO'); + if (!array_key_exists($default, Localboot::BOOT_METHODS)) { + $default = 'AUTO'; + } + $optionList = $this->makeSelectArray(Localboot::BOOT_METHODS, $default); + // Exceptions + $cutoff = strtotime('-90 days'); + $models = []; + $res = Database::simpleQuery('SELECT m.systemmodel, cnt, sl.bootmethod FROM ( + SELECT m2.systemmodel, Count(*) AS cnt FROM machine m2 + WHERE m2.lastseen > :cutoff + GROUP BY systemmodel + ) m + LEFT JOIN serversetup_localboot sl USING (systemmodel) + ORDER BY systemmodel', ['cutoff' => $cutoff]); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $row['options'] = $this->makeSelectArray(Localboot::BOOT_METHODS, $row['bootmethod']); + $models[] = $row; + } + // Output + $data = [ + 'default' => $default, + 'options' => $optionList, + 'exceptions' => $models, + ]; + Render::addTemplate('localboot', $data); + } + + private function showBootentryList() + { + $allowEdit = User::hasPermission('ipxe.bootentry.edit'); + + $res = Database::simpleQuery("SELECT be.entryid, be.hotkey, be.title, be.builtin, Count(*) AS refs FROM serversetup_bootentry be + INNER JOIN serversetup_menuentry sm USING (entryid) + GROUP BY be.entryid + ORDER BY be.title ASC"); + $bootentryTable = []; + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $bootentryTable[] = $row; + } + + Render::addTemplate('bootentry-list', array( + 'bootentryTable' => $bootentryTable, + 'allowEdit' => $allowEdit, + )); + } + + private function showMenuList() + { + $allowedEdit = User::getAllowedLocations('ipxe.menu.edit'); + + // TODO Permission::addGlobalTags($perms, null, ['edit.menu', 'edit.address', 'download']); + + $res = Database::simpleQuery("SELECT m.menuid, m.title, m.isdefault, GROUP_CONCAT(l.locationid) AS locations, + GROUP_CONCAT(ll.locationname SEPARATOR ', ') AS locnames + FROM serversetup_menu m + LEFT JOIN serversetup_menu_location l USING (menuid) + LEFT JOIN location ll USING (locationid) + GROUP BY menuid + ORDER BY title"); + $menuTable = []; + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + if (empty($row['locations'])) { + $locations = []; + $row['allowEdit'] = in_array(0, $allowedEdit); + } else { + $locations = explode(',', $row['locations']); + $row['allowEdit'] = empty(array_diff($locations, $allowedEdit)); + } + $row['locationCount'] = empty($locations) ? '' : count($locations); + $menuTable[] = $row; + } + + Render::addTemplate('menu-list', array( + 'menuTable' => $menuTable, + 'showSetDefault' => User::hasPermission('ipxe.menu.edit', 0) + )); + } + + private function hasMenuPermission($menuid, $permission) + { + $allowedEditLocations = User::getAllowedLocations($permission); + $allowEdit = in_array(0, $allowedEditLocations); + if (!$allowEdit) { + // Get locations + $locations = Database::queryColumnArray('SELECT locationid FROM serversetup_menu_location + WHERE menuid = :menuid', compact('menuid')); + if (!empty($locations)) { + $allowEdit = count(array_diff($locations, $allowedEditLocations)) === 0; + } + } + return $allowEdit; + } + + private function showEditMenu() + { + $id = Request::get('id', false, 'int'); + // if = edit, else = add new + if ($id !== 0) { + $menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid, isdefault + FROM serversetup_menu WHERE menuid = :id", compact('id')); + } else { + $menu = []; + $menu['menuid'] = 0; + $menu['timeoutms'] = 0; + $menu['title'] = ''; + $menu['defaultentryid'] = null; + $menu['isdefault'] = false; + } + + if ($menu === false) { + Message::addError('invalid-menu-id', $id); + Util::redirect('?do=serversetup&show=menu'); + } + $highlight = Request::get('highlight', false, 'string'); + if ($id !== 0 && !$this->hasMenuPermission($id, 'ipxe.menu.edit')) { + $menu['readonly'] = 'readonly'; + $menu['disabled'] = 'disabled'; + $menu['plainpass'] = ''; + } + if (!User::hasPermission('ipxe.menu.edit', 0)) { + $menu['globalMenuWarning'] = true; + } + + $menu['timeout'] = round($menu['timeoutms'] / 1000); + $menu['entries'] = []; + $res = Database::simpleQuery("SELECT menuentryid, entryid, hotkey, title, hidden, sortval, plainpass FROM + serversetup_menuentry WHERE menuid = :id ORDER BY sortval ASC", compact('id')); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + if ($row['entryid'] == $highlight) { + $row['highlight'] = 'active'; + } + $menu['entries'][] = $row; + } + $menu['keys'] = array_map(function ($item) { return ['key' => $item]; }, MenuEntry::getKeyList()); + $menu['entrylist'] = Database::queryAll("SELECT entryid, title, hotkey, data FROM serversetup_bootentry ORDER BY title ASC"); + foreach ($menu['entrylist'] as &$bootentry) { + //$bootentry['json'] = $bootentry['data']; + $bootentry['data'] = json_decode($bootentry['data'], true); + if (array_key_exists('arch', $bootentry['data'])) { + $bootentry['data']['PCBIOS'] = array('executable' => $bootentry['data']['executable']['PCBIOS'], + 'initRd' => $bootentry['data']['initRd']['PCBIOS'], + 'commandLine' => $bootentry['data']['commandLine']['PCBIOS']); + $bootentry['data']['EFI'] = array('executable' => $bootentry['data']['executable']['EFI'], + 'initRd' => $bootentry['data']['initRd']['EFI'], + 'commandLine' => $bootentry['data']['commandLine']['EFI']); + + if ($bootentry['data']['arch'] === 'PCBIOS') { + $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_biosOnly', true); + unset($bootentry['data']['EFI']); + } else if ($bootentry['data']['arch'] === 'EFI') { + $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_efiOnly', true); + unset($bootentry['data']['PCBIOS']); + } else { + $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_archBoth', true); + } + + } elseif (!array_key_exists('script', $bootentry['data'])) { + $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_archAgnostic', true); + $bootentry['data']['archAgnostic'] = array('executable' => $bootentry['data']['executable'], + 'initRd' => $bootentry['data']['initRd'], + 'commandLine' => $bootentry['data']['commandLine']); + } + } + foreach ($menu['entries'] as &$entry) { + $entry['isdefault'] = ($entry['menuentryid'] == $menu['defaultentryid']); + // TODO: plainpass only when permissions + } + + Permission::addGlobalTags($menu['perms'], 0, ['ipxe.menu.edit']); + Render::addTemplate('menu-edit', $menu); + } + + private function showEditBootEntry() + { + $params = []; + $id = Request::get('id', false, 'string'); + if ($id === false) { + $params['exec_checked'] = 'checked'; + $params['entryid'] = 'u-' . dechex(mt_rand(0x1000, 0xffff)) . '-' . dechex(time()); + $params['entries'] = [ + ['mode' => 'PCBIOS'], + ['mode' => 'EFI'], + ]; + } else { + // Query existing entry + $row = Database::queryFirst('SELECT entryid, title, builtin, data FROM serversetup_bootentry + WHERE entryid = :id LIMIT 1', ['id' => $id]); + if ($row === false) { + Message::addError('invalid-boot-entry', $id); + Util::redirect('?do=serversetup'); + } + $entry = BootEntry::fromJson($row['data']); + if ($entry === null) { + Message::addError('unknown-bootentry-type', $id); + Util::redirect('?do=serversetup'); + } + $entry->addFormFields($params); + $params['title'] = $row['title']; + $params['entryid'] = $row['entryid']; + $params['builtin'] = $row['builtin']; + $params['menus'] = Database::queryAll('SELECT m.menuid, m.title FROM serversetup_menu m + INNER JOIN serversetup_menuentry me ON (me.menuid = m.menuid) + WHERE me.entryid = :entryid', ['entryid' => $row['entryid']]); + } + + Render::addTemplate('ipxe-new-boot-entry', $params); + } + + private function showEditAddress() + { + Render::addTemplate('ipaddress', array( + 'ips' => $this->addrListTask['data']['addresses'], + 'chooseHintClass' => $this->hasIpSet ? '' : 'alert alert-danger', + 'disabled' => ($this->getCompileTask() === false) ? '' : 'disabled', + )); + } + + // ----------------------------------------------------------------------------------------------- + + private function getLocalAddresses() + { + $this->addrListTask = Taskmanager::submit('LocalAddressesList', array()); + + if ($this->addrListTask === false) { + $this->addrListTask['data']['addresses'] = false; + return false; + } + + if (!Taskmanager::isFinished($this->addrListTask)) { // TODO: Async if just displaying + $this->addrListTask = Taskmanager::waitComplete($this->addrListTask['id'], 4000); + } + + if (Taskmanager::isFailed($this->addrListTask) || !isset($this->addrListTask['data']['addresses'])) { + $this->addrListTask['data']['addresses'] = false; + return false; + } + + $sortIp = array(); + foreach (array_keys($this->addrListTask['data']['addresses']) as $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; + } + if ($this->currentAddress === $item['ip']) { + $item['default'] = true; + $this->hasIpSet = true; + } + $sortIp[] = $item['ip']; + } + unset($item); + array_multisort($sortIp, SORT_STRING, $this->addrListTask['data']['addresses']); + return true; + } + + private function deleteBootEntry() { + $id = Request::post('deleteid', false, 'string'); + if ($id === false) { + Message::addError('main.parameter-missing', 'deleteid'); + return; + } + Database::exec("DELETE FROM serversetup_bootentry WHERE entryid = :entryid", array("entryid" => $id)); + // TODO: Redirect to &show=bootentry + Message::addSuccess('bootentry-deleted'); + } + + private function setDefaultMenu() + { + $id = Request::post('menuid', false, 'int'); + if ($id === false) { + Message::addError('main.parameter-missing', 'menuid'); + return; + } + Database::exec('UPDATE serversetup_menu SET isdefault = (menuid = :menuid)', ['menuid' => $id]); + Message::addSuccess('menu-set-default'); + } + + private function deleteMenu() + { + $id = Request::post('deleteid', false, 'int'); + if ($id === false) { + Message::addError('main.parameter-missing', 'deleteid'); + return; + } + if (!$this->hasMenuPermission($id, 'ipxe.menu.edit')) { + Message::addError('locations.no-permission-location', $id); + return; + } + Database::exec("DELETE FROM serversetup_menu WHERE menuid = :menuid", array("menuid" => $id)); + Message::addSuccess('menu-deleted'); + } + + private function saveMenu() + { + $id = Request::post('menuid', false, 'int'); + if ($id === false) { + Message::addError('main.parameter-missing', 'menuid'); + return; + } + + $insertParams = [ + 'title' => IPxe::sanitizeIpxeString(Request::post('title', '', 'string')), + 'timeoutms' => abs(Request::post('timeout', 0, 'int') * 1000), + ]; + if ($id === 0) { + Database::exec("INSERT INTO serversetup_menu (title, timeoutms, isdefault) VALUES (:title, :timeoutms, 0)", $insertParams); + $menu['menuid'] = $id = Database::lastInsertId(); + } else { + $menu = Database::queryFirst("SELECT m.menuid + FROM serversetup_menu m + WHERE menuid = :id", compact('id')); + if ($menu === false) { + Message::addError('no-such-menu', $id); + return; + } + $insertParams['menuid'] = $id; + Database::exec('UPDATE serversetup_menu SET title = :title, timeoutms = :timeoutms + WHERE menuid = :menuid', $insertParams); + } + + $keepIds = []; + $entries = Request::post('entry', false, 'array'); + $wantedDefaultEntryId = Request::post('defaultentry', null, 'string'); + $defaultEntryId = null; + + if ($entries) { + foreach ($entries as $key => $entry) { + if (!isset($entry['sortval'])) { + error_log(print_r($entry, true)); + continue; + } + // Fallback defaults + $entry += [ + 'entryid' => null, + 'title' => '', + 'hidden' => 0, + 'plainpass' => '', + ]; + $params = [ + 'title' => IPxe::sanitizeIpxeString($entry['title']), + 'sortval' => (int)$entry['sortval'], + 'menuid' => $menu['menuid'], + ]; + if (empty($entry['entryid'])) { + // Spacer + $params += [ + 'entryid' => null, + 'hotkey' => '', + 'hidden' => 0, // Doesn't make any sense + 'plainpass' => '', // Doesn't make any sense + ]; + } else { + $params += [ + 'entryid' => $entry['entryid'], // TODO validate? + 'hotkey' => MenuEntry::filterKeyName($entry['hotkey']), + 'hidden' => (int)$entry['hidden'], // TODO (needs hotkey to make sense) + 'plainpass' => $entry['plainpass'], + ]; + } + if (is_numeric($key)) { + if ((string)$key === $wantedDefaultEntryId) { // Check now that we have generated our key + $defaultEntryId = $key; + } + $keepIds[] = $key; + $params['menuentryid'] = $key; + $params['md5pass'] = IPxe::makeMd5Pass($entry['plainpass'], $key); + $ret = Database::exec('UPDATE serversetup_menuentry + SET entryid = :entryid, hotkey = :hotkey, title = :title, hidden = :hidden, sortval = :sortval, + plainpass = :plainpass, md5pass = :md5pass + WHERE menuid = :menuid AND menuentryid = :menuentryid', $params, true); + } else { + $ret = Database::exec("INSERT INTO serversetup_menuentry + (menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass) + VALUES (:menuid, :entryid, :hotkey, :title, :hidden, :sortval, :plainpass, '')", $params, true); + if ($ret) { + $newKey = Database::lastInsertId(); + if ((string)$key === $wantedDefaultEntryId) { // Check now that we have generated our key + $defaultEntryId = $newKey; + } + $keepIds[] = (int)$newKey; + if (!empty($entry['plainpass'])) { + Database::exec('UPDATE serversetup_menuentry SET md5pass = :md5pass WHERE menuentryid = :id', [ + 'md5pass' => IPxe::makeMd5Pass($entry['plainpass'], $newKey), + 'id' => $newKey, + ]); + } + } + } + + if ($ret === false) { + Message::addWarning('error-saving-entry', $entry['title'], Database::lastError()); + } + } + Database::exec('DELETE FROM serversetup_menuentry WHERE menuid = :menuid AND menuentryid NOT IN (:keep)', + ['menuid' => $menu['menuid'], 'keep' => $keepIds]); + // Set default entry + Database::exec('UPDATE serversetup_menu SET defaultentryid = :default WHERE menuid = :menuid', + ['menuid' => $menu['menuid'], 'default' => $defaultEntryId]); + } else { + Database::exec('DELETE FROM serversetup_menuentry WHERE menuid = :menuid', ['menuid' => $menu['menuid']]); + Database::exec('UPDATE serversetup_menu SET defaultentryid = NULL WHERE menuid = :menuid', ['menuid' => $menu['menuid']]); + } + + Message::addSuccess('menu-saved'); + } + + private function updateLocalAddress() + { + $newAddress = Request::post('ip', 'none', 'string'); + $valid = false; + foreach ($this->addrListTask['data']['addresses'] as $item) { + if ($item['ip'] !== $newAddress) + continue; + $valid = true; + break; + } + if ($valid) { + Property::setServerIp($newAddress); + Util::redirect('?do=ServerSetup'); + } else { + Message::addError('invalid-ip', $newAddress); + } + Util::redirect(); + } + + private function handleGetImage() + { + $file = "/opt/openslx/ipxe/openslx-bootstick.raw"; + if (!is_readable($file)) { + Message::addError('image-not-found'); + return; + } + Header('Content-Type: application/octet-stream'); + Header('Content-Disposition: attachment; filename="openslx-bootstick-' . Property::getServerIp() . '-raw.img"'); + readfile($file); + exit; + } + + private function saveBootEntry() + { + $oldEntryId = Request::post('entryid', false, 'string'); + $newId = Request::post('newid', false, 'string'); + if (!preg_match('/^[a-z0-9\-_]{1,16}$/', $newId)) { + Message::addError('main.parameter-empty', 'newid'); + return; + } + $data = Request::post('entry', false); + if (!is_array($data)) { + Message::addError('missing-bootentry-data'); + return; + } + $type = Request::post('type', false, 'string'); + if ($type === 'exec') { + $entry = BootEntry::newStandardBootEntry($data); + } elseif ($type === 'script') { + $entry = BootEntry::newCustomBootEntry($data); + } else { + Message::addError('unknown-bootentry-type', $type); + return; + } + if ($entry === null) { + Message::addError('main.empty-field'); + Util::redirect('?do=serversetup&show=bootentry'); + } + $params = [ + 'entryid' => $newId, + 'title' => Request::post('title', '', 'string'), + 'data' => json_encode($entry->toArray()), + ]; + // New or update? + if (empty($oldEntryId)) { + // New entry + Database::exec('INSERT INTO serversetup_bootentry (entryid, title, builtin, data) + VALUES (:entryid, :title, 0, :data)', $params); + Message::addSuccess('boot-entry-created', $newId); + } else { + // Edit existing entry + $params['oldid'] = $oldEntryId; + Database::exec('UPDATE serversetup_bootentry SET entryid = :entryid, title = :title, data = :data + WHERE entryid = :oldid', $params); + Message::addSuccess('boot-entry-updated', $newId); + } + Util::redirect('?do=serversetup&show=bootentry'); + } + + private function showEditLocation() + { + $locationId = Request::get('locationid', false, 'int'); + $loc = Location::get($locationId); + if ($loc === false) { + Message::addError('locations.invalid-location-id', $locationId); + return; + } + User::assertPermission('ipxe.menu.assign', $locationId); + // List of menu entries + $res = Database::simpleQuery('SELECT menuentryid, title FROM serversetup_menuentry'); + $menuEntries = $res->fetchAll(PDO::FETCH_KEY_PAIR); + // List of menus + $data = [ + 'locationid' => $locationId, + 'locationName' => $loc['locationname'], + ]; + $res = Database::simpleQuery('SELECT m.menuid, m.title, ml.locationid, ml.defaultentryid, GROUP_CONCAT(me.menuentryid) AS entries FROM serversetup_menu m + LEFT JOIN serversetup_menu_location ml ON (m.menuid = ml.menuid AND ml.locationid = :locationid) + INNER JOIN serversetup_menuentry me ON (m.menuid = me.menuid AND me.entryid IS NOT NULL) + GROUP BY menuid + ORDER BY m.title ASC', ['locationid' => $locationId]); + $menus = []; + $hasDefault = false; + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $eids = explode(',', $row['entries']); + $row['entries'] = []; + foreach ($eids as $eid) { + $row['entries'][] = [ + 'id' => $eid, + 'title' => $menuEntries[$eid], + 'selected' => ($eid == $row['defaultentryid'] ? 'selected' : ''), + ]; + } + if ($row['locationid'] !== null) { + $hasDefault = true; + $row['menu_selected'] = 'checked'; + } + $menus[] = $row; + } + if (!$hasDefault) { + $data['default_selected'] = 'checked'; + } + $data['list'] = $menus; + Render::addTemplate('menu-assign-location', $data); + } + + private function saveLocationMenu() + { + $locationId = Request::post('locationid', false, 'int'); + $loc = Location::get($locationId); + if ($loc === false) { + Message::addError('locations.invalid-location-id', $locationId); + return; + } + User::assertPermission('ipxe.menu.assign', $locationId); + $menuId = Request::post('menuid', false, 'int'); + if ($menuId === 0) { + Database::exec('DELETE FROM serversetup_menu_location WHERE locationid = :locationid', + ['locationid' => $locationId]); + Message::addSuccess('location-use-default', $loc['locationname']); + return; + } + $defaultEntryId = Request::post('defaultentryid-' . $menuId, 0, 'int'); + if ($defaultEntryId === 0) { + $defaultEntryId = null; + } + Database::exec('INSERT INTO serversetup_menu_location (menuid, locationid, defaultentryid) + VALUES (:menuid, :locationid, :defaultentryid) + ON DUPLICATE KEY UPDATE menuid = :menuid, defaultentryid = :defaultentryid', [ + 'menuid' => $menuId, + 'locationid' => $locationId, + 'defaultentryid' => $defaultEntryId + ]); + Message::addSuccess('location-menu-assigned', $loc['locationname']); + } + + private function saveLocalboot() + { + $default = Request::post('default', 'AUTO', 'string'); + if (!array_key_exists($default, Localboot::BOOT_METHODS)) { + Message::addError('localboot-invalid-method', $default); + return; + } + Property::set(Localboot::PROPERTY_KEY, $default); + $overrides = Request::post('override', [], 'array'); + Database::exec('TRUNCATE TABLE serversetup_localboot'); + foreach ($overrides as $model => $mode) { + if (empty($mode)) // No override + continue; + if (!array_key_exists($mode, Localboot::BOOT_METHODS)) { + Message::addWarning('localboot-invalid-method', $mode); + continue; + } + Database::exec('INSERT INTO serversetup_localboot (systemmodel, bootmethod) + VALUES (:model, :mode)', compact('model', 'mode')); + } + Message::addSuccess('localboot-saved'); + Util::redirect('?do=serversetup&show=localboot'); + } + +} diff --git a/modules-available/serversetup-bwlp-ipxe/permissions/permissions.json b/modules-available/serversetup-bwlp-ipxe/permissions/permissions.json new file mode 100644 index 00000000..33cc9cea --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/permissions/permissions.json @@ -0,0 +1,29 @@ +{ + "access-page": { + "location-aware": false + }, + "download": { + "location-aware": false + }, + "edit.address": { + "location-aware": false + }, + "ipxe.bootentry.view": { + "location-aware": false + }, + "ipxe.bootentry.edit": { + "location-aware": false + }, + "ipxe.menu.view": { + "location-aware": false + }, + "ipxe.menu.edit": { + "location-aware": false + }, + "ipxe.menu.assign": { + "location-aware": true + }, + "ipxe.localboot.edit": { + "location-aware": false + } +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/templates/bootentry-list.html b/modules-available/serversetup-bwlp-ipxe/templates/bootentry-list.html new file mode 100644 index 00000000..0cf005c5 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/bootentry-list.html @@ -0,0 +1,83 @@ +

{{lang_bootentryHead}}

+ +

+ {{lang_bootentryIntro}} +

+ + + + + + + + + + + + + {{#bootentryTable}} + + + + + + + + {{/bootentryTable}} + +
{{lang_bootentryTitle}}{{lang_hotkey}}{{lang_refCount}}{{lang_edit}}{{lang_delete}}
+ {{title}} + + {{hotkey}} + + {{refs}} + + {{#allowEdit}} + + + + {{/allowEdit}} + + {{#allowEdit}} + + {{/allowEdit}} +
+
+ {{#allowEdit}} + + + {{lang_addBootentry}} + + {{/allowEdit}} +
+ + +
+ + +
+ + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/templates/download.html b/modules-available/serversetup-bwlp-ipxe/templates/download.html new file mode 100644 index 00000000..62064b66 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/download.html @@ -0,0 +1,53 @@ +
+
+ {{lang_downloadBootImage}} +
+
+ + {{#files}} + + + + + + + {{/files}} +
{{name}}{{size}}{{modified}}({{features}})
+

+ + + {{lang_usbImgHelpBtn}} + +

+

+ {{lang_additionalInfoLink}} {{lang_ipxeWikiUrl}} +

+
+
+ + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/templates/heading.html b/modules-available/serversetup-bwlp-ipxe/templates/heading.html new file mode 100644 index 00000000..e2aa0bff --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/heading.html @@ -0,0 +1,3 @@ + \ 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 new file mode 100644 index 00000000..ea19c417 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/ipaddress.html @@ -0,0 +1,44 @@ +
+
+ {{lang_bootAddress}} +
+
+
+ {{lang_chooseIP}} +
+
+ + + + {{#ips}} + + + {{#default}} + + {{/default}} + {{^default}} + + {{/default}} + + {{/ips}} +
{{ip}} + {{lang_active}} + + +
+

+ {{lang_recompileHint}} +

+
+
+ + +
+
+
\ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/templates/ipxe-new-boot-entry.html b/modules-available/serversetup-bwlp-ipxe/templates/ipxe-new-boot-entry.html new file mode 100644 index 00000000..7e82b5cc --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/ipxe-new-boot-entry.html @@ -0,0 +1,165 @@ +

{{lang_newBootEntryHead}}

+ +{{#builtin}} +
+ {{lang_editBuiltinWarn}} +
+{{/builtin}} + +
+
+ {{lang_bootEntryData}} +
+
+
+ + + + +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+ + +
+
+ + +
+ +
+
+ {{#entries}} +
+
+
+

{{mode}}

+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+ {{/entries}} +
+
+ +
+
+ + +
+
+ + {{#builtin}} +
+ {{lang_editBuiltinWarn}} +
+ {{/builtin}} + +

{{lang_referencingMenus}}:

+ + +
+ +
+
+
+
+ + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/templates/ipxe_update.html b/modules-available/serversetup-bwlp-ipxe/templates/ipxe_update.html new file mode 100644 index 00000000..344d3905 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/ipxe_update.html @@ -0,0 +1,54 @@ +
+
{{lang_menuGeneration}}
+
+
+ {{#files}} +
+ + {{name}} +
+ {{/files}} +
+
+
+ {{lang_generationFailed}} +
+
+
{{lang_menuGeneration}}
+
+
+ + diff --git a/modules-available/serversetup-bwlp-ipxe/templates/localboot.html b/modules-available/serversetup-bwlp-ipxe/templates/localboot.html new file mode 100644 index 00000000..3037de2a --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/localboot.html @@ -0,0 +1,59 @@ +

{{lang_localBootHead}}

+ +

{{lang_localBootIntro}}

+ +
+ + + + +
+
+ + +
+ +
+

+ {{lang_localBootExceptions}} +

+ + + + + + + {{#exceptions}} + + + + + + {{/exceptions}} +
{{lang_systemmodel}}{{lang_count}}{{lang_override}}
{{systemmodel}}{{cnt}} + +
+ +
+ + +
+ +
diff --git a/modules-available/serversetup-bwlp-ipxe/templates/menu-assign-location.html b/modules-available/serversetup-bwlp-ipxe/templates/menu-assign-location.html new file mode 100644 index 00000000..077d137e --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/menu-assign-location.html @@ -0,0 +1,69 @@ +

{{lang_assignMenuToLocation}}

+

{{locationName}}

+ +
+ + + + + + + + + + + + + + + + + + + {{#list}} + + + + + + {{/list}} + +
{{lang_menuTitle}}{{lang_menuEntryOverride}}
+
+ + +
+
+ {{lang_useDefaultMenu}} +
+
+ + +
+
+ {{title}} + + +
+ +
+ +
+ +
+ +
+ + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/templates/menu-edit.html b/modules-available/serversetup-bwlp-ipxe/templates/menu-edit.html new file mode 100644 index 00000000..1598a2b7 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/menu-edit.html @@ -0,0 +1,368 @@ +

{{lang_editMenuHead}}

+ + + + +
+
+ {{title}} + {{^title}} + {{lang_newMenu}} + {{/title}} +
+
+
+ + + + +
+
+ +
+
+ +
+
+
+
+ +
+
+
+ + {{lang_seconds}} +
+
+
+
+ + + + + + + + + + + + + + + {{#entries}} + + + + + + + + + + + + + + + + + {{/entries}} + +
{{lang_entryId}}{{lang_title}}{{lang_hotkey}}{{lang_password}}
+ + +
+ + +
+
+ + + + + + + + + +
+ + +
+
+ +
+
+
+ +
+
+ {{lang_cancel}} + +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/templates/menu-list.html b/modules-available/serversetup-bwlp-ipxe/templates/menu-list.html new file mode 100644 index 00000000..545f22a9 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/menu-list.html @@ -0,0 +1,100 @@ +

{{lang_listOfMenus}}

+ +

+ {{lang_menuListIntro}} +

+ + + + + + + + + + + + + {{#menuTable}} + + + + + + + + {{/menuTable}} + +
{{lang_menuTitle}}{{lang_locationCount}}{{lang_isDefault}}{{lang_edit}}{{lang_delete}}
+ {{title}} + + {{locationCount}} + + {{^isdefault}} + {{#showSetDefault}} +
+ + + +
+ {{/showSetDefault}} + {{/isdefault}} + {{#isdefault}} + + {{/isdefault}} +
+ {{#allowEdit}} + + + + {{/allowEdit}} + + {{#allowDelete}} + + {{/allowDelete}} +
+ + +
+ + + +
+ + +
+ + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/config.json b/modules-available/serversetup-bwlp-pxelinux/config.json new file mode 100644 index 00000000..36268c6a --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/config.json @@ -0,0 +1,3 @@ +{ + "category": "main.settings-server" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/hooks/ipxe-update.inc.php b/modules-available/serversetup-bwlp-pxelinux/hooks/ipxe-update.inc.php new file mode 100644 index 00000000..baa7a1bf --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/hooks/ipxe-update.inc.php @@ -0,0 +1,9 @@ + ['string', 'title'], + 'menu default' => ['true', 'isDefault'], + 'menu hide' => ['true', 'isHidden'], + 'menu disabled' => ['true', 'isDisabled'], + 'menu indent' => ['int', 'indent'], + 'kernel' => ['string', 'kernel'], + 'initrd' => ['string', 'initrd'], + 'append' => ['string', 'append'], + 'ipappend' => ['int', 'ipAppend'], + 'localboot' => ['int', 'localBoot'], + ]; + $globalPropMap = [ + 'timeout' => ['int', 'timeoutMs', 100], + 'totaltimeout' => ['int', 'totalTimeoutMs', 100], + 'menu title' => ['string', 'title'], + 'menu clear' => ['true', 'menuClear'], + 'menu immediate' => ['true', 'immediateHotkeys'], + 'ontimeout' => ['string', 'timeoutLabel'], + ]; + $lines = preg_split('/[\r\n]+/', $input); + $section = null; + $count = count($lines); + for ($li = 0; $li < $count; ++$li) { + $line =& $lines[$li]; + if (!preg_match('/^\s*([^m]\S*|menu\s+\S+)(\s+.*?|)\s*$/i', $line, $out)) + continue; + $key = trim($out[1]); + $key = strtolower($key); + $key = preg_replace('/\s+/', ' ', $key); + if ($key === 'label') { + if ($section !== null) { + $menu->sections[] = $section; + } + $section = new PxeSection($out[2]); + } elseif ($key === 'menu separator') { + if ($section !== null) { + $menu->sections[] = $section; + $section = null; + } + $menu->sections[] = new PxeSection(null); + } elseif (self::handleKeyword($key, $out[2], $globalPropMap, $menu)) { + continue; + } elseif ($section === null) { + continue; + } elseif ($key === 'text' && strtolower($out[2]) === 'help') { + $text = ''; + while (++$li < $count) { + $line =& $lines[$li]; + if (strtolower(trim($line)) === 'endtext') + break; + $text .= $line . "\n"; + } + $section->helpText = $text; + } elseif (self::handleKeyword($key, $out[2], $sectionPropMap, $section)) { + continue; + } + } + if ($section !== null) { + $menu->sections[] = $section; + } + return $menu; + } + + /** + * Check if keyword is valid and if so, add its interpreted value + * 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 + * @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) + { + if (!isset($map[$key])) + return false; + $opt = $map[$key]; + // opt[0] is the type the value should be cast to; special case "true" means + // this is a bool option that will be set as soon as the keyword is present, + // as it doesn't have any parameters + if ($opt[0] === 'true') { + $val = true; + } else { + settype($val, $opt[0]); + } + // If opt[2] is present it's a multiplier for the value + if (isset($opt[2])) { + $val *= $opt[2]; + } + $object->{$opt[1]} = $val; + return true; + } + +} + +/** + * 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 presed instead of just highlighting + */ + public $immediateHotkeys = false; + /** + * @var PxeSection[] list of sections the menu contains + */ + public $sections = []; +} + +/** + * 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 initrd to load for the kernel + */ + 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 crypted 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 Value of the LOCALBOOT field + */ + public $localBoot; + + public function __construct($label) { $this->label = $label; } +} + diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/de/messages.json b/modules-available/serversetup-bwlp-pxelinux/lang/de/messages.json new file mode 100644 index 00000000..3e2cc834 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/de/messages.json @@ -0,0 +1,5 @@ +{ + "image-not-found": "USB-Image nicht gefunden. Generieren Sie das Bootmen\u00fc neu.", + "invalid-ip": "Kein Interface ist auf die Adresse {{0}} konfiguriert", + "no-ip-addr-set": "Bitte w\u00e4hlen Sie die prim\u00e4re IP-Adresse des Servers" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/de/module.json b/modules-available/serversetup-bwlp-pxelinux/lang/de/module.json new file mode 100644 index 00000000..da71d558 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/de/module.json @@ -0,0 +1,4 @@ +{ + "module_name": "iPXE \/ Boot Menu", + "page_title": "PXE- und Boot-Einstellungen" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/de/permissions.json b/modules-available/serversetup-bwlp-pxelinux/lang/de/permissions.json new file mode 100644 index 00000000..98baec3c --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/de/permissions.json @@ -0,0 +1,6 @@ +{ + "access-page": "Seite sehen.", + "download": "USB-Image herunterladen.", + "edit.address": "Boot-Adresse des Servers ausw\u00e4hlen.", + "edit.menu": "Bootmen\u00fc anpassen." +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/de/template-tags.json b/modules-available/serversetup-bwlp-pxelinux/lang/de/template-tags.json new file mode 100644 index 00000000..8d612ab0 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/de/template-tags.json @@ -0,0 +1,33 @@ +{ + "lang_active": "Aktiv", + "lang_bootAddress": "Boot-Adresse des Servers", + "lang_bootBehavior": "Standard-Bootverhalten", + "lang_bootHint": "Das Bootmen\u00fc muss nach einer \u00c4nderung der IP-Adresse neu generiert werden. In der Regel geschieht dies automatisch, der Vorgang kann in der Sektion Bootmen\u00fc allerdings auch manuell ausgel\u00f6st werden.", + "lang_bootInfo": "Hier k\u00f6nnen Anpassungen am Erscheinungsbild des Bootmen\u00fcs vorgenommen werden.", + "lang_bootMenu": "Bootmen\u00fc", + "lang_bootMenuCreate": "Bootmen\u00fc erzeugen", + "lang_chooseIP": "Bitte w\u00e4hlen Sie die IP-Adresse, \u00fcber die der Server von den Clients zum Booten angesprochen werden soll.", + "lang_customEntry": "Eigener Eintrag", + "lang_downloadImage": "USB-Image herunterladen", + "lang_downloadRufus": "Rufus herunterladen", + "lang_example": "Beispiel", + "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_localHDD": "Lokale HDD", + "lang_masterPassword": "Master-Passwort", + "lang_masterPasswordHelp": "Das Master-Passwort wird ben\u00f6tigt, um einen Booteintrag direkt am Client tempor\u00e4r durch Dr\u00fccken der Tab-Taste zu editieren. Da dies f\u00fcr Manipulation am Client genutzt werden kann, sollte diese Funktion unbedingt mit einem Passwort gesch\u00fctzt werden.", + "lang_menuCustom": "Benutzerdefinierter Men\u00fczusatz", + "lang_menuCustomHint1": "Hier haben Sie die M\u00f6glichkeit, eigenen Men\u00fc-Code zum angezeigten PXE-Men\u00fc hinzuzuf\u00fcgen, um z.B. auf weitere PXE-Server zu verweisen. Das Format entspricht dem syslinux Men\u00fcformat.", + "lang_menuCustomHint2": "Sie k\u00f6nnen ein oder mehrere Eintr\u00e4ge erzeugen. Wenn Sie einen Eintrag erzeugen m\u00f6chten, der automatisch gestartet wird, wenn der Benutzer keine Auswahl t\u00e4tigt, vergeben Sie als", + "lang_menuCustomHint3": "und w\u00e4hlen Sie als Standard-Bootverhalten ebenfalls custom.", + "lang_menuDisplayTime": "Anzeigedauer des Men\u00fcs", + "lang_menuGeneration": "Erzeugen des Bootmen\u00fcs", + "lang_moduleHeading": "iPXE \/ Boot Menu", + "lang_pxeBuilt": "PXE-Binary gebaut", + "lang_seconds": "Sekunden", + "lang_set": "Setzen", + "lang_usbBuilt": "USB-Image gebaut", + "lang_usbImage": "USB-Image", + "lang_usbImgHelp": "Mit dem USB-Image k\u00f6nnen Sie einen bootbaren USB-Stick erstellen, \u00fcber den sich bwLehrpool an Rechnern starten l\u00e4sst, die keinen Netzwerkboot unterst\u00fctzen, bzw. f\u00fcr die keine entsprechende DHCP-Konfiguration vorhanden ist. Dies erfordert dann lediglich, dass in der BIOS-Konfiguration des Rechners USB-Boot zugelassen ist. Der Stick dient dabei lediglich als Einstiegspunkt; es ist nach wie vor ein bwLehrpool-Satellitenserver f\u00fcr den eigentlichen Bootvorgang von N\u00f6ten.", + "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!)" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/en/messages.json b/modules-available/serversetup-bwlp-pxelinux/lang/en/messages.json new file mode 100644 index 00000000..d4ba6905 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/en/messages.json @@ -0,0 +1,5 @@ +{ + "image-not-found": "USB image not found. Try regenerating the boot menu first.", + "invalid-ip": "No interface is configured with the address {{0}}", + "no-ip-addr-set": "Please set the server's primary IP address" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/en/module.json b/modules-available/serversetup-bwlp-pxelinux/lang/en/module.json new file mode 100644 index 00000000..aeea610c --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/en/module.json @@ -0,0 +1,3 @@ +{ + "module_name": "iPXE \/ Boot Menu" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/en/permissions.json b/modules-available/serversetup-bwlp-pxelinux/lang/en/permissions.json new file mode 100644 index 00000000..44d1c519 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/en/permissions.json @@ -0,0 +1,6 @@ +{ + "access-page": "View page.", + "download": "Download USB Image.", + "edit.address": "Choose boot address of the server.", + "edit.menu": "Customize boot menu." +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/en/template-tags.json b/modules-available/serversetup-bwlp-pxelinux/lang/en/template-tags.json new file mode 100644 index 00000000..9bb55f93 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/en/template-tags.json @@ -0,0 +1,33 @@ +{ + "lang_active": "Active", + "lang_bootAddress": "Boot Address of the Server", + "lang_bootBehavior": "Default Boot Behavior", + "lang_bootHint": "The Boot menu must be recreated after changing the IP address. Usually this is done automatically, but the process can also be triggered manually in the section of the boot menu.", + "lang_bootInfo": "Here adjustments can be made to the appearance of the boot menu.", + "lang_bootMenu": "Boot Menu", + "lang_bootMenuCreate": "Create Boot Menu", + "lang_chooseIP": "Please select the IP address that the client server will use to boot.", + "lang_customEntry": "Custom entry", + "lang_downloadImage": "Download USB Image", + "lang_downloadRufus": "Download Rufus", + "lang_example": "Example", + "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_localHDD": "Local HDD", + "lang_masterPassword": "Master Password", + "lang_masterPasswordHelp": "The master password is required to edit a boot menu entry. This should be set for security reasons.", + "lang_menuCustom": "Custom Extra Menu", + "lang_menuCustomHint1": "Here you have the opportunity to add your own menu code to the displayed PXE menu, eg to refer to other PXE server. The format corresponds to the syslinux menu format.", + "lang_menuCustomHint2": "You can create one or more entries. If you want to create an entry that starts automatically when the user makes a selection, assign as", + "lang_menuCustomHint3": "and select as the default boot behavior custom as well.", + "lang_menuDisplayTime": "Menu Display Time", + "lang_menuGeneration": "Generating boot menu...", + "lang_moduleHeading": "iPXE \/ Boot Menu", + "lang_pxeBuilt": "Built PXE binary", + "lang_seconds": "Seconds", + "lang_set": "Set", + "lang_usbBuilt": "Built USB image", + "lang_usbImage": "USB image", + "lang_usbImgHelp": "The USB image can be used to create a bootable USB stick, which enables you to boot bwLehrpool without changing your DHCP settings or enabling network boot in the clients. The only requirement is that you enable USB boot in the client's BIOS. The USB stick is only used for bootstrapping, the actual bwLehrpool system is still loaded via network from your local bwLehrpool server.", + "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." +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/pt/messages.json b/modules-available/serversetup-bwlp-pxelinux/lang/pt/messages.json new file mode 100644 index 00000000..65745768 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/pt/messages.json @@ -0,0 +1,3 @@ +{ + "invalid-ip": "Nenhuma interface est\u00e1 configurada com o endere\u00e7o {{0}}" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/pt/module.json b/modules-available/serversetup-bwlp-pxelinux/lang/pt/module.json new file mode 100644 index 00000000..aeea610c --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/pt/module.json @@ -0,0 +1,3 @@ +{ + "module_name": "iPXE \/ Boot Menu" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/pt/template-tags.json b/modules-available/serversetup-bwlp-pxelinux/lang/pt/template-tags.json new file mode 100644 index 00000000..14788767 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/pt/template-tags.json @@ -0,0 +1,38 @@ +{ + "lang_active": "Ativo", + "lang_bootAddress": "Endere\u00e7o Boot do Servidor", + "lang_bootBehavior": "Comportamento Padr\u00e3o de Boot", + "lang_bootHint": "O menu de boot deve ser recriado ap\u00f3s alterar o endere\u00e7o IP. Geralmente isso \u00e9 feito automaticamente, mas o processo tamb\u00e9m pode ser acionado manualmente na se\u00e7\u00e3o do menu de boot.", + "lang_bootInfo": "Aqui ajustes podem ser feitos na apar\u00eancia do menu de boot.", + "lang_bootMenu": "Menu de Boot", + "lang_bootMenuCreate": "Criar Menu de Boot", + "lang_chooseIP": "Por favor, selecione o endere\u00e7o IP que o servidor do cliente utilizar\u00e1 realizar o boot.", + "lang_close": "Fechar", + "lang_compile": "Compilar", + "lang_compileIso": "Compilar .iso", + "lang_compileKkpxe": "Compilar .kkpxe", + "lang_compileUsb": "Compilar .usb", + "lang_compilingIpxe": "Compilando iPXE", + "lang_customScript": "Script Customizado", + "lang_download": "Baixar", + "lang_example": "Exemplo", + "lang_extension": "Extens\u00e3o", + "lang_ipxeAdv": "Gerar iPXE no Modo Avan\u00e7ado", + "lang_ipxeInfo": "Aqui \u00e9 poss\u00edvel compilar e baixar o iPXE utilizando um script customiz\u00e1vel.", + "lang_ipxeSmp": "Gerar iPXE no Modo Simples", + "lang_ipxeSmpInfo": "Aqui voc\u00ea pode escolher gerar o iPXE escolhendo apenas uma das extens\u00f5es abaixo", + "lang_ipxeWarning": "Se esta for a primeira vez compilando, poder\u00e1 levar entre 1 e 4 minutos para que termine.", + "lang_loading": "Carregando", + "lang_localHDD": "HDD Local", + "lang_menuCustom": "Menu Adicional Customizado", + "lang_menuCustomHint1": "Aqui voc\u00ea tem a oportunidade de adicionar seu pr\u00f3prio c\u00f3digo de menu para o menu PXE exibido, por exemplo, para se referir a outro servidor PXE. O formato corresponde ao formato de menu syslinux.", + "lang_menuCustomHint2": "Voc\u00ea pode criar uma ou mais entradas. Se voc\u00ea quiser criar uma entrada que \u00e9 iniciada automaticamente quando o usu\u00e1rio faz uma sele\u00e7\u00e3o, atribua como", + "lang_menuCustomHint3": "e selecione como o comportamento de boot padr\u00e3o tamb\u00e9m my-entry.", + "lang_menuDisplayTime": "Tempo de Exibi\u00e7\u00e3o do Menu", + "lang_mountIpxe": "Montar iPXE", + "lang_restoreDefault": "Restaurar Padr\u00e3o", + "lang_saveScript": "Salvar Script", + "lang_seconds": "Segundos", + "lang_set": "Definir", + "lang_success": "Arquivo criado com sucesso:" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/page.inc.php b/modules-available/serversetup-bwlp-pxelinux/page.inc.php new file mode 100644 index 00000000..52b3afe4 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/page.inc.php @@ -0,0 +1,187 @@ +handleGetImage(); + } + + $this->currentMenu = Property::getBootMenu(); + + $action = Request::post('action'); + + if ($action === false) { + $this->currentAddress = Property::getServerIp(); + $this->getLocalAddresses(); + } + + if ($action === 'ip') { + User::assertPermission("edit.address"); + // New address is to be set + $this->getLocalAddresses(); + $this->updateLocalAddress(); + } + + if ($action === 'ipxe') { + User::assertPermission("edit.menu"); + // iPXE stuff changes + $this->updatePxeMenu(); + } + + if (Request::isPost()) { + Util::redirect('?do=serversetup'); + } + + User::assertPermission('access-page'); + } + + protected function doRender() + { + Render::addTemplate("heading"); + $task = Property::get('ipxe-task-id'); + if ($task !== false) { + $task = Taskmanager::status($task); + if (!Taskmanager::isTask($task) || Taskmanager::isFinished($task)) { + $task = false; + } + } + if ($task !== false) { + Render::addTemplate('ipxe_update', array('taskid' => $task['id'])); + } + + Permission::addGlobalTags($perms, null, ['edit.menu', 'edit.address', 'download']); + + Render::addTemplate('ipaddress', array( + 'ips' => $this->taskStatus['data']['addresses'], + 'chooseHintClass' => $this->hasIpSet ? '' : 'alert alert-danger', + 'editAllowed' => User::hasPermission("edit.address"), + 'perms' => $perms, + )); + $data = $this->currentMenu; + if (!User::hasPermission('edit.menu')) { + unset($data['masterpasswordclear']); + } + if (!isset($data['defaultentry'])) { + $data['defaultentry'] = 'net'; + } + if ($data['defaultentry'] === 'net') { + $data['active-net'] = 'checked'; + } + if ($data['defaultentry'] === 'hdd') { + $data['active-hdd'] = 'checked'; + } + if ($data['defaultentry'] === 'custom') { + $data['active-custom'] = 'checked'; + } + $data['perms'] = $perms; + Render::addTemplate('ipxe', $data); + } + + // ----------------------------------------------------------------------------------------------- + + private function getLocalAddresses() + { + $this->taskStatus = Taskmanager::submit('LocalAddressesList', array()); + + if ($this->taskStatus === false) { + $this->taskStatus['data']['addresses'] = false; + return false; + } + + if (!Taskmanager::isFinished($this->taskStatus)) { // TODO: Async if just displaying + $this->taskStatus = Taskmanager::waitComplete($this->taskStatus['id'], 4000); + } + + if (Taskmanager::isFailed($this->taskStatus) || !isset($this->taskStatus['data']['addresses'])) { + $this->taskStatus['data']['addresses'] = false; + return false; + } + + $sortIp = array(); + foreach (array_keys($this->taskStatus['data']['addresses']) as $key) { + $item = & $this->taskStatus['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->taskStatus['data']['addresses'][$key]); + continue; + } + if ($this->currentAddress === $item['ip']) { + $item['default'] = true; + $this->hasIpSet = true; + } + $sortIp[] = $item['ip']; + } + unset($item); + array_multisort($sortIp, SORT_STRING, $this->taskStatus['data']['addresses']); + return true; + } + + private function updateLocalAddress() + { + $newAddress = Request::post('ip', 'none'); + $valid = false; + foreach ($this->taskStatus['data']['addresses'] as $item) { + if ($item['ip'] !== $newAddress) + continue; + $valid = true; + break; + } + if ($valid) { + Property::setServerIp($newAddress); + Util::redirect('?do=ServerSetup'); + } else { + Message::addError('invalid-ip', $newAddress); + } + Util::redirect(); + } + + private function updatePxeMenu() + { + $timeout = Request::post('timeout', 10); + if ($timeout === '') + $timeout = 0; + if (!is_numeric($timeout) || $timeout < 0) { + Message::addError('main.value-invalid', 'timeout', $timeout); + } + $this->currentMenu['defaultentry'] = Request::post('defaultentry', 'net'); + $this->currentMenu['timeout'] = $timeout; + $this->currentMenu['custom'] = Request::post('custom', ''); + $this->currentMenu['masterpasswordclear'] = Request::post('masterpassword', ''); + if (empty($this->currentMenu['masterpasswordclear'])) + $this->currentMenu['masterpassword'] = 'invalid'; + else + $this->currentMenu['masterpassword'] = Crypto::hash6($this->currentMenu['masterpasswordclear']); + Property::setBootMenu($this->currentMenu); + Trigger::ipxe(); + Util::redirect('?do=ServerSetup'); + } + + private function handleGetImage() + { + $file = "/opt/openslx/ipxe/openslx-bootstick.raw"; + if (!is_readable($file)) { + Message::addError('image-not-found'); + return; + } + Header('Content-Type: application/octet-stream'); + Header('Content-Disposition: attachment; filename="openslx-bootstick-' . Property::getServerIp() . '-raw.img"'); + readfile($file); + exit; + } + +} diff --git a/modules-available/serversetup-bwlp-pxelinux/permissions/permissions.json b/modules-available/serversetup-bwlp-pxelinux/permissions/permissions.json new file mode 100644 index 00000000..44927506 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/permissions/permissions.json @@ -0,0 +1,14 @@ +{ + "access-page": { + "location-aware": false + }, + "download": { + "location-aware": false + }, + "edit.address": { + "location-aware": false + }, + "edit.menu": { + "location-aware": false + } +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/templates/heading.html b/modules-available/serversetup-bwlp-pxelinux/templates/heading.html new file mode 100644 index 00000000..d68360f1 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/templates/heading.html @@ -0,0 +1 @@ +

{{lang_moduleHeading}}

\ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/templates/ipaddress.html b/modules-available/serversetup-bwlp-pxelinux/templates/ipaddress.html new file mode 100644 index 00000000..8d73dfac --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/templates/ipaddress.html @@ -0,0 +1,37 @@ +
+
+ {{lang_bootAddress}} +
+
+
+ {{lang_chooseIP}} +
+
+ + + + {{#ips}} + + + {{#default}} + + {{/default}} + {{^default}} + + {{/default}} + + {{/ips}} +
{{ip}} + {{lang_active}} + + +
+

+ {{lang_bootHint}} +

+
+
+
\ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/templates/ipxe.html b/modules-available/serversetup-bwlp-pxelinux/templates/ipxe.html new file mode 100644 index 00000000..f4b0b4d3 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/templates/ipxe.html @@ -0,0 +1,117 @@ +
+ + + + +
+
+ {{lang_bootMenu}} +
+
+

+ {{lang_bootInfo}} +

+
+ +
+ {{lang_bootBehavior}} +
+ + +
+
+ + +
+
+ + +
+
+ +
+ {{lang_menuDisplayTime}} +
+ + {{lang_seconds}} +
+
+ +
+ {{lang_masterPassword}} +
+ +
+ {{lang_masterPasswordHelp}} +
+ +
+ {{lang_menuCustom}} + +
+
+ + +
+
+ + + + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/templates/ipxe_update.html b/modules-available/serversetup-bwlp-pxelinux/templates/ipxe_update.html new file mode 100644 index 00000000..c5aafa1c --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/templates/ipxe_update.html @@ -0,0 +1,38 @@ +
+
{{lang_menuGeneration}}
+
+ + +
+
+ {{lang_generationFailed}} +
+
+
{{lang_menuGeneration}}
+
+
+ + diff --git a/modules-available/serversetup-bwlp/api.inc.php b/modules-available/serversetup-bwlp/api.inc.php deleted file mode 100644 index 1df0e6e7..00000000 --- a/modules-available/serversetup-bwlp/api.inc.php +++ /dev/null @@ -1,254 +0,0 @@ - $v) { - $query .= $k . '=' . $v . '&'; - } - //$query = substr($query, 0, -1); - echo << $uuid]); - if ($row !== false && !empty($row['systemmodel'])) { - $model = $row['systemmodel']; - } -} -if ($model === false) { - // Otherwise use what iPXE sent us - function modfilt($str) - { - if (empty($str) || preg_match('/product\s+name|be\s+filled|unknown|default\s+string/i', $str)) - return false; - return trim(preg_replace('/\s+/', ' ', $str)); - } - $manuf = modfilt($manuf); - $product = modfilt($product); - if (!empty($product)) { - $model = $product; - if (!empty($manuf)) { - $model .= " ($manuf)"; - } - } -} -// Query -if ($model !== false) { - $row = Database::queryFirst("SELECT bootmethod FROM serversetup_localboot WHERE systemmodel = :model LIMIT 1", - ['model' => $model]); - if ($row !== false) { - $localboot = $row['bootmethod']; - } -} -if ($localboot === false || !isset($BOOT_METHODS[$localboot])) { - $localboot = Property::get(Localboot::PROPERTY_KEY, 'AUTO'); - if (!isset($BOOT_METHODS[$localboot])) { - $localboot = 'AUTO'; - } -} -if (isset($BOOT_METHODS[$localboot])) { - // Move preferred method first - $BOOT_METHODS[] = $BOOT_METHODS[$localboot]; - unset($BOOT_METHODS[$localboot]); - $BOOT_METHODS = array_reverse($BOOT_METHODS); -} - -if ($slxExtensions) { - $slxConsoleUpdate = '--update'; -} else { - $slxConsoleUpdate = ''; -} - -$output = <<getMenuDefinition('target', $platform, $slxExtensions); - -$output .= <<getItemsCode($platform); - -/* - -:i5 -chain -a /tftp/memtest.0 passes=1 onepass || goto membad -prompt Memory OK. Press a key. -goto init - -:i8 -set x:int32 0 -:again -console --left 60 --top 130 --right 67 --bottom 96 --picture bg-load --keep || -console --left 55 --top 88 --right 63 --bottom 64 --picture bg-menu --keep || -inc x -iseq \${x} 20 || goto again -prompt DONE. Press dein Knie. -goto slx_menu - -:membad -iseq \${errno} 0x1 || goto memaborted -params -param scrot \${vram} -imgfetch -a http://132.230.8.113/screen.php##params || -prompt Memory is bad. Press a key. -goto init - -:memaborted -params -param scrot \${vram} -imgfetch -a http://132.230.8.113/screen.php##params || -prompt Memory test aborted. Press a key. -goto init - -*/ - -$output .= << 0) { - EventLog::info('Imported old PXELinux menu, with ' . $num . ' additional IP-range based menus.'); - } else { - EventLog::info('Imported old PXELinux menu.'); - } -} diff --git a/modules-available/serversetup-bwlp/hooks/ipxe-update.inc.php b/modules-available/serversetup-bwlp/hooks/ipxe-update.inc.php deleted file mode 100644 index 166e80a8..00000000 --- a/modules-available/serversetup-bwlp/hooks/ipxe-update.inc.php +++ /dev/null @@ -1,10 +0,0 @@ - Property::getServerIp() -]; -$task = Taskmanager::submit('CompileIPxeNew', $data); -if (!isset($task['id'])) -return false; -Property::set('ipxe-task-id', $task['id'], 15); -return $task['id']; \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/hooks/main-warning.inc.php b/modules-available/serversetup-bwlp/hooks/main-warning.inc.php deleted file mode 100644 index a2eba6ff..00000000 --- a/modules-available/serversetup-bwlp/hooks/main-warning.inc.php +++ /dev/null @@ -1,6 +0,0 @@ - $value) { - if (property_exists($this, $key)) { - $this->{$key} = $value; - } - } - } - } - - public abstract function supportsMode($mode); - - public abstract function toScript($failLabel, $mode); - - public abstract function toArray(); - - public abstract function addFormFields(&$array); - - /* - * - */ - - /** - * Return a BootEntry instance from the serialized data. - * - * @param string $jsonString serialized entry data - * @return BootEntry|null instance representing boot entry, null on error - */ - public static function fromJson($data) - { - if (is_string($data)) { - $data = json_decode($data, true); - } - if (isset($data['script'])) { - return new CustomBootEntry($data); - } - if (isset($data['executable'])) { - return new StandardBootEntry($data); - } - return null; - } - - public static function newStandardBootEntry($initData) - { - $ret = new StandardBootEntry($initData); - $list = []; - if ($ret->arch() !== StandardBootEntry::EFI) { - $list[] = StandardBootEntry::BIOS; - } - if ($ret->arch() === StandardBootEntry::EFI || $ret->arch() === StandardBootEntry::BOTH) { - $list[] = StandardBootEntry::EFI; - } - foreach ($list as $mode) { - if (empty($initData['executable'][$mode])) - return null; - } - return $ret; - } - - public static function newCustomBootEntry($initData) - { - if (empty($initData['script'])) - return null; - return new CustomBootEntry($initData); - } - - /** - * 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 - */ - public static function fromDatabaseId($id) - { - $row = Database::queryFirst("SELECT data FROM serversetup_bootentry - WHERE entryid = :id LIMIT 1", ['id' => $id]); - if ($row === false) - return false; - return self::fromJson($row['data']); - } - -} - -class StandardBootEntry extends BootEntry -{ - protected $executable; - protected $initRd; - protected $commandLine; - protected $replace; - protected $autoUnload; - protected $resetConsole; - protected $arch; // Constants below - - const BIOS = 'PCBIOS'; // Only valid for legacy BIOS boot - const EFI = 'EFI'; // Only valid for EFI boot - const BOTH = 'PCBIOS-EFI'; // Supports both via distinct entry - const AGNOSTIC = 'agnostic'; // Supports both via same entry (PCBIOS entry) - - public function __construct($data = false) - { - if ($data instanceof PxeSection) { - // Gets arrayfied below - $this->executable = $data->kernel; - $this->initRd = $data->initrd; - $this->commandLine = ' ' . str_replace('vga=current', '', $data->append) . ' '; - $this->resetConsole = true; - $this->replace = true; - $this->autoUnload = true; - if (strpos($this->commandLine, ' quiet ') !== false) { - $this->commandLine .= ' loglevel=5 rd.systemd.show_status=auto'; - } - if ($data->ipAppend & 1) { - $this->commandLine .= ' ${ipappend1}'; - } - if ($data->ipAppend & 2) { - $this->commandLine .= ' ${ipappend2}'; - } - if ($data->ipAppend & 4) { - $this->commandLine .= ' SYSUUID=${uuid}'; - } - $this->commandLine = trim(preg_replace('/\s+/', ' ', $this->commandLine)); - } else { - parent::__construct($data); - } - // Convert legacy DB format - foreach (['executable', 'initRd', 'commandLine', 'replace', 'autoUnload', 'resetConsole'] as $key) { - if (!is_array($this->{$key})) { - $this->{$key} = [ 'PCBIOS' => $this->{$key}, 'EFI' => '' ]; - } - } - if ($this->arch === null) { - $this->arch = self::AGNOSTIC; - } - } - - public function arch() - { - return $this->arch; - } - - public function supportsMode($mode) - { - if ($mode === $this->arch || $this->arch === self::AGNOSTIC) - return true; - if ($mode === self::BIOS || $mode === self::EFI) { - return $this->arch === self::BOTH; - } - error_log('Unknown iPXE platform: ' . $mode); - return false; - } - - public function toScript($failLabel, $mode) - { - if (!$this->supportsMode($mode)) { - return "prompt Entry doesn't have an executable for mode $mode\n"; - } - if ($this->arch === self::AGNOSTIC) { - $mode = self::BIOS; - } - - $script = ''; - if ($this->resetConsole[$mode]) { - $script .= "console ||\n"; - } - if (!empty($this->initRd[$mode])) { - $script .= "imgfree ||\n"; - if (!is_array($this->initRd[$mode])) { - $script .= "initrd {$this->initRd[$mode]} || goto $failLabel\n"; - } else { - foreach ($this->initRd[$mode] as $initrd) { - $script .= "initrd $initrd || goto $failLabel\n"; - } - } - } - $script .= "boot "; - if ($this->autoUnload[$mode]) { - $script .= "-a "; - } - if ($this->replace[$mode]) { - $script .= "-r "; - } - $script .= $this->executable[$mode]; - $rdBase = basename($this->initRd[$mode]); - if (!empty($this->commandLine[$mode])) { - $script .= " initrd=$rdBase {$this->commandLine[$mode]}"; - } - $script .= " || goto $failLabel\n"; - if ($this->resetConsole[$mode]) { - $script .= "goto start ||\n"; - } - return $script; - } - - public function addFormFields(&$array) - { - $array[$this->arch . '_selected'] = 'selected'; - foreach ([self::BIOS, self::EFI] as $mode) { - $array['entries'][] = [ - 'is' . $mode => true, - 'mode' => $mode, - 'executable' => $this->executable[$mode], - 'initRd' => $this->initRd[$mode], - 'commandLine' => $this->commandLine[$mode], - 'replace_checked' => $this->replace[$mode] ? 'checked' : '', - 'autoUnload_checked' => $this->autoUnload[$mode] ? 'checked' : '', - 'resetConsole_checked' => $this->resetConsole[$mode] ? 'checked' : '', - ]; - } - $array['exec_checked'] = 'checked'; - } - - public function toArray() - { - return [ - 'executable' => $this->executable, - 'initRd' => $this->initRd, - 'commandLine' => $this->commandLine, - 'replace' => $this->replace, - 'autoUnload' => $this->autoUnload, - 'resetConsole' => $this->resetConsole, - 'arch' => $this->arch, - ]; - } -} - -class CustomBootEntry extends BootEntry -{ - protected $script; - - public function supportsMode($mode) - { - return true; - } - - public function toScript($failLabel, $mode) - { - return str_replace('%fail%', $failLabel, $this->script) . "\n"; - } - - public function addFormFields(&$array) - { - $array['entry'] = [ - 'script' => $this->script, - ]; - $array['script_checked'] = 'checked'; - } - - public function toArray() - { - return ['script' => $this->script]; - } -} diff --git a/modules-available/serversetup-bwlp/inc/ipxe.inc.php b/modules-available/serversetup-bwlp/inc/ipxe.inc.php deleted file mode 100644 index d34839f0..00000000 --- a/modules-available/serversetup-bwlp/inc/ipxe.inc.php +++ /dev/null @@ -1,453 +0,0 @@ -= :start AND endaddr <= :end", compact('start', 'end')); - $locations = []; - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - foreach ($locations as &$loc) { - if ($row['startaddr'] <= $loc['startaddr'] && $row['endaddr'] >= $loc['endaddr']) { - $loc = false; - } elseif ($row['startaddr'] >= $loc['startaddr'] && $row['endaddr'] <= $loc['endaddr']) { - continue 2; - } - } - unset($loc); - $locations[] = $row; - } - $menu = PxeLinux::parsePxeLinux($content); - $key = $menu->hash(true); - if (isset($menus[$key])) { - $menuId = $menus[$key]; - $defId = null; - // Figure out the default label, get it's label name - foreach ($menu->sections as $section) { - if ($section->isDefault) { - $defId = $section; - } elseif ($defId === null && $section->label === $menu->timeoutLabel) { - $defId = $section; - } - } - if ($defId !== null) { - $defId = self::cleanLabelFixLocal($defId); - // Confirm it actually exists (it should since the menu seems identical) and get menuEntryId - $me = Database::queryFirst('SELECT m.defaultentryid, me.menuentryid FROM serversetup_bootentry be - INNER JOIN serversetup_menuentry me ON (be.entryid = me.entryid) - INNER JOIN serversetup_menu m ON (m.menuid = me.menuid) - WHERE be.entryid = :id AND me.menuid = :menuid', - ['id' => $defId, 'menuid' => $menuId]); - if ($me === false || $me['defaultentryid'] == $me['menuentryid']) { - $defId = null; // Not found, or is already default - don't override if it's the same - } else { - $defId = $me['menuentryid']; - } - } - } else { - $menuId = self::insertMenu($menu, 'Imported', false, 0, [], []); - $menus[$key] = $menuId; - $defId = null; - $importCount++; - } - if ($menuId === false) - continue; - foreach ($locations as $loc) { - if ($loc === false) - continue; - Database::exec('INSERT IGNORE INTO serversetup_menu_location (menuid, locationid, defaultentryid) - VALUES (:menuid, :locationid, :def)', [ - 'menuid' => $menuId, - 'locationid' => $loc['locationid'], - 'def' => $defId, - ]); - } - } - return $importCount; - } - - public static function importLegacyMenu($force = false) - { - if (!$force && false !== Database::queryFirst("SELECT entryid FROM serversetup_bootentry WHERE entryid = 'bwlp-default'")) - return false; // Already exists - // Now create the default entry - self::createDefaultEntries(); - $prepend = ['bwlp-default' => false, 'localboot' => false]; - $defaultLabel = 'bwlp-default'; - $menuTitle = 'bwLehrpool Bootauswahl'; - $pxeConfig = ''; - $timeoutSecs = 60; - // Try to import any customization - $oldMenu = Property::getBootMenu(); - if (is_array($oldMenu)) { - // - if (isset($oldMenu['timeout'])) { - $timeoutSecs = (int)$oldMenu['timeout']; - } - if (isset($oldMenu['defaultentry'])) { - if ($oldMenu['defaultentry'] === 'net') { - $defaultLabel = 'bwlp-default'; - } elseif ($oldMenu['defaultentry'] === 'hdd') { - $defaultLabel = 'localboot'; - } elseif ($oldMenu['defaultentry'] === 'custom') { - $defaultLabel = 'custom'; - } - } - if (!empty($oldMenu['custom'])) { - $pxeConfig = $oldMenu['custom']; - } - } - $append = [ - '', - 'bwlp-default-dbg' => false, - '', - 'poweroff' => false, - ]; - return self::insertMenu(PxeLinux::parsePxeLinux($pxeConfig), $menuTitle, $defaultLabel, $timeoutSecs, $prepend, $append); - } - - /** - * @param PxeMenu $pxeMenu - * @param string $menuTitle - * @param string|false $defaultLabel - * @param $defaultTimeoutSeconds - * @param $prepend - * @param $append - * @return bool|int - */ - private static function insertMenu($pxeMenu, $menuTitle, $defaultLabel, $defaultTimeoutSeconds, $prepend, $append) - { - $timeoutMs = []; - $menuEntries = $prepend; - settype($menuEntries, 'array'); - if (!empty($pxeMenu)) { - $pxe =& $pxeMenu; - if (!empty($pxe->title)) { - $menuTitle = $pxe->title; - } - if ($pxe->timeoutLabel !== null) { - $defaultLabel = $pxe->timeoutLabel; - } - $timeoutMs[] = $pxe->timeoutMs; - $timeoutMs[] = $pxe->totalTimeoutMs; - foreach ($pxe->sections as $section) { - if ($section->localBoot || preg_match('/chain\.c32$/i', $section->kernel)) { - $menuEntries['localboot'] = 'localboot'; - 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; - } - continue; - } - $entry = self::pxe2BootEntry($section); - if ($entry === null) - continue; - $label = self::cleanLabelFixLocal($section); - if ($defaultLabel === $section->label) { - $defaultLabel = $label; - } - $hotkey = MenuEntry::filterKeyName($section->hotkey); - // Create boot entry - $data = $entry->toArray(); - Database::exec('INSERT IGNORE INTO serversetup_bootentry (entryid, hotkey, title, builtin, data) - VALUES (:label, :hotkey, :title, 0, :data)', [ - 'label' => $label, - 'hotkey' => $hotkey, - 'title' => self::sanitizeIpxeString($section->title), - 'data' => json_encode($data), - ]); - $menuEntries[$label] = $section; - } - } - if (is_array($append)) { - $menuEntries += $append; - } - if (empty($menuEntries)) - return false; - // Make menu - $timeoutMs = array_filter($timeoutMs, 'is_int'); - if (empty($timeoutMs)) { - $timeoutMs = (int)($defaultTimeoutSeconds * 1000); - } else { - $timeoutMs = min($timeoutMs); - } - $isDefault = (int)(Database::queryFirst('SELECT menuid FROM serversetup_menu WHERE isdefault = 1') === false); - Database::exec("INSERT INTO serversetup_menu (timeoutms, title, defaultentryid, isdefault) - VALUES (:timeoutms, :title, NULL, :isdefault)", [ - 'title' => self::sanitizeIpxeString($menuTitle), - 'timeoutms' => $timeoutMs, - 'isdefault' => $isDefault, - ]); - $menuId = Database::lastInsertId(); - if (!array_key_exists($defaultLabel, $menuEntries) && $timeoutMs > 0) { - $defaultLabel = array_keys($menuEntries)[0]; - } - // Link boot entries to menu - $defaultEntryId = null; - $order = 1000; - foreach ($menuEntries as $label => $entry) { - if (is_string($entry)) { - // Gap entry - Database::exec("INSERT INTO serversetup_menuentry - (menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass) - VALUES (:menuid, :entryid, :hotkey, :title, :hidden, :sortval, '', '')", [ - 'menuid' => $menuId, - 'entryid' => null, - 'hotkey' => '', - 'title' => self::sanitizeIpxeString($entry), - 'hidden' => 0, - 'sortval' => $order += 100, - ]); - continue; - } - $data = Database::queryFirst("SELECT entryid, hotkey, title FROM serversetup_bootentry WHERE entryid = :entryid", ['entryid' => $label]); - if ($data === false) - continue; - $data['pass'] = ''; - $data['hidden'] = 0; - if ($entry instanceof PxeSection) { - $data['hidden'] = (int)$entry->isHidden; - // Prefer explicit data from this imported menu over the defaults - $data['title'] = self::sanitizeIpxeString($entry->title); - if (MenuEntry::getKeyCode($entry->hotkey) !== false) { - $data['hotkey'] = $entry->hotkey; - } - if (!empty($entry->passwd)) { - // Most likely it's a hash so we cannot recover; ask people to reset - $data['pass'] ='please_reset'; - } - } - $data['menuid'] = $menuId; - $data['sortval'] = $order += 100; - $res = Database::exec("INSERT INTO serversetup_menuentry - (menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass) - VALUES (:menuid, :entryid, :hotkey, :title, :hidden, :sortval, :pass, :pass)", $data); - if ($res !== false && $label === $defaultLabel) { - $defaultEntryId = Database::lastInsertId(); - } - } - // Now we can set default entry - if (!empty($defaultEntryId)) { - Database::exec("UPDATE serversetup_menu SET defaultentryid = :entryid WHERE menuid = :menuid", - ['menuid' => $menuId, 'entryid' => $defaultEntryId]); - } - // TODO: masterpw? rather pointless.... - //$oldMenu['masterpasswordclear']; - return $menuId; - } - - private static function createDefaultEntries() - { - $query = 'INSERT IGNORE INTO serversetup_bootentry (entryid, hotkey, title, builtin, data) - VALUES (:entryid, :hotkey, :title, 1, :data)'; - Database::exec($query, - [ - 'entryid' => 'bwlp-default', - 'hotkey' => 'B', - 'title' => 'bwLehrpool-Umgebung starten', - 'data' => json_encode([ - 'executable' => '/boot/default/kernel', - 'initRd' => '/boot/default/initramfs-stage31', - 'commandLine' => 'slxbase=boot/default quiet splash loglevel=5 rd.systemd.show_status=auto intel_iommu=igfx_off ${ipappend1} ${ipappend2}', - 'replace' => true, - 'autoUnload' => true, - 'resetConsole' => true, - ]), - ]); - Database::exec($query, - [ - 'entryid' => 'bwlp-default-dbg', - 'hotkey' => '', - 'title' => 'bwLehrpool-Umgebung starten (nosplash, debug)', - 'data' => json_encode([ - 'executable' => '/boot/default/kernel', - 'initRd' => '/boot/default/initramfs-stage31', - 'commandLine' => 'slxbase=boot/default loglevel=7 intel_iommu=igfx_off ${ipappend1} ${ipappend2}', - 'replace' => true, - 'autoUnload' => true, - 'resetConsole' => true, - ]), - ]); - Database::exec($query, - [ - 'entryid' => 'localboot', - 'hotkey' => 'L', - 'title' => 'Lokales System starten', - 'data' => json_encode([ - 'script' => 'goto slx_localboot || goto %fail% ||', - ]), - ]); - Database::exec($query, - [ - 'entryid' => 'poweroff', - 'hotkey' => 'P', - 'title' => 'Power off', - 'data' => json_encode([ - 'script' => 'poweroff || goto %fail% ||', - ]), - ]); - Database::exec($query, - [ - 'entryid' => 'reboot', - 'hotkey' => 'R', - 'title' => 'Reboot', - 'data' => json_encode([ - 'script' => 'reboot || goto %fail% ||', - ]), - ]); - } - - /** - * Create unique label for a boot entry. It will try to figure out whether - * this is one of our default entries and if not, create a unique label - * representing the menu entry contents. - * Also it patches the entry if it's referencing the local bwlp install - * because side effects. - * - * @param PxeSection $section - * @return string - */ - private static function cleanLabelFixLocal($section) - { - $myip = Property::getServerIp(); - // Detect our "old" entry types - if (count($section->initrd) === 1 && preg_match(",$myip/boot/default/kernel\$,", $section->kernel) - && preg_match(",$myip/boot/default/initramfs-stage31\$,", $section->initrd[0])) { - // Kernel and initrd match, examine KCL - if ($section->append === 'slxbase=boot/default vga=current quiet splash') { - // Normal - return 'bwlp-default'; - } elseif ($section->append === 'slxbase=boot/default') { - // Debug output - return 'bwlp-default-dbg'; - } else { - // Transform to relative URL, leave KCL, fall through to generic label gen - $section->kernel = '/boot/default/kernel'; - $section->initrd = ['/boot/default/initramfs-stage31']; - } - } - // Generic -- "smart" hash of kernel, initrd and command line - $str = $section->kernel . ' ' . implode(',', $section->initrd); - $array = preg_split('/\s+/', $section->append, -1, PREG_SPLIT_NO_EMPTY); - sort($array); - $str .= ' ' . implode(' ', $array); - - return 'i-' . substr(md5($str), 0, 12); - } - - /** - * @param PxeSection $section - * @return BootEntry|null The according boot entry, null if it's unparsable - */ - private static function pxe2BootEntry($section) - { - if (preg_match('/(pxechain\.com|pxechn\.c32)$/i', $section->kernel)) { - // Chaining -- create script - $args = preg_split('/\s+/', $section->append); - $script = ''; - $file = false; - for ($i = 0; $i < count($args); ++$i) { - $arg = $args[$i]; - if ($arg === '-c') { // PXELINUX config file option - ++$i; - $script .= "set 209:string {$args[$i]} || goto %fail%\n"; - } elseif ($arg === '-p') { // PXELINUX prefix path option - ++$i; - $script .= "set 210:string {$args[$i]} || goto %fail%\n"; - } elseif ($arg === '-t') { // PXELINUX timeout option - ++$i; - $script .= "set 211:int32 {$args[$i]} || goto %fail%\n"; - } elseif ($arg === '-o') { // Overriding various DHCP options - ++$i; - if (preg_match('/^((?:0x)?[a-f0-9]{1,4})\.([bwlsh])=(.*)$/i', $args[$i], $out)) { - // TODO: 'q' (8byte) unsupported for now - $opt = intval($out[1], 0); - if ($opt > 0 && $opt < 255) { - static $optType = ['b' => 'uint8', 'w' => 'uint16', 'l' => 'int32', 's' => 'string', 'h' => 'hex']; - $type = $optType[$out[2]]; - $script .= "set {$opt}:{$type} {$args[$i]} || goto %fail%\n"; - } - } - } elseif ($arg{0} === '-') { - continue; - } elseif ($file === false) { - $file = self::parseFile($arg); - } - } - if ($file !== false) { - $url = parse_url($file); - if (isset($url['host'])) { - $script .= "set next-server {$url['host']} || goto %fail%\n"; - } - if (isset($url['path'])) { - $script .= "set filename {$url['path']} || goto %fail%\n"; - } - $script .= "chain -ar {$file} || goto %fail%\n"; - return new CustomBootEntry(['script' => $script]); - } - return null; - } - // "Normal" entry that should be convertible into a StandardBootEntry - $section->kernel = self::parseFile($section->kernel); - foreach ($section->initrd as &$initrd) { - $initrd = self::parseFile($initrd); - } - return BootEntry::newStandardBootEntry($section); - } - - /** - * Parse PXELINUX file notion. Basically, turn - * server::file into tftp://server/file. - * - * @param string $file - * @return string - */ - private static function parseFile($file) - { - if (preg_match(',^([^:/]+)::(.*)$,', $file, $out)) { - return 'tftp://' . $out[1] . '/' . $out[2]; - } - return $file; - } - - public static function sanitizeIpxeString($string) - { - return str_replace(['&', '|', ';', '$', "\r", "\n"], ['+', '/', ':', 'S', ' ', ' '], $string); - } - - public static function makeMd5Pass($plainpass, $salt) - { - if (empty($plainpass)) - return ''; - return md5(md5($plainpass) . '-' . $salt); - } - -} diff --git a/modules-available/serversetup-bwlp/inc/ipxemenu.inc.php b/modules-available/serversetup-bwlp/inc/ipxemenu.inc.php deleted file mode 100644 index 5c1a87d5..00000000 --- a/modules-available/serversetup-bwlp/inc/ipxemenu.inc.php +++ /dev/null @@ -1,142 +0,0 @@ - $menu]); - if (!is_array($menu)) { - $menu = ['menuid' => 'foo', 'title' => 'Invalid Menu ID: ' . (int)$menu]; - } - } - $this->menuid = (int)$menu['menuid']; - $this->timeoutMs = (int)$menu['timeoutms']; - $this->title = $menu['title']; - $this->defaultEntryId = $menu['defaultentryid']; - $res = Database::simpleQuery("SELECT e.menuentryid, e.entryid, e.hotkey, e.title, e.hidden, e.sortval, e.md5pass, - b.data AS bootentry - 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)) { - $this->items[] = new MenuEntry($row); - } - } - - public function getMenuDefinition($targetVar, $mode, $slxExtensions) - { - $str = "menu -- {$this->title}\n"; - foreach ($this->items as $item) { - $str .= $item->getMenuItemScript("m_{$this->menuid}", $this->defaultEntryId, $mode, $slxExtensions); - } - if ($this->defaultEntryId === null) { - $defaultLabel = "mx_{$this->menuid}_poweroff"; - } else { - $defaultLabel = "m_{$this->menuid}_{$this->defaultEntryId}"; - } - $str .= "choose"; - if ($this->timeoutMs > 0) { - $str .= " --timeout {$this->timeoutMs}"; - } - $str .= " $targetVar || goto $defaultLabel || goto fail\n"; - if ($this->defaultEntryId === null) { - $str .= "goto skip_{$defaultLabel}\n" - . ":{$defaultLabel}\n" - . "poweroff || goto fail\n" - . ":skip_{$defaultLabel}\n"; - } - return $str; - } - - public function getItemsCode($mode) - { - $str = ''; - foreach ($this->items as $item) { - $str .= $item->getBootEntryScript("m_{$this->menuid}", 'fail', $mode); - $str .= "goto slx_menu\n"; - } - return $str; - } - - /* - * - */ - - public static function forLocation($locationId) - { - $chain = null; - if (Module::isAvailable('location')) { - $chain = Location::getLocationRootChain($locationId); - } - if (!empty($chain)) { - $res = Database::simpleQuery("SELECT m.menuid, m.timeoutms, m.title, IFNULL(ml.defaultentryid, m.defaultentryid) AS defaultentryid, ml.locationid - FROM serversetup_menu m - INNER JOIN serversetup_menu_location ml USING (menuid) - WHERE ml.locationid IN (:chain)", ['chain' => $chain]); - 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)) { - // Overwrite the value (numeric ascending values, useless) with menu array of according location - $chain[(int)$row['locationid']] = $row; - } - // Use first one that was found - foreach ($chain as $menu) { - if (is_array($menu)) { - return new IPxeMenu($menu); - } - } - // Should never end up here, but we'd just fall through and use the default - } - } - // We're here, no specific menu, use default - $menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid - FROM serversetup_menu - ORDER BY isdefault DESC LIMIT 1"); - if ($menu === false) { - return new EmptyIPxeMenu; - } - return new IPxeMenu($menu); - } - - public static function forClient($ip, $uuid) - { - $locationId = 0; - if (Module::isAvailable('location')) { - $locationId = Location::getFromIpAndUuid($ip, $uuid); - } - return self::forLocation($locationId); - } - -} - -class EmptyIPxeMenu extends IPxeMenu -{ - - /** @noinspection PhpMissingParentConstructorInspection */ - public function __construct() - { - $this->title = 'No menu defined'; - $this->menuid = -1; - $this->items[] = new MenuEntry([ - 'title' => 'Please create a menu in Server-Setup first' - ]); - $this->items[] = new MenuEntry([ - 'title' => 'Bitte erstellen Sie zunächst ein Menü' - ]); - } - -} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/inc/localboot.inc.php b/modules-available/serversetup-bwlp/inc/localboot.inc.php deleted file mode 100644 index 3ab81862..00000000 --- a/modules-available/serversetup-bwlp/inc/localboot.inc.php +++ /dev/null @@ -1,15 +0,0 @@ - 'iseq efi ${platform} && exit 1 || sanboot --no-describe', - 'EXIT' => 'exit 1', - 'COMBOOT' => 'chain /tftp/chain.c32 hd0', - 'SANBOOT' => 'sanboot --no-describe', - ]; - -} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/inc/menuentry.inc.php b/modules-available/serversetup-bwlp/inc/menuentry.inc.php deleted file mode 100644 index d243fd23..00000000 --- a/modules-available/serversetup-bwlp/inc/menuentry.inc.php +++ /dev/null @@ -1,177 +0,0 @@ - $value) { - if (property_exists($this, $key)) { - $this->{$key} = $value; - } - } - $this->hotkey = self::getKeyCode($row['hotkey']); - if (!empty($row['bootentry'])) { - $this->bootEntry = BootEntry::fromJson($row['bootentry']); - } - $this->gap = (array_key_exists('entryid', $row) && $row['entryid'] === null); - } - settype($this->hidden, 'bool'); - settype($this->gap, 'bool'); - settype($this->sortval, 'int'); - settype($this->menuentryid, 'int'); - } - - public function getMenuItemScript($lblPrefix, $requestedDefaultId, $mode, $slxExtensions) - { - if ($this->bootEntry !== null && !$this->bootEntry->supportsMode($mode)) - return ''; - $str = 'item '; - if ($this->gap) { - $str .= '--gap '; - } else { - if ($this->hidden && $slxExtensions) { - if ($this->hotkey === false) - return ''; // Hidden entries without hotkey are illegal - $str .= '--hidden '; - } - if ($this->hotkey !== false) { - $str .= '--key ' . $this->hotkey . ' '; - } - if ($this->menuentryid == $requestedDefaultId) { - $str .= '--default '; - } - $str .= "{$lblPrefix}_{$this->menuentryid} "; - } - if (empty($this->title)) { - $str .= '${}'; - } else { - $str .= $this->title; - } - return $str . " || prompt Could not create menu item for {$lblPrefix}_{$this->menuentryid}\n"; - } - - public function getBootEntryScript($lblPrefix, $failLabel, $mode) - { - if ($this->bootEntry === null || !$this->bootEntry->supportsMode($mode)) - return ''; - $str = ":{$lblPrefix}_{$this->menuentryid}\n"; - if (!empty($this->md5pass)) { - $str .= "set slx_hash {$this->md5pass} || goto $failLabel\n" - . "set slx_salt {$this->menuentryid} || goto $failLabel\n" - . "set slx_pw_ok {$lblPrefix}_ok || goto $failLabel\n" - . "set slx_pw_fail slx_menu || goto $failLabel\n" - . "goto slx_pass_check || goto $failLabel\n" - . ":{$lblPrefix}_ok\n"; - } - return $str . $this->bootEntry->toScript($failLabel, $mode); - } - - /* - * - */ - - private static function getKeyArray() - { - static $data = false; - if ($data === false) { - $data = [ - 'F5' => 0x107e, - 'F6' => 0x127e, - 'F7' => 0x137e, - 'F8' => 0x147e, - 'F9' => 0x157e, - 'F10' => 0x167e, - 'F11' => 0x187e, - 'F12' => 0x197e, - ]; - for ($i = 1; $i <= 26; ++$i) { - $letter = chr(0x40 + $i); - $data['SHIFT_' . $letter] = 0x40 + $i; - if ($letter !== 'C') { - $data['CTRL_' . $letter] = $i; - } - $data[$letter] = 0x60 + $i; - } - for ($i = 0; $i <= 9; ++$i) { - $data[chr(0x30 + $i)] = 0x30 + $i; - } - asort($data, SORT_NUMERIC); - } - return $data; - } - - /** - * Get all the known/supported keys, usable for menu items. - * - * @return string[] list of known key names - */ - public static function getKeyList() - { - return array_keys(self::getKeyArray()); - } - - /** - * 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) - { - $data = self::getKeyArray(); - if (isset($data[$keyName])) - return '0x' . dechex($data[$keyName]); - return false; - } - - /** - * @param string $keyName desired key name - * @return string $keyName if it's known, empty string otherwise - */ - public static function filterKeyName($keyName) - { - $data = self::getKeyArray(); - if (isset($data[$keyName])) - return $keyName; - return ''; - } - -} diff --git a/modules-available/serversetup-bwlp/inc/pxelinux.inc.php b/modules-available/serversetup-bwlp/inc/pxelinux.inc.php deleted file mode 100644 index 1d022fef..00000000 --- a/modules-available/serversetup-bwlp/inc/pxelinux.inc.php +++ /dev/null @@ -1,302 +0,0 @@ - ['string', 'title'], - 'menu default' => ['true', 'isDefault'], - 'menu hide' => ['true', 'isHidden'], - 'menu disabled' => ['true', 'isDisabled'], - 'menu indent' => ['int', 'indent'], - 'kernel' => ['string', 'kernel'], - 'com32' => ['string', 'kernel'], - 'pxe' => ['string', 'kernel'], - 'initrd' => ['string', 'initrd'], - 'append' => ['string', 'append'], - 'ipappend' => ['int', 'ipAppend'], - 'sysappend' => ['int', 'ipAppend'], - 'localboot' => ['int', 'localBoot'], - ]; - $globalPropMap = [ - 'timeout' => ['int', 'timeoutMs', 100], - 'totaltimeout' => ['int', 'totalTimeoutMs', 100], - 'menu title' => ['string', 'title'], - 'menu clear' => ['true', 'menuClear'], - 'menu immediate' => ['true', 'immediateHotkeys'], - 'ontimeout' => ['string', 'timeoutLabel'], - ]; - $lines = preg_split('/[\r\n]+/', $input); - $section = null; - $count = count($lines); - for ($li = 0; $li < $count; ++$li) { - $line =& $lines[$li]; - if (!preg_match('/^\s*([^m]\S*|menu\s+\S+)(\s+.*?|)\s*$/i', $line, $out)) - continue; - $val = trim($out[2]); - $key = trim($out[1]); - $key = strtolower($key); - $key = preg_replace('/\s+/', ' ', $key); - if ($key === 'label') { - if ($section !== null) { - $menu->sections[] = $section; - } - $section = new PxeSection($val); - } elseif ($key === 'menu separator') { - if ($section !== null) { - $menu->sections[] = $section; - $section = null; - } - $menu->sections[] = new PxeSection(null); - } elseif (self::handleKeyword($key, $val, $globalPropMap, $menu)) { - continue; - } elseif ($section === null) { - continue; - } elseif ($key === 'text' && strtolower($val) === 'help') { - $text = ''; - while (++$li < $count) { - $line =& $lines[$li]; - if (strtolower(trim($line)) === 'endtext') - break; - $text .= $line . "\n"; - } - $section->helpText = $text; - } elseif (self::handleKeyword($key, $val, $sectionPropMap, $section)) { - continue; - } - } - if ($section !== null) { - $menu->sections[] = $section; - } - foreach ($menu->sections as $section) { - $section->mangle(); - } - return $menu; - } - - /** - * Check if keyword is valid and if so, add its interpreted value - * 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 - * @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) - { - if (!isset($map[$key])) - return false; - $opt = $map[$key]; - // opt[0] is the type the value should be cast to; special case "true" means - // this is a bool option that will be set as soon as the keyword is present, - // as it doesn't have any parameters - if ($opt[0] === 'true') { - $val = true; - } else { - settype($val, $opt[0]); - } - // If opt[2] is present it's a multiplier for the value - if (isset($opt[2])) { - $val *= $opt[2]; - } - $object->{$opt[1]} = $val; - return true; - } - -} - -/** - * 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 = []; - - public function hash($fuzzy) - { - $ctx = hash_init('md5'); - if (!$fuzzy) { - hash_update($ctx, $this->title); - hash_update($ctx, $this->timeoutLabel); - } - hash_update($ctx, $this->timeoutMs); - foreach ($this->sections as $section) { - if ($fuzzy) { - hash_update($ctx, mb_strtolower(preg_replace('/[^a-zA-Z0-9]/', '', $section->title))); - } else { - hash_update($ctx, $section->label); - hash_update($ctx, $section->title); - hash_update($ctx, $section->indent); - hash_update($ctx, $section->helpText); - hash_update($ctx, $section->isDefault); - hash_update($ctx, $section->hotkey); - } - hash_update($ctx, $section->kernel); - hash_update($ctx, $section->append); - hash_update($ctx, $section->ipAppend); - hash_update($ctx, $section->passwd); - hash_update($ctx, $section->isHidden); - hash_update($ctx, $section->isDisabled); - hash_update($ctx, $section->localBoot); - foreach ($section->initrd as $initrd) { - hash_update($ctx, $initrd); - } - } - return hash_final($ctx, 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 crypted 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 Value of the LOCALBOOT field - */ - public $localBoot; - /** - * @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/install.inc.php b/modules-available/serversetup-bwlp/install.inc.php deleted file mode 100644 index 25579c13..00000000 --- a/modules-available/serversetup-bwlp/install.inc.php +++ /dev/null @@ -1,89 +0,0 @@ -compileTask !== null) - return $this->compileTask; - $this->compileTask = Property::get('ipxe-task-id'); - if ($this->compileTask !== false) { - $this->compileTask = Taskmanager::status($this->compileTask); - if (!Taskmanager::isTask($this->compileTask) || Taskmanager::isFinished($this->compileTask)) { - $this->compileTask = false; - } - } - return $this->compileTask; - } - - protected function doPreprocess() - { - User::load(); - - if (!User::isLoggedIn()) { - Message::addError('main.no-permission'); - Util::redirect('?do=Main'); - } - - if (Request::any('action') === 'getimage') { - User::assertPermission("download"); - $this->handleGetImage(); - } - - $this->currentMenu = Property::getBootMenu(); - - $action = Request::post('action'); - - if ($action === false) { - $this->currentAddress = Property::getServerIp(); - $this->getLocalAddresses(); - } - - if ($action === 'compile') { - User::assertPermission("edit.address"); - if ($this->getCompileTask() === false) { - Trigger::ipxe(); - } - Util::redirect('?do=serversetup'); - } - - if ($action === 'ip') { - User::assertPermission("edit.address"); - // New address is to be set - $this->getLocalAddresses(); - $this->updateLocalAddress(); - } - - if ($action === 'savebootentry') { - User::assertPermission('ipxe.bootentry.edit'); - $this->saveBootEntry(); - } - - if ($action === 'deleteBootentry') { - User::assertPermission('ipxe.bootentry.edit'); - $this->deleteBootEntry(); - } - - if ($action === 'savemenu') { - User::assertPermission('ipxe.menu.edit'); - $this->saveMenu(); - } - - if ($action === 'savelocation') { - // Permcheck in function - $this->saveLocationMenu(); - Util::redirect('?do=locations'); - } - - if ($action === 'savelocalboot') { - User::assertPermission('ipxe.localboot.edit'); - $this->saveLocalboot(); - } - - if ($action === 'deleteMenu') { - // Permcheck in function - $this->deleteMenu(); - } - - if ($action === 'setDefaultMenu') { - User::assertPermission('ipxe.menu.edit', 0); - $this->setDefaultMenu(); - } - - if (Request::isPost()) { - Util::redirect('?do=serversetup'); - } - - User::assertPermission('access-page'); - - if (User::hasPermission('ipxe.*')) { - Dashboard::addSubmenu('?do=serversetup&show=menu', Dictionary::translate('submenu_menu', true)); - Dashboard::addSubmenu('?do=serversetup&show=bootentry', Dictionary::translate('submenu_bootentry', true)); - } - if (User::hasPermission('edit.address')) { - Dashboard::addSubmenu('?do=serversetup&show=address', Dictionary::translate('submenu_address', true)); - } - if (User::hasPermission('download')) { - Dashboard::addSubmenu('?do=serversetup&show=download', Dictionary::translate('submenu_download', true)); - } - if (User::hasPermission('ipxe.localboot.*')) { - Dashboard::addSubmenu('?do=serversetup&show=localboot', Dictionary::translate('submenu_localboot', true)); - } - if (Request::get('show') === false) { - $subs = Dashboard::getSubmenus(); - if (empty($subs)) { - User::assertPermission('download'); - } else { - Util::redirect($subs[0]['url']); - } - } - } - - protected function doRender() - { - Render::addTemplate("heading"); - - $task = $this->getCompileTask(); - if ($task !== false) { - $files = []; - if ($task['data'] && $task['data']['files']) { - foreach ($task['data']['files'] as $k => $v) { - $files[] = ['name' => $k, 'namehyphen' => str_replace(['/', '.'], '-', $k)]; - } - } - Render::addTemplate('ipxe_update', array('taskid' => $task['id'], 'files' => $files)); - } - - switch (Request::get('show')) { - case 'editbootentry': - User::assertPermission('ipxe.bootentry.edit'); - $this->showEditBootEntry(); - break; - case 'editmenu': - User::assertPermission('ipxe.menu.view'); - $this->showEditMenu(); - break; - case 'download': - User::assertPermission('download'); - $this->showDownload(); - break; - case 'menu': - User::assertPermission('ipxe.menu.view'); - $this->showMenuList(); - break; - case 'bootentry': - User::assertPermission('ipxe.bootentry.view'); - $this->showBootentryList(); - break; - case 'address': - User::assertPermission('edit.address'); - $this->showEditAddress(); - break; - case 'assignlocation': - // Permcheck in function - $this->showEditLocation(); - break; - case 'localboot': - User::assertPermission('ipxe.localboot.*'); - $this->showLocalbootConfig(); - break; - default: - Util::redirect('?do=serversetup'); - break; - } - } - - private function showDownload() - { - $list = glob('/srv/openslx/www/boot/download/*', GLOB_NOSORT); - usort($list, function ($a, $b) { - return strcmp(substr($a, -4), substr($b, -4)) * 100 + strcmp($a, $b); - }); - $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], - ]; - foreach ($list as $file) { - if ($file{0} === '.') - continue; - if (is_file($file)) { - $base = basename($file); - $features = []; - foreach (preg_split('/[\-\.\/]+/', $base, -1, PREG_SPLIT_NO_EMPTY) as $p) { - if (array_key_exists($p, $strings)) { - $features += $strings[$p]; - } - } - asort($features); - $files[] = [ - 'name' => $base, - 'size' => Util::readableFileSize(filesize($file)), - 'modified' => Util::prettyTime(filemtime($file)), - 'class' => substr($base, -4) === '.usb' ? 'slx-bold' : '', - 'features' => implode(', ', array_keys($features)), - ]; - } - } - Render::addTemplate('download', ['files' => $files]); - } - - private function makeSelectArray($list, $default) - { - $ret = []; - foreach (array_keys($list) as $k) { - $ret[] = [ - 'key' => $k, - 'selected' => ($k === $default ? 'selected' : ''), - ]; - } - return $ret; - } - - private function showLocalbootConfig() - { - // Default setting - $default = Property::get(Localboot::PROPERTY_KEY, 'AUTO'); - if (!array_key_exists($default, Localboot::BOOT_METHODS)) { - $default = 'AUTO'; - } - $optionList = $this->makeSelectArray(Localboot::BOOT_METHODS, $default); - // Exceptions - $cutoff = strtotime('-90 days'); - $models = []; - $res = Database::simpleQuery('SELECT m.systemmodel, cnt, sl.bootmethod FROM ( - SELECT m2.systemmodel, Count(*) AS cnt FROM machine m2 - WHERE m2.lastseen > :cutoff - GROUP BY systemmodel - ) m - LEFT JOIN serversetup_localboot sl USING (systemmodel) - ORDER BY systemmodel', ['cutoff' => $cutoff]); - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - $row['options'] = $this->makeSelectArray(Localboot::BOOT_METHODS, $row['bootmethod']); - $models[] = $row; - } - // Output - $data = [ - 'default' => $default, - 'options' => $optionList, - 'exceptions' => $models, - ]; - Render::addTemplate('localboot', $data); - } - - private function showBootentryList() - { - $allowEdit = User::hasPermission('ipxe.bootentry.edit'); - - $res = Database::simpleQuery("SELECT be.entryid, be.hotkey, be.title, be.builtin, Count(*) AS refs FROM serversetup_bootentry be - INNER JOIN serversetup_menuentry sm USING (entryid) - GROUP BY be.entryid - ORDER BY be.title ASC"); - $bootentryTable = []; - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - $bootentryTable[] = $row; - } - - Render::addTemplate('bootentry-list', array( - 'bootentryTable' => $bootentryTable, - 'allowEdit' => $allowEdit, - )); - } - - private function showMenuList() - { - $allowedEdit = User::getAllowedLocations('ipxe.menu.edit'); - - // TODO Permission::addGlobalTags($perms, null, ['edit.menu', 'edit.address', 'download']); - - $res = Database::simpleQuery("SELECT m.menuid, m.title, m.isdefault, GROUP_CONCAT(l.locationid) AS locations, - GROUP_CONCAT(ll.locationname SEPARATOR ', ') AS locnames - FROM serversetup_menu m - LEFT JOIN serversetup_menu_location l USING (menuid) - LEFT JOIN location ll USING (locationid) - GROUP BY menuid - ORDER BY title"); - $menuTable = []; - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - if (empty($row['locations'])) { - $locations = []; - $row['allowEdit'] = in_array(0, $allowedEdit); - } else { - $locations = explode(',', $row['locations']); - $row['allowEdit'] = empty(array_diff($locations, $allowedEdit)); - } - $row['locationCount'] = empty($locations) ? '' : count($locations); - $menuTable[] = $row; - } - - Render::addTemplate('menu-list', array( - 'menuTable' => $menuTable, - 'showSetDefault' => User::hasPermission('ipxe.menu.edit', 0) - )); - } - - private function hasMenuPermission($menuid, $permission) - { - $allowedEditLocations = User::getAllowedLocations($permission); - $allowEdit = in_array(0, $allowedEditLocations); - if (!$allowEdit) { - // Get locations - $locations = Database::queryColumnArray('SELECT locationid FROM serversetup_menu_location - WHERE menuid = :menuid', compact('menuid')); - if (!empty($locations)) { - $allowEdit = count(array_diff($locations, $allowedEditLocations)) === 0; - } - } - return $allowEdit; - } - - private function showEditMenu() - { - $id = Request::get('id', false, 'int'); - // if = edit, else = add new - if ($id !== 0) { - $menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid, isdefault - FROM serversetup_menu WHERE menuid = :id", compact('id')); - } else { - $menu = []; - $menu['menuid'] = 0; - $menu['timeoutms'] = 0; - $menu['title'] = ''; - $menu['defaultentryid'] = null; - $menu['isdefault'] = false; - } - - if ($menu === false) { - Message::addError('invalid-menu-id', $id); - Util::redirect('?do=serversetup&show=menu'); - } - $highlight = Request::get('highlight', false, 'string'); - if ($id !== 0 && !$this->hasMenuPermission($id, 'ipxe.menu.edit')) { - $menu['readonly'] = 'readonly'; - $menu['disabled'] = 'disabled'; - $menu['plainpass'] = ''; - } - if (!User::hasPermission('ipxe.menu.edit', 0)) { - $menu['globalMenuWarning'] = true; - } - - $menu['timeout'] = round($menu['timeoutms'] / 1000); - $menu['entries'] = []; - $res = Database::simpleQuery("SELECT menuentryid, entryid, hotkey, title, hidden, sortval, plainpass FROM - serversetup_menuentry WHERE menuid = :id ORDER BY sortval ASC", compact('id')); - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - if ($row['entryid'] == $highlight) { - $row['highlight'] = 'active'; - } - $menu['entries'][] = $row; - } - $menu['keys'] = array_map(function ($item) { return ['key' => $item]; }, MenuEntry::getKeyList()); - $menu['entrylist'] = Database::queryAll("SELECT entryid, title, hotkey, data FROM serversetup_bootentry ORDER BY title ASC"); - foreach ($menu['entrylist'] as &$bootentry) { - //$bootentry['json'] = $bootentry['data']; - $bootentry['data'] = json_decode($bootentry['data'], true); - if (array_key_exists('arch', $bootentry['data'])) { - $bootentry['data']['PCBIOS'] = array('executable' => $bootentry['data']['executable']['PCBIOS'], - 'initRd' => $bootentry['data']['initRd']['PCBIOS'], - 'commandLine' => $bootentry['data']['commandLine']['PCBIOS']); - $bootentry['data']['EFI'] = array('executable' => $bootentry['data']['executable']['EFI'], - 'initRd' => $bootentry['data']['initRd']['EFI'], - 'commandLine' => $bootentry['data']['commandLine']['EFI']); - - if ($bootentry['data']['arch'] === 'PCBIOS') { - $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_biosOnly', true); - unset($bootentry['data']['EFI']); - } else if ($bootentry['data']['arch'] === 'EFI') { - $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_efiOnly', true); - unset($bootentry['data']['PCBIOS']); - } else { - $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_archBoth', true); - } - - } elseif (!array_key_exists('script', $bootentry['data'])) { - $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_archAgnostic', true); - $bootentry['data']['archAgnostic'] = array('executable' => $bootentry['data']['executable'], - 'initRd' => $bootentry['data']['initRd'], - 'commandLine' => $bootentry['data']['commandLine']); - } - } - foreach ($menu['entries'] as &$entry) { - $entry['isdefault'] = ($entry['menuentryid'] == $menu['defaultentryid']); - // TODO: plainpass only when permissions - } - - Permission::addGlobalTags($menu['perms'], 0, ['ipxe.menu.edit']); - Render::addTemplate('menu-edit', $menu); - } - - private function showEditBootEntry() - { - $params = []; - $id = Request::get('id', false, 'string'); - if ($id === false) { - $params['exec_checked'] = 'checked'; - $params['entryid'] = 'u-' . dechex(mt_rand(0x1000, 0xffff)) . '-' . dechex(time()); - $params['entries'] = [ - ['mode' => 'PCBIOS'], - ['mode' => 'EFI'], - ]; - } else { - // Query existing entry - $row = Database::queryFirst('SELECT entryid, title, builtin, data FROM serversetup_bootentry - WHERE entryid = :id LIMIT 1', ['id' => $id]); - if ($row === false) { - Message::addError('invalid-boot-entry', $id); - Util::redirect('?do=serversetup'); - } - $entry = BootEntry::fromJson($row['data']); - if ($entry === null) { - Message::addError('unknown-bootentry-type', $id); - Util::redirect('?do=serversetup'); - } - $entry->addFormFields($params); - $params['title'] = $row['title']; - $params['entryid'] = $row['entryid']; - $params['builtin'] = $row['builtin']; - $params['menus'] = Database::queryAll('SELECT m.menuid, m.title FROM serversetup_menu m - INNER JOIN serversetup_menuentry me ON (me.menuid = m.menuid) - WHERE me.entryid = :entryid', ['entryid' => $row['entryid']]); - } - - Render::addTemplate('ipxe-new-boot-entry', $params); - } - - private function showEditAddress() - { - Render::addTemplate('ipaddress', array( - 'ips' => $this->addrListTask['data']['addresses'], - 'chooseHintClass' => $this->hasIpSet ? '' : 'alert alert-danger', - 'disabled' => ($this->getCompileTask() === false) ? '' : 'disabled', - )); - } - - // ----------------------------------------------------------------------------------------------- - - private function getLocalAddresses() - { - $this->addrListTask = Taskmanager::submit('LocalAddressesList', array()); - - if ($this->addrListTask === false) { - $this->addrListTask['data']['addresses'] = false; - return false; - } - - if (!Taskmanager::isFinished($this->addrListTask)) { // TODO: Async if just displaying - $this->addrListTask = Taskmanager::waitComplete($this->addrListTask['id'], 4000); - } - - if (Taskmanager::isFailed($this->addrListTask) || !isset($this->addrListTask['data']['addresses'])) { - $this->addrListTask['data']['addresses'] = false; - return false; - } - - $sortIp = array(); - foreach (array_keys($this->addrListTask['data']['addresses']) as $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; - } - if ($this->currentAddress === $item['ip']) { - $item['default'] = true; - $this->hasIpSet = true; - } - $sortIp[] = $item['ip']; - } - unset($item); - array_multisort($sortIp, SORT_STRING, $this->addrListTask['data']['addresses']); - return true; - } - - private function deleteBootEntry() { - $id = Request::post('deleteid', false, 'string'); - if ($id === false) { - Message::addError('main.parameter-missing', 'deleteid'); - return; - } - Database::exec("DELETE FROM serversetup_bootentry WHERE entryid = :entryid", array("entryid" => $id)); - // TODO: Redirect to &show=bootentry - Message::addSuccess('bootentry-deleted'); - } - - private function setDefaultMenu() - { - $id = Request::post('menuid', false, 'int'); - if ($id === false) { - Message::addError('main.parameter-missing', 'menuid'); - return; - } - Database::exec('UPDATE serversetup_menu SET isdefault = (menuid = :menuid)', ['menuid' => $id]); - Message::addSuccess('menu-set-default'); - } - - private function deleteMenu() - { - $id = Request::post('deleteid', false, 'int'); - if ($id === false) { - Message::addError('main.parameter-missing', 'deleteid'); - return; - } - if (!$this->hasMenuPermission($id, 'ipxe.menu.edit')) { - Message::addError('locations.no-permission-location', $id); - return; - } - Database::exec("DELETE FROM serversetup_menu WHERE menuid = :menuid", array("menuid" => $id)); - Message::addSuccess('menu-deleted'); - } - - private function saveMenu() - { - $id = Request::post('menuid', false, 'int'); - if ($id === false) { - Message::addError('main.parameter-missing', 'menuid'); - return; - } - - $insertParams = [ - 'title' => IPxe::sanitizeIpxeString(Request::post('title', '', 'string')), - 'timeoutms' => abs(Request::post('timeout', 0, 'int') * 1000), - ]; - if ($id === 0) { - Database::exec("INSERT INTO serversetup_menu (title, timeoutms, isdefault) VALUES (:title, :timeoutms, 0)", $insertParams); - $menu['menuid'] = $id = Database::lastInsertId(); - } else { - $menu = Database::queryFirst("SELECT m.menuid - FROM serversetup_menu m - WHERE menuid = :id", compact('id')); - if ($menu === false) { - Message::addError('no-such-menu', $id); - return; - } - $insertParams['menuid'] = $id; - Database::exec('UPDATE serversetup_menu SET title = :title, timeoutms = :timeoutms - WHERE menuid = :menuid', $insertParams); - } - - $keepIds = []; - $entries = Request::post('entry', false, 'array'); - $wantedDefaultEntryId = Request::post('defaultentry', null, 'string'); - $defaultEntryId = null; - - if ($entries) { - foreach ($entries as $key => $entry) { - if (!isset($entry['sortval'])) { - error_log(print_r($entry, true)); - continue; - } - // Fallback defaults - $entry += [ - 'entryid' => null, - 'title' => '', - 'hidden' => 0, - 'plainpass' => '', - ]; - $params = [ - 'title' => IPxe::sanitizeIpxeString($entry['title']), - 'sortval' => (int)$entry['sortval'], - 'menuid' => $menu['menuid'], - ]; - if (empty($entry['entryid'])) { - // Spacer - $params += [ - 'entryid' => null, - 'hotkey' => '', - 'hidden' => 0, // Doesn't make any sense - 'plainpass' => '', // Doesn't make any sense - ]; - } else { - $params += [ - 'entryid' => $entry['entryid'], // TODO validate? - 'hotkey' => MenuEntry::filterKeyName($entry['hotkey']), - 'hidden' => (int)$entry['hidden'], // TODO (needs hotkey to make sense) - 'plainpass' => $entry['plainpass'], - ]; - } - if (is_numeric($key)) { - if ((string)$key === $wantedDefaultEntryId) { // Check now that we have generated our key - $defaultEntryId = $key; - } - $keepIds[] = $key; - $params['menuentryid'] = $key; - $params['md5pass'] = IPxe::makeMd5Pass($entry['plainpass'], $key); - $ret = Database::exec('UPDATE serversetup_menuentry - SET entryid = :entryid, hotkey = :hotkey, title = :title, hidden = :hidden, sortval = :sortval, - plainpass = :plainpass, md5pass = :md5pass - WHERE menuid = :menuid AND menuentryid = :menuentryid', $params, true); - } else { - $ret = Database::exec("INSERT INTO serversetup_menuentry - (menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass) - VALUES (:menuid, :entryid, :hotkey, :title, :hidden, :sortval, :plainpass, '')", $params, true); - if ($ret) { - $newKey = Database::lastInsertId(); - if ((string)$key === $wantedDefaultEntryId) { // Check now that we have generated our key - $defaultEntryId = $newKey; - } - $keepIds[] = (int)$newKey; - if (!empty($entry['plainpass'])) { - Database::exec('UPDATE serversetup_menuentry SET md5pass = :md5pass WHERE menuentryid = :id', [ - 'md5pass' => IPxe::makeMd5Pass($entry['plainpass'], $newKey), - 'id' => $newKey, - ]); - } - } - } - - if ($ret === false) { - Message::addWarning('error-saving-entry', $entry['title'], Database::lastError()); - } - } - Database::exec('DELETE FROM serversetup_menuentry WHERE menuid = :menuid AND menuentryid NOT IN (:keep)', - ['menuid' => $menu['menuid'], 'keep' => $keepIds]); - // Set default entry - Database::exec('UPDATE serversetup_menu SET defaultentryid = :default WHERE menuid = :menuid', - ['menuid' => $menu['menuid'], 'default' => $defaultEntryId]); - } else { - Database::exec('DELETE FROM serversetup_menuentry WHERE menuid = :menuid', ['menuid' => $menu['menuid']]); - Database::exec('UPDATE serversetup_menu SET defaultentryid = NULL WHERE menuid = :menuid', ['menuid' => $menu['menuid']]); - } - - Message::addSuccess('menu-saved'); - } - - private function updateLocalAddress() - { - $newAddress = Request::post('ip', 'none', 'string'); - $valid = false; - foreach ($this->addrListTask['data']['addresses'] as $item) { - if ($item['ip'] !== $newAddress) - continue; - $valid = true; - break; - } - if ($valid) { - Property::setServerIp($newAddress); - Util::redirect('?do=ServerSetup'); - } else { - Message::addError('invalid-ip', $newAddress); - } - Util::redirect(); - } - - private function handleGetImage() - { - $file = "/opt/openslx/ipxe/openslx-bootstick.raw"; - if (!is_readable($file)) { - Message::addError('image-not-found'); - return; - } - Header('Content-Type: application/octet-stream'); - Header('Content-Disposition: attachment; filename="openslx-bootstick-' . Property::getServerIp() . '-raw.img"'); - readfile($file); - exit; - } - - private function saveBootEntry() - { - $oldEntryId = Request::post('entryid', false, 'string'); - $newId = Request::post('newid', false, 'string'); - if (!preg_match('/^[a-z0-9\-_]{1,16}$/', $newId)) { - Message::addError('main.parameter-empty', 'newid'); - return; - } - $data = Request::post('entry', false); - if (!is_array($data)) { - Message::addError('missing-bootentry-data'); - return; - } - $type = Request::post('type', false, 'string'); - if ($type === 'exec') { - $entry = BootEntry::newStandardBootEntry($data); - } elseif ($type === 'script') { - $entry = BootEntry::newCustomBootEntry($data); - } else { - Message::addError('unknown-bootentry-type', $type); - return; - } - if ($entry === null) { - Message::addError('main.empty-field'); - Util::redirect('?do=serversetup&show=bootentry'); - } - $params = [ - 'entryid' => $newId, - 'title' => Request::post('title', '', 'string'), - 'data' => json_encode($entry->toArray()), - ]; - // New or update? - if (empty($oldEntryId)) { - // New entry - Database::exec('INSERT INTO serversetup_bootentry (entryid, title, builtin, data) - VALUES (:entryid, :title, 0, :data)', $params); - Message::addSuccess('boot-entry-created', $newId); - } else { - // Edit existing entry - $params['oldid'] = $oldEntryId; - Database::exec('UPDATE serversetup_bootentry SET entryid = :entryid, title = :title, data = :data - WHERE entryid = :oldid', $params); - Message::addSuccess('boot-entry-updated', $newId); - } - Util::redirect('?do=serversetup&show=bootentry'); - } - - private function showEditLocation() - { - $locationId = Request::get('locationid', false, 'int'); - $loc = Location::get($locationId); - if ($loc === false) { - Message::addError('locations.invalid-location-id', $locationId); - return; - } - User::assertPermission('ipxe.menu.assign', $locationId); - // List of menu entries - $res = Database::simpleQuery('SELECT menuentryid, title FROM serversetup_menuentry'); - $menuEntries = $res->fetchAll(PDO::FETCH_KEY_PAIR); - // List of menus - $data = [ - 'locationid' => $locationId, - 'locationName' => $loc['locationname'], - ]; - $res = Database::simpleQuery('SELECT m.menuid, m.title, ml.locationid, ml.defaultentryid, GROUP_CONCAT(me.menuentryid) AS entries FROM serversetup_menu m - LEFT JOIN serversetup_menu_location ml ON (m.menuid = ml.menuid AND ml.locationid = :locationid) - INNER JOIN serversetup_menuentry me ON (m.menuid = me.menuid AND me.entryid IS NOT NULL) - GROUP BY menuid - ORDER BY m.title ASC', ['locationid' => $locationId]); - $menus = []; - $hasDefault = false; - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - $eids = explode(',', $row['entries']); - $row['entries'] = []; - foreach ($eids as $eid) { - $row['entries'][] = [ - 'id' => $eid, - 'title' => $menuEntries[$eid], - 'selected' => ($eid == $row['defaultentryid'] ? 'selected' : ''), - ]; - } - if ($row['locationid'] !== null) { - $hasDefault = true; - $row['menu_selected'] = 'checked'; - } - $menus[] = $row; - } - if (!$hasDefault) { - $data['default_selected'] = 'checked'; - } - $data['list'] = $menus; - Render::addTemplate('menu-assign-location', $data); - } - - private function saveLocationMenu() - { - $locationId = Request::post('locationid', false, 'int'); - $loc = Location::get($locationId); - if ($loc === false) { - Message::addError('locations.invalid-location-id', $locationId); - return; - } - User::assertPermission('ipxe.menu.assign', $locationId); - $menuId = Request::post('menuid', false, 'int'); - if ($menuId === 0) { - Database::exec('DELETE FROM serversetup_menu_location WHERE locationid = :locationid', - ['locationid' => $locationId]); - Message::addSuccess('location-use-default', $loc['locationname']); - return; - } - $defaultEntryId = Request::post('defaultentryid-' . $menuId, 0, 'int'); - if ($defaultEntryId === 0) { - $defaultEntryId = null; - } - Database::exec('INSERT INTO serversetup_menu_location (menuid, locationid, defaultentryid) - VALUES (:menuid, :locationid, :defaultentryid) - ON DUPLICATE KEY UPDATE menuid = :menuid, defaultentryid = :defaultentryid', [ - 'menuid' => $menuId, - 'locationid' => $locationId, - 'defaultentryid' => $defaultEntryId - ]); - Message::addSuccess('location-menu-assigned', $loc['locationname']); - } - - private function saveLocalboot() - { - $default = Request::post('default', 'AUTO', 'string'); - if (!array_key_exists($default, Localboot::BOOT_METHODS)) { - Message::addError('localboot-invalid-method', $default); - return; - } - Property::set(Localboot::PROPERTY_KEY, $default); - $overrides = Request::post('override', [], 'array'); - Database::exec('TRUNCATE TABLE serversetup_localboot'); - foreach ($overrides as $model => $mode) { - if (empty($mode)) // No override - continue; - if (!array_key_exists($mode, Localboot::BOOT_METHODS)) { - Message::addWarning('localboot-invalid-method', $mode); - continue; - } - Database::exec('INSERT INTO serversetup_localboot (systemmodel, bootmethod) - VALUES (:model, :mode)', compact('model', 'mode')); - } - Message::addSuccess('localboot-saved'); - Util::redirect('?do=serversetup&show=localboot'); - } - -} diff --git a/modules-available/serversetup-bwlp/permissions/permissions.json b/modules-available/serversetup-bwlp/permissions/permissions.json deleted file mode 100644 index 33cc9cea..00000000 --- a/modules-available/serversetup-bwlp/permissions/permissions.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "access-page": { - "location-aware": false - }, - "download": { - "location-aware": false - }, - "edit.address": { - "location-aware": false - }, - "ipxe.bootentry.view": { - "location-aware": false - }, - "ipxe.bootentry.edit": { - "location-aware": false - }, - "ipxe.menu.view": { - "location-aware": false - }, - "ipxe.menu.edit": { - "location-aware": false - }, - "ipxe.menu.assign": { - "location-aware": true - }, - "ipxe.localboot.edit": { - "location-aware": false - } -} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/bootentry-list.html b/modules-available/serversetup-bwlp/templates/bootentry-list.html deleted file mode 100644 index 0cf005c5..00000000 --- a/modules-available/serversetup-bwlp/templates/bootentry-list.html +++ /dev/null @@ -1,83 +0,0 @@ -

{{lang_bootentryHead}}

- -

- {{lang_bootentryIntro}} -

- - - - - - - - - - - - - {{#bootentryTable}} - - - - - - - - {{/bootentryTable}} - -
{{lang_bootentryTitle}}{{lang_hotkey}}{{lang_refCount}}{{lang_edit}}{{lang_delete}}
- {{title}} - - {{hotkey}} - - {{refs}} - - {{#allowEdit}} - - - - {{/allowEdit}} - - {{#allowEdit}} - - {{/allowEdit}} -
-
- {{#allowEdit}} - - - {{lang_addBootentry}} - - {{/allowEdit}} -
- - -
- - -
- - \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/download.html b/modules-available/serversetup-bwlp/templates/download.html deleted file mode 100644 index 62064b66..00000000 --- a/modules-available/serversetup-bwlp/templates/download.html +++ /dev/null @@ -1,53 +0,0 @@ -
-
- {{lang_downloadBootImage}} -
-
- - {{#files}} - - - - - - - {{/files}} -
{{name}}{{size}}{{modified}}({{features}})
-

- - - {{lang_usbImgHelpBtn}} - -

-

- {{lang_additionalInfoLink}} {{lang_ipxeWikiUrl}} -

-
-
- - \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/heading.html b/modules-available/serversetup-bwlp/templates/heading.html deleted file mode 100644 index e2aa0bff..00000000 --- a/modules-available/serversetup-bwlp/templates/heading.html +++ /dev/null @@ -1,3 +0,0 @@ - \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/ipaddress.html b/modules-available/serversetup-bwlp/templates/ipaddress.html deleted file mode 100644 index ea19c417..00000000 --- a/modules-available/serversetup-bwlp/templates/ipaddress.html +++ /dev/null @@ -1,44 +0,0 @@ -
-
- {{lang_bootAddress}} -
-
-
- {{lang_chooseIP}} -
-
- - - - {{#ips}} - - - {{#default}} - - {{/default}} - {{^default}} - - {{/default}} - - {{/ips}} -
{{ip}} - {{lang_active}} - - -
-

- {{lang_recompileHint}} -

-
-
- - -
-
-
\ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html b/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html deleted file mode 100644 index 7e82b5cc..00000000 --- a/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html +++ /dev/null @@ -1,165 +0,0 @@ -

{{lang_newBootEntryHead}}

- -{{#builtin}} -
- {{lang_editBuiltinWarn}} -
-{{/builtin}} - -
-
- {{lang_bootEntryData}} -
-
-
- - - - -
-
- - -
-
- - -
-
- -
- - -
-
- - -
-
- - -
- -
-
- {{#entries}} -
-
-
-

{{mode}}

-
- - -
-
- - -
-
- - -
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
-
- {{/entries}} -
-
- -
-
- - -
-
- - {{#builtin}} -
- {{lang_editBuiltinWarn}} -
- {{/builtin}} - -

{{lang_referencingMenus}}:

- - -
- -
-
-
-
- - \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/ipxe_update.html b/modules-available/serversetup-bwlp/templates/ipxe_update.html deleted file mode 100644 index 344d3905..00000000 --- a/modules-available/serversetup-bwlp/templates/ipxe_update.html +++ /dev/null @@ -1,54 +0,0 @@ -
-
{{lang_menuGeneration}}
-
-
- {{#files}} -
- - {{name}} -
- {{/files}} -
-
-
- {{lang_generationFailed}} -
-
-
{{lang_menuGeneration}}
-
-
- - diff --git a/modules-available/serversetup-bwlp/templates/localboot.html b/modules-available/serversetup-bwlp/templates/localboot.html deleted file mode 100644 index 3037de2a..00000000 --- a/modules-available/serversetup-bwlp/templates/localboot.html +++ /dev/null @@ -1,59 +0,0 @@ -

{{lang_localBootHead}}

- -

{{lang_localBootIntro}}

- -
- - - - -
-
- - -
- -
-

- {{lang_localBootExceptions}} -

- - - - - - - {{#exceptions}} - - - - - - {{/exceptions}} -
{{lang_systemmodel}}{{lang_count}}{{lang_override}}
{{systemmodel}}{{cnt}} - -
- -
- - -
- -
diff --git a/modules-available/serversetup-bwlp/templates/menu-assign-location.html b/modules-available/serversetup-bwlp/templates/menu-assign-location.html deleted file mode 100644 index 077d137e..00000000 --- a/modules-available/serversetup-bwlp/templates/menu-assign-location.html +++ /dev/null @@ -1,69 +0,0 @@ -

{{lang_assignMenuToLocation}}

-

{{locationName}}

- -
- - - - - - - - - - - - - - - - - - - {{#list}} - - - - - - {{/list}} - -
{{lang_menuTitle}}{{lang_menuEntryOverride}}
-
- - -
-
- {{lang_useDefaultMenu}} -
-
- - -
-
- {{title}} - - -
- -
- -
- -
- -
- - \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/menu-edit.html b/modules-available/serversetup-bwlp/templates/menu-edit.html deleted file mode 100644 index 1598a2b7..00000000 --- a/modules-available/serversetup-bwlp/templates/menu-edit.html +++ /dev/null @@ -1,368 +0,0 @@ -

{{lang_editMenuHead}}

- - - - -
-
- {{title}} - {{^title}} - {{lang_newMenu}} - {{/title}} -
-
-
- - - - -
-
- -
-
- -
-
-
-
- -
-
-
- - {{lang_seconds}} -
-
-
-
- - - - - - - - - - - - - - - {{#entries}} - - - - - - - - - - - - - - - - - {{/entries}} - -
{{lang_entryId}}{{lang_title}}{{lang_hotkey}}{{lang_password}}
- - -
- - -
-
- - - - - - - - - -
- - -
-
- -
-
-
- -
-
- {{lang_cancel}} - -
-
- - -
-
- - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/menu-list.html b/modules-available/serversetup-bwlp/templates/menu-list.html deleted file mode 100644 index 545f22a9..00000000 --- a/modules-available/serversetup-bwlp/templates/menu-list.html +++ /dev/null @@ -1,100 +0,0 @@ -

{{lang_listOfMenus}}

- -

- {{lang_menuListIntro}} -

- - - - - - - - - - - - - {{#menuTable}} - - - - - - - - {{/menuTable}} - -
{{lang_menuTitle}}{{lang_locationCount}}{{lang_isDefault}}{{lang_edit}}{{lang_delete}}
- {{title}} - - {{locationCount}} - - {{^isdefault}} - {{#showSetDefault}} -
- - - -
- {{/showSetDefault}} - {{/isdefault}} - {{#isdefault}} - - {{/isdefault}} -
- {{#allowEdit}} - - - - {{/allowEdit}} - - {{#allowDelete}} - - {{/allowDelete}} -
- - -
- - - -
- - -
- - \ No newline at end of file -- cgit v1.2.3-55-g7522