From f535295c86bc029da86582fe6a1ba3f96d64a1da Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Fri, 8 Mar 2019 16:13:49 +0100 Subject: [serversetup-bwlp-ipxe] Fix bugs, add import feature --- .../serversetup-bwlp-ipxe/hooks/bootup.inc.php | 2 +- .../serversetup-bwlp-ipxe/inc/ipxe.inc.php | 111 ++++++++++++--------- .../serversetup-bwlp-ipxe/inc/pxelinux.inc.php | 13 +-- .../serversetup-bwlp-ipxe/lang/de/messages.json | 2 + .../serversetup-bwlp-ipxe/lang/de/module.json | 3 +- .../lang/de/template-tags.json | 7 +- .../serversetup-bwlp-ipxe/lang/en/messages.json | 2 + .../serversetup-bwlp-ipxe/lang/en/module.json | 1 + .../lang/en/template-tags.json | 5 + .../serversetup-bwlp-ipxe/page.inc.php | 90 ++++++++++++++--- .../serversetup-bwlp-ipxe/templates/menu-list.html | 4 +- .../templates/page-import.html | 32 ++++++ 12 files changed, 197 insertions(+), 75 deletions(-) create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/page-import.html diff --git a/modules-available/serversetup-bwlp-ipxe/hooks/bootup.inc.php b/modules-available/serversetup-bwlp-ipxe/hooks/bootup.inc.php index 50ac04ae..c5fbaadb 100644 --- a/modules-available/serversetup-bwlp-ipxe/hooks/bootup.inc.php +++ b/modules-available/serversetup-bwlp-ipxe/hooks/bootup.inc.php @@ -2,7 +2,7 @@ $ret = IPxe::importLegacyMenu(false); if ($ret !== false) { - $num = IPxe::importPxeMenus('/srv/openslx/tftp/pxelinux.cfg'); + $num = IPxe::importSubnetPxeMenus('/srv/openslx/tftp/pxelinux.cfg'); if ($num > 0) { EventLog::info('Imported old PXELinux menu, with ' . $num . ' additional IP-range based menus.'); } else { diff --git a/modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php index 4683c843..e11c1a89 100644 --- a/modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php +++ b/modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php @@ -9,7 +9,7 @@ class IPxe * @param string $configPath The pxelinux.cfg path where to look for menu files in hexadecimal IP format. * @return Number of menus imported */ - public static function importPxeMenus($configPath) + public static function importSubnetPxeMenus($configPath) { $importCount = 0; $menus = []; @@ -23,9 +23,11 @@ class IPxe $start = hexdec(str_pad($file,8, '0')); $end = hexdec(str_pad($file,8, 'f')); // TODO ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^PREFIX error_log('From ' . long2ip($start) . ' to ' . long2ip($end)); + // Get all subnets that lie within the range defined by the pxelinux filename $res = Database::simpleQuery("SELECT locationid, startaddr, endaddr FROM subnet WHERE startaddr >= :start AND endaddr <= :end", compact('start', 'end')); $locations = []; + // Iterate over result, eliminate those that are dominated by others while ($row = $res->fetch(PDO::FETCH_ASSOC)) { foreach ($locations as &$loc) { if ($row['startaddr'] <= $loc['startaddr'] && $row['endaddr'] >= $loc['endaddr']) { @@ -37,7 +39,7 @@ class IPxe unset($loc); $locations[] = $row; } - $menu = PxeLinux::parsePxeLinux($content); + $menu = PxeLinux::parsePxeLinux($content, true); $key = $menu->hash(true); if (isset($menus[$key])) { $menuId = $menus[$key]; @@ -123,19 +125,19 @@ class IPxe '', 'poweroff' => false, ]; - return self::insertMenu(PxeLinux::parsePxeLinux($pxeConfig), $menuTitle, $defaultLabel, $timeoutSecs, $prepend, $append); + return self::insertMenu(PxeLinux::parsePxeLinux($pxeConfig, false), $menuTitle, $defaultLabel, $timeoutSecs, $prepend, $append); } /** * @param PxeMenu $pxeMenu * @param string $menuTitle * @param string|false $defaultLabel - * @param $defaultTimeoutSeconds - * @param $prepend - * @param $append - * @return bool|int + * @param int $defaultTimeoutSeconds + * @param array $prepend + * @param array $append + * @return int|false */ - private static function insertMenu($pxeMenu, $menuTitle, $defaultLabel, $defaultTimeoutSeconds, $prepend, $append) + public static function insertMenu($pxeMenu, $menuTitle, $defaultLabel, $defaultTimeoutSeconds, $prepend, $append) { $timeoutMs = []; $menuEntries = $prepend; @@ -150,42 +152,8 @@ class IPxe } $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; - } + error_log(print_r($timeoutMs, true)); + self::importPxeMenuEntries($pxe, $menuEntries, $defaultLabel); } if (is_array($append)) { $menuEntries += $append; @@ -193,7 +161,7 @@ class IPxe if (empty($menuEntries)) return false; // Make menu - $timeoutMs = array_filter($timeoutMs, 'is_int'); + $timeoutMs = array_filter($timeoutMs, function($x) { return is_int($x) && $x > 0; }); if (empty($timeoutMs)) { $timeoutMs = (int)($defaultTimeoutSeconds * 1000); } else { @@ -267,6 +235,59 @@ class IPxe return $menuId; } + /** + * Import only the bootentries from the given PXELinux menu + * @param PxeMenu $pxe + * @param array $menuEntries Where to append the generated menu items to + * @param string|false $defaultLabel IN/OUT: Wanted default entry, map to new entrid if found + */ + public static function importPxeMenuEntries($pxe, &$menuEntries, &$defaultLabel) + { + 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(); + $title = self::sanitizeIpxeString($section->title); + if (empty($title)) { + $title = self::sanitizeIpxeString($section->label); + } + if (empty($title)) { + $title = $label; + } + Database::exec('INSERT IGNORE INTO serversetup_bootentry (entryid, hotkey, title, builtin, data) + VALUES (:label, :hotkey, :title, 0, :data)', [ + 'label' => $label, + 'hotkey' => $hotkey, + 'title' => $title, + 'data' => json_encode($data), + ]); + $menuEntries[$label] = $section; + } + } + private static function createDefaultEntries() { Database::exec( 'INSERT IGNORE INTO serversetup_bootentry (entryid, hotkey, title, builtin, data) diff --git a/modules-available/serversetup-bwlp-ipxe/inc/pxelinux.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/pxelinux.inc.php index 1d022fef..82e36afc 100644 --- a/modules-available/serversetup-bwlp-ipxe/inc/pxelinux.inc.php +++ b/modules-available/serversetup-bwlp-ipxe/inc/pxelinux.inc.php @@ -10,16 +10,11 @@ class PxeLinux * @param string $input The pxelinux menu to parse * @return PxeMenu the parsed menu */ - public static function parsePxeLinux($input) + public static function parsePxeLinux($input, $isCp437) { - /* - LABEL openslx-debug - MENU LABEL ^bwLehrpool-Umgebung starten (nosplash, debug) - KERNEL http://IPADDR/boot/default/kernel - INITRD http://IPADDR/boot/default/initramfs-stage31 - APPEND slxbase=boot/default - IPAPPEND 3 - */ + if ($isCp437) { + $input = iconv('IBM437', 'UTF8//TRANSLIT//IGNORE', $input); + } $menu = new PxeMenu; $sectionPropMap = [ 'menu label' => ['string', 'title'], diff --git a/modules-available/serversetup-bwlp-ipxe/lang/de/messages.json b/modules-available/serversetup-bwlp-ipxe/lang/de/messages.json index 8c5aab54..3dfcf088 100644 --- a/modules-available/serversetup-bwlp-ipxe/lang/de/messages.json +++ b/modules-available/serversetup-bwlp-ipxe/lang/de/messages.json @@ -4,6 +4,8 @@ "bootentry-deleted": "Men\u00fceintrag gel\u00f6scht", "error-saving-entry": "Fehler beim Speichern des Eintrags {{0}}: {{1}}", "image-not-found": "USB-Image nicht gefunden. Generieren Sie das Bootmen\u00fc neu.", + "import-error": "Fehler beim Importieren", + "import-no-entries": "Nichts importiert: Men\u00fc scheint leer zu sein", "invalid-boot-entry": "Ung\u00fcltiger Men\u00fceintrag: {{0}}", "invalid-ip": "Kein Interface ist auf die Adresse {{0}} konfiguriert", "invalid-menu-id": "Ung\u00fcltige Men\u00fc-ID: {{0}}", diff --git a/modules-available/serversetup-bwlp-ipxe/lang/de/module.json b/modules-available/serversetup-bwlp-ipxe/lang/de/module.json index 9a8de39c..a5038cf8 100644 --- a/modules-available/serversetup-bwlp-ipxe/lang/de/module.json +++ b/modules-available/serversetup-bwlp-ipxe/lang/de/module.json @@ -14,6 +14,7 @@ "submenu_address": "Server-Adresse", "submenu_bootentry": "Men\u00fceintr\u00e4ge verwalten", "submenu_download": "Downloads", + "submenu_import": "Importieren", "submenu_localboot": "HDD-Boot", "submenu_menu": "Men\u00fcs verwalten" -} +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/lang/de/template-tags.json b/modules-available/serversetup-bwlp-ipxe/lang/de/template-tags.json index 1942790a..30b46e82 100644 --- a/modules-available/serversetup-bwlp-ipxe/lang/de/template-tags.json +++ b/modules-available/serversetup-bwlp-ipxe/lang/de/template-tags.json @@ -30,11 +30,12 @@ "lang_execAutoUnload": "Nach Ausf\u00fchrung entladen (--autofree)", "lang_execReplace": "Aktuellen iPXE-Stack erstzen (--replace)", "lang_execResetConsole": "Konsole vor Ausf\u00fchrung zur\u00fccksetzen", - "lang_forceRecompile": "Jetzt neu compilieren", + "lang_forceRecompile": "Jetzt neu kompilieren", "lang_generationFailed": "Erzeugen des Bootmen\u00fcs fehlgeschlagen. Der Netzwerkboot von bwLehrpool wird wahrscheinlich nicht funktionieren. Wenn Sie den Fehler nicht selbst beheben k\u00f6nnen, melden Sie bitte die Logausgabe an das bwLehrpool-Projekt.", "lang_hotkey": "Hotkey", "lang_idFormatHint": "(Max. 16 Zeichen, nur a-z 0-9 - _)", "lang_imageToLoad": "Zu ladendes Image (z.B. Kernel)", + "lang_import": "Importieren", "lang_initRd": "Zu ladendes initramfs", "lang_ipxeWikiUrl": "im iPXE Wiki", "lang_isDefault": "Standard", @@ -56,6 +57,10 @@ "lang_none": "(keine)", "lang_ok": "OK", "lang_override": "\u00dcberschreiben", + "lang_pxeMenuContent": "pxelinux.cfg\/ Men\u00fcdefinition", + "lang_pxelinuxEntriesOnly": "Nur Eintr\u00e4ge importieren, kein Men\u00fc erzeugen", + "lang_pxelinuxImport": "PXE-Men\u00fc importieren", + "lang_pxelinuxImportIntro": "Hier k\u00f6nnen Sie ein PXE-Men\u00fc einf\u00fcgen und in entsprechende Men\u00fceintr\u00e4ge f\u00fcr iPXE umwandeln lassen.", "lang_recompileHint": "iPXE-Binaries jetzt neu kompilieren. Normalerweise wird dieser Vorgang bei \u00c4nderungen automatisch ausgef\u00fchrt. Sollten Bootprobleme auftreten, k\u00f6nnen Sie hier den Vorgang manuell ansto\u00dfen.", "lang_refCount": "Referenzen", "lang_referencingMenus": "Verkn\u00fcpfte Men\u00fcs", diff --git a/modules-available/serversetup-bwlp-ipxe/lang/en/messages.json b/modules-available/serversetup-bwlp-ipxe/lang/en/messages.json index dcdf4be1..8c1e2791 100644 --- a/modules-available/serversetup-bwlp-ipxe/lang/en/messages.json +++ b/modules-available/serversetup-bwlp-ipxe/lang/en/messages.json @@ -4,6 +4,8 @@ "bootentry-deleted": "Deleted menu item", "error-saving-entry": "Error saving item {{0}}: {{1}}", "image-not-found": "USB image not found. Try regenerating the boot menu first.", + "import-error": "Error importing menu", + "import-no-entries": "Nothing imported: Menu seems to be empty", "invalid-boot-entry": "Invalid menu item: {{0}}", "invalid-ip": "No interface is configured with the address {{0}}", "invalid-menu-id": "Invalid menu id: {{0}}", diff --git a/modules-available/serversetup-bwlp-ipxe/lang/en/module.json b/modules-available/serversetup-bwlp-ipxe/lang/en/module.json index 9e73e865..30865907 100644 --- a/modules-available/serversetup-bwlp-ipxe/lang/en/module.json +++ b/modules-available/serversetup-bwlp-ipxe/lang/en/module.json @@ -14,6 +14,7 @@ "submenu_address": "Server address", "submenu_bootentry": "Manage menu items", "submenu_download": "Downloads", + "submenu_import": "Import", "submenu_localboot": "HDD boot", "submenu_menu": "Manage menus" } \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/lang/en/template-tags.json b/modules-available/serversetup-bwlp-ipxe/lang/en/template-tags.json index 1eb7a64a..ba6d3a85 100644 --- a/modules-available/serversetup-bwlp-ipxe/lang/en/template-tags.json +++ b/modules-available/serversetup-bwlp-ipxe/lang/en/template-tags.json @@ -35,6 +35,7 @@ "lang_hotkey": "Hotkey", "lang_idFormatHint": "(16 chars max, a-z 0-9 - _)", "lang_imageToLoad": "Image to load (e.g. kernel)", + "lang_import": "Import", "lang_initRd": "Optional initrd\/initramfs to load", "lang_ipxeWikiUrl": "at the iPXE wiki", "lang_isDefault": "Default", @@ -56,6 +57,10 @@ "lang_none": "(none)", "lang_ok": "OK", "lang_override": "Override", + "lang_pxeMenuContent": "pxelinux.cfg\/ menu definition", + "lang_pxelinuxEntriesOnly": "Import menu entries only, don't create menu", + "lang_pxelinuxImport": "Import PXELinux menu", + "lang_pxelinuxImportIntro": "Here you can paste a pxelinux menu to convert it to an iPXE menu.", "lang_recompileHint": "Recompile iPXE binaries now. Usually this happens automatically on changes, but if you suspect problems caused by outdated binaries, you can trigger recompilation here.", "lang_refCount": "References", "lang_referencingMenus": "Referencing menus", diff --git a/modules-available/serversetup-bwlp-ipxe/page.inc.php b/modules-available/serversetup-bwlp-ipxe/page.inc.php index 07683df3..95ebb69f 100644 --- a/modules-available/serversetup-bwlp-ipxe/page.inc.php +++ b/modules-available/serversetup-bwlp-ipxe/page.inc.php @@ -87,6 +87,11 @@ class Page_ServerSetup extends Page $this->saveLocalboot(); } + if ($action === 'import') { + User::assertPermission('ipxe.bootentry.edit'); + $this->execImportPxeMenu(); + } + if ($action === 'deleteMenu') { // Permcheck in function $this->deleteMenu(); @@ -116,6 +121,9 @@ class Page_ServerSetup extends Page if (User::hasPermission('ipxe.localboot.*')) { Dashboard::addSubmenu('?do=serversetup&show=localboot', Dictionary::translate('submenu_localboot', true)); } + if (User::hasPermission('ipxe.bootentry.*')) { + Dashboard::addSubmenu('?do=serversetup&show=import', Dictionary::translate('submenu_import', true)); + } if (Request::get('show') === false) { $subs = Dashboard::getSubmenus(); if (empty($subs)) { @@ -174,6 +182,10 @@ class Page_ServerSetup extends Page User::assertPermission('ipxe.localboot.*'); $this->showLocalbootConfig(); break; + case 'import': + User::assertPermission('ipxe.bootentry.edit'); + $this->showImportMenu(); + break; default: Util::redirect('?do=serversetup'); break; @@ -267,6 +279,11 @@ class Page_ServerSetup extends Page Render::addTemplate('localboot', $data); } + private function showImportMenu() + { + Render::addTemplate('page-import'); + } + private function showBootentryList() { $allowEdit = User::hasPermission('ipxe.bootentry.edit'); @@ -375,32 +392,45 @@ class Page_ServerSetup extends Page } $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"); + class_exists('BootEntry'); // Leave this here for StandardBootEntry foreach ($menu['entrylist'] as &$bootentry) { - //$bootentry['json'] = $bootentry['data']; $bootentry['data'] = json_decode($bootentry['data'], true); + // Transform stuff suitable for mustache 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') { + // BIOS/EFI or both + if ($bootentry['data']['arch'] === StandardBootEntry::BIOS + || $bootentry['data']['arch'] === StandardBootEntry::BOTH) { + $bootentry['data']['PCBIOS'] = array('executable' => $bootentry['data']['executable']['PCBIOS'], + 'initRd' => $bootentry['data']['initRd']['PCBIOS'], + 'commandLine' => $bootentry['data']['commandLine']['PCBIOS']); + } + if ($bootentry['data']['arch'] === StandardBootEntry::EFI + || $bootentry['data']['arch'] === StandardBootEntry::BOTH) { + $bootentry['data']['EFI'] = array('executable' => $bootentry['data']['executable']['EFI'], + 'initRd' => $bootentry['data']['initRd']['EFI'], + 'commandLine' => $bootentry['data']['commandLine']['EFI']); + } + // Naming and agnostic + if ($bootentry['data']['arch'] === StandardBootEntry::BIOS) { $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_biosOnly', true); unset($bootentry['data']['EFI']); - } else if ($bootentry['data']['arch'] === 'EFI') { + } elseif ($bootentry['data']['arch'] === StandardBootEntry::EFI) { $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_efiOnly', true); unset($bootentry['data']['PCBIOS']); + } elseif ($bootentry['data']['arch'] === StandardBootEntry::AGNOSTIC) { + $bootentry['data']['archAgnostic'] = array('executable' => $bootentry['data']['executable']['PCBIOS'], + 'initRd' => $bootentry['data']['initRd']['PCBIOS'], + 'commandLine' => $bootentry['data']['commandLine']['PCBIOS']); + $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_archAgnostic', true); + unset($bootentry['data']['EFI']); } else { $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_archBoth', true); } - - } 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 ($bootentry['data'] as &$e) { + if (isset($e['initRd']) && is_array($e['initRd'])) { + $e['initRd'] = implode(',', $e['initRd']); + } + } } } foreach ($menu['entries'] as &$entry) { @@ -827,4 +857,32 @@ class Page_ServerSetup extends Page Util::redirect('?do=serversetup&show=localboot'); } + private function execImportPxeMenu() + { + $content = Request::post('pxemenu', false, 'string'); + if (empty($content)) { + Message::addError('main.parameter-empty', 'pxemenu'); + Util::redirect('?do=serversetup&show=import'); + } + $menu = PxeLinux::parsePxeLinux($content, false); + if (empty($menu->sections)) { + Message::addWarning('import-no-entries'); + Util::redirect('?do=serversetup&show=import'); + } + if (Request::post('entries-only', 0, 'int') !== 0) { + $foo = []; + $bar = false; + IPxe::importPxeMenuEntries($menu, $foo, $bar); + Util::redirect('?do=serversetup&show=bootentry'); + } else { + $id = IPxe::insertMenu($menu, 'Imported Menu', false, 0, [], []); + if ($id === false) { + Message::addError('import-error'); + Util::redirect('?do=serversetup&show=import'); + } else { + Util::redirect('?do=serversetup&show=editmenu&id=' . $id); + } + } + } + } diff --git a/modules-available/serversetup-bwlp-ipxe/templates/menu-list.html b/modules-available/serversetup-bwlp-ipxe/templates/menu-list.html index 545f22a9..ed12e596 100644 --- a/modules-available/serversetup-bwlp-ipxe/templates/menu-list.html +++ b/modules-available/serversetup-bwlp-ipxe/templates/menu-list.html @@ -47,11 +47,11 @@ {{/allowEdit}} - {{#allowDelete}} + {{#allowEdit}} - {{/allowDelete}} + {{/allowEdit}} {{/menuTable}} diff --git a/modules-available/serversetup-bwlp-ipxe/templates/page-import.html b/modules-available/serversetup-bwlp-ipxe/templates/page-import.html new file mode 100644 index 00000000..62a24325 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/page-import.html @@ -0,0 +1,32 @@ +

{{lang_pxelinuxImport}}

+ +

+ {{lang_pxelinuxImportIntro}} +

+ + + + +
+
+ {{lang_pxelinuxImport}} +
+
+
+ + + + +
+ + +
+
+ +
+
+
+
\ No newline at end of file -- cgit v1.2.3-55-g7522