summaryrefslogtreecommitdiffstats
path: root/modules-available/minilinux
diff options
context:
space:
mode:
Diffstat (limited to 'modules-available/minilinux')
-rw-r--r--modules-available/minilinux/hooks/bootup.inc.php3
-rw-r--r--modules-available/minilinux/inc/linuxbootentryhook.inc.php81
-rw-r--r--modules-available/minilinux/inc/minilinux.inc.php324
-rw-r--r--modules-available/minilinux/install.inc.php32
-rw-r--r--modules-available/minilinux/lang/de/messages.json12
-rw-r--r--modules-available/minilinux/lang/de/module.json10
-rw-r--r--modules-available/minilinux/lang/de/permissions.json5
-rw-r--r--modules-available/minilinux/lang/de/template-tags.json8
-rw-r--r--modules-available/minilinux/lang/en/messages.json6
-rw-r--r--modules-available/minilinux/lang/en/module.json11
-rw-r--r--modules-available/minilinux/lang/en/permissions.json5
-rw-r--r--modules-available/minilinux/lang/en/template-tags.json42
-rw-r--r--modules-available/minilinux/page.inc.php119
-rw-r--r--modules-available/minilinux/templates/branches.html31
-rw-r--r--modules-available/minilinux/templates/filelist.html16
-rw-r--r--modules-available/minilinux/templates/page-minilinux.html17
-rw-r--r--modules-available/minilinux/templates/sources.html4
-rw-r--r--modules-available/minilinux/templates/versionlist.html44
18 files changed, 589 insertions, 181 deletions
diff --git a/modules-available/minilinux/hooks/bootup.inc.php b/modules-available/minilinux/hooks/bootup.inc.php
new file mode 100644
index 00000000..e33aaa70
--- /dev/null
+++ b/modules-available/minilinux/hooks/bootup.inc.php
@@ -0,0 +1,3 @@
+<?php
+
+MiniLinux::updateList(); \ No newline at end of file
diff --git a/modules-available/minilinux/inc/linuxbootentryhook.inc.php b/modules-available/minilinux/inc/linuxbootentryhook.inc.php
index 324ffc7e..1424b6b9 100644
--- a/modules-available/minilinux/inc/linuxbootentryhook.inc.php
+++ b/modules-available/minilinux/inc/linuxbootentryhook.inc.php
@@ -10,38 +10,41 @@
class LinuxBootEntryHook extends BootEntryHook
{
- public function name()
+ public function name(): string
{
- return Dictionary::translateFileModule('minilinux', 'module', 'module_name', true);
+ return Dictionary::translateFileModule('minilinux', 'module', 'module_name');
}
- public function extraFields()
+ public function extraFields(): array
{
/* For translate module:
* Dictionary::translate('ipxe-kcl-extra');
* Dictionary::translate('ipxe-debug');
* Dictionary::translate('ipxe-insecure-cpu');
+ * Dictionary::translate('ipxe-force-init-dhcp');
*/
return [
new HookExtraField('kcl-extra', 'string', ''),
new HookExtraField('debug', 'bool', false),
new HookExtraField('insecure-cpu', 'bool', false),
+ new HookExtraField('force-init-dhcp', 'bool', false),
];
}
/**
* @return HookEntryGroup[]
*/
- protected function groupsInternal()
+ protected function groupsInternal(): array
{
/*
* Dictionary::translate('default_boot_entry');
* Dictionary::translate('not_installed_hint');
+ * Dictionary::translate('latest_of_branch');
*/
$array = [];
$array[] = new HookEntryGroup($this->name(), [
new HookEntry('default',
- Dictionary::translateFileModule('minilinux', 'module', 'default_boot_entry', true),
+ Dictionary::translateFileModule('minilinux', 'module', 'default_boot_entry'),
MiniLinux::updateCurrentBootSetting())
]);
$branches = Database::queryAll('SELECT sourceid, branchid, title FROM minilinux_branch ORDER BY title');
@@ -49,26 +52,32 @@ class LinuxBootEntryHook extends BootEntryHook
// Group by branch for detailed listing
foreach ($branches as $branch) {
if (isset($versions[$branch['branchid']])) {
- $group = [];
+ $group = [
+ new HookEntry($branch['branchid'],
+ $branch['branchid'] . ' '
+ . Dictionary::translateFileModule('minilinux', 'module',
+ 'latest_of_branch'),
+ true),
+ ];
foreach ($versions[$branch['branchid']] as $version) {
- $valid = $version['installed'] != 0;
+ $valid = $version['installed'] != MiniLinux::INSTALL_MISSING;
$title = $version['versionid'] . ' ' . $version['title'];
if (!$valid) {
- $title .= ' ' . Dictionary::translateFileModule('minilinux', 'module', 'not_installed_hint');
+ $title .= ' ' . Dictionary::translateFileModule('minilinux', 'module',
+ 'not_installed_hint');
}
$group[] = new HookEntry($version['versionid'], $title, $valid);
}
- $array[] = new HookEntryGroup($branch['title'] ? $branch['title'] : $branch['branchid'], $group);
+ $array[] = new HookEntryGroup($branch['title'] ?: $branch['branchid'], $group);
}
}
return $array;
}
/**
- * @param $id
- * @return BootEntry the actual boot entry instance for given entry, false if invalid id
+ * @return ?BootEntry the actual boot entry instance for given entry, false if invalid id
*/
- public function getBootEntryInternal($localData)
+ public function getBootEntryInternal(array $localData): ?BootEntry
{
$id = $localData['id'];
if ($id === 'default') { // Special case
@@ -76,13 +85,19 @@ class LinuxBootEntryHook extends BootEntryHook
} else {
$effectiveId = $id;
}
- $res = Database::queryFirst('SELECT installed, data FROM minilinux_version WHERE versionid = :id', ['id' => $effectiveId]);
+ $res = Database::queryFirst('SELECT versionid, installed, data FROM minilinux_version WHERE versionid = :id',
+ ['id' => $effectiveId]);
if ($res === false) {
- return BootEntry::newCustomBootEntry(['script' => 'prompt Invalid minilinux boot entry id: ' . $id]);
+ // Maybe this is a branchid, which means latest from according branch (installed only)
+ $res = Database::queryFirst('SELECT versionid, installed, data FROM minilinux_version
+ WHERE branchid = :id AND installed = :ok
+ ORDER BY dateline DESC LIMIT 1',
+ ['id' => $effectiveId, 'ok' => MiniLinux::INSTALL_OK]);
}
- if ($res['installed'] == 0) {
+ if ($res === false) {
return BootEntry::newCustomBootEntry(['script' => 'prompt Selected version not currently installed on server: ' . $effectiveId]);
}
+ $effectiveId = $res['versionid']; // In case we selected from a branchid, so above message doesn't show versionid
$remoteData = json_decode($res['data'], true);
$bios = $efi = false;
if (!@is_array($remoteData['agnostic']) && !@is_array($remoteData['efi']) && !@is_array($remoteData['bios'])) {
@@ -106,10 +121,10 @@ class LinuxBootEntryHook extends BootEntryHook
$arch = BootEntry::EFI;
}
}
- return BootEntry::newStandardBootEntry($bios, $efi, $arch);
+ return BootEntry::newStandardBootEntry($bios, $efi, $arch, 'ml-' . $id);
}
- private function generateExecData($effectiveId, $remoteData, $localData)
+ private function generateExecData($effectiveId, $remoteData, $localData): ExecData
{
$exec = new ExecData();
// Defaults
@@ -117,7 +132,9 @@ class LinuxBootEntryHook extends BootEntryHook
$exec->executable = 'kernel';
$exec->initRd = ['initramfs-stage31'];
$exec->imageFree = true;
- $exec->commandLine = 'slxbase=boot/%ID% slxsrv=${serverip} quiet splash ${ipappend1} ${ipappend2}';
+ $exec->commandLine = 'slxbase=boot/%ID% slxsrv=${serverip} quiet splash ${ipappend1} ${ipappend2}'
+ . ' ipv4.ip=${ip} ipv4.router=${gateway} ipv4.dns=${dns} ipv4.hostname=${hostname} ipv4.domain=${domain} ipv4.search=${dnssl}'
+ . ' ipv4.if=${mac} ipv4.ntpsrv=${ntpsrv} ipv4.subnet=${netmask}';
// Overrides
foreach (['executable', 'commandLine', 'initRd', 'imageFree'] as $key) {
if (isset($remoteData[$key])) {
@@ -128,23 +145,34 @@ class LinuxBootEntryHook extends BootEntryHook
if (!empty($localData['debug'])) {
// Debug boot enabled
$exec->commandLine = IPxe::modifyCommandLine($exec->commandLine,
- isset($remoteData['debugCommandLineModifier'])
- ? $remoteData['debugCommandLineModifier']
- : '-vga -quiet -splash -loglevel loglevel=7'
+ $remoteData['debugCommandLineModifier'] ?? '-vga -quiet -splash -loglevel loglevel=7'
);
}
// disable all CPU sidechannel attack mitigations etc.
if (!empty($localData['insecure-cpu'])) {
$exec->commandLine = IPxe::modifyCommandLine($exec->commandLine,
- 'noibrs noibpb nopti nospectre_v2 nospectre_v1 l1tf=off nospec_store_bypass_disable no_stf_barrier mds=off mitigations=off');
+ 'noibrs noibpb nopti nospectre_v2 nospectre_v1 l1tf=off nospec_store_bypass_disable no_stf_barrier mds=off mitigations=off i915.mitigations=off');
+ }
+ // force that we
+ if (!empty($localData['force-init-dhcp'])) {
+ $exec->commandLine = IPxe::modifyCommandLine($exec->commandLine,
+ '-ipv4.router -ipv4.dns -ipv4.subnet');
+ }
+ // GVT, PCI Pass-thru etc.
+ if (Module::isAvailable('statistics')) {
+ $hwextra = HardwareInfo::getKclModifications();
+ if (!empty($hwextra)) {
+ $exec->commandLine = IPxe::modifyCommandLine($exec->commandLine, $hwextra);
+ }
}
+ // User-supplied modifications
if (!empty($localData['kcl-extra'])) {
$exec->commandLine = IPxe::modifyCommandLine($exec->commandLine, $localData['kcl-extra']);
}
$exec->commandLine = str_replace('%ID%', $effectiveId, $exec->commandLine);
$exec->executable = $root . $exec->executable;
foreach ($exec->initRd as &$rd) {
- if ($rd{0} !== '/') {
+ if ($rd[0] !== '/') {
$rd = $root . $rd;
}
}
@@ -152,11 +180,14 @@ class LinuxBootEntryHook extends BootEntryHook
return $exec;
}
- public function isValidId($id)
+ public function isValidId(string $id): bool
{
if ($id === 'default')
return true; // Meta-version that links to whatever the default is set to
$res = Database::queryFirst('SELECT installed FROM minilinux_version WHERE versionid = :id', ['id' => $id]);
- return $res !== false && $res['installed'];
+ if ($res !== false && $res['installed'] != MiniLinux::INSTALL_MISSING)
+ return true;
+ $res = Database::queryFirst('SELECT branchid FROM minilinux_branch WHERE branchid = :id', ['id' => $id]);
+ return $res !== false;
}
}
diff --git a/modules-available/minilinux/inc/minilinux.inc.php b/modules-available/minilinux/inc/minilinux.inc.php
index 005b81fa..cbc797f2 100644
--- a/modules-available/minilinux/inc/minilinux.inc.php
+++ b/modules-available/minilinux/inc/minilinux.inc.php
@@ -11,21 +11,27 @@ class MiniLinux
const INVALID = 'invalid';
+ const INSTALL_MISSING = 0;
+
+ const INSTALL_OK = 1;
+
+ const INSTALL_BROKEN = 2;
+
/*
* Update of available versions by querying sources
*/
/**
- * Query all known sources for meta data
+ * Query all known sources for metadata
* @return int number of sources query was just initialized for
*/
- public static function updateList()
+ public static function updateList(): int
{
$stamp = time();
$last = Property::get(self::PROPERTY_KEY_FETCHTIME);
- if ($last !== false && $last + 10 > $stamp)
+ if ($last !== false && $last + 3 > $stamp)
return 0; // In progress...
- Property::set(self::PROPERTY_KEY_FETCHTIME, $stamp, 1);
+ Property::set(self::PROPERTY_KEY_FETCHTIME, $stamp, 10);
Database::exec('LOCK TABLES callback WRITE,
minilinux_source WRITE, minilinux_branch WRITE, minilinux_version WRITE');
Database::exec('UPDATE minilinux_source SET taskid = UUID()');
@@ -33,7 +39,8 @@ class MiniLinux
Database::exec("UPDATE minilinux_version
INNER JOIN minilinux_branch USING (branchid)
INNER JOIN minilinux_source USING (sourceid)
- SET orphan = orphan + 1 WHERE minilinux_source.lastupdate < $cutoff");
+ SET orphan = orphan + 1
+ WHERE minilinux_source.lastupdate < $cutoff AND orphan < 100");
$list = Database::queryAll('SELECT sourceid, url, taskid FROM minilinux_source');
foreach ($list as $source) {
Taskmanager::submit('DownloadText', array(
@@ -48,20 +55,22 @@ class MiniLinux
/**
* Called when downloading metadata from a specific update source is finished
- * @param mixed $task task structure
+ *
+ * @param array $task task structure
* @param string $sourceid see minilinux_source table
*/
- public static function listDownloadCallback($task, $sourceid)
+ public static function listDownloadCallback(array $task, string $sourceid): void
{
- if ($task['statusCode'] !== 'TASK_FINISHED')
+ if (!Taskmanager::isFinished($task))
return;
$taskId = $task['id'];
- $data = json_decode($task['data']['content'], true);
- if (!is_array($data)) {
- EventLog::warning('Cannot download Linux version meta data for ' . $sourceid);
+ $data = json_decode($task['data']['content'] ?? '', true);
+ if (!is_array($data) || empty($data['systems'])) {
+ EventLog::warning('Cannot download Linux version meta data for ' . $sourceid,
+ ($task['data']['error'] ?? '') . "\n\nContent:\n" . ($task['data']['content'] ?? ''));
$lastupdate = 'lastupdate';
} else {
- if (@is_array($data['systems'])) {
+ if (is_array($data['systems'])) {
self::addBranches($sourceid, $data['systems']);
}
$lastupdate = 'UNIX_TIMESTAMP()';
@@ -70,7 +79,8 @@ class MiniLinux
WHERE sourceid = :sourceid AND taskid = :taskid",
['sourceid' => $sourceid, 'taskid' => $taskId]);
// Clean up -- delete orphaned versions that are not installed
- Database::exec('DELETE FROM minilinux_version WHERE orphan > 4 AND installed = 0');
+ Database::exec('DELETE FROM minilinux_version WHERE orphan > 4 AND installed = :missing',
+ ['missing' => self::INSTALL_MISSING]);
// FKC makes sure we only delete orphaned ones
Database::exec('DELETE IGNORE FROM minilinux_branch WHERE 1', [], true);
}
@@ -81,18 +91,31 @@ class MiniLinux
if (!self::isValidIdPart($system['id']))
continue;
$branchid = $sourceid . '/' . $system['id'];
- $title = empty($system['title']) ? $branchid : $system['title'];
- $description = empty($system['description']) ? '' : $system['description'];
- Database::exec('INSERT INTO minilinux_branch (branchid, sourceid, title, description)
- VALUES (:branchid, :sourceid, :title, :description)
- ON DUPLICATE KEY UPDATE title = VALUES(title), description = VALUES(description)', [
- 'branchid' => $branchid,
- 'sourceid' => $sourceid,
- 'title' => $title,
- 'description' => $description,
- ]);
- if (@is_array($system['versions'])) {
+ $title = mb_substr(empty($system['title']) ? $branchid : $system['title'], 0, 150);
+ $description = $system['description'] ?? '';
+ $color = $system['color'] ?? '';
+ if (!empty($system['versions']) && is_array($system['versions'])) {
+ Database::exec('INSERT INTO minilinux_branch (branchid, sourceid, title, color, description)
+ VALUES (:branchid, :sourceid, :title, :color, :description)
+ ON DUPLICATE KEY UPDATE title = VALUES(title), color = VALUES(color), description = VALUES(description)', [
+ 'branchid' => $branchid,
+ 'sourceid' => $sourceid,
+ 'title' => $title,
+ 'color' => $color,
+ 'description' => $description,
+ ]);
self::addVersions($branchid, $system['versions']);
+ } else {
+ // Empty branch - only update metadata if branch exists locally
+ Database::exec('UPDATE minilinux_branch
+ SET title = :title, color = :color, description = :description
+ WHERE sourceid = :sourceid AND branchid = :branchid', [
+ 'branchid' => $branchid,
+ 'sourceid' => $sourceid,
+ 'title' => $title,
+ 'color' => $color,
+ 'description' => $description,
+ ]);
}
}
}
@@ -115,7 +138,8 @@ class MiniLinux
return;
}
$versionid = $branchid . '/' . $version['version'];
- $title = empty($version['title']) ? '' : $version['title'];
+ $title = $version['title'] ?? '';
+ $description = $version['description'] ?? '';
$dateline = empty($version['releasedate']) ? time() : (int)$version['releasedate'];
unset($version['version'], $version['title'], $version['releasedate']);
// Sanitize files array
@@ -148,18 +172,20 @@ class MiniLinux
$version['files'] = array_values($version['files']);
}
$data = json_encode($version);
- Database::exec('INSERT INTO minilinux_version (versionid, branchid, title, dateline, data, orphan)
- VALUES (:versionid, :branchid, :title, :dateline, :data, 0)
- ON DUPLICATE KEY UPDATE title = VALUES(title), data = VALUES(data), orphan = 0', [
+ Database::exec('INSERT INTO minilinux_version (versionid, branchid, title, description, dateline, data, orphan)
+ VALUES (:versionid, :branchid, :title, :description, :dateline, :data, 0)
+ ON DUPLICATE KEY UPDATE title = VALUES(title), description = VALUES(description),
+ dateline = VALUES(dateline), data = VALUES(data), orphan = 0', [
'versionid' => $versionid,
'branchid' => $branchid,
- 'title' => $title,
+ 'title' => mb_substr($title, 0, 150),
+ 'description' => $description,
'dateline' => $dateline,
'data' => $data,
]);
}
- private static function isValidIdPart($str)
+ private static function isValidIdPart(string $str): bool
{
return preg_match('/^[a-z0-9_\-]+$/', $str) > 0;
}
@@ -168,10 +194,10 @@ class MiniLinux
* Download of specific version
*/
- public static function validateDownloadTask($versionid, $taskid)
+ public static function validateDownloadTask(string $versionid, ?string $taskid): ?string
{
if ($taskid === null)
- return false;
+ return null;
$task = Taskmanager::status($taskid);
if (Taskmanager::isTask($task) && !Taskmanager::isFailed($task)
&& (is_dir(CONFIG_HTTP_DIR . '/' . $versionid) || !Taskmanager::isFinished($task)))
@@ -179,15 +205,13 @@ class MiniLinux
Database::exec('UPDATE minilinux_version SET taskid = NULL
WHERE versionid = :versionid AND taskid = :taskid',
['versionid' => $versionid, 'taskid' => $taskid]);
- return false;
+ return null;
}
/**
* Download the files for the given version id
- * @param $versionid
- * @return bool
*/
- public static function downloadVersion($versionid)
+ public static function downloadVersion(string $versionid): ?string
{
$ver = Database::queryFirst('SELECT s.url, s.pubkey, v.versionid, v.taskid, v.data FROM minilinux_version v
INNER JOIN minilinux_branch b USING (branchid)
@@ -195,17 +219,17 @@ class MiniLinux
WHERE versionid = :versionid',
['versionid' => $versionid]);
if ($ver === false)
- return false;
+ return null;
$taskid = self::validateDownloadTask($versionid, $ver['taskid']);
- if ($taskid !== false)
+ if ($taskid !== null)
return $taskid;
$data = json_decode($ver['data'], true);
if (!is_array($data)) {
EventLog::warning("Cannot download Linux '$versionid': Corrupted meta data.", $ver['data']);
- return false;
+ return null;
}
if (empty($data['files']))
- return false;
+ return null;
$list = [];
$legacyDir = preg_replace(',^[^/]*/,', '', $versionid);
foreach ($data['files'] as $file) {
@@ -224,6 +248,7 @@ class MiniLinux
Database::exec('LOCK TABLES minilinux_version WRITE');
$aff = Database::exec('UPDATE minilinux_version SET taskid = :taskid WHERE versionid = :versionid AND taskid IS NULL',
['taskid' => $uuid, 'versionid' => $versionid]);
+ $task = false;
if ($aff > 0) {
$task = Taskmanager::submit('DownloadFiles', [
'id' => $uuid,
@@ -234,22 +259,22 @@ class MiniLinux
if (Taskmanager::isFailed($task)) {
$task = false;
} else {
- $task = $task['id'];
+ $task = (string)$task['id'];
}
- } else {
- $task = false;
}
Database::exec('UNLOCK TABLES');
if ($task !== false) {
// Callback for db column
TaskmanagerCallback::addCallback($task, 'mlGotLinux', $versionid);
+ self::checkStage4($data);
}
+ // Race - someone else wrote a taskid to DB, just call self again to get that one
if ($aff === 0)
return self::downloadVersion($versionid);
return $task;
}
- public static function fileToId($versionid, $fileName)
+ public static function fileToId(string $versionid, string $fileName): string
{
return 'x' . substr(md5($fileName . $versionid), 0, 8);
}
@@ -259,10 +284,10 @@ class MiniLinux
*/
/**
- * Geenrate messages regarding setup und update availability.
+ * Generate messages regarding setup und update availability.
* @return bool true if severe problems were found, false otherwise
*/
- public static function generateUpdateNotice()
+ public static function generateUpdateNotice(): bool
{
// Messages in here are with module name, as required by the
// main-warning hook.
@@ -305,7 +330,7 @@ class MiniLinux
* actually installed locally.
* @return bool true if installed locally, false otherwise
*/
- public static function updateCurrentBootSetting()
+ public static function updateCurrentBootSetting(): bool
{
$default = Property::get(self::PROPERTY_DEFAULT_BOOT);
if ($default === false)
@@ -318,7 +343,7 @@ class MiniLinux
} elseif ($slashes === 1) {
// Latest from branch
$ver = Database::queryFirst('SELECT versionid, installed FROM minilinux_version
- WHERE branchid = :branchid AND installed = 1 ORDER BY dateline DESC', ['branchid' => $default]);
+ WHERE branchid = :branchid AND installed = :ok ORDER BY dateline DESC', ['branchid' => $default, 'ok' => self::INSTALL_OK]);
} else {
// Unknown
return false;
@@ -329,33 +354,218 @@ class MiniLinux
return false;
}
Property::set(self::PROPERTY_DEFAULT_BOOT_EFFECTIVE, $ver['versionid']);
- return $ver['installed'] != 0;
+ return $ver['installed'] != self::INSTALL_MISSING;
}
public static function linuxDownloadCallback($task, $versionid)
{
- self::setInstalledState($versionid, $task['statusCode'] === 'TASK_FINISHED');
+ self::setInstalledState($versionid, $task['statusCode'] === 'TASK_FINISHED' ? self::INSTALL_OK : self::INSTALL_BROKEN);
}
- public static function setInstalledState($versionid, $installed)
+ public static function setInstalledState($versionid, int $installed): void
{
- settype($installed, 'int');
- error_log("Setting $versionid to $installed");
Database::exec('UPDATE minilinux_version SET installed = :installed WHERE versionid = :versionid', [
'versionid' => $versionid,
'installed' => $installed,
]);
+ if ($installed === self::INSTALL_OK) {
+ $res = Database::queryFirst('SELECT Count(*) AS cnt FROM minilinux_version WHERE installed = :ok',
+ ['ok' => self::INSTALL_OK]);
+ if ($res['cnt'] == 1) {
+ self::setDefaultVersion($versionid);
+ }
+ }
}
- public static function queryAllVersionsByBranch()
+ public static function queryAllVersionsByBranch(): array
{
$list = [];
- $res = Database::simpleQuery('SELECT branchid, versionid, title, dateline, orphan, taskid, installed
+ $res = Database::simpleQuery('SELECT branchid, versionid, title, Length(description) AS desclen,
+ dateline, orphan, taskid, installed
FROM minilinux_version ORDER BY branchid, dateline, versionid');
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
$list[$row['branchid']][$row['versionid']] = $row;
}
return $list;
}
-} \ No newline at end of file
+ public static function setDefaultVersion($versionId)
+ {
+ Property::set(MiniLinux::PROPERTY_DEFAULT_BOOT, $versionId);
+ self::updateCurrentBootSetting();
+ // Legacy PXELINUX boot menu (TODO: Remove this when we get rid of PXELINUX support)
+ $task = Taskmanager::submit('Symlink', [
+ 'target' => $versionId,
+ 'linkname' => CONFIG_HTTP_DIR . '/default',
+ ]);
+ if ($task !== false) {
+ Taskmanager::release($task);
+ }
+ }
+
+ /**
+ * Check whether an optionally required stage4 is available.
+ * Return true if there is no stage4, otherwise check filesystem,
+ * or try to request from local dnbd3-server.
+ *
+ * @param array $data decoded data column from minilinux_version
+ * @param string[] $errors in array of error messages if not available
+ * @return bool true if stage4 is available or none required
+ */
+ public static function checkStage4(array $data, &$errors = false): bool
+ {
+ $errors = [];
+ $image = false;
+ $rid = 0;
+ foreach (['agnostic', 'efi', 'bios'] as $type) {
+ if (!isset($data[$type]) || !isset($data[$type]['commandLine']))
+ continue;
+ if (!preg_match('/\bslx\.stage4\.path=(\S+)/', $data[$type]['commandLine'], $out))
+ continue;
+ $image = $out[1];
+ if (preg_match('/\bslx\.stage4\.rid=(\d+)/', $data[$type]['commandLine'], $out)) {
+ $rid = (int)$out[1];
+ }
+ break;
+ }
+ if ($image === false)
+ return true; // No stage4
+ if ($rid === 0) {
+ // Get latest local revision
+ foreach (glob(CONFIG_VMSTORE_DIR . '/' . $image . '.r*', GLOB_NOSORT) as $file) {
+ if (preg_match('/\.r(\d+)$/', $file, $out)) {
+ $cmp = (int)$out[1];
+ if ($cmp > $rid) {
+ $rid = $cmp;
+ }
+ }
+ }
+ }
+ if ($rid > 0 && file_exists(CONFIG_VMSTORE_DIR . '/' . $image . '.r' . $rid)
+ && !file_exists(CONFIG_VMSTORE_DIR . '/' . $image . '.r' . $rid . '.map')) {
+ // Accept if image exists locally and no map file (map file would mean incomplete)
+ return true;
+ }
+ // Not found locally -- try to replicate
+ $sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
+ if ($sock === false) {
+ $errors[] = 'Error creatring socket to connect to dnbd3-server';
+ return false;
+ }
+ socket_set_option($sock, SOL_SOCKET, SO_SNDTIMEO, array('sec' => 1, 'usec' => 0));
+ socket_set_option($sock, SOL_SOCKET, SO_RCVTIMEO, array('sec' => 5, 'usec' => 0));
+ if (@socket_connect($sock, '127.0.0.1', 5003) === false) {
+ $errors[] = 'Could not connect to local dnbd3-server';
+ socket_close($sock);
+ return false;
+ }
+ // proto-version(16), image name\0, rid(16), flags(8)
+ $payload = pack('vA*xvC', 3, $image, $rid, 0);
+ // magic(16), cmd(16), payload-len(32), offset(64), handle(64) XXX 32Bit compat
+ $packet = pack('A*vVVVVV', 'sr', 2, strlen($payload), 0, 0, 1234, 0) . $payload;
+ if (!socket_send($sock, $packet, strlen($packet), 0)) {
+ $errors[] = 'Cannot send request to dnbd3-server';
+ socket_close($sock);
+ return false;
+ }
+ $len = socket_recv($sock, $reply, 16, MSG_WAITALL);
+ if ($len === 0) {
+ $errors[] = 'Local dnbd3-server cannot replicate required stage4 from master-server';
+ socket_close($sock);
+ return false;
+ }
+ if ($len !== 16) {
+ $errors[] = 'Incomplete reply received from local dnbd3-server. Stage4 might not replicate!';
+ socket_close($sock);
+ return false;
+ }
+ socket_close($sock);
+ // Try to decode header
+ $reply = unpack('A2magic/vcmd/Vsize/Vhandlelow/Vhandlehigh', $reply);
+ if ($reply['magic'] !== 'sr') {
+ $errors[] = 'Reply has wrong magic';
+ }
+ if ($reply['cmd'] !== 2) {
+ $errors[] = 'Reply is not CMD_IMAGE_REPLY';
+ }
+ return empty($errors);
+ }
+
+ /**
+ * Determine by which menus/locations each MiniLinux version is being used.
+ */
+ public static function getBootMenuUsage(): array
+ {
+ if (!Module::isAvailable('serversetup') || !class_exists('BootEntryHook'))
+ return [];
+ $res = Database::simpleQuery("SELECT be.entryid, be.data,
+ GROUP_CONCAT(DISTINCT me.menuid) AS menus,
+ GROUP_CONCAT(DISTINCT ml.locationid) AS locations
+ FROM serversetup_bootentry be
+ LEFT JOIN serversetup_menuentry me USING (entryid)
+ LEFT JOIN serversetup_menu_location ml USING (menuid)
+ WHERE module = 'minilinux'
+ GROUP BY be.data");
+ $return = [];
+ $usedMenuIds = [];
+ foreach ($res as $row) {
+ $data = json_decode($row['data'], true);
+ if (!isset($data['id']))
+ continue;
+ $id = self::resolveEntryId($data['id']);
+ $new = [
+ 'entryids' => [$row['entryid']],
+ 'menus' => explode(',', $row['menus'] ?? ''),
+ 'locations' => explode(',', $row['locations'] ?? ''),
+ ];
+ $usedMenuIds = array_merge($usedMenuIds, $new['menus']);
+ if (isset($return[$id])) {
+ $return[$id] = array_merge_recursive($return[$id], $new);
+ } else {
+ $return[$id] = $new;
+ }
+ }
+ // Build id => title map for menus
+ $res = Database::simpleQuery("SELECT menuid, title FROM serversetup_menu m
+ WHERE menuid IN (:menuid)", ['menuid' => array_unique($usedMenuIds)]);
+ $menus = [];
+ foreach ($res as $row) {
+ $menus[$row['menuid']] = $row['title'];
+ }
+ // Build output array
+ foreach ($return as &$item) {
+ $item['locations'] = array_map(function ($i) {
+ return ['locationid' => $i, 'locationname' => Location::getName($i)];
+ }, array_unique(array_filter($item['locations'], 'is_numeric')));
+ $item['menus'] = array_map(function ($i) use ($menus) {
+ return ['menuid' => $i, 'menuname' => $menus[$i]];
+ }, array_unique(array_filter($item['menus'], 'is_numeric')));
+ $item['locationCount'] = count($item['locations']);
+ $item['menuCount'] = count($item['menus']);
+ $item['entryCount'] = count($item['entryids']);
+ }
+ return $return;
+ }
+
+ /**
+ * Take a configured versionid from a bootentry (serversetup module) and translate
+ * it, in case it's "default" or just a branch name.
+ */
+ private static function resolveEntryId(string $id): string
+ {
+ if ($id === 'default') { // Special case
+ $id = Property::get(MiniLinux::PROPERTY_DEFAULT_BOOT_EFFECTIVE);
+ }
+ if (substr_count($id, '/') < 2) {
+ // Maybe this is a branchid, which means latest from according branch (installed only)
+ $res = Database::queryFirst('SELECT versionid FROM minilinux_version WHERE branchid = :id AND installed = :ok
+ ORDER BY dateline DESC LIMIT 1',
+ ['id' => $id, 'ok' => self::INSTALL_OK]);
+ if ($res !== false) {
+ $id = $res['versionid'];
+ }
+ }
+ return $id;
+ }
+
+}
diff --git a/modules-available/minilinux/install.inc.php b/modules-available/minilinux/install.inc.php
index e71e3c10..7ef82d74 100644
--- a/modules-available/minilinux/install.inc.php
+++ b/modules-available/minilinux/install.inc.php
@@ -2,7 +2,7 @@
$result[] = tableCreate('minilinux_source', "
`sourceid` varchar(8) CHARACTER SET ascii NOT NULL,
- `title` varchar(100) NOT NULL,
+ `title` varchar(150) NOT NULL,
`url` varchar(200) NOT NULL,
`lastupdate` int(10) UNSIGNED NOT NULL DEFAULT '0',
`taskid` char(36) CHARACTER SET ascii DEFAULT NULL,
@@ -13,7 +13,8 @@ $result[] = tableCreate('minilinux_source', "
$result[] = tableCreate('minilinux_branch', "
`sourceid` varchar(8) CHARACTER SET ascii DEFAULT NULL,
`branchid` varchar(40) CHARACTER SET ascii NOT NULL,
- `title` varchar(100) NOT NULL,
+ `title` varchar(150) NOT NULL,
+ `color` varchar(7) NOT NULL,
`description` blob NOT NULL,
PRIMARY KEY (`branchid`),
KEY (`title`)
@@ -21,7 +22,8 @@ $result[] = tableCreate('minilinux_branch', "
$result[] = tableCreate('minilinux_version', "
`branchid` varchar(40) CHARACTER SET ascii NOT NULL,
`versionid` varchar(72) CHARACTER SET ascii NOT NULL,
- `title` varchar(100) NOT NULL,
+ `title` varchar(150) NOT NULL,
+ `description` blob NOT NULL,
`dateline` int(10) UNSIGNED NOT NULL,
`data` blob NOT NULL,
`orphan` tinyint(3) UNSIGNED NOT NULL,
@@ -39,4 +41,28 @@ $result[] = tableAddConstraint('minilinux_version', 'branchid', 'minilinux_branc
$result[] = tableAddConstraint('minilinux_branch', 'sourceid', 'minilinux_source', 'sourceid',
'ON UPDATE CASCADE ON DELETE SET NULL');
+// 2022-10-17: Add color to branch, description to version
+if (!tableHasColumn('minilinux_branch', 'color')) {
+ if (Database::exec("ALTER TABLE `minilinux_branch` ADD COLUMN `color` varchar(7) NOT NULL DEFAULT '' AFTER `title`") !== false) {
+ $result[] = UPDATE_DONE;
+ } else {
+ finalResponse(UPDATE_FAILED, Database::lastError());
+ }
+}
+if (!tableHasColumn('minilinux_version', 'description')) {
+ // BLOB/TEXT cannot have non-NULL default on older MariaDB
+ if (Database::exec("ALTER TABLE `minilinux_version` ADD COLUMN `description` blob NULL DEFAULT NULL AFTER `title`") !== false) {
+ $result[] = UPDATE_DONE;
+ } else {
+ finalResponse(UPDATE_FAILED, Database::lastError());
+ }
+}
+
+// 2023-07-17: Make title columns larger
+foreach (['minilinux_source', 'minilinux_branch', 'minilinux_version'] as $table) {
+ if (stripos(tableColumnType($table, 'title'), 'varchar(150)') === false) {
+ Database::exec("ALTER TABLE `$table` MODIFY `title` varchar(150) NOT NULL");
+ }
+}
+
responseFromArray($result);
diff --git a/modules-available/minilinux/lang/de/messages.json b/modules-available/minilinux/lang/de/messages.json
index e957ee09..c32fa20d 100644
--- a/modules-available/minilinux/lang/de/messages.json
+++ b/modules-available/minilinux/lang/de/messages.json
@@ -1,10 +1,10 @@
{
- "default-is-invalid": "Gew\u00e4hltes Linux-Standardsystem ist ung\u00fcltig",
- "default-not-installed": "Gew\u00e4hltes Linux-Standardsystem {{0}} ist nicht (mehr) installiert",
- "default-update-available": "F\u00fcr das Gew\u00e4hlte Linux-Standardsystem {{0}} ist die Aktualisierung {{1}} verf\u00fcgbar",
+ "default-is-invalid": "Gew\u00e4hltes Netboot-Grundsystem ist ung\u00fcltig",
+ "default-not-installed": "Gew\u00e4hltes Netboot-Grundsystem {{0}} ist nicht (mehr) installiert",
+ "default-update-available": "F\u00fcr das Gew\u00e4hlte Netboot-Grundsystem {{0}} ist die Aktualisierung {{1}} verf\u00fcgbar",
"delete-error": "Fehler beim L\u00f6schen der Version {{0}}: {{1}}",
- "no-default-set": "Kein Linux-Standardsystem festgelegt",
+ "no-default-set": "Kein Netboot-Grundsystem als Standard festgelegt",
"no-such-version": "Ung\u00fcltige\/Unbekannte Version: {{0}}",
- "please-download-minilinux": "Wichtige Dateien der MiniLinux-Installation fehlen",
+ "please-download-minilinux": "Wichtige Dateien der Netboot-Grundsysteminstallation fehlen",
"version-deleted": "Version {{0}} wurde gel\u00f6scht"
-} \ No newline at end of file
+}
diff --git a/modules-available/minilinux/lang/de/module.json b/modules-available/minilinux/lang/de/module.json
index 133e428f..f47249d5 100644
--- a/modules-available/minilinux/lang/de/module.json
+++ b/modules-available/minilinux/lang/de/module.json
@@ -6,11 +6,13 @@
"file-ok": "OK",
"file-size-mismatch": "Dateigr\u00f6\u00dfe stimmt nicht",
"ipxe-debug": "Debug-Ausgaben statt Bootlogo",
- "ipxe-insecure-cpu": "Alle Mitigations for CPU-Sicherheitsl\u00fccken deaktivieren",
+ "ipxe-force-init-dhcp": "Erzwinge erneuten DHCP-Request nach Laden des initramfs",
+ "ipxe-insecure-cpu": "Alle Mitigations f\u00fcr CPU-Sicherheitsl\u00fccken deaktivieren",
"ipxe-kcl-extra": "Modifikation der Kernel-Command-Line",
+ "latest_of_branch": "(Neueste lokal vorhandene Version)",
"menu-sources": "Update-Quellen",
"menu-versions": "Verf\u00fcgbare Versionen",
- "module_name": "Netboot Grundsystem",
+ "module_name": "Netboot-Grundsystem",
"not_installed_hint": "(nicht installiert)",
- "page_title": "Linuxvarianten f\u00fcr Netboot verwalten"
-} \ No newline at end of file
+ "page_title": "Netboot-Grundsystemverwaltung"
+}
diff --git a/modules-available/minilinux/lang/de/permissions.json b/modules-available/minilinux/lang/de/permissions.json
index 29012620..4773611a 100644
--- a/modules-available/minilinux/lang/de/permissions.json
+++ b/modules-available/minilinux/lang/de/permissions.json
@@ -1,4 +1,5 @@
{
- "view": "Zeige Komponenten des Minilinux. Wird nicht benötigt, wenn Nutzer eine der anderen Rechte hat.",
- "update": "Aktualisieren von Komponenten des Minilinux."
+ "delete": "Ein heruntergeladenes Netboot-Grundsystem l\u00f6schen.",
+ "update": "Aktualisieren von Komponenten des Netboot-Grundsystems.",
+ "view": "Zeige Komponenten des Netboot-Grundsystems. Wird nicht ben\u00f6tigt, wenn Nutzer eine der anderen Rechte hat."
} \ No newline at end of file
diff --git a/modules-available/minilinux/lang/de/template-tags.json b/modules-available/minilinux/lang/de/template-tags.json
index 690ba010..894b864b 100644
--- a/modules-available/minilinux/lang/de/template-tags.json
+++ b/modules-available/minilinux/lang/de/template-tags.json
@@ -6,11 +6,14 @@
"lang_download": "Herunterladen",
"lang_id": "ID",
"lang_installed": "Installiert",
- "lang_introText": "Hier gibts MiniLinux.",
"lang_isGlobalDefault": "Ist globaler Standard",
"lang_key": "GPG-Key",
"lang_lastUpdate": "Zuletzt \u00fcberpr\u00fcft",
- "lang_minilinuxHeading": "Netboot Linux verwalten",
+ "lang_locations": "R\u00e4ume \/ Orte",
+ "lang_maybeMissingStage4": "Stage 4 m\u00f6glicherweise nicht verf\u00fcgbar",
+ "lang_menuEntries": "Men\u00fceintr\u00e4ge",
+ "lang_menus": "Men\u00fcs",
+ "lang_minilinuxHeading": "Netboot-Grundsystem verwalten",
"lang_orphanedVersion": "Verwaist",
"lang_orphanedVersionToolTip": "Diese Version wird vom Update-Server nicht mehr angeboten",
"lang_releaseDate": "Ver\u00f6ffentlichungsdatum",
@@ -21,6 +24,7 @@
"lang_title": "Titel",
"lang_updateSourcesButton": "Nach neuen Updates suchen",
"lang_url": "URL",
+ "lang_usedBy": "Verwendet",
"lang_verify": "Integrit\u00e4t \u00fcberpr\u00fcfen",
"lang_verifyToolTip": "Dateiintegrit\u00e4t anhand von Pr\u00fcfsummen verifizieren",
"lang_version": "Version"
diff --git a/modules-available/minilinux/lang/en/messages.json b/modules-available/minilinux/lang/en/messages.json
index 6dc736a4..193b18fa 100644
--- a/modules-available/minilinux/lang/en/messages.json
+++ b/modules-available/minilinux/lang/en/messages.json
@@ -1,6 +1,10 @@
{
+ "default-is-invalid": "Currently selected default is invalid",
+ "default-not-installed": "Currently selected default of {{0}} is not locally installed (any more).",
+ "default-update-available": "You selected default system {{0}} can be updated to {{1}}",
"delete-error": "Error deleting version {{0}}: {{1}}",
+ "no-default-set": "No default system selected",
"no-such-version": "No such version: {{0}}",
- "please-download-minilinux": "Important files from the mini Linux installation are missing.",
+ "please-download-minilinux": "Important files from the netboot Linux installation are missing.",
"version-deleted": "Deleted version {{0}}"
} \ No newline at end of file
diff --git a/modules-available/minilinux/lang/en/module.json b/modules-available/minilinux/lang/en/module.json
index b1526869..ff5c7a49 100644
--- a/modules-available/minilinux/lang/en/module.json
+++ b/modules-available/minilinux/lang/en/module.json
@@ -1,9 +1,18 @@
{
+ "default_boot_entry": "(Use global default)",
"file-checksum-bad": "Bad checksum",
"file-missing": "File missing",
"file-not-readable": "File not readable",
"file-ok": "OK",
"file-size-mismatch": "File size mismatch",
- "module_name": "Minilinux",
+ "ipxe-debug": "Print debug messages instead of showing splash screen",
+ "ipxe-force-init-dhcp": "Force another DHCP request after loading initramfs",
+ "ipxe-insecure-cpu": "Disable all mitigations for CPU security flaws",
+ "ipxe-kcl-extra": "Modifications to the kernel command line",
+ "latest_of_branch": "(Latest locally available version)",
+ "menu-sources": "Sources for updates",
+ "menu-versions": "Available versions",
+ "module_name": "Net-boot OS",
+ "not_installed_hint": "(not installed)",
"page_title": "Manage Netboot Linux flavors"
} \ No newline at end of file
diff --git a/modules-available/minilinux/lang/en/permissions.json b/modules-available/minilinux/lang/en/permissions.json
index b8389e62..9d97ad00 100644
--- a/modules-available/minilinux/lang/en/permissions.json
+++ b/modules-available/minilinux/lang/en/permissions.json
@@ -1,4 +1,5 @@
{
- "view": "Show list of minilinux components. Not needed if User has any of the other permissions.",
- "update": "Update minilinux components."
+ "delete": "Delete a downloaded netboot Linux version.",
+ "update": "Update netboot Linux components.",
+ "view": "Show list of netboot Linux components. Not needed if user has any of the other permissions."
} \ No newline at end of file
diff --git a/modules-available/minilinux/lang/en/template-tags.json b/modules-available/minilinux/lang/en/template-tags.json
index 48ba0c15..5b3c77e4 100644
--- a/modules-available/minilinux/lang/en/template-tags.json
+++ b/modules-available/minilinux/lang/en/template-tags.json
@@ -1,15 +1,31 @@
{
- "lang_canUpdate1": "At least one component of",
- "lang_canUpdate2": "Can be updated. For a smooth operation, it is recommended to keep all components up to date.",
- "lang_configurationPackageNotFound": "Configuration package not found!",
- "lang_desiredVersion": "Desired version",
- "lang_errorGetting": "Error while downloading list!",
- "lang_filesInVersion": "Files for version",
- "lang_listObtained": "Downloading list...",
- "lang_outdated": "Outdated",
- "lang_redownload": "Download again",
- "lang_systemUpdated": "The system is up to date.",
- "lang_update": "Update",
- "lang_updateAll": "Update all modules",
- "lang_uptodate": "Up to date"
+ "lang_branchesHeading": "Available branches and versions",
+ "lang_changelog": "Change log",
+ "lang_confirmDeleteVersion": "Do you want to delete this version?",
+ "lang_default": "Default",
+ "lang_download": "Download",
+ "lang_id": "ID",
+ "lang_installed": "Installed",
+ "lang_isGlobalDefault": "Global default",
+ "lang_key": "GPG key",
+ "lang_lastUpdate": "Last updated",
+ "lang_locations": "Rooms \/ Locations",
+ "lang_maybeMissingStage4": "Stage 4 might be missing",
+ "lang_menuEntries": "Menu entries",
+ "lang_menus": "Menus",
+ "lang_minilinuxHeading": "Manage netboot base system",
+ "lang_orphanedVersion": "Orphaned",
+ "lang_orphanedVersionToolTip": "This version is not offered by the update server any more",
+ "lang_releaseDate": "Release date",
+ "lang_selectedDefaultIs": "Current default is",
+ "lang_setGlobalDefault": "Set as global default",
+ "lang_sources": "Sources",
+ "lang_sourcesIntro": "List of update sources that will be checked for available branches and versions.",
+ "lang_title": "Title",
+ "lang_updateSourcesButton": "Check for new updates",
+ "lang_url": "URL",
+ "lang_usedBy": "Used",
+ "lang_verify": "Check file integrity",
+ "lang_verifyToolTip": "Check all files against known checksums",
+ "lang_version": "Version"
} \ No newline at end of file
diff --git a/modules-available/minilinux/page.inc.php b/modules-available/minilinux/page.inc.php
index 575177a8..8004f1ab 100644
--- a/modules-available/minilinux/page.inc.php
+++ b/modules-available/minilinux/page.inc.php
@@ -25,47 +25,72 @@ class Page_MiniLinux extends Page
}
User::assertPermission('view');
- Dashboard::addSubmenu('?do=minilinux', Dictionary::translate('menu-versions', true));
- Dashboard::addSubmenu('?do=minilinux&show=sources', Dictionary::translate('menu-sources', true));
+ Dashboard::addSubmenu('?do=minilinux', Dictionary::translate('menu-versions'));
+ Dashboard::addSubmenu('?do=minilinux&show=sources', Dictionary::translate('menu-sources'));
}
protected function doRender()
{
- Render::addTemplate('page-minilinux', ['default' => Property::get(MiniLinux::PROPERTY_DEFAULT_BOOT)]);
- // Warning
- if (!MiniLinux::updateCurrentBootSetting()) {
- Message::addError('default-not-installed', Property::get(MiniLinux::PROPERTY_DEFAULT_BOOT));
- }
$show = Request::get('show', 'list', 'string');
if ($show === 'list') {
// List branches and versions
- $branches = Database::queryAll('SELECT sourceid, branchid, title, description FROM minilinux_branch ORDER BY title ASC');
+ $branches = Database::queryAll('SELECT sourceid, branchid, title, color, description FROM minilinux_branch ORDER BY title ASC');
$versions = MiniLinux::queryAllVersionsByBranch();
- // Group by branch for detailed listing
+ $usage = MiniLinux::getBootMenuUsage();
+ $sourceList = [];
+ // Group by branch for detailed listing, add usage info
foreach ($branches as &$branch) {
+ // Little hack: We abuse the title for ordering, so if the second char is a space, assume the first one
+ // is just for sort order and remove it.
+ if ($branch['title'][1] === ' ') {
+ $branch['title'] = substr($branch['title'], 2);
+ }
+ $bid = 'div-' . str_replace('/', '-', $branch['branchid']);
+ if (!isset($sourceList[$branch['sourceid']])) {
+ $sourceList[$branch['sourceid']] = ['sourceid' => $branch['sourceid'], 'list' => []];
+ }
+ $sourceList[$branch['sourceid']]['list'][] = [
+ 'title' => $branch['title'],
+ 'color' => $branch['color'],
+ 'bid' => $bid
+ ];
+ $branch['bid'] = $bid;
if (isset($versions[$branch['branchid']])) {
- $branch['versionlist'] = $this->renderVersionList($versions[$branch['branchid']]);
+ $branch['versionlist'] = $this->renderVersionList($versions[$branch['branchid']], $usage);
}
}
unset($branch);
- Render::addTemplate('branches', ['branches' => $branches]);
+ $sourceList = array_values($sourceList);
} elseif ($show === 'sources') {
// List sources
$res = Database::simpleQuery('SELECT sourceid, title, url, lastupdate, pubkey FROM minilinux_source ORDER BY title, sourceid');
- $data = ['list' => [], 'show_refresh' => true];
+ $sourceViewData = ['list' => [], 'show_refresh' => true];
$tooOld = strtotime('-7 days');
- $showRefresh = strtotime('-10 minutes');
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $showRefresh = strtotime('-5 minutes');
+ foreach ($res as $row) {
$row['lastupdate_s'] = Util::prettyTime($row['lastupdate']);
if ($row['lastupdate'] != 0 && $row['lastupdate'] < $tooOld) {
$row['update_class'] = 'text-danger';
}
if ($row['lastupdate'] > $showRefresh) {
- $data['show_refresh'] = false;
+ $sourceViewData['show_refresh'] = false;
}
- $data['list'][] = $row;
+ $sourceViewData['list'][] = $row;
}
- Render::addTemplate('sources', $data);
+ }
+ // Output
+ Render::addTemplate('page-minilinux', [
+ 'default' => Property::get(MiniLinux::PROPERTY_DEFAULT_BOOT),
+ 'sources' => $sourceList ?? null,
+ ]);
+ // Warning
+ if (!MiniLinux::updateCurrentBootSetting()) {
+ Message::addError('default-not-installed', Property::get(MiniLinux::PROPERTY_DEFAULT_BOOT));
+ }
+ if (isset($branches)) {
+ Render::addTemplate('branches', ['branches' => $branches]);
+ } elseif (isset($sourceViewData)) {
+ Render::addTemplate('sources', $sourceViewData);
} else {
Message::addError('main.invalid-action', $show);
}
@@ -82,23 +107,26 @@ class Page_MiniLinux extends Page
}
}
- private function renderVersionList($versions)
+ private function renderVersionList(array $versions, array $usage): string
{
$def = Property::get(MiniLinux::PROPERTY_DEFAULT_BOOT);
- $eff = Property::get(MiniLinux::PROPERTY_DEFAULT_BOOT_EFFECTIVE);
+ //$eff = Property::get(MiniLinux::PROPERTY_DEFAULT_BOOT_EFFECTIVE);
foreach ($versions as &$version) {
$version['dateline_s'] = Util::prettyTime($version['dateline']);
- $version['orphan'] = ($version['orphan'] > 2);
+ $version['orphan'] = ($version['orphan'] > 0 && $version['installed'] == MiniLinux::INSTALL_MISSING) || ($version['orphan'] > 1);
$version['downloading'] = $version['taskid'] && Taskmanager::isRunning(Taskmanager::status($version['taskid']));
- if ($version['installed'] && $version['versionid'] !== $def) {
+ if ($version['installed'] != MiniLinux::INSTALL_MISSING && $version['versionid'] !== $def) {
$version['showsetdefault'] = true;
}
if ($version['versionid'] === $def) {
$version['isdefault'] = true;
- if (!$version['installed']) {
+ if (!$version['installed'] != MiniLinux::INSTALL_OK) {
$version['default_class'] = 'bg-danger';
}
}
+ if (isset($usage[$version['versionid']])) {
+ $version['usage'] = $usage[$version['versionid']];
+ }
}
return Render::parse('versionlist', ['versions' => array_values($versions)]);
}
@@ -111,7 +139,8 @@ class Page_MiniLinux extends Page
if ($versionid === false) {
die('What!');
}
- $ver = Database::queryFirst('SELECT versionid, taskid, data, installed FROM minilinux_version WHERE versionid = :versionid',
+ $ver = Database::queryFirst('SELECT versionid, description, taskid, data, installed
+ FROM minilinux_version WHERE versionid = :versionid',
['versionid' => $versionid]);
if ($ver === false) {
die('No such version');
@@ -123,7 +152,7 @@ class Page_MiniLinux extends Page
}
$data['versionid'] = $versionid;
$data['dltask'] = MiniLinux::validateDownloadTask($versionid, $ver['taskid']);
- $data['verify_button'] = !$verify && $data['dltask'] === false;
+ $data['verify_button'] = !$verify && $data['dltask'] === null;
if (is_array($data['files'])) {
$valid = true;
$sort = [];
@@ -147,7 +176,7 @@ class Page_MiniLinux extends Page
if (isset($file['mtime'])) {
$file['mtime_s'] = Util::prettyTime($file['mtime']);
}
- if ($data['dltask']) {
+ if ($data['dltask'] !== null) {
$file['fileid'] = MiniLinux::fileToId($versionid, $file['name']);
}
}
@@ -155,14 +184,17 @@ class Page_MiniLinux extends Page
array_multisort($sort, SORT_ASC, $data['files']);
if (!$valid) {
$data['verify_button'] = false;
- $data['download_button'] = !$data['dltask'];
- if ($ver['installed']) {
- MiniLinux::setInstalledState($versionid, false);
+ if ($ver['installed'] != MiniLinux::INSTALL_MISSING) {
+ MiniLinux::setInstalledState($versionid, MiniLinux::INSTALL_BROKEN);
}
- } elseif (!$ver['installed'] && $verify) {
- MiniLinux::setInstalledState($versionid, true);
+ } elseif ($ver['installed'] != MiniLinux::INSTALL_OK && $verify) {
+ MiniLinux::setInstalledState($versionid, MiniLinux::INSTALL_OK);
}
}
+ if ($data['dltask'] !== null || $ver['installed'] != MiniLinux::INSTALL_MISSING) {
+ MiniLinux::checkStage4($data, $data['s4_errors']);
+ }
+ $data['changelog'] = Util::markup($ver['description'] ?? '');
echo Render::parse('filelist', $data);
}
@@ -172,7 +204,7 @@ class Page_MiniLinux extends Page
const FILE_CHECKSUM_BAD = 3;
const FILE_NOT_READABLE = 4;
- private function getFileState($versionid, $file, $verify)
+ private function getFileState(string $versionid, array $file, bool $verify): int
{
$path = CONFIG_HTTP_DIR . '/' . $versionid . '/' . $file['name'];
if (!is_file($path))
@@ -199,15 +231,15 @@ class Page_MiniLinux extends Page
{
switch ($state) {
case self::FILE_CHECKSUM_BAD:
- return Dictionary::translate('file-checksum-bad', true);
+ return Dictionary::translate('file-checksum-bad');
case self::FILE_SIZE_MISMATCH:
- return Dictionary::translate('file-size-mismatch', true);
+ return Dictionary::translate('file-size-mismatch');
case self::FILE_MISSING:
- return Dictionary::translate('file-missing', true);
+ return Dictionary::translate('file-missing');
case self::FILE_NOT_READABLE:
- return Dictionary::translate('file-not-readable', true);
+ return Dictionary::translate('file-not-readable');
case self::FILE_OK:
- return Dictionary::translate('file-ok', true);
+ return Dictionary::translate('file-ok');
}
return '???';
}
@@ -220,7 +252,7 @@ class Page_MiniLinux extends Page
die('No version');
}
$task = MiniLinux::downloadVersion($version);
- if ($task === false) {
+ if ($task === null) {
Message::addError('no-such-version', $version);
Message::renderList();
} else {
@@ -242,7 +274,6 @@ class Page_MiniLinux extends Page
Message::addError('no-such-version');
return;
}
- MiniLinux::setInstalledState($version['versionid'], false);
$path = CONFIG_HTTP_DIR . '/' . $version['versionid'];
$task = Taskmanager::submit('DeleteDirectory', [
'path' => $path,
@@ -251,8 +282,10 @@ class Page_MiniLinux extends Page
if ($task !== false) {
$task = Taskmanager::waitComplete($task, 2500);
if (Taskmanager::isFailed($task)) {
+ MiniLinux::setInstalledState($version['versionid'], MiniLinux::INSTALL_BROKEN);
Message::addError('delete-error', $versionid, $task['data']['error']);
} else {
+ MiniLinux::setInstalledState($version['versionid'], MiniLinux::INSTALL_MISSING);
Message::addSuccess('version-deleted', $versionid);
}
}
@@ -285,15 +318,7 @@ class Page_MiniLinux extends Page
Message::addError('no-such-version');
return;
}
- Property::set(MiniLinux::PROPERTY_DEFAULT_BOOT, $version['versionid']);
- // Legacy PXELINUX boot menu (TODO: Remove this when we get rid of PXELINUX support)
- $task = Taskmanager::submit('Symlink', [
- 'target' => $version['versionid'],
- 'linkname' => CONFIG_HTTP_DIR . '/default',
- ]);
- if ($task !== false) {
- Taskmanager::release($task);
- }
+ MiniLinux::setDefaultVersion($version['versionid']);
}
}
diff --git a/modules-available/minilinux/templates/branches.html b/modules-available/minilinux/templates/branches.html
index d8fb1f68..372321e2 100644
--- a/modules-available/minilinux/templates/branches.html
+++ b/modules-available/minilinux/templates/branches.html
@@ -1,23 +1,48 @@
<h3>{{lang_branchesHeading}}</h3>
+<div class="clearfix"></div>
+
<div id="ibm-mainframe">
{{#branches}}
- <div class="panel panel-default">
+ <a id="a-{{bid}}"></a>
+ <div class="panel panel-default" {{#color}}style="background:linear-gradient(90deg, {{color}} 0%, {{color}} 4px, rgba(255,255,255,0) 4px)"{{/color}}>
<div class="panel-heading">
- <div class="pull-right">
- {{sourceid}} {{branchid}}
+ <div class="pull-right slx-pointer" data-toggle="collapse" data-target="#{{bid}}">
+ {{sourceid}} {{branchid}} <b class="caret"></b>
</div>
<b>{{title}}</b>
</div>
+ <div class="collapse in branch-item" id="{{bid}}">
<div class="panel-body">
{{description}}
</div>
{{{versionlist}}}
+ </div>
</div>
{{/branches}}
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
+ // Remember collapsed state
+ var c = localStorage.getItem('ml-collapse');
+ if (c) {
+ c = JSON.parse(c);
+ for (var e in c) {
+ if (c.hasOwnProperty(e)) {
+ $('#' + e).collapse('hide');
+ }
+ }
+ } else {
+ c = {};
+ }
+ $('.branch-item').on('hide.bs.collapse', function() {
+ c[this.id] = true;
+ localStorage.setItem('ml-collapse', JSON.stringify(c));
+ }).on('show.bs.collapse', function() {
+ delete c[this.id];
+ localStorage.setItem('ml-collapse', JSON.stringify(c));
+ });
+ // Button magic
var addHandlers = function(parent) {
parent.find('.btn-verify').click(function() {
loadDetails($(this).data('version'), { show: "version", verify: 1 });
diff --git a/modules-available/minilinux/templates/filelist.html b/modules-available/minilinux/templates/filelist.html
index 0d8b9901..241d1264 100644
--- a/modules-available/minilinux/templates/filelist.html
+++ b/modules-available/minilinux/templates/filelist.html
@@ -8,12 +8,6 @@
{{lang_verify}}
</button>
{{/verify_button}}
- {{#download_button}}
- <button type="button" class="btn btn-xs btn-success btn-download" data-version="{{versionid}}">
- <span class="glyphicon glyphicon-download"></span>
- {{lang_download}}
- </button>
- {{/download_button}}
{{#delete_button}}
<button type="submit" name="show" value="delete" class="btn btn-xs btn-danger"
data-confirm="{{lang_confirmDeleteVersion}}">
@@ -47,12 +41,18 @@
</tr>
{{/files}}
</table>
+{{#s4_errors}}
+<div class="alert alert-warning">{{lang_maybeMissingStage4}}: {{.}}</div>
+{{/s4_errors}}
{{#dltask}}
<div class="hidden" data-tm-id="{{dltask}}" data-tm-callback="dlTmCb"></div>
<pre class="collapse" id="error-{{dltask}}"></pre>
{{/dltask}}
{{#changelog}}
-<h4>{{lang_changelog}}</h4>
-{{changelog}}
+ <div class="slx-space"></div>
+<div style="border:1px solid #bbb;padding:4px;border-radius: 3px">
+ <h4>{{lang_changelog}}</h4>
+ {{{changelog}}}
+</div>
{{/changelog}}
<div class="slx-space"></div> \ No newline at end of file
diff --git a/modules-available/minilinux/templates/page-minilinux.html b/modules-available/minilinux/templates/page-minilinux.html
index 3059e827..c66de597 100644
--- a/modules-available/minilinux/templates/page-minilinux.html
+++ b/modules-available/minilinux/templates/page-minilinux.html
@@ -1,5 +1,18 @@
-<h1>{{lang_minilinuxHeading}}</h1>
+{{#sources}}
+<div class="panel panel-default pull-right" style="margin:2px">
+ <table class="table table-condensed">
+ <tr style="background:#eee">
+ <th>{{sourceid}}</th>
+ </tr>
+ {{#list}}
+ <tr {{#color}}style="background:linear-gradient(90deg, {{color}} 0%, {{color}} 4px, rgba(255,255,255,0) 4px)"{{/color}}>
+ <td><a href="#a-{{bid}}">{{title}}</a></td>
+ </tr>
+ {{/list}}
+ </table>
+</div>
+{{/sources}}
-<p>{{lang_introText}}</p>
+<h1>{{lang_minilinuxHeading}}</h1>
{{lang_selectedDefaultIs}}: <b>{{default}}</b> \ No newline at end of file
diff --git a/modules-available/minilinux/templates/sources.html b/modules-available/minilinux/templates/sources.html
index dabc7f4d..50ad7c6f 100644
--- a/modules-available/minilinux/templates/sources.html
+++ b/modules-available/minilinux/templates/sources.html
@@ -20,10 +20,10 @@
<td class="small">{{url}}</td>
<td class="{{update_class}}">{{lastupdate_s}}</td>
<td class="text-center">
- <button type="button" class="btn btn-default btn-xs" data-confirm="#confirm-{{source}}" data-close="{{lang_close}}">
+ <button type="button" class="btn btn-default btn-xs" data-confirm="#confirm-{{sourceid}}" data-close="{{lang_close}}">
<span class="glyphicon glyphicon-eye-open"></span>
</button>
- <pre id="confirm-{{source}}" class="hidden">{{pubkey}}</pre>
+ <pre id="confirm-{{sourceid}}" class="hidden">{{pubkey}}</pre>
</td>
</tr>
{{/list}}
diff --git a/modules-available/minilinux/templates/versionlist.html b/modules-available/minilinux/templates/versionlist.html
index 0acfecac..e66960b2 100644
--- a/modules-available/minilinux/templates/versionlist.html
+++ b/modules-available/minilinux/templates/versionlist.html
@@ -3,6 +3,7 @@
<th class="slx-smallcol">{{lang_version}}</th>
<th class="slx-smallcol">{{lang_releaseDate}}</th>
<th>{{lang_title}}</th>
+ <th class="slx-smallcol">{{lang_usedBy}}</th>
<th class="slx-smallcol"></th>
<th class="slx-smallcol" style="width:100px">{{lang_default}}</th>
<th class="slx-smallcol" style="width:150px">{{lang_download}}</th>
@@ -15,9 +16,44 @@
<b class="caret"></b>
</a>
</td>
- <td class="text-nowrap">{{dateline_s}}</td>
+ <td class="text-nowrap">
+ {{#desclen}}
+ <div style="float:right;margin-right:-6px">
+ <span class="glyphicon glyphicon-list-alt"></span>
+ </div>
+ {{/desclen}}
+ {{dateline_s}}
+ </td>
<td>{{title}}</td>
<td class="text-nowrap">
+ {{#usage.entryids.0}}
+ <div class="dropdown">
+ <button class="btn btn-default btn-xs dropdown-toggle" type="button" data-toggle="dropdown">
+ {{usage.entryCount}} / {{usage.menuCount}} / {{usage.locationCount}}
+ <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu">
+ <li role="separator" class="dropdown-header slx-bold">{{lang_menuEntries}}</li>
+ {{#usage.entryids}}
+ <li><a href="?do=serversetup&amp;show=editbootentry&amp;id={{.}}">{{.}}</a></li>
+ {{/usage.entryids}}
+ {{#usage.menus.0}}
+ <li role="separator" class="dropdown-header slx-bold">{{lang_menus}}</li>
+ {{/usage.menus.0}}
+ {{#usage.menus}}
+ <li><a href="?do=serversetup&amp;show=editmenu&amp;id={{menuid}}">{{menuname}}</a></li>
+ {{/usage.menus}}
+ {{#usage.locations.0}}
+ <li role="separator" class="dropdown-header slx-bold">{{lang_locations}}</li>
+ {{/usage.locations.0}}
+ {{#usage.locations}}
+ <li class="disabled"><a href="#">{{locationname}}</a></li>
+ {{/usage.locations}}
+ </ul>
+ </div>
+ {{/usage.entryids.0}}
+ </td>
+ <td class="text-nowrap">
{{#orphan}}
<span class="label label-danger" title="{{lang_orphanedVersionToolTip}}">{{lang_orphanedVersion}}</span>
{{/orphan}}
@@ -36,22 +72,24 @@
<span class="glyphicon glyphicon-ok" title="{{lang_isGlobalDefault}}"></span>
{{/isdefault}}
</td>
- <td class="text-nowrap text-center">
+ <td class="text-nowrap text-right">
{{#installed}}
<span class="label label-info">{{lang_installed}}</span>
{{/installed}}
{{^installed}}
+ {{^orphan}}
{{^downloading}}
<button type="button" class="btn btn-xs btn-success btn-download" data-version="{{versionid}}">
<span class="glyphicon glyphicon-download"></span>
{{lang_download}}
</button>
{{/downloading}}
+ {{/orphan}}
{{/installed}}
</td>
</tr>
<tr>
- <td colspan="6" class="version-container collapse" data-version="{{versionid}}"></td>
+ <td colspan="7" class="version-container collapse" data-version="{{versionid}}"></td>
</tr>
{{/versions}}
</table> \ No newline at end of file