<?php
/**
* Class LinuxBootEntryHook.
* Only to be used in the ipxe-bootentry hook, as this depends on
* the existence of BootEntryHook, a class from serversetup-bwlp-ipxe.
* This module is usually not activated when interacting with the
* minilinux module.
*/
class LinuxBootEntryHook extends BootEntryHook
{
public function name()
{
return Dictionary::translateFileModule('minilinux', 'module', 'module_name', true);
}
public function extraFields(): array
{
/* For translate module:
* Dictionary::translate('ipxe-kcl-extra');
* Dictionary::translate('ipxe-debug');
* Dictionary::translate('ipxe-insecure-cpu');
*/
return [
new HookExtraField('kcl-extra', 'string', ''),
new HookExtraField('debug', 'bool', false),
new HookExtraField('insecure-cpu', 'bool', false),
];
}
/**
* @return HookEntryGroup[]
*/
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),
MiniLinux::updateCurrentBootSetting())
]);
$branches = Database::queryAll('SELECT sourceid, branchid, title FROM minilinux_branch ORDER BY title');
$versions = MiniLinux::queryAllVersionsByBranch();
// Group by branch for detailed listing
foreach ($branches as $branch) {
if (isset($versions[$branch['branchid']])) {
$group = [
new HookEntry($branch['branchid'],
$branch['branchid'] . ' '
. Dictionary::translateFileModule('minilinux', 'module',
'latest_of_branch', true),
true),
];
foreach ($versions[$branch['branchid']] as $version) {
$valid = $version['installed'] != 0;
$title = $version['versionid'] . ' ' . $version['title'];
if (!$valid) {
$title .= ' ' . Dictionary::translateFileModule('minilinux', 'module',
'not_installed_hint', true);
}
$group[] = new HookEntry($version['versionid'], $title, $valid);
}
$array[] = new HookEntryGroup($branch['title'] ? $branch['title'] : $branch['branchid'], $group);
}
}
return $array;
}
/**
* @param $localData
* @return BootEntry the actual boot entry instance for given entry, false if invalid id
*/
public function getBootEntryInternal($localData): BootEntry
{
$id = $localData['id'];
if ($id === 'default') { // Special case
$effectiveId = Property::get(MiniLinux::PROPERTY_DEFAULT_BOOT_EFFECTIVE);
} else {
$effectiveId = $id;
}
$res = Database::queryFirst('SELECT versionid, installed, data FROM minilinux_version WHERE versionid = :id',
['id' => $effectiveId]);
if ($res === false) {
// 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
ORDER BY installed DESC, dateline DESC LIMIT 1', // Order by installed instead of WHERE for better errormsg
['id' => $effectiveId]);
}
if ($res === false) {
return BootEntry::newCustomBootEntry(['script' => 'prompt Invalid minilinux boot entry id: ' . $id]);
}
if ($res['installed'] == 0) {
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'])) {
$remoteData['agnostic'] = []; // We got nothing at all so fake this entry, resulting in a generic default entry
}
if (@is_array($remoteData['agnostic'])) {
$bios = $this->generateExecData($effectiveId, $remoteData['agnostic'], $localData);
$arch = BootEntry::AGNOSTIC;
} else {
if (@is_array($remoteData['efi'])) {
$efi = $this->generateExecData($effectiveId, $remoteData['efi'], $localData);
}
if (@is_array($remoteData['bios'])) {
$bios = $this->generateExecData($effectiveId, $remoteData['bios'], $localData);
}
if ($bios && $efi) {
$arch = BootEntry::BOTH;
} elseif ($bios) {
$arch = BootEntry::BIOS;
} else {
$arch = BootEntry::EFI;
}
}
return BootEntry::newStandardBootEntry($bios, $efi, $arch);
}
private function generateExecData($effectiveId, $remoteData, $localData): ExecData
{
$exec = new ExecData();
// Defaults
$root = '/boot/' . $effectiveId . '/';
$exec->executable = 'kernel';
$exec->initRd = ['initramfs-stage31'];
$exec->imageFree = true;
$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])) {
$exec->{$key} = $remoteData[$key];
}
}
// KCL hacks
if (!empty($localData['debug'])) {
// Debug boot enabled
$exec->commandLine = IPxe::modifyCommandLine($exec->commandLine,
$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');
}
// 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] !== '/') {
$rd = $root . $rd;
}
}
unset($rd);
return $exec;
}
public function isValidId($id)
{
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]);
if ($res !== false && $res['installed'])
return true;
$res = Database::queryFirst('SELECT branchid FROM minilinux_branch WHERE branchid = :id', ['id' => $id]);
return $res !== false;
}
}