summaryrefslogtreecommitdiffstats
path: root/modules-available
diff options
context:
space:
mode:
authorSimon Rettberg2019-02-15 15:55:10 +0100
committerSimon Rettberg2019-02-15 15:55:10 +0100
commitbc9aa8105db4742255265a35594fafc93269d59d (patch)
tree338dd9588519ed2b996c675a920160258c8762ba /modules-available
parent[dozmod] Add UI to create preset network rules (diff)
parent[serversetup-bwlp-ipxe] ipxe script: Don't load unused png (diff)
downloadslx-admin-bc9aa8105db4742255265a35594fafc93269d59d.tar.gz
slx-admin-bc9aa8105db4742255265a35594fafc93269d59d.tar.xz
slx-admin-bc9aa8105db4742255265a35594fafc93269d59d.zip
Merge branch 'ipxe'
Diffstat (limited to 'modules-available')
-rw-r--r--modules-available/locations/lang/de/template-tags.json1
-rw-r--r--modules-available/locations/lang/en/template-tags.json1
-rw-r--r--modules-available/locations/page.inc.php60
-rw-r--r--modules-available/locations/templates/locations.html17
-rw-r--r--modules-available/serversetup-bwlp-ipxe/api.inc.php252
-rw-r--r--modules-available/serversetup-bwlp-ipxe/config.json8
-rw-r--r--modules-available/serversetup-bwlp-ipxe/hooks/bootup.inc.php11
-rw-r--r--modules-available/serversetup-bwlp-ipxe/hooks/ipxe-update.inc.php10
-rw-r--r--modules-available/serversetup-bwlp-ipxe/hooks/main-warning.inc.php (renamed from modules-available/serversetup-bwlp/hooks/main-warning.inc.php)0
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php258
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php453
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php142
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/localboot.inc.php15
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php177
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/pxelinux.inc.php302
-rw-r--r--modules-available/serversetup-bwlp-ipxe/install.inc.php89
-rw-r--r--modules-available/serversetup-bwlp-ipxe/lang/de/messages.json21
-rw-r--r--modules-available/serversetup-bwlp-ipxe/lang/de/module.json19
-rw-r--r--modules-available/serversetup-bwlp-ipxe/lang/de/permissions.json12
-rw-r--r--modules-available/serversetup-bwlp-ipxe/lang/de/template-tags.json94
-rw-r--r--modules-available/serversetup-bwlp-ipxe/lang/en/messages.json (renamed from modules-available/serversetup-bwlp/lang/en/messages.json)0
-rw-r--r--modules-available/serversetup-bwlp-ipxe/lang/en/module.json (renamed from modules-available/serversetup-bwlp/lang/en/module.json)0
-rw-r--r--modules-available/serversetup-bwlp-ipxe/lang/en/permissions.json (renamed from modules-available/serversetup-bwlp/lang/en/permissions.json)0
-rw-r--r--modules-available/serversetup-bwlp-ipxe/lang/en/template-tags.json42
-rw-r--r--modules-available/serversetup-bwlp-ipxe/lang/pt/messages.json (renamed from modules-available/serversetup-bwlp/lang/pt/messages.json)0
-rw-r--r--modules-available/serversetup-bwlp-ipxe/lang/pt/module.json (renamed from modules-available/serversetup-bwlp/lang/pt/module.json)0
-rw-r--r--modules-available/serversetup-bwlp-ipxe/lang/pt/template-tags.json (renamed from modules-available/serversetup-bwlp/lang/pt/template-tags.json)0
-rw-r--r--modules-available/serversetup-bwlp-ipxe/page.inc.php829
-rw-r--r--modules-available/serversetup-bwlp-ipxe/permissions/permissions.json29
-rw-r--r--modules-available/serversetup-bwlp-ipxe/templates/bootentry-list.html83
-rw-r--r--modules-available/serversetup-bwlp-ipxe/templates/download.html53
-rw-r--r--modules-available/serversetup-bwlp-ipxe/templates/heading.html3
-rw-r--r--modules-available/serversetup-bwlp-ipxe/templates/ipaddress.html44
-rw-r--r--modules-available/serversetup-bwlp-ipxe/templates/ipxe-new-boot-entry.html165
-rw-r--r--modules-available/serversetup-bwlp-ipxe/templates/ipxe_update.html54
-rw-r--r--modules-available/serversetup-bwlp-ipxe/templates/localboot.html59
-rw-r--r--modules-available/serversetup-bwlp-ipxe/templates/menu-assign-location.html69
-rw-r--r--modules-available/serversetup-bwlp-ipxe/templates/menu-edit.html368
-rw-r--r--modules-available/serversetup-bwlp-ipxe/templates/menu-list.html100
-rw-r--r--modules-available/serversetup-bwlp-pxelinux/config.json (renamed from modules-available/serversetup-bwlp/config.json)0
-rw-r--r--modules-available/serversetup-bwlp-pxelinux/hooks/ipxe-update.inc.php9
-rw-r--r--modules-available/serversetup-bwlp-pxelinux/hooks/main-warning.inc.php6
-rw-r--r--modules-available/serversetup-bwlp-pxelinux/inc/ipxe.inc.php (renamed from modules-available/serversetup-bwlp/inc/ipxe.inc.php)0
-rw-r--r--modules-available/serversetup-bwlp-pxelinux/lang/de/messages.json (renamed from modules-available/serversetup-bwlp/lang/de/messages.json)0
-rw-r--r--modules-available/serversetup-bwlp-pxelinux/lang/de/module.json (renamed from modules-available/serversetup-bwlp/lang/de/module.json)0
-rw-r--r--modules-available/serversetup-bwlp-pxelinux/lang/de/permissions.json (renamed from modules-available/serversetup-bwlp/lang/de/permissions.json)0
-rw-r--r--modules-available/serversetup-bwlp-pxelinux/lang/de/template-tags.json (renamed from modules-available/serversetup-bwlp/lang/de/template-tags.json)0
-rw-r--r--modules-available/serversetup-bwlp-pxelinux/lang/en/messages.json5
-rw-r--r--modules-available/serversetup-bwlp-pxelinux/lang/en/module.json3
-rw-r--r--modules-available/serversetup-bwlp-pxelinux/lang/en/permissions.json6
-rw-r--r--modules-available/serversetup-bwlp-pxelinux/lang/en/template-tags.json (renamed from modules-available/serversetup-bwlp/lang/en/template-tags.json)0
-rw-r--r--modules-available/serversetup-bwlp-pxelinux/lang/pt/messages.json3
-rw-r--r--modules-available/serversetup-bwlp-pxelinux/lang/pt/module.json3
-rw-r--r--modules-available/serversetup-bwlp-pxelinux/lang/pt/template-tags.json38
-rw-r--r--modules-available/serversetup-bwlp-pxelinux/page.inc.php (renamed from modules-available/serversetup-bwlp/page.inc.php)0
-rw-r--r--modules-available/serversetup-bwlp-pxelinux/permissions/permissions.json (renamed from modules-available/serversetup-bwlp/permissions/permissions.json)0
-rw-r--r--modules-available/serversetup-bwlp-pxelinux/templates/heading.html (renamed from modules-available/serversetup-bwlp/templates/heading.html)0
-rw-r--r--modules-available/serversetup-bwlp-pxelinux/templates/ipaddress.html (renamed from modules-available/serversetup-bwlp/templates/ipaddress.html)0
-rw-r--r--modules-available/serversetup-bwlp-pxelinux/templates/ipxe.html (renamed from modules-available/serversetup-bwlp/templates/ipxe.html)0
-rw-r--r--modules-available/serversetup-bwlp-pxelinux/templates/ipxe_update.html (renamed from modules-available/serversetup-bwlp/templates/ipxe_update.html)0
60 files changed, 3895 insertions, 18 deletions
diff --git a/modules-available/locations/lang/de/template-tags.json b/modules-available/locations/lang/de/template-tags.json
index 43142555..96273ce4 100644
--- a/modules-available/locations/lang/de/template-tags.json
+++ b/modules-available/locations/lang/de/template-tags.json
@@ -3,6 +3,7 @@
"lang_areYouSureNoUndo": "Sind Sie sicher? Diese Aktion kann nicht r\u00fcckg\u00e4ngig gemacht werden.",
"lang_assignSubnetExplanation": "Rechner, die in einen der hier aufgef\u00fchrten Adressbereiche fallen, werden diesem Ort zugeschrieben und erhalten damit z.B. f\u00fcr diesen Raum angepasste Veranstaltungslisten.",
"lang_assignedSubnets": "Zugeordnete Subnetze bzw. IP-Bereiche",
+ "lang_bootMenu": "Bootmen\u00fc",
"lang_deleteChildLocations": "Untergeordnete Orte ebenfalls l\u00f6schen",
"lang_deleteLocation": "Ort l\u00f6schen",
"lang_deleteSubnet": "Bereich l\u00f6schen",
diff --git a/modules-available/locations/lang/en/template-tags.json b/modules-available/locations/lang/en/template-tags.json
index 41261726..64211e27 100644
--- a/modules-available/locations/lang/en/template-tags.json
+++ b/modules-available/locations/lang/en/template-tags.json
@@ -3,6 +3,7 @@
"lang_areYouSureNoUndo": "Are you sure? This cannot be undone!",
"lang_assignSubnetExplanation": "Client machines which fall into an IP range listed below will be assigned to this location and will see an according lecture list (e.g. they will see lectures that are exclusively assigned to this location).",
"lang_assignedSubnets": "Assigned subnets \/ IP ranges",
+ "lang_bootMenu": "Boot menu",
"lang_deleteChildLocations": "Delete child locations aswell",
"lang_deleteLocation": "Delete location",
"lang_deleteSubnet": "Delete range",
diff --git a/modules-available/locations/page.inc.php b/modules-available/locations/page.inc.php
index 9beae163..2d8f5ff9 100644
--- a/modules-available/locations/page.inc.php
+++ b/modules-available/locations/page.inc.php
@@ -375,6 +375,11 @@ class Page_Locations extends Page
} else {
$locationList[$lid]['havestatistics'] = false;
}
+ if (User::hasPermission('.serversetup.ipxe.menu.assign', $lid)) {
+ $visibleLocationIds[] = $lid;
+ } else {
+ $locationList[$lid]['haveipxe'] = false;
+ }
if (!in_array($lid, $visibleLocationIds)) {
unset($locationList[$lid]);
} elseif (!in_array($lid, $allowedLocationIds)) {
@@ -440,22 +445,7 @@ class Page_Locations extends Page
$locationList[$locId] += array('configName' => $conf['title'], 'configClass' => 'slx-bold');
}
}
- $depth = array();
- foreach ($locationList as &$loc) {
- $d = $loc['depth'];
- if (!isset($loc['configName'])) {
- // Has no explicit config assignment
- if ($d === 0) {
- $loc['configName'] = $defaultConfig;
- } else {
- $loc['configName'] = $depth[$d - 1];
- }
- $loc['configClass'] = 'gray';
- }
- $depth[$d] = $loc['configName'];
- unset($depth[$d + 1]);
- }
- unset($loc);
+ $this->propagateFields($locationList, $defaultConfig, 'configName', 'configClass');
}
// Count overridden config vars
if (Module::get('baseconfig') !== false) {
@@ -467,6 +457,24 @@ class Page_Locations extends Page
$locationList[$lid]['overriddenVars'] = $row['cnt'];
}
}
+ // Confusing because the count might be inaccurate within a branch
+ //$this->propagateFields($locationList, '', 'overriddenVars', 'overriddenClass');
+ }
+ // Show ipxe menu
+ if (Module::get('serversetup') !== false) {
+ $res = Database::simpleQuery("SELECT ml.locationid, m.title, ml.defaultentryid FROM serversetup_menu m
+ INNER JOIN serversetup_menu_location ml USING (menuid)
+ WHERE locationid IN (:allowedLocationIds) GROUP BY locationid", compact('allowedLocationIds'));
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $lid = (int)$row['locationid'];
+ if (isset($locationList[$lid])) {
+ if ($row['defaultentryid'] !== null) {
+ $row['title'] .= '(*)';
+ }
+ $locationList[$lid]['customMenu'] = $row['title'];
+ }
+ }
+ $this->propagateFields($locationList, '', 'customMenu', 'customMenuClass');
}
$addAllowedLocs = User::getAllowedLocations("location.add");
@@ -484,6 +492,7 @@ class Page_Locations extends Page
'havestatistics' => Module::get('statistics') !== false,
'havebaseconfig' => Module::get('baseconfig') !== false,
'havesysconfig' => Module::get('sysconfig') !== false,
+ 'haveipxe' => Module::get('serversetup') !== false,
'overlapSelf' => $overlapSelf,
'overlapOther' => $overlapOther,
'haveOverlapSelf' => !empty($overlapSelf),
@@ -637,4 +646,23 @@ class Page_Locations extends Page
return $result;
}
+ private function propagateFields(&$locationList, $defaultValue, $name, $class)
+ {
+ $depth = array();
+ foreach ($locationList as &$loc) {
+ $d = $loc['depth'];
+ if (!isset($loc[$name])) {
+ // Has no explicit config assignment
+ if ($d === 0) {
+ $loc[$name] = $defaultValue;
+ } else {
+ $loc[$name] = $depth[$d - 1];
+ }
+ $loc[$class] = 'gray';
+ }
+ $depth[$d] = $loc[$name];
+ unset($depth[$d + 1]);
+ }
+ }
+
}
diff --git a/modules-available/locations/templates/locations.html b/modules-available/locations/templates/locations.html
index 67f22744..06d32020 100644
--- a/modules-available/locations/templates/locations.html
+++ b/modules-available/locations/templates/locations.html
@@ -37,6 +37,9 @@
<th class="text-nowrap">
{{#havesysconfig}}{{lang_sysConfig}}{{/havesysconfig}}
</th>
+ <th class="text-nowrap">
+ {{#haveipxe}}{{lang_bootMenu}}{{/haveipxe}}
+ </th>
</tr>
{{#list}}
<tr>
@@ -67,7 +70,7 @@
{{clientLoad}}
{{/havestatistics}}
</td>
- <td class="text-nowrap">
+ <td class="text-nowrap {{overriddenClass}}">
{{#havebaseconfig}}
<div class="pull-right" style="z-index:-1">
<a class="btn btn-default btn-xs" href="?do=baseconfig&amp;module=locations&amp;locationid={{locationid}}"><span class="glyphicon glyphicon-edit"></span></a>
@@ -87,6 +90,16 @@
</span>
{{/havesysconfig}}
</td>
+ <td class="text-nowrap">
+ {{#haveipxe}}
+ <div class="pull-right">
+ <a class="btn btn-default btn-xs" href="?do=serversetup&amp;show=assignlocation&amp;locationid={{locationid}}"><span class="glyphicon glyphicon-edit"></span></a>
+ </div>
+ <span class="{{customMenuClass}}">
+ {{customMenu}}&emsp;&emsp;
+ </span>
+ {{/haveipxe}}
+ </td>
</tr>
{{/list}}
{{#unassignedCount}}
@@ -170,7 +183,7 @@ function slxOpenLocation(e, lid) {
}
return;
}
- var td = $('<td>').attr('colspan', '5').css('padding', '0px 0px 12px');
+ var td = $('<td>').attr('colspan', '6').css('padding', '0px 0px 12px');
var tr = $('<tr>').attr('id', 'location-details-' + lid);
tr.append(td);
$(e).closest('tr').addClass('active slx-bold').after(tr);
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..73461901
--- /dev/null
+++ b/modules-available/serversetup-bwlp-ipxe/api.inc.php
@@ -0,0 +1,252 @@
+<?php
+
+// Menu mode
+
+$serverIp = Property::getServerIp();
+
+// Check if required arguments are given; if not, spit out according script and chain to self
+$uuid = Request::any('uuid', false, 'string');
+// Get platform - EFI or PCBIOS
+$platform = Request::any('platform', false, 'string');
+$manuf = Request::any('manuf', false, 'string');
+$product = Request::any('product', false, 'string');
+$slxExtensions = Request::any('slx-extensions', false, 'int');
+
+if ($platform === false || ($uuid === false && $product === false) || $slxExtensions === false) {
+ // Redirect to self with added parameters
+ $url = parse_url($_SERVER['REQUEST_URI']);
+ if (isset($_SERVER['SCRIPT_URI']) && preg_match('#^(\w+://[^/]+)#', $_SERVER['SCRIPT_URI'], $out)) {
+ $urlbase = $out[1];
+ } elseif (isset($_SERVER['REQUEST_SCHEME']) && isset($_SERVER['SERVER_NAME'])) {
+ $urlbase = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['SERVER_NAME'];
+ } elseif (isset($_SERVER['REQUEST_SCHEME']) && isset($_SERVER['SERVER_ADDR'])) {
+ $urlbase = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['SERVER_ADDR'];
+ } else {
+ $urlbase = 'http://' . $serverIp;
+ }
+ $urlbase .= $url['path'];
+ if (empty($url['query'])) {
+ $arr = [];
+ } else {
+ parse_str($url['query'], $arr);
+ foreach ($arr as &$v) {
+ $v = urlencode($v);
+ }
+ unset($v);
+ }
+ $arr['uuid'] = '${uuid}';
+ $arr['mac'] = '${mac}';
+ $arr['manuf'] = '${manufacturer:uristring}';
+ $arr['product'] = '${product:uristring}';
+ $arr['platform'] = '${platform:uristring}';
+ $query = '?';
+ foreach ($arr as $k => $v) {
+ $query .= $k . '=' . $v . '&';
+ }
+ //$query = substr($query, 0, -1);
+ echo <<<HERE
+#!ipxe
+set slxtest:string something ||
+iseq \${slxtest:md5} \${} && set slxext 0 || set slxext 1 ||
+clear slxtest ||
+set self {$urlbase}{$query}slx-extensions=\${slxext}
+:retry
+echo Chaining to \${self}
+chain -ar \${self} ||
+echo Chaining to self failed with \${errno}, retrying in a bit...
+sleep 5
+goto retry
+HERE;
+ exit;
+}
+// ipxe has it lowercase, but we use uppercase
+$platform = strtoupper($platform);
+
+$BOOT_METHODS = Localboot::BOOT_METHODS;
+
+$ip = $_SERVER['REMOTE_ADDR'];
+if (substr($ip, 0, 7) === '::ffff:') {
+ $ip = substr($ip, 7);
+}
+$menu = IPxeMenu::forClient($ip, $uuid);
+
+
+// Get preferred localboot method, depending on system model
+$localboot = false;
+$model = false;
+if ($uuid !== false && Module::get('statistics') !== false) {
+ // If we have the machine table, we rather try to look up the system model from there, using the UUID
+ $row = Database::queryFirst('SELECT systemmodel FROM machine WHERE machineuuid = :uuid', ['uuid' => $uuid]);
+ if ($row !== false && !empty($row['systemmodel'])) {
+ $model = $row['systemmodel'];
+ }
+}
+if ($model === false) {
+ // Otherwise use what iPXE sent us
+ function modfilt($str)
+ {
+ if (empty($str) || preg_match('/product\s+name|be\s+filled|unknown|default\s+string/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 = <<<HERE
+#!ipxe
+
+goto init || goto fail ||
+
+# functions
+
+# password check with gotos
+# set slx_hash to the expected hash
+# slx_salt to the salt to use
+# slx_pw_ok to the label to jump on success
+# slx_pw_fail to label for wrong pw
+:slx_pass_check
+login ||
+set slxtmp_pw \${password:md5}-\${slx_salt} || goto fail
+set slxtmp_pw \${slxtmp_pw:md5} || goto fail
+clear password ||
+iseq \${slxtmp_pw} \${slx_hash} || prompt Wrong password. Press a key. ||
+iseq \${slxtmp_pw} \${slx_hash} || goto \${slx_pw_fail} ||
+iseq \${slxtmp_pw} \${slx_hash} && goto \${slx_pw_ok} ||
+goto fail
+
+# local boot with either exit 1 or sanboot
+:slx_localboot
+console ||
+
+HERE;
+
+foreach ($BOOT_METHODS as $line) {
+ $output .= "$line || goto fail\n";
+}
+
+$output .= <<<HERE
+goto fail
+
+# start
+:init
+
+set ipappend1 ip=\${ip}:{$serverIp}:\${gateway}:\${netmask}
+set ipappend2 BOOTIF=01-\${mac:hexhyp}
+set serverip $serverIp ||
+
+# Clean up in case we've been chained to
+imgfree ||
+
+imgfetch --name bg-menu /tftp/pxe-menu.png ||
+
+:start
+
+console --left 55 --top 88 --right 63 --bottom 64 --keep --picture bg-menu ||
+
+colour --rgb 0xffffff 7
+colour --rgb 0xcccccc 5
+cpair --foreground 0 --background 4 1
+cpair --foreground 7 --background 5 2
+cpair --foreground 7 --background 9 0
+
+:slx_menu
+
+console --left 55 --top 88 --right 63 --bottom 64 $slxConsoleUpdate --keep --picture bg-menu ||
+
+HERE;
+
+$output .= $menu->getMenuDefinition('target', $platform, $slxExtensions);
+
+$output .= <<<HERE
+
+console --left 60 --top 130 --right 67 --bottom 86 $slxConsoleUpdate ||
+goto \${target} ||
+echo Could not find menu entry in script.
+prompt Press any key to continue.
+goto start
+
+HERE;
+
+$output .= $menu->getItemsCode($platform);
+
+/*
+
+:i5
+chain -a /tftp/memtest.0 passes=1 onepass || goto membad
+prompt Memory OK. Press a key.
+goto init
+
+:i8
+set x:int32 0
+:again
+console --left 60 --top 130 --right 67 --bottom 96 --picture bg-load --keep ||
+console --left 55 --top 88 --right 63 --bottom 64 --picture bg-menu --keep ||
+inc x
+iseq \${x} 20 || goto again
+prompt DONE. Press dein Knie.
+goto slx_menu
+
+:membad
+iseq \${errno} 0x1 || goto memaborted
+params
+param scrot \${vram}
+imgfetch -a http://132.230.8.113/screen.php##params ||
+prompt Memory is bad. Press a key.
+goto init
+
+:memaborted
+params
+param scrot \${vram}
+imgfetch -a http://132.230.8.113/screen.php##params ||
+prompt Memory test aborted. Press a key.
+goto init
+
+*/
+
+$output .= <<<HERE
+:fail
+prompt Boot failed. Press any key to start.
+goto init
+HERE;
+
+if ($platform === 'EFI') {
+ $cs = 'ASCII';
+} else {
+ $cs = 'IBM437';
+}
+Header('Content-Type: text/plain; charset=' . $cs);
+
+echo iconv('UTF-8', $cs . '//TRANSLIT//IGNORE', $output);
diff --git a/modules-available/serversetup-bwlp-ipxe/config.json b/modules-available/serversetup-bwlp-ipxe/config.json
new file mode 100644
index 00000000..8b3ce2a3
--- /dev/null
+++ b/modules-available/serversetup-bwlp-ipxe/config.json
@@ -0,0 +1,8 @@
+{
+ "category": "main.settings-server",
+ "dependencies" : [
+ "locations",
+ "js_jqueryui",
+ "bootstrap_multiselect"
+ ]
+} \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp-ipxe/hooks/bootup.inc.php b/modules-available/serversetup-bwlp-ipxe/hooks/bootup.inc.php
new file mode 100644
index 00000000..50ac04ae
--- /dev/null
+++ b/modules-available/serversetup-bwlp-ipxe/hooks/bootup.inc.php
@@ -0,0 +1,11 @@
+<?php
+
+$ret = IPxe::importLegacyMenu(false);
+if ($ret !== false) {
+ $num = IPxe::importPxeMenus('/srv/openslx/tftp/pxelinux.cfg');
+ if ($num > 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 @@
+<?php
+
+$data = [
+ 'ipaddress' => 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-ipxe/hooks/main-warning.inc.php
index a2eba6ff..a2eba6ff 100644
--- a/modules-available/serversetup-bwlp/hooks/main-warning.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/hooks/main-warning.inc.php
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php
new file mode 100644
index 00000000..69adffd3
--- /dev/null
+++ b/modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php
@@ -0,0 +1,258 @@
+<?php
+
+abstract class BootEntry
+{
+
+ public function __construct($data = false)
+ {
+ if (is_array($data)) {
+ foreach ($data as $key => $value) {
+ if (property_exists($this, $key)) {
+ $this->{$key} = $value;
+ }
+ }
+ }
+ }
+
+ public abstract function supportsMode($mode);
+
+ public abstract function toScript($failLabel, $mode);
+
+ 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 @@
+<?php
+
+class IPxe
+{
+
+ /**
+ * Import all IP-Range based pxe menus from the given directory.
+ *
+ * @param string $configPath The pxelinux.cfg path where to look for menu files in hexadecimal IP format.
+ * @return Number of menus imported
+ */
+ public static function importPxeMenus($configPath)
+ {
+ $importCount = 0;
+ $menus = [];
+ foreach (glob($configPath . '/*', GLOB_NOSORT) as $file) {
+ if (!is_file($file) || !preg_match('~/[A-F0-9]{1,8}$~', $file))
+ continue;
+ $content = file_get_contents($file);
+ if ($content === false)
+ continue;
+ $file = basename($file);
+ $start = hexdec(str_pad($file,8, '0'));
+ $end = hexdec(str_pad($file,8, 'f')); // TODO ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^PREFIX
+ error_log('From ' . long2ip($start) . ' to ' . long2ip($end));
+ $res = Database::simpleQuery("SELECT locationid, startaddr, endaddr FROM subnet
+ WHERE startaddr >= :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 @@
+<?php
+
+class IPxeMenu
+{
+
+ protected $menuid;
+ protected $timeoutMs;
+ protected $title;
+ protected $defaultEntryId;
+ /**
+ * @var MenuEntry[]
+ */
+ protected $items = [];
+
+ public function __construct($menu)
+ {
+ if (!is_array($menu)) {
+ $menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid FROM serversetup_menu
+ WHERE menuid = :menuid LIMIT 1", ['menuid' => $menu]);
+ if (!is_array($menu)) {
+ $menu = ['menuid' => 'foo', 'title' => 'Invalid Menu ID: ' . (int)$menu];
+ }
+ }
+ $this->menuid = (int)$menu['menuid'];
+ $this->timeoutMs = (int)$menu['timeoutms'];
+ $this->title = $menu['title'];
+ $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 @@
+<?php
+
+class Localboot
+{
+
+ const PROPERTY_KEY = 'serversetup.localboot';
+
+ const BOOT_METHODS = [
+ 'AUTO' => '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 @@
+<?php
+
+class MenuEntry
+{
+ /**
+ * @var int id of entry, used for pw
+ */
+ private $menuentryid;
+ /**
+ * @var false|string key code as expected by iPXE
+ */
+ private $hotkey;
+ /**
+ * @var string
+ */
+ private $title;
+ /**
+ * @var bool
+ */
+ private $hidden;
+ /**
+ * @var bool
+ */
+ private $gap;
+ /**
+ * @var int
+ */
+ private $sortval;
+ /**
+ * @var BootEntry
+ */
+ private $bootEntry = null;
+
+ private $md5pass = null;
+
+ /**
+ * MenuEntry constructor.
+ *
+ * @param array $row row from database
+ */
+ public function __construct($row)
+ {
+ if (is_array($row)) {
+ foreach ($row as $key => $value) {
+ if (property_exists($this, $key)) {
+ $this->{$key} = $value;
+ }
+ }
+ $this->hotkey = self::getKeyCode($row['hotkey']);
+ if (!empty($row['bootentry'])) {
+ $this->bootEntry = BootEntry::fromJson($row['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 @@
+<?php
+
+
+class PxeLinux
+{
+
+ /**
+ * Takes a (partial) pxelinux menu and parses it into
+ * a PxeMenu object.
+ * @param string $input The pxelinux menu to parse
+ * @return PxeMenu the parsed menu
+ */
+ public static function parsePxeLinux($input)
+ {
+ /*
+ 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
+ */
+ $menu = new PxeMenu;
+ $sectionPropMap = [
+ 'menu label' => ['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 @@
+<?php
+
+$res = array();
+
+$res[] = tableCreate('serversetup_bootentry', "
+ `entryid` varchar(16) CHARACTER SET ascii NOT NULL,
+ `hotkey` varchar(8) CHARACTER SET ascii NOT NULL,
+ `title` varchar(100) NOT NULL,
+ `builtin` tinyint(1) NOT NULL,
+ `data` blob NOT NULL,
+ PRIMARY KEY (`entryid`)
+");
+
+$res[] = tableCreate('serversetup_menu', "
+ `menuid` int(11) NOT NULL AUTO_INCREMENT,
+ `timeoutms` int(10) unsigned NOT NULL,
+ `title` varchar(100) NOT NULL COMMENT 'Escaped/Sanitized for iPXE!',
+ `defaultentryid` int(11) DEFAULT NULL,
+ `isdefault` tinyint(1) NOT NULL,
+ PRIMARY KEY (`menuid`),
+ KEY `defaultentryid` (`defaultentryid`),
+ KEY `isdefault` (`isdefault`)
+");
+
+$res[] = tableCreate('serversetup_menuentry', "
+ `menuentryid` int(11) NOT NULL AUTO_INCREMENT,
+ `menuid` int(11) NOT NULL,
+ `entryid` varchar(16) CHARACTER SET ascii NULL COMMENT 'If NULL, entry is gap',
+ `hotkey` varchar(8) CHARACTER SET ascii NOT NULL,
+ `title` varchar(100) NOT NULL COMMENT 'Sanitize this before insert',
+ `hidden` tinyint(1) NOT NULL,
+ `sortval` int(11) NOT NULL,
+ `plainpass` varchar(80) NOT NULL,
+ `md5pass` char(32) CHARACTER SET ascii NOT NULL,
+ PRIMARY KEY (`menuentryid`),
+ KEY `menuid` (`menuid`,`entryid`),
+ KEY `entryid` (`entryid`)
+");
+
+$res[] = tableCreate('serversetup_menu_location', '
+ `menuid` int(11) NOT NULL,
+ `locationid` int(11) NOT NULL,
+ `defaultentryid` int(11) DEFAULT NULL,
+ PRIMARY KEY (`menuid`,`locationid`),
+ UNIQUE `locationid` (`locationid`),
+ KEY `defaultentryid` (`defaultentryid`)
+');
+
+$res[] = tableCreate('serversetup_localboot', "
+ `systemmodel` varchar(120) NOT NULL,
+ `bootmethod` enum('EXIT','COMBOOT','SANBOOT') CHARACTER SET ascii NOT NULL,
+ PRIMARY KEY (`systemmodel`)
+");
+
+// Add defaultentry override column
+if (!tableHasColumn('serversetup_menu_location', 'defaultentryid')) {
+ if (Database::exec('ALTER TABLE serversetup_menu_location ADD COLUMN `defaultentryid` int(11) DEFAULT NULL,
+ ADD KEY `defaultentryid` (`defaultentryid`)') !== false) {
+ $res[] = UPDATE_DONE;
+ } else {
+ $res[] = UPDATE_FAILED;
+ }
+}
+
+$res[] = tableAddConstraint('serversetup_menu', 'defaultentryid', 'serversetup_menuentry', 'menuentryid',
+ 'ON DELETE SET NULL');
+
+$res[] = tableAddConstraint('serversetup_menuentry', 'entryid', 'serversetup_bootentry', 'entryid',
+ 'ON UPDATE CASCADE ON DELETE CASCADE');
+
+$res[] = tableAddConstraint('serversetup_menuentry', 'menuid', 'serversetup_menu', 'menuid',
+ 'ON UPDATE CASCADE ON DELETE CASCADE');
+
+$res[] = tableAddConstraint('serversetup_menu_location', 'menuid', 'serversetup_menu', 'menuid',
+ 'ON UPDATE CASCADE ON DELETE CASCADE');
+
+$res[] = tableAddConstraint('serversetup_menu_location', 'defaultentryid', 'serversetup_menuentry', 'menuentryid',
+ 'ON UPDATE CASCADE ON DELETE SET NULL');
+
+if (Module::get('location') !== false) {
+ if (!tableExists('location')) {
+ $res[] = UPDATE_RETRY;
+ } else {
+ $res[] = tableAddConstraint('serversetup_menu_location', 'locationid', 'location', 'locationid',
+ 'ON UPDATE CASCADE ON DELETE CASCADE');
+ }
+}
+
+responseFromArray($res);
diff --git a/modules-available/serversetup-bwlp-ipxe/lang/de/messages.json b/modules-available/serversetup-bwlp-ipxe/lang/de/messages.json
new file mode 100644
index 00000000..0772a7e4
--- /dev/null
+++ b/modules-available/serversetup-bwlp-ipxe/lang/de/messages.json
@@ -0,0 +1,21 @@
+{
+ "boot-entry-created": "Men\u00fceintrag {{0}} erzeugt",
+ "boot-entry-updated": "Men\u00fceintrag {{0}} aktualisiert",
+ "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.",
+ "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}}",
+ "localboot-invalid-method": "Ung\u00fcltige localboot-Methode: {{0}}",
+ "localboot-saved": "Einstellungen gespeichert",
+ "location-menu-assigned": "{{0}} wurde ein Men\u00fc zugewiesen",
+ "location-use-default": "{{0}} verwendet jetzt das Standardmen\u00fc",
+ "menu-deleted": "Men\u00fc gel\u00f6scht",
+ "menu-saved": "Men\u00fc wurde gespeichert",
+ "menu-set-default": "Standardmen\u00fc wurde gesetzt",
+ "missing-bootentry-data": "Fehlende Daten f\u00fcr den Men\u00fceintrag",
+ "no-ip-addr-set": "Bitte w\u00e4hlen Sie die prim\u00e4re IP-Adresse des Servers",
+ "no-such-menu": "Men\u00fc mit ID {{0}} existiert nicht",
+ "unknown-bootentry-type": "Unbekannter Eintrags-Typ: {{0}}"
+}
diff --git a/modules-available/serversetup-bwlp-ipxe/lang/de/module.json b/modules-available/serversetup-bwlp-ipxe/lang/de/module.json
new file mode 100644
index 00000000..9a8de39c
--- /dev/null
+++ b/modules-available/serversetup-bwlp-ipxe/lang/de/module.json
@@ -0,0 +1,19 @@
+{
+ "dl-efi": "UEFI",
+ "dl-hd": "HDD Partitions-Image",
+ "dl-i386": "32\u2009Bit",
+ "dl-lkrn": "In Kernel-Header gewrappt",
+ "dl-pcbios": "Legacy BIOS",
+ "dl-pcinic": "Mit PCI(e) Netzwerktreibern",
+ "dl-snp": "Verwendet SNP-Netzwerkschnittstelle",
+ "dl-usb": "USB-Image",
+ "dl-usbnic": "Mit USB Netzwerktreibern",
+ "dl-x86_64": "64\u2009Bit",
+ "module_name": "iPXE \/ Boot Menu",
+ "page_title": "PXE- und Boot-Einstellungen",
+ "submenu_address": "Server-Adresse",
+ "submenu_bootentry": "Men\u00fceintr\u00e4ge verwalten",
+ "submenu_download": "Downloads",
+ "submenu_localboot": "HDD-Boot",
+ "submenu_menu": "Men\u00fcs verwalten"
+}
diff --git a/modules-available/serversetup-bwlp-ipxe/lang/de/permissions.json b/modules-available/serversetup-bwlp-ipxe/lang/de/permissions.json
new file mode 100644
index 00000000..5cd02119
--- /dev/null
+++ b/modules-available/serversetup-bwlp-ipxe/lang/de/permissions.json
@@ -0,0 +1,12 @@
+{
+ "access-page": "Seite sehen.",
+ "download": "USB-Image herunterladen.",
+ "edit.address": "Boot-Adresse des Servers ausw\u00e4hlen.",
+ "edit.menu": "Bootmen\u00fc anpassen.",
+ "ipxe.bootentry.edit": "Einen Boot-Eintrag bearbeiten.",
+ "ipxe.bootentry.view": "Liste aller Boot-Eintr\u00e4ge sehen.",
+ "ipxe.localboot.edit": "Ausnahmeliste f\u00fcr Localboot-Modus bearbeiten.",
+ "ipxe.menu.assign": "Men\u00fc einem Raum\/Ort zuweisen.",
+ "ipxe.menu.edit": "Men\u00fc editieren.",
+ "ipxe.menu.view": "Liste der Men\u00fcs sehen."
+} \ 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
new file mode 100644
index 00000000..198a1517
--- /dev/null
+++ b/modules-available/serversetup-bwlp-ipxe/lang/de/template-tags.json
@@ -0,0 +1,94 @@
+{
+ "lang_active": "Aktiv",
+ "lang_add": "Hinzuf\u00fcgen",
+ "lang_addBootentry": "Men\u00fceintrag hinzuf\u00fcgen",
+ "lang_addMenu": "Men\u00fc hinzuf\u00fcgen",
+ "lang_additionalInfoLink": "Weitere Informationen",
+ "lang_archAgnostic": "Architekturunabh\u00e4ngig",
+ "lang_archBoth": "BIOS und EFI",
+ "lang_archSelector": "Architekturauswahl",
+ "lang_assignMenuToLocation": "Ort ein Men\u00fc zuweisen",
+ "lang_biosOnly": "Nur BIOS",
+ "lang_bootAddress": "Boot-Adresse des Servers",
+ "lang_bootBehavior": "Standard-Bootverhalten",
+ "lang_bootEntryData": "Daten des Men\u00fceintrags",
+ "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_bootentryDeleteConfirm": "Sind Sie sicher, dass Sie diesen Men\u00fceintrag l\u00f6schen wollen?",
+ "lang_bootentryHead": "Men\u00fceintr\u00e4ge",
+ "lang_bootentryIntro": "Hier k\u00f6nnen Sie Men\u00fceintr\u00e4ge definieren, die sich sp\u00e4ter einem Men\u00fc zuweisen lassen. Ein Men\u00fceintrag besteht entweder aus einem zu ladenen Kernel\/Image plus optional initrd, oder aus einem iPXE-Script.",
+ "lang_bootentryTitle": "Men\u00fceintrag",
+ "lang_chooseIP": "Bitte w\u00e4hlen Sie die IP-Adresse, \u00fcber die der Server von den Clients zum Booten angesprochen werden soll.",
+ "lang_commandLine": "Command line",
+ "lang_count": "Anzahl",
+ "lang_customEntry": "Eigener Eintrag",
+ "lang_downloadBootImage": "Boot-Image herunterladen",
+ "lang_downloadRufus": "Rufus herunterladen",
+ "lang_editBuiltinWarn": "Achtung! Sie bearbeiten einen der vorgegebenen Eintr\u00e4ge! Bei einem Update k\u00f6nnten Ihre \u00c4nderungen wieder \u00fcberschrieben werden",
+ "lang_editMenuHead": "Men\u00fc bearbeiten",
+ "lang_efiOnly": "Nur EFI",
+ "lang_entryChooserTitle": "Men\u00fceintrag ausw\u00e4hlen",
+ "lang_entryId": "ID",
+ "lang_entryTitle": "Bezeichnung",
+ "lang_example": "Beispiel",
+ "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_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_globalMenuWarning": "Dieses Men\u00fc ist keinem Raum zugeordnet",
+ "lang_hotkey": "Hotkey",
+ "lang_idFormatHint": "(Max. 16 Zeichen, nur a-z 0-9 - _)",
+ "lang_imageToLoad": "Zu ladendes Image (z.B. Kernel)",
+ "lang_initRd": "Zu ladendes initramfs",
+ "lang_ipxeWikiUrl": "im iPXE Wiki",
+ "lang_isDefault": "Standard",
+ "lang_listOfMenus": "Men\u00fcliste",
+ "lang_localBootDefault": "Standardm\u00e4\u00dfig verwendete Methode, um von Festplatte zu booten",
+ "lang_localBootExceptions": "Ausnahmen, pro Rechnermodell definierbar",
+ "lang_localBootHead": "Boot von Festplatte",
+ "lang_localBootIntro": "Aus dem iPXE Bootmen\u00fc kann auf verschiedene Arten ein Boot von der prim\u00e4ren Festplatte ausgel\u00f6st werden. In den allermeisten F\u00e4llen ist die Einstellung \"AUTO\" ausreichend, bei bestimmten Rechnermodellen kann es allerdings erforderlich sein, eine der alternativen Methoden zu erzwingen. Falls Sie einem solchen Modell begegnen, k\u00f6nnen Sie im unteren Teil dieser Seite eine solche Ausnahme festlegen. In einigen F\u00e4llen l\u00e4sst sich das Problem auch durch ein BIOS-Update auf den entsprechenden Ger\u00e4ten beheben.",
+ "lang_localHDD": "Lokale HDD",
+ "lang_locationCount": "Anzahl Orte",
+ "lang_masterPassword": "Master-Passwort",
+ "lang_masterPasswordHelp": "Das Master-Passwort wird ben\u00f6tigt, um einen Men\u00fceintrag 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_menuDeleteConfirm": "Sind Sie sicher, dass Sie dieses Men\u00fc l\u00f6schen wollen?",
+ "lang_menuDisplayTime": "Anzeigedauer des Men\u00fcs",
+ "lang_menuEntryOverride": "Standardeintrag \u00fcberschreiben",
+ "lang_menuGeneration": "Erzeugen des Bootmen\u00fcs",
+ "lang_menuListIntro": "Hier sehen Sie eine Liste aller vorhandenen Men\u00fcs, deren Zuordnung zu R\u00e4umen sowie die M\u00f6glichkeit, diese zu editieren oder l\u00f6schen. Um ein Men\u00fc einem bestimmten Raum zuzuweisen, besuchen Sie bitte den Men\u00fcpunkt \"R\u00e4ume\/Orte\".",
+ "lang_menuLocations": "Zugewiesene Orte",
+ "lang_menuTimeout": "Timeout",
+ "lang_menuTitle": "Men\u00fc",
+ "lang_moduleHeading": "iPXE \/ Boot Menu",
+ "lang_newBootEntryHead": "Neuer Men\u00fceintrag",
+ "lang_newMenu": "Neues Men\u00fc",
+ "lang_none": "(keine)",
+ "lang_override": "\u00dcberschreiben",
+ "lang_pxeBuilt": "PXE-Binary gebaut",
+ "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",
+ "lang_scriptContent": "Script",
+ "lang_seconds": "Sekunden",
+ "lang_set": "Setzen",
+ "lang_spacer": "Abstandhalter\/\u00dcberschrift",
+ "lang_systemmodel": "System-Modell",
+ "lang_title": "Titel",
+ "lang_typeExecEntry": "Standardeintrag",
+ "lang_typeScriptEntry": "Benutzerdefiniertes Script",
+ "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_usbImgHelpBtn": "Bootbaren USB-Stick erstellen",
+ "lang_usbImgHelpLinux": "Nutzen Sie dd, um das Image auf einen USB-Stick zu schreiben. Das Image enth\u00e4lt bereits eine Partitionstabelle, achten Sie daher darauf, dass Sie das Image z.B. nach \/dev\/sdx schreiben, und nicht nach \/dev\/sdx1",
+ "lang_usbImgHelpWindows": "Unter Windows muss zun\u00e4chst ein Programm besorgt werden, mit dem sich Images direkt auf einen USB-Stick schreiben lassen. Es gibt gleich mehrere kostenlose und quelloffene Programme, eines davon ist Rufus. Rufus wurde mit dem bwLehrpool-Image gestetet. Nach dem Starten des Programms ist lediglich das heruntergeladene Image zu \u00f6ffnen, sowie in der Liste der Laufwerke der richtige USB-Stick auszuw\u00e4hlen (damit Sie nicht versehentlich Daten auf dem falschen Laufwerk \u00fcberschreiben!)",
+ "lang_useDefaultMenu": "\u00dcbergeordnetes Men\u00fc verwenden",
+ "lang_useDefaultMenuEntry": "(Vorgabe des Men\u00fcs)"
+}
diff --git a/modules-available/serversetup-bwlp/lang/en/messages.json b/modules-available/serversetup-bwlp-ipxe/lang/en/messages.json
index d4ba6905..d4ba6905 100644
--- a/modules-available/serversetup-bwlp/lang/en/messages.json
+++ b/modules-available/serversetup-bwlp-ipxe/lang/en/messages.json
diff --git a/modules-available/serversetup-bwlp/lang/en/module.json b/modules-available/serversetup-bwlp-ipxe/lang/en/module.json
index aeea610c..aeea610c 100644
--- a/modules-available/serversetup-bwlp/lang/en/module.json
+++ b/modules-available/serversetup-bwlp-ipxe/lang/en/module.json
diff --git a/modules-available/serversetup-bwlp/lang/en/permissions.json b/modules-available/serversetup-bwlp-ipxe/lang/en/permissions.json
index 44d1c519..44d1c519 100644
--- a/modules-available/serversetup-bwlp/lang/en/permissions.json
+++ b/modules-available/serversetup-bwlp-ipxe/lang/en/permissions.json
diff --git a/modules-available/serversetup-bwlp-ipxe/lang/en/template-tags.json b/modules-available/serversetup-bwlp-ipxe/lang/en/template-tags.json
new file mode 100644
index 00000000..121ed3e7
--- /dev/null
+++ b/modules-available/serversetup-bwlp-ipxe/lang/en/template-tags.json
@@ -0,0 +1,42 @@
+{
+ "lang_active": "Active",
+ "lang_addBootentry": "Add Bootentry",
+ "lang_addMenu": "Add Menu",
+ "lang_bootAddress": "Boot Address of the Server",
+ "lang_bootBehavior": "Default Boot Behavior",
+ "lang_bootentryTitle": "Bootentry",
+ "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_bootentryDeleteConfirm": "Are you sure you want to delete this bootentry?",
+ "lang_menuDeleteConfirm": "Are you sure you want to delete this menu?",
+ "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_isDefault": "Default",
+ "lang_listOfMenus": "Menulist",
+ "lang_localHDD": "Local HDD",
+ "lang_locationCount": "Number of Locations",
+ "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_menuTitle": "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/lang/pt/messages.json b/modules-available/serversetup-bwlp-ipxe/lang/pt/messages.json
index 65745768..65745768 100644
--- a/modules-available/serversetup-bwlp/lang/pt/messages.json
+++ b/modules-available/serversetup-bwlp-ipxe/lang/pt/messages.json
diff --git a/modules-available/serversetup-bwlp/lang/pt/module.json b/modules-available/serversetup-bwlp-ipxe/lang/pt/module.json
index aeea610c..aeea610c 100644
--- a/modules-available/serversetup-bwlp/lang/pt/module.json
+++ b/modules-available/serversetup-bwlp-ipxe/lang/pt/module.json
diff --git a/modules-available/serversetup-bwlp/lang/pt/template-tags.json b/modules-available/serversetup-bwlp-ipxe/lang/pt/template-tags.json
index 14788767..14788767 100644
--- a/modules-available/serversetup-bwlp/lang/pt/template-tags.json
+++ b/modules-available/serversetup-bwlp-ipxe/lang/pt/template-tags.json
diff --git a/modules-available/serversetup-bwlp-ipxe/page.inc.php b/modules-available/serversetup-bwlp-ipxe/page.inc.php
new file mode 100644
index 00000000..bb69fecf
--- /dev/null
+++ b/modules-available/serversetup-bwlp-ipxe/page.inc.php
@@ -0,0 +1,829 @@
+<?php
+
+class Page_ServerSetup extends Page
+{
+
+ private $addrListTask;
+ private $compileTask = null;
+ private $currentAddress;
+ private $currentMenu;
+ private $hasIpSet = false;
+
+ private function getCompileTask()
+ {
+ if ($this->compileTask !== null)
+ return $this->compileTask;
+ $this->compileTask = Property::get('ipxe-task-id');
+ 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 @@
+<h2>{{lang_bootentryHead}}</h2>
+
+<p>
+ {{lang_bootentryIntro}}
+</p>
+
+<table class="table">
+ <thead>
+ <tr>
+ <th>{{lang_bootentryTitle}}</th>
+ <th>{{lang_hotkey}}</th>
+ <th class="slx-smallcol">{{lang_refCount}}</th>
+ <th class="slx-smallcol">{{lang_edit}}</th>
+ <th class="slx-smallcol">{{lang_delete}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#bootentryTable}}
+ <tr>
+ <td>
+ {{title}}
+ </td>
+ <td>
+ {{hotkey}}
+ </td>
+ <td align="right">
+ {{refs}}
+ </td>
+ <td align="center">
+ {{#allowEdit}}
+ <a href="?do=serversetup&amp;show=editbootentry&amp;id={{entryid}}" class="btn btn-xs btn-default">
+ <span class="glyphicon glyphicon-edit"></span>
+ </a>
+ {{/allowEdit}}
+ </td>
+ <td align="center">
+ {{#allowEdit}}
+ <button type="button" class="btn btn-xs btn-danger" data-toggle="modal" data-target="#deleteModal" onclick="deleteBootentry('{{entryid}}', '{{builtin}}')">
+ <span class="glyphicon glyphicon-trash"></span>
+ </button>
+ {{/allowEdit}}
+ </td>
+ </tr>
+ {{/bootentryTable}}
+ </tbody>
+</table>
+<div class="pull-right">
+ {{#allowEdit}}
+ <a href="?do=serversetup&amp;show=editbootentry" class="btn btn-success">
+ <span class="glyphicon glyphicon-plus"></span>
+ {{lang_addBootentry}}
+ </a>
+ {{/allowEdit}}
+</div>
+
+<!-- Modals -->
+<form method="post" action="?do=serversetup">
+ <input type="hidden" name="token" value="{{token}}">
+ <div class ="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+ <h4 class="modal-title" id="myModalLabel">{{lang_delete}}</h4>
+ </div>
+ <div class="modal-body">
+ <p>{{lang_bootentryDeleteConfirm}}</p>
+ </div>
+ <div class="modal-footer">
+ <input type="hidden" id="delete-bootentry-id" name="deleteid" value="">
+ <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button>
+ <button type="submit" name="action" value="deleteBootentry" class="btn btn-danger"><span class="glyphicon glyphicon-trash"></span> {{lang_delete}}</button>
+ </div>
+ </div>
+ </div>
+ </div>
+</form>
+
+<script>
+ function deleteBootentry(entryid) {
+ $("#delete-bootentry-id").val(entryid);
+ }
+</script> \ 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 @@
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_downloadBootImage}}
+ </div>
+ <div class="panel-body">
+ <table class="slx-table">
+ {{#files}}
+ <tr>
+ <td><a class="{{class}}" href="/boot/download/{{name}}">{{name}}</a></td>
+ <td class="text-right">{{size}}</td>
+ <td class="text-right">{{modified}}</td>
+ <td>({{features}})</td>
+ </tr>
+ {{/files}}
+ </table>
+ <p>
+ <span class="btn btn-default" data-toggle="modal" data-target="#help-usbimg">
+ <span class="glyphicon glyphicon-question-sign"></span>
+ {{lang_usbImgHelpBtn}}
+ </span>
+ </p>
+ <p>
+ {{lang_additionalInfoLink}} <a href="https://ipxe.org/appnote/buildtargets" target="_blank">{{lang_ipxeWikiUrl}}</a>
+ </p>
+ </div>
+</div>
+
+<div class="modal fade" id="help-usbimg" tabindex="-1" role="dialog">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+ {{lang_usbImage}}
+ </div>
+ <div class="modal-body">
+ <p>{{lang_usbImgHelp}}</p>
+ <p>
+ <b>Linux</b>
+ <br>
+ {{lang_usbImgHelpLinux}}
+ </p>
+ <p>
+ <b>Windows</b>
+ <br>
+ {{lang_usbImgHelpWindows}}
+ </p>
+ <p>
+ <a href="https://rufus.akeo.ie/#download" target="_blank">{{lang_downloadRufus}}</a>
+ </p>
+ </div>
+ </div>
+ </div>
+</div> \ 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 @@
+<div class="page-header">
+ <h1>{{lang_moduleHeading}}</h1>
+</div> \ 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 @@
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_bootAddress}}
+ </div>
+ <div class="panel-body">
+ <div class="{{chooseHintClass}}">
+ {{lang_chooseIP}}
+ </div>
+ <form method="post" action="?do=ServerSetup">
+ <input type="hidden" name="action" value="ip">
+ <input type="hidden" name="token" value="{{token}}">
+ <table class="slx-table">
+ {{#ips}}
+ <tr>
+ <td>{{ip}}</td>
+ {{#default}}
+ <td>
+ <span class="btn btn-success btn-xs"><span class="glyphicon glyphicon-ok"></span> {{lang_active}}</span>
+ </td>
+ {{/default}}
+ {{^default}}
+ <td>
+ <button class="btn btn-primary btn-xs" name="ip" value="{{ip}}" {{disabled}}>
+ <span class="glyphicon glyphicon-flag"></span>
+ {{lang_set}}
+ </button>
+ </td>
+ {{/default}}
+ </tr>
+ {{/ips}}
+ </table>
+ <p>
+ {{lang_recompileHint}}
+ </p>
+ </form>
+ <form method="post" action="?do=ServerSetup">
+ <input type="hidden" name="token" value="{{token}}">
+ <button class="btn btn-default" name="action" value="compile" {{disabled}}>
+ <span class="glyphicon glyphicon-refresh"></span>
+ {{lang_forceRecompile}}
+ </button>
+ </form>
+ </div>
+</div> \ No newline at end of file
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 @@
+<h2>{{lang_newBootEntryHead}}</h2>
+
+{{#builtin}}
+ <div class="alert alert-warning">
+ {{lang_editBuiltinWarn}}
+ </div>
+{{/builtin}}
+
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_bootEntryData}}
+ </div>
+ <div class="panel-body">
+ <form method="post" action="?do=serversetup">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="savebootentry">
+ <input type="hidden" name="entryid" value="{{entryid}}">
+
+ <div class="form-group">
+ <div class="radio">
+ <input class="type-radio" type="radio" name="type" value="exec" id="type-exec" {{exec_checked}}>
+ <label for="type-exec">{{lang_typeExecEntry}}</label>
+ </div>
+ <div class="radio">
+ <input class="type-radio" type="radio" name="type" value="script" id="type-script" {{script_checked}}>
+ <label for="type-script">{{lang_typeScriptEntry}}</label>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="input-id">
+ {{lang_entryId}} {{lang_idFormatHint}}
+ </label>
+ <input id="input-id" class="form-control" name="newid" value="{{entryid}}" pattern="^[a-z0-9\-_]{1,16}$" minlength="1" maxlength="16" required>
+ </div>
+ <div class="form-group">
+ <label for="input-title">
+ {{lang_entryTitle}}
+ </label>
+ <input id="input-title" class="form-control" name="title" value="{{title}}" maxlength="100">
+ </div>
+ <div class="form-group">
+ <label for="arch-selector">
+ {{lang_archSelector}}
+ </label>
+ <select id="arch-selector" class="form-control" name="entry[arch]">
+ <option value="agnostic" {{agnostic_selected}}>{{lang_archAgnostic}}</option>
+ <option value="PCBIOS" {{PCBIOS_selected}}>{{lang_biosOnly}}</option>
+ <option value="EFI" {{EFI_selected}}>{{lang_efiOnly}}</option>
+ <option value="PCBIOS-EFI" {{PCBIOS-EFI_selected}}>{{lang_archBoth}}</option>
+ </select>
+ </div>
+
+ <div class="type-form" id="form-exec">
+ <div class="row">
+ {{#entries}}
+ <div class="mode-class col-md-6" id="col-{{mode}}">
+ <div class="panel panel-default">
+ <div class="panel-body">
+ <h4 class="arch-heading">{{mode}}</h4>
+ <div class="form-group">
+ <label for="input-ex">
+ {{lang_imageToLoad}}
+ </label>
+ <input id="input-ex" class="form-control" name="entry[executable][{{mode}}]" value="{{executable}}">
+ </div>
+ <div class="form-group">
+ <label for="input-rd">
+ {{lang_initRd}}
+ </label>
+ <input id="input-rd" class="form-control" name="entry[initRd][{{mode}}]" value="{{initRd}}">
+ </div>
+ <div class="form-group">
+ <label for="input-cmd">
+ {{lang_commandLine}}
+ </label>
+ <input id="input-cmd" class="form-control" name="entry[commandLine][{{mode}}]"
+ value="{{commandLine}}">
+ </div>
+ <div class="form-group">
+ <div class="checkbox checkbox-inline">
+ <input id="exec-replace-{{mode}}" class="form-control" type="checkbox"
+ name="entry[replace][{{mode}}]" {{replace_checked}}>
+ <label for="exec-replace-{{mode}}">{{lang_execReplace}}</label>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="checkbox checkbox-inline">
+ <input id="exec-au-{{mode}}" class="form-control" type="checkbox"
+ name="entry[autoUnload][{{mode}}]" {{autoUnload_checked}}>
+ <label for="exec-au-{{mode}}">{{lang_execAutoUnload}}</label>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="checkbox checkbox-inline">
+ <input id="exec-reset-{{mode}}" class="form-control" type="checkbox"
+ name="entry[resetConsole][{{mode}}]" {{resetConsole_checked}}>
+ <label for="exec-reset-{{mode}}">{{lang_execResetConsole}}</label>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ {{/entries}}
+ </div>
+ </div>
+
+ <div class="type-form" id="form-script">
+ <div class="form-group">
+ <label for="script-ta">
+ {{lang_scriptContent}}
+ </label>
+ <textarea id="script-ta" class="form-control" rows="10"
+ name="entry[script]">{{entry.script}}</textarea>
+ </div>
+ </div>
+
+ {{#builtin}}
+ <div class="alert alert-warning">
+ {{lang_editBuiltinWarn}}
+ </div>
+ {{/builtin}}
+
+ <p class="slx-bold">{{lang_referencingMenus}}:</p>
+ <ul>
+ {{#menus}}
+ <a href="?do=serversetup&amp;show=editmenu&amp;id={{menuid}}&amp;highlight={{entryid}}">{{title}}</a>
+ {{/menus}}
+ </ul>
+
+ <div class="buttonbar text-right">
+ <button type="submit" class="btn btn-primary">
+ <span class="glyphicon glyphicon-floppy-disk"></span>
+ {{lang_save}}
+ </button>
+ </div>
+ </form>
+ </div>
+</div>
+
+<script><!--
+document.addEventListener('DOMContentLoaded', function () {
+ $('.type-radio').click(function () {
+ $('.type-form').hide();
+ $('#form-' + $(this).val()).show();
+ });
+ $('.type-radio[checked]').click();
+ var $as = $('#arch-selector');
+ $as.change(function() {
+ var v = $as.val();
+ if (v === 'agnostic') {
+ v = 'PCBIOS';
+ $('.arch-heading').hide();
+ } else {
+ $('.arch-heading').show();
+ }
+ var vs = v.split('-');
+ var cols = 12 / vs.length;
+ $('.mode-class').hide();
+ for (var i = 0; i < vs.length; ++i) {
+ $('#col-' + vs[i]).attr('class', 'mode-class col-md-' + cols).show();
+ }
+ }).change();
+});
+// --></script> \ 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 @@
+<div class="panel panel-default">
+ <div class="panel-heading">{{lang_menuGeneration}}</div>
+ <div class="panel-body">
+ <div id="file-list">
+ {{#files}}
+ <div id="built-{{namehyphen}}">
+ <span class="glyphicon glyphicon-question-sign"></span>
+ {{name}}
+ </div>
+ {{/files}}
+ </div>
+ <div id="genfailed" class="collapse">
+ <div class="alert alert-danger">
+ {{lang_generationFailed}}
+ </div>
+ </div>
+ <div id="tm-compile-div" data-tm-id="{{taskid}}" data-tm-log="log" data-tm-log-height="36em" data-tm-callback="ipxeGenCb">{{lang_menuGeneration}}</div>
+ </div>
+</div>
+
+<script type="text/javascript">
+ document.addEventListener('DOMContentLoaded', function() {
+ var slxFileList = $('#file-list').find('.glyphicon');
+ });
+
+ function ipxeGenCb(task)
+ {
+ if (!task || !task.statusCode)
+ return;
+
+ if (task.statusCode === 'TASK_FINISHED') {
+ $('#tm-compile-div').find('pre').hide();
+ }
+ if (task.statusCode === 'TASK_ERROR') {
+ var $gf = $('#genfailed');
+ if (task.data && task.data.errors) {
+ $gf.append($('<pre>').text(task.data.errors));
+ }
+ $gf.show('slow');
+ slxFileList.find('.glyphicon-question-sign').removeClass('glyphicon-question-sign').addClass('glyphicon-stop');
+ } else {
+ // Working or finished
+ if (task.data && task.data.files && task.data.files) {
+ for (var k in task.data.files) {
+ if (!task.data.files[k])
+ continue;
+ var f = '#built-' + k.replace('/', '-').replace('.', '-');
+ var $e = $(f);
+ $e.find('.glyphicon-question-sign').removeClass('glyphicon-question-sign').addClass('glyphicon-ok text-success');
+ }
+ }
+ }
+ }
+</script>
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 @@
+<h2>{{lang_localBootHead}}</h2>
+
+<p>{{lang_localBootIntro}}</p>
+
+<form method="post" action="?do=serversetup">
+
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="savelocalboot">
+
+ <br>
+ <div class="form-group">
+ <label for="default-selector">
+ {{lang_localBootDefault}}
+ </label>
+ <select id="default-selector" class="form-control" name="default">
+ {{#options}}
+ <option {{selected}}>{{key}}</option>
+ {{/options}}
+ </select>
+ </div>
+
+ <br>
+ <h3>
+ {{lang_localBootExceptions}}
+ </h3>
+ <table class="table">
+ <tr>
+ <th>{{lang_systemmodel}}</th>
+ <th class="slx-smallcol">{{lang_count}}</th>
+ <th class="slx-smallcol">{{lang_override}}</th>
+ </tr>
+ {{#exceptions}}
+ <tr>
+ <td><a href="?do=statistics&show=list&filters=systemmodel+%3D+{{systemmodel}}">{{systemmodel}}</a></td>
+ <td class="text-right">{{cnt}}</td>
+ <td>
+ <select class="form-control" name="override[{{systemmodel}}]">
+ <option value="" {{^bootmethod}}selected{{/bootmethod}}>{{lang_none}}</option>
+ {{#options}}
+ <option {{selected}}>{{key}}</option>
+ {{/options}}
+ </select>
+ </td>
+ </tr>
+ {{/exceptions}}
+ </table>
+
+ <div class="text-right">
+ <button class="btn btn-warning" type="reset">
+ <span class="glyphicon glyphicon-refresh"></span>
+ {{lang_reset}}
+ </button>
+ <button class="btn btn-primary" type="submit">
+ <span class="glyphicon glyphicon-floppy-disk"></span>
+ {{lang_save}}
+ </button>
+ </div>
+
+</form>
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 @@
+<h2>{{lang_assignMenuToLocation}}</h2>
+<h3>{{locationName}}</h3>
+
+<form method="post" action="?do=serversetup">
+
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="savelocation">
+ <input type="hidden" name="locationid" value="{{locationid}}">
+ <table class="table">
+ <thead>
+ <tr>
+ <th class="slx-smallcol"></th>
+ <th>{{lang_menuTitle}}</th>
+ <th class="slx-smallcol">{{lang_menuEntryOverride}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ <div class="radio radio-inline">
+ <input type="radio" name="menuid" value="0" {{default_selected}}>
+ <label></label>
+ </div>
+ </td>
+ <td>
+ <i>{{lang_useDefaultMenu}}</i>
+ </td>
+ <td></td>
+ </tr>
+ {{#list}}
+ <tr>
+ <td>
+ <div class="radio radio-inline">
+ <input type="radio" name="menuid" value="{{menuid}}" {{menu_selected}}>
+ <label></label>
+ </div>
+ </td>
+ <td>
+ {{title}}
+ </td>
+ <td class="text-right">
+ <select name="defaultentryid-{{menuid}}" class="form-control">
+ <option value="0" style="font-style:italic">{{lang_useDefaultMenuEntry}}</option>
+ {{#entries}}
+ <option value="{{id}}" {{selected}}>{{title}}</option>
+ {{/entries}}
+ </select>
+ </td>
+ </tr>
+ {{/list}}
+ </tbody>
+ </table>
+
+ <div class="pull-right">
+ <button type="submit" class="btn btn-primary">
+ <span class="glyphicon glyphicon-floppy-disk"></span>
+ {{lang_save}}
+ </button>
+ </div>
+
+</form>
+
+<div class="clearfix"></div>
+
+<script>
+ function deleteMenu(menuid) {
+ $("#delete-menu-id").val(menuid);
+ }
+</script> \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp-ipxe/templates/menu-edit.html b/modules-available/serversetup-bwlp-ipxe/templates/menu-edit.html
new file mode 100644
index 00000000..1598a2b7
--- /dev/null
+++ b/modules-available/serversetup-bwlp-ipxe/templates/menu-edit.html
@@ -0,0 +1,368 @@
+<h2>{{lang_editMenuHead}}</h2>
+
+<input type="text" name="prevent_autofill" id="prevent_autofill" value="" style="position:absolute;top:-2000px" tabindex="-1">
+<input type="password" name="password_fake" id="password_fake" value="" style="position:absolute;top:-2000px" tabindex="-1">
+
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{title}}
+ {{^title}}
+ {{lang_newMenu}}
+ {{/title}}
+ </div>
+ <div class="panel-body list-group">
+ <form method="post" action="?do=serversetup">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="savemenu">
+ <input type="hidden" name="menuid" value="{{menuid}}">
+
+ <div class="row list-group-item">
+ <div class="col-sm-3">
+ <label for="panel-title">{{lang_menuTitle}}</label>
+ </div>
+ <div class="col-sm-9">
+ <input class="form-control" name="title" id="panel-title" type="text" value="{{title}}" {{readonly}}>
+ </div>
+ </div>
+ <div class="row list-group-item">
+ <div class="col-sm-3">
+ <label for="panel-timeout">{{lang_menuTimeout}}</label>
+ </div>
+ <div class="col-sm-9">
+ <div class="input-group">
+ <input class="form-control" name="timeout" id="panel-timeout" type="number" min="0" max="9999"
+ value="{{timeout}}" {{readonly}}>
+ <span class="input-group-addon">{{lang_seconds}}</span>
+ </div>
+ </div>
+ </div>
+ <div>
+ <table class="table">
+ <thead>
+ <tr>
+ <th class="slx-smallcol"></th>
+ <th class="slx-smallcol"></th>
+ <th class="slx-smallcol">{{lang_entryId}}</th>
+ <th>{{lang_title}}</th>
+ <th width="11%">{{lang_hotkey}}</th>
+ <th width="15%">{{lang_password}}</th>
+ <th class="slx-smallcol"><span class="glyphicon glyphicon-eye-close"></span></th>
+ <th class="slx-smallcol"></th>
+ </tr>
+ </thead>
+ <tbody id="table-body" style="overflow: auto;">
+ {{#entries}}
+ <tr class="{{highlight}}">
+ <input type="hidden" class="sort-val" name="entry[{{menuentryid}}][sortval]" value="{{sortval}}">
+ <input type="hidden" name="entry[{{menuentryid}}][hidden]" value="0">
+ <td class="drag-handler" style="cursor: pointer;text-align: center; vertical-align: middle;">
+ <span class="glyphicon glyphicon-th-list"></span>
+ </td>
+
+ <td class="slx-smallcol" style="text-align: center; vertical-align: middle;">
+ <div class="radio radio-inline no-spacer" style="margin: 0;{{^entryid}}display: none;{{/entryid}}">
+ <input type="radio" name="defaultentry" value="{{menuentryid}}"
+ {{#isdefault}}checked{{/isdefault}} {{perms.ipxe.menu.edit.disabled}} {{disabled}}>
+ <label></label>
+ </div>
+ </td>
+
+ <td class="text-nowrap">
+ <input class="entry-id" type="hidden" name="entry[{{menuentryid}}][entryid]" value="{{entryid}}">
+ <button type="button" class="btn btn-default" style="width: 100%; text-align: left" {{disabled}} data-toggle="modal" data-target="#entry-chooser-modal">
+ {{#entryid}}
+ {{entryid}}
+ {{/entryid}}
+ {{^entryid}}
+ {{lang_spacer}}
+ {{/entryid}}
+ </button>
+ </td>
+ <td>
+ <input class="form-control title" name="entry[{{menuentryid}}][title]" value="{{title}}"
+ maxlength="100" {{readonly}}>
+ </td>
+
+ <td>
+ <select class="form-control key-list no-spacer" {{^entryid}}style="display: none;"{{/entryid}} name="entry[{{menuentryid}}][hotkey]" {{readonly}} data-default="{{hotkey}}">
+ </select>
+ </td>
+
+ <td>
+ <input class="form-control no-spacer" {{^entryid}}style="display: none;"{{/entryid}} name="entry[{{menuentryid}}][plainpass]" type="{{password_type}}"
+ value="{{plainpass}}" {{readonly}}>
+ </td>
+ <td class="slx-smallcol" style="text-align: center; vertical-align: middle;">
+ <div class="checkbox checkbox-inline no-spacer" style="text-align: left;margin: 0;{{^entryid}}display: none;{{/entryid}}">
+ <input name="entry[{{menuentryid}}][hidden]" value="1" type="checkbox" {{#hidden}}checked{{/hidden}}>
+ <label></label>
+ </div>
+ </td>
+ <td class="slx-smallcol" style="text-align: center; vertical-align: middle;">
+ <button type="button" class="btn btn-default remove-button"><span class="glyphicon glyphicon-remove"></span></button>
+ </td>
+ </tr>
+ {{/entries}}
+ </tbody>
+ </table>
+ </div>
+ <div class="text-right" style="margin-bottom: 20px">
+ <button id="add-btn" type="button" class="btn btn-success" {{disabled}}>
+ <span class="glyphicon glyphicon-plus-sign"></span>
+ {{lang_add}}
+ </button>
+ </div>
+ <div class="text-right">
+ <a href="?do=serversetup&show=menu" type="button" class="btn btn-default">{{lang_cancel}}</a>
+ <button id="save-button" type="submit" class="btn btn-primary" {{disabled}}>
+ <span class="glyphicon glyphicon-floppy-disk"></span>
+ {{lang_save}}
+ </button>
+ </div>
+ </form>
+
+ <div class="modal fade" id="entry-chooser-modal" tabindex="-1" role="dialog">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h5 class="modal-title">{{lang_entryChooserTitle}}</h5>
+ </div>
+ <div class="modal-body">
+ <div class="form-group">
+ <select id="entry-list" class="form-control">
+ <option value="">{{lang_spacer}}</option>
+ {{#entrylist}}
+ <option value="{{entryid}}">{{entryid}}</option>
+ {{/entrylist}}
+ </select>
+ </div>
+ {{#entrylist}}
+ <div id="entrydata-{{entryid}}" class="entrydata">
+ <div class="form-group">
+ <label for="{{entryid}}-name">{{lang_entryTitle}}</label>
+ <pre id="{{entryid}}-name">{{title}}</pre>
+ </div>
+ {{#data}}
+ {{#script}}
+ <div class="form-group">
+ <label for="{{entryid}}-script">{{lang_scriptContent}}</label>
+ <pre id="{{entryid}}-script">{{.}}</pre>
+ </div>
+ {{/script}}
+ {{^script}}
+ <div class="form-group">
+ <label for="{{entryid}}-script">{{lang_archSelector}}</label>
+ <pre id="{{entryid}}-arch">{{arch}}</pre>
+ </div>
+ {{#archAgnostic}}
+ <div class="form-group">
+ <label for="{{entryid}}-executable">{{lang_imageToLoad}}</label>
+ <pre id="{{entryid}}-executable">{{executable}}</pre>
+ </div>
+ <div class="form-group">
+ <label for="{{entryid}}-initRd">{{lang_initRd}}</label>
+ <pre id="{{entryid}}-initRd">{{initRd}}</pre>
+ </div>
+ <div class="form-group">
+ <label for="{{entryid}}-commandLine">{{lang_commandLine}}</label>
+ <pre id="{{entryid}}-commandLine" >{{commandLine}}</pre>
+ </div>
+ {{/archAgnostic}}
+ {{#PCBIOS}}
+ <div class="panel panel-default">
+ <div class="panel-heading">PCBIOS</div>
+ <div class="panel-body">
+ <div class="form-group">
+ <label for="{{entryid}}-executable">{{lang_imageToLoad}}</label>
+ <pre id="{{entryid}}-executable">{{executable}}</pre>
+ </div>
+ <div class="form-group">
+ <label for="{{entryid}}-initRd">{{lang_initRd}}</label>
+ <pre id="{{entryid}}-initRd">{{initRd}}</pre>
+ </div>
+ <div class="form-group">
+ <label for="{{entryid}}-commandLine">{{lang_commandLine}}</label>
+ <pre id="{{entryid}}-commandLine" >{{commandLine}}</pre>
+ </div>
+ </div>
+ </div>
+ {{/PCBIOS}}
+ {{#EFI}}
+ <div class="panel panel-default">
+ <div class="panel-heading">EFI</div>
+ <div class="panel-body">
+ <div class="form-group">
+ <label for="{{entryid}}-executable">{{lang_imageToLoad}}</label>
+ <pre id="{{entryid}}-executable">{{executable}}</pre>
+ </div>
+ <div class="form-group">
+ <label for="{{entryid}}-initRd">{{lang_initRd}}</label>
+ <pre id="{{entryid}}-initRd">{{initRd}}</pre>
+ </div>
+ <div class="form-group">
+ <label for="{{entryid}}-commandLine">{{lang_commandLine}}</label>
+ <pre id="{{entryid}}-commandLine" >{{commandLine}}</pre>
+ </div>
+ </div>
+ </div>
+ {{/EFI}}
+ {{/script}}
+ {{/data}}
+ </div>
+ {{/entrylist}}
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button>
+ <button type="button" class="btn btn-primary" id="choose-entry">{{lang_save}}</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+<div class="hidden">
+ <select id="key-list-template">
+ <option value="">{{lang_none}}</option>
+ {{#keys}}
+ <option value="{{key}}">{{key}}</option>
+ {{/keys}}
+ </select>
+</div>
+<table class="hidden" id="row-template">
+ <tr>
+ <input type="hidden" class="sort-val" name="entry[%new%][sortval]" value="99999">
+ <td class="drag-handler" style="cursor: pointer;text-align: center; vertical-align: middle;">
+ <span class="glyphicon glyphicon-th-list"></span>
+ </td>
+
+ <td class="slx-smallcol" style="text-align: center; vertical-align: middle;">
+ <div class="radio radio-inline no-spacer" style="margin: 0; display: none;">
+ <input type="radio" name="defaultentry" value="%new%">
+ <label></label>
+ </div>
+ </td>
+
+ <td class="text-nowrap">
+ <input class="entry-id" type="hidden" name="entry[%new%][entryid]" value="">
+ <button type="button" class="btn btn-default" style="width: 100%; text-align: left" {{disabled}} data-toggle="modal" data-target="#entry-chooser-modal">
+ {{lang_spacer}}
+ </button>
+ </td>
+ <td>
+ <input class="form-control title" data-old="#new#" name="entry[%new%][title]" maxlength="100">
+ </td>
+ <td>
+ <select class="form-control key-list no-spacer" style="display: none;" name="entry[%new%][hotkey]">
+ </select>
+ </td>
+ <td>
+ <input class="form-control no-spacer" style="display: none;" name="entry[%new%][plainpass]" type="{{password_type}}">
+ </td>
+ <td class="slx-smallcol" style="text-align: center; vertical-align: middle;">
+ <div class="checkbox checkbox-inline no-spacer" style="text-align: left;margin: 0;{{^entryid}}display: none;{{/entryid}}">
+ <input name="entry[%new%][hidden]" value="1" type="checkbox">
+ <label></label>
+ </div>
+ </td>
+ <td class="slx-smallcol" style="text-align: center; vertical-align: middle;">
+ <button type="button" class="btn btn-default remove-button"><span class="glyphicon glyphicon-remove"></span></button>
+ </td>
+ </tr>
+</table>
+
+<script type="text/javascript">
+ var spacerText = "{{lang_spacer}}";
+
+ document.addEventListener("DOMContentLoaded", function() {
+
+ function reassignSortValues() {
+ var startValue = 1;
+ $('.sort-val').each(function(index, element) {
+ element.value = startValue * 10;
+ startValue++;
+ });
+ }
+
+ $('#table-body').sortable({
+ opacity: 0.8,
+ handle: '.drag-handler',
+ start: function(evt, ui) {
+ ui.placeholder.css("visibility", "visible");
+ ui.placeholder.css("opacity", "0.152");
+ ui.placeholder.css("background-color", "#ddd");
+ },
+ stop: reassignSortValues
+ });
+
+ $('.key-list').each(function() {
+ $select = $(this);
+ $source = $('#key-list-template').find('option');
+ var def = $select.data('default');
+ $select.append($source.clone(true));
+ $select.find('option[value="' + def + '"]').attr('selected', true);
+ });
+ var newIndex = 0;
+ $('#add-btn').click(function() {
+ var $new = $('#row-template').find('tr').clone(true);
+ newIndex++;
+ $('#table-body').append($new);
+ $new.find('[name]').each(function() {
+ var $this = $(this);
+ var val = $this.val();
+ var name = $this.attr('name');
+ if (name) {
+ $this.attr('name', name.replace('%new%', 'new-' + newIndex));
+ }
+ if (val) {
+ $this.val(val.replace('%new%', 'new-' + newIndex));
+ }
+ });
+ reassignSortValues();
+ });
+
+ $('.remove-button').click(function() {
+ $(this).parent().parent().remove();
+ reassignSortValues();
+ });
+
+ $('#entry-list').change(function(e) {
+ var modal = $('#entry-chooser-modal');
+ modal.find('.entrydata').hide();
+ modal.find('#entrydata-' + $(this).val()).show();
+ });
+
+ var currentEntryButton = null;
+
+ $('#entry-chooser-modal').on('show.bs.modal', function(e) {
+ currentEntryButton = $(e.relatedTarget);
+ var entryId = currentEntryButton.parent().find('.entry-id').val();
+ $('#entry-list').val(entryId).change();
+ });
+
+ $('#choose-entry').click(function() {
+ $('#entry-chooser-modal').modal('hide');
+ var entryId = $('#entry-list').val();
+ currentEntryButton.parent().find('.entry-id').val(entryId);
+ currentEntryButton.text(entryId || spacerText);
+ var tableRow = currentEntryButton.parent().parent();
+ if (!entryId) {
+ tableRow.find('.no-spacer').hide();
+ tableRow.find('input.no-spacer').val('');
+ tableRow.find('div.no-spacer').find('input').prop('checked', false);
+
+ } else {
+ tableRow.find('.no-spacer').show();
+ }
+ var $title = tableRow.find('.title');
+ var oldval = $title.data('old');
+ if (oldval === '#stop#')
+ return;
+ if (oldval !== '#new#' && oldval !== $title.val()) {
+ $title.data('old', '#stop#');
+ return;
+ }
+ var text = $('#' + entryId + '-name').text();
+ $title.val(text).data('old', text);
+ });
+ });
+</script> \ 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 @@
+<h2>{{lang_listOfMenus}}</h2>
+
+<p>
+ {{lang_menuListIntro}}
+</p>
+
+<table class="table">
+ <thead>
+ <tr>
+ <th>{{lang_menuTitle}}</th>
+ <th class="slx-smallcol">{{lang_locationCount}}</th>
+ <th class="slx-smallcol">{{lang_isDefault}}</th>
+ <th class="slx-smallcol">{{lang_edit}}</th>
+ <th class="slx-smallcol">{{lang_delete}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#menuTable}}
+ <tr>
+ <td>
+ {{title}}
+ </td>
+ <td class="text-right">
+ <span class="badge" data-toggle="tooltip" data-placement="top" title="{{locnames}}">{{locationCount}}</span>
+ </td>
+ <td align="center">
+ {{^isdefault}}
+ {{#showSetDefault}}
+ <form method="post" action="?do=serversetup">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="menuid" value="{{menuid}}">
+ <button type="submit" name="action" value="setDefaultMenu" class="btn btn-xs btn-info">
+ <span class="glyphicon glyphicon-flag"></span>
+ </button>
+ </form>
+ {{/showSetDefault}}
+ {{/isdefault}}
+ {{#isdefault}}
+ <span class="glyphicon glyphicon-ok"></span>
+ {{/isdefault}}
+ </td>
+ <td align="center">
+ {{#allowEdit}}
+ <a href="?do=serversetup&amp;show=editmenu&amp;id={{menuid}}" class="btn btn-xs btn-default">
+ <span class="glyphicon glyphicon-edit"></span>
+ </a>
+ {{/allowEdit}}
+ </td>
+ <td align="center">
+ {{#allowDelete}}
+ <button type="button" class="btn btn-xs btn-danger" data-toggle="modal" data-target="#deleteModal" onclick="deleteMenu('{{menuid}}')">
+ <span class="glyphicon glyphicon-trash"></span>
+ </button>
+ {{/allowDelete}}
+ </td>
+ </tr>
+ {{/menuTable}}
+ </tbody>
+</table>
+<div class="pull-right">
+ <a href="?do=serversetup&amp;show=editmenu&amp;id=0" class="btn btn-success {{allowAddMenu}}">
+ <span class="glyphicon glyphicon-plus"></span>
+ {{lang_addMenu}}
+ </a>
+</div>
+
+<div class="clearfix"></div>
+
+
+<!-- Modals -->
+<form method="post" action="?do=serversetup">
+ <input type="hidden" name="token" value="{{token}}">
+ <div class ="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+ <h4 class="modal-title" id="myModalLabel">{{lang_delete}}</h4>
+ </div>
+ <div class="modal-body">
+ <p>{{lang_menuDeleteConfirm}}</p>
+ </div>
+ <div class="modal-footer">
+ <input type="hidden" id="delete-menu-id" name="deleteid" value="">
+ <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button>
+ <button type="submit" name="action" value="deleteMenu" class="btn btn-danger"><span class="glyphicon glyphicon-trash"></span> {{lang_delete}}</button>
+ </div>
+ </div>
+ </div>
+ </div>
+</form>
+
+<script>
+ function deleteMenu(menuid) {
+ $("#delete-menu-id").val(menuid);
+ }
+ document.addEventListener('DOMContentLoaded', function() {
+ $('[data-toggle="tooltip"]').tooltip();
+ });
+</script> \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp/config.json b/modules-available/serversetup-bwlp-pxelinux/config.json
index 36268c6a..36268c6a 100644
--- a/modules-available/serversetup-bwlp/config.json
+++ b/modules-available/serversetup-bwlp-pxelinux/config.json
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 @@
+<?php
+
+$data = Property::getBootMenu();
+$data['ipaddress'] = Property::getServerIp();
+$task = Taskmanager::submit('CompileIPxeLegacy', $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-pxelinux/hooks/main-warning.inc.php b/modules-available/serversetup-bwlp-pxelinux/hooks/main-warning.inc.php
new file mode 100644
index 00000000..a2eba6ff
--- /dev/null
+++ b/modules-available/serversetup-bwlp-pxelinux/hooks/main-warning.inc.php
@@ -0,0 +1,6 @@
+<?php
+
+if (!preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', Property::getServerIp())) {
+ Message::addError('serversetup.no-ip-addr-set', true);
+ $needSetup = true;
+}
diff --git a/modules-available/serversetup-bwlp/inc/ipxe.inc.php b/modules-available/serversetup-bwlp-pxelinux/inc/ipxe.inc.php
index c42de80b..c42de80b 100644
--- a/modules-available/serversetup-bwlp/inc/ipxe.inc.php
+++ b/modules-available/serversetup-bwlp-pxelinux/inc/ipxe.inc.php
diff --git a/modules-available/serversetup-bwlp/lang/de/messages.json b/modules-available/serversetup-bwlp-pxelinux/lang/de/messages.json
index 3e2cc834..3e2cc834 100644
--- a/modules-available/serversetup-bwlp/lang/de/messages.json
+++ b/modules-available/serversetup-bwlp-pxelinux/lang/de/messages.json
diff --git a/modules-available/serversetup-bwlp/lang/de/module.json b/modules-available/serversetup-bwlp-pxelinux/lang/de/module.json
index da71d558..da71d558 100644
--- a/modules-available/serversetup-bwlp/lang/de/module.json
+++ b/modules-available/serversetup-bwlp-pxelinux/lang/de/module.json
diff --git a/modules-available/serversetup-bwlp/lang/de/permissions.json b/modules-available/serversetup-bwlp-pxelinux/lang/de/permissions.json
index 98baec3c..98baec3c 100644
--- a/modules-available/serversetup-bwlp/lang/de/permissions.json
+++ b/modules-available/serversetup-bwlp-pxelinux/lang/de/permissions.json
diff --git a/modules-available/serversetup-bwlp/lang/de/template-tags.json b/modules-available/serversetup-bwlp-pxelinux/lang/de/template-tags.json
index 8d612ab0..8d612ab0 100644
--- a/modules-available/serversetup-bwlp/lang/de/template-tags.json
+++ b/modules-available/serversetup-bwlp-pxelinux/lang/de/template-tags.json
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/lang/en/template-tags.json b/modules-available/serversetup-bwlp-pxelinux/lang/en/template-tags.json
index 9bb55f93..9bb55f93 100644
--- a/modules-available/serversetup-bwlp/lang/en/template-tags.json
+++ b/modules-available/serversetup-bwlp-pxelinux/lang/en/template-tags.json
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/page.inc.php b/modules-available/serversetup-bwlp-pxelinux/page.inc.php
index 52b3afe4..52b3afe4 100644
--- a/modules-available/serversetup-bwlp/page.inc.php
+++ b/modules-available/serversetup-bwlp-pxelinux/page.inc.php
diff --git a/modules-available/serversetup-bwlp/permissions/permissions.json b/modules-available/serversetup-bwlp-pxelinux/permissions/permissions.json
index 44927506..44927506 100644
--- a/modules-available/serversetup-bwlp/permissions/permissions.json
+++ b/modules-available/serversetup-bwlp-pxelinux/permissions/permissions.json
diff --git a/modules-available/serversetup-bwlp/templates/heading.html b/modules-available/serversetup-bwlp-pxelinux/templates/heading.html
index d68360f1..d68360f1 100644
--- a/modules-available/serversetup-bwlp/templates/heading.html
+++ b/modules-available/serversetup-bwlp-pxelinux/templates/heading.html
diff --git a/modules-available/serversetup-bwlp/templates/ipaddress.html b/modules-available/serversetup-bwlp-pxelinux/templates/ipaddress.html
index 8d73dfac..8d73dfac 100644
--- a/modules-available/serversetup-bwlp/templates/ipaddress.html
+++ b/modules-available/serversetup-bwlp-pxelinux/templates/ipaddress.html
diff --git a/modules-available/serversetup-bwlp/templates/ipxe.html b/modules-available/serversetup-bwlp-pxelinux/templates/ipxe.html
index f4b0b4d3..f4b0b4d3 100644
--- a/modules-available/serversetup-bwlp/templates/ipxe.html
+++ b/modules-available/serversetup-bwlp-pxelinux/templates/ipxe.html
diff --git a/modules-available/serversetup-bwlp/templates/ipxe_update.html b/modules-available/serversetup-bwlp-pxelinux/templates/ipxe_update.html
index c5aafa1c..c5aafa1c 100644
--- a/modules-available/serversetup-bwlp/templates/ipxe_update.html
+++ b/modules-available/serversetup-bwlp-pxelinux/templates/ipxe_update.html