$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/webGui/include/Helpers.php";
require_once "$docroot/webGui/include/Custom.php";
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
// add translations
if (substr($_SERVER['REQUEST_URI'],0,4) != '/VMs') {
$_SERVER['REQUEST_URI'] = 'vms';
require_once "$docroot/webGui/include/Translations.php";
}
$arrValidMachineTypes = getValidMachineTypes();
$arrValidGPUDevices = getValidGPUDevices();
$arrValidAudioDevices = getValidAudioDevices();
$arrValidOtherDevices = getValidOtherDevices();
$arrValidUSBDevices = getValidUSBDevices();
$arrValidDiskDrivers = getValidDiskDrivers();
$arrValidNetworks = getValidNetworks();
$strCPUModel = getHostCPUModel();
$arrValidProtocols = getValidVMRCProtocols();
// Read localpaths in from openelec.cfg
$strOpenELECConfig = "/boot/config/plugins/dynamix.vm.manager/openelec.cfg";
$arrOpenELECConfig = [];
if (file_exists($strOpenELECConfig)) {
$arrOpenELECConfig = parse_ini_file($strOpenELECConfig);
} elseif (!file_exists(dirname($strOpenELECConfig))) {
@mkdir(dirname($strOpenELECConfig), 0777, true);
}
// Compare openelec.cfg and populate 'localpath' in $arrOEVersion
foreach ($arrOpenELECConfig as $strID => $strLocalpath) {
if (array_key_exists($strID, $arrOpenELECVersions)) {
$arrOpenELECVersions[$strID]['localpath'] = $strLocalpath;
if (file_exists($strLocalpath)) {
$arrOpenELECVersions[$strID]['valid'] = '1';
}
}
}
if (isset($_POST['delete_version'])) {
$arrDeleteOpenELEC = [];
if (array_key_exists($_POST['delete_version'], $arrOpenELECVersions)) {
$arrDeleteOpenELEC = $arrOpenELECVersions[$_POST['delete_version']];
}
$reply = [];
if (empty($arrDeleteOpenELEC)) {
$reply = ['error' => 'Unknown version: ' . $_POST['delete_version']];
} else {
// delete img file
@unlink($arrDeleteOpenELEC['localpath']);
// Save to strOpenELECConfig
unset($arrOpenELECConfig[$_POST['delete_version']]);
$text = '';
foreach ($arrOpenELECConfig as $key => $value) $text .= "$key=\"$value\"\n";
file_put_contents($strOpenELECConfig, $text);
$reply = ['status' => 'ok'];
}
echo json_encode($reply);
exit;
}
if (isset($_POST['download_path'])) {
$arrDownloadOpenELEC = [];
if (array_key_exists($_POST['download_version'], $arrOpenELECVersions)) {
$arrDownloadOpenELEC = $arrOpenELECVersions[$_POST['download_version']];
}
if (empty($arrDownloadOpenELEC)) {
$reply = ['error' => _('Unknown version').': ' . $_POST['download_version']];
} elseif (empty($_POST['download_path'])) {
$reply = ['error' => _('Please choose a folder the OpenELEC image will download to')];
} else {
@mkdir($_POST['download_path'], 0777, true);
$_POST['download_path'] = realpath($_POST['download_path']) . '/';
// Check free space
if (disk_free_space($_POST['download_path']) < $arrDownloadOpenELEC['size']+10000) {
$reply = ['error' => _('Not enough free space, need at least').' ' . ceil($arrDownloadOpenELEC['size']/1000000).'MB'];
echo json_encode($reply);
exit;
}
$boolCheckOnly = !empty($_POST['checkonly']);
$strInstallScript = '/tmp/OpenELEC_' . $_POST['download_version'] . '_install.sh';
$strInstallScriptPgrep = '-f "OpenELEC_' . $_POST['download_version'] . '_install.sh"';
$strTempFile = $_POST['download_path'] . basename($arrDownloadOpenELEC['url']);
$strLogFile = $strTempFile . '.log';
$strMD5File = $strTempFile . '.md5';
$strMD5StatusFile = $strTempFile . '.md5status';
$strExtractedFile = $_POST['download_path'] . basename($arrDownloadOpenELEC['url'], 'tar.xz') . 'img';
// Save to strOpenELECConfig
$arrOpenELECConfig[$_POST['download_version']] = $strExtractedFile;
$text = '';
foreach ($arrOpenELECConfig as $key => $value) $text .= "$key=\"$value\"\n";
file_put_contents($strOpenELECConfig, $text);
$strDownloadCmd = 'wget -nv -c -O ' . escapeshellarg($strTempFile) . ' ' . escapeshellarg($arrDownloadOpenELEC['url']);
$strDownloadPgrep = '-f "wget.*' . $strTempFile . '.*' . $arrDownloadOpenELEC['url'] . '"';
$strVerifyCmd = 'md5sum -c ' . escapeshellarg($strMD5File);
$strVerifyPgrep = '-f "md5sum.*' . $strMD5File . '"';
$strExtractCmd = 'tar Jxf ' . escapeshellarg($strTempFile) . ' -C ' . escapeshellarg(dirname($strTempFile));
$strExtractPgrep = '-f "tar.*' . $strTempFile . '.*' . dirname($strTempFile) . '"';
$strCleanCmd = '(chmod 777 ' . escapeshellarg($_POST['download_path']) . ' ' . escapeshellarg($strExtractedFile) . '; chown nobody:users ' . escapeshellarg($_POST['download_path']) . ' ' . escapeshellarg($strExtractedFile) . '; rm ' . escapeshellarg($strTempFile) . ' ' . escapeshellarg($strMD5File) . ' ' . escapeshellarg($strMD5StatusFile) . ')';
$strCleanPgrep = '-f "chmod.*chown.*rm.*' . $strMD5StatusFile . '"';
$strAllCmd = "#!/bin/bash\n\n";
$strAllCmd .= $strDownloadCmd . ' >>' . escapeshellarg($strLogFile) . ' 2>&1 && ';
$strAllCmd .= 'echo "' . $arrDownloadOpenELEC['md5'] . ' ' . $strTempFile . '" > ' . escapeshellarg($strMD5File) . ' && ';
$strAllCmd .= $strVerifyCmd . ' >' . escapeshellarg($strMD5StatusFile) . ' 2>/dev/null && ';
$strAllCmd .= $strExtractCmd . ' >>' . escapeshellarg($strLogFile) . ' 2>&1 && ';
$strAllCmd .= $strCleanCmd . ' >>' . escapeshellarg($strLogFile) . ' 2>&1 && ';
$strAllCmd .= 'rm ' . escapeshellarg($strLogFile) . ' && ';
$strAllCmd .= 'rm ' . escapeshellarg($strInstallScript);
$reply = [];
if (file_exists($strExtractedFile)) {
if (!file_exists($strTempFile)) {
// Status = done
$reply['status'] = 'Done';
$reply['localpath'] = $strExtractedFile;
$reply['localfolder'] = dirname($strExtractedFile);
} else {
if (pgrep($strExtractPgrep, false)) {
// Status = running extract
$reply['status'] = _('Extracting').' ... ';
} else {
// Status = cleanup
$reply['status'] = _('Cleanup').' ... ';
}
}
} elseif (file_exists($strTempFile)) {
if (pgrep($strDownloadPgrep, false)) {
// Get Download percent completed
$intSize = filesize($strTempFile);
$strPercent = 0;
if ($intSize > 0) {
$strPercent = round(($intSize / $arrDownloadOpenELEC['size']) * 100);
}
$reply['status'] = _('Downloading').' ... ' . $strPercent . '%';
} elseif (pgrep($strVerifyPgrep, false)) {
// Status = running md5 check
$reply['status'] = _('Verifying').' ... ';
} elseif (file_exists($strMD5StatusFile)) {
// Status = running extract
$reply['status'] = _('Extracting').' ... ';
if (!pgrep($strExtractPgrep, false)) {
// Examine md5 status
$strMD5StatusContents = file_get_contents($strMD5StatusFile);
if (strpos($strMD5StatusContents, ': FAILED') !== false) {
// ERROR: MD5 check failed
unset($reply['status']);
$reply['error'] = _('MD5 verification failed, your download is incomplete or corrupted').'.';
}
}
} elseif (!file_exists($strMD5File)) {
// Status = running md5 check
$reply['status'] = _('Downloading').' ... 100%';
if (!pgrep($strInstallScriptPgrep, false) && !$boolCheckOnly) {
// Run all commands
file_put_contents($strInstallScript, $strAllCmd);
chmod($strInstallScript, 0777);
exec($strInstallScript . ' >/dev/null 2>&1 &');
}
}
} elseif (!$boolCheckOnly) {
if (!pgrep($strInstallScriptPgrep, false)) {
// Run all commands
file_put_contents($strInstallScript, $strAllCmd);
chmod($strInstallScript, 0777);
exec($strInstallScript . ' >/dev/null 2>&1 &');
}
$reply['status'] = _('Downloading').' ... ';
}
$reply['pid'] = pgrep($strInstallScriptPgrep, false);
}
echo json_encode($reply);
exit;
}
$arrOpenELECVersion = reset($arrOpenELECVersions);
$strOpenELECVersionID = key($arrOpenELECVersions);
$arrConfigDefaults = [
'template' => [
'name' => $strSelectedTemplate,
'icon' => $arrAllTemplates[$strSelectedTemplate]['icon'],
'openelec' => $strOpenELECVersionID
],
'domain' => [
'name' => $strSelectedTemplate,
'persistent' => 1,
'uuid' => $lv->domain_generate_uuid(),
'clock' => 'utc',
'arch' => 'x86_64',
'machine' => getLatestMachineType('q35'),
'mem' => 512 * 1024,
'maxmem' => 512 * 1024,
'password' => '',
'cpumode' => 'host-passthrough',
'vcpus' => 1,
'vcpu' => [0],
'hyperv' => 0,
'ovmf' => 1,
'usbmode' => 'usb3'
],
'media' => [
'cdrom' => '',
'cdrombus' => '',
'drivers' => '',
'driversbus' => ''
],
'disk' => [
[
'image' => $arrOpenELECVersion['localpath'],
'size' => '',
'driver' => 'raw',
'dev' => 'hda',
'readonly' => 1,
'boot' => 1
]
],
'gpu' => [
[
'id' => 'virtual',
'protocol' => 'vnc',
'autoport' => 'yes',
'model' => 'qxl',
'keymap' => 'en-us',
'port' => -1 ,
'wsport' => -1
]
],
'audio' => [
[
'id' => ''
]
],
'pci' => [],
'nic' => [
[
'network' => $domain_bridge,
'mac' => $lv->generate_random_mac_addr(),
'model' => 'virtio-net'
]
],
'usb' => [],
'shares' => [
[
'source' => (is_dir('/mnt/user/appdata') ? '/mnt/user/appdata/OpenELEC/' : ''),
'target' => 'appconfig'
]
]
];
$hdrXML = "\n"; // XML encoding declaration
// Merge in any default values from the VM template
if ($arrAllTemplates[$strSelectedTemplate] && $arrAllTemplates[$strSelectedTemplate]['overrides']) {
$arrConfigDefaults = array_replace_recursive($arrConfigDefaults, $arrAllTemplates[$strSelectedTemplate]['overrides']);
}
// create new VM
if (isset($_POST['createvm'])) {
if (isset($_POST['xmldesc'])) {
// XML view
$new = $lv->domain_define($_POST['xmldesc'], $_POST['domain']['xmlstartnow']==1);
if ($new){
$lv->domain_set_autostart($new, $_POST['domain']['autostart']==1);
$reply = ['success' => true];
} else {
$reply = ['error' => $lv->get_last_error()];
}
} else {
// form view
if (isset($_POST['shares'][0]['source'])) {
@mkdir($_POST['shares'][0]['source'], 0777, true);
}
$_POST['clock'] = $arrDefaultClocks["other"] ;
if ($lv->domain_new($_POST)){
$reply = ['success' => true];
} else {
$reply = ['error' => $lv->get_last_error()];
}
}
echo json_encode($reply);
exit;
}
// update existing VM
if (isset($_POST['updatevm'])) {
$uuid = $_POST['domain']['uuid'];
$dom = $lv->domain_get_domain_by_uuid($uuid);
$oldAutoStart = $lv->domain_get_autostart($dom)==1;
$newAutoStart = $_POST['domain']['autostart']==1;
$strXML = $lv->domain_get_xml($dom);
if ($lv->domain_get_state($dom)=='running') {
$arrErrors = [];
$arrExistingConfig = domain_to_config($uuid);
$arrNewUSBIDs = $_POST['usb'];
// hot-attach any new usb devices
foreach ($arrNewUSBIDs as $strNewUSBID) {
if (strpos($strNewUSBID,"#remove")) continue ;
$remove = explode('#', $strNewUSBID) ;
$strNewUSBID2 = $remove[0] ;
foreach ($arrExistingConfig['usb'] as $arrExistingUSB) {
if ($strNewUSBID2 == $arrExistingUSB['id']) continue 2;
}
[$strVendor,$strProduct] = my_explode(':', $strNewUSBID2);
// hot-attach usb
file_put_contents('/tmp/hotattach.tmp', "
| _(Name)_: |
Give the VM a name (e.g. OpenELEC Family Room, OpenELEC Theatre, OpenELEC)
| _(Description)_: |
Give the VM a brief description (optional field).
| _(OpenELEC Version)_: |
Select which OpenELEC version to download or use for this VM
| _(Download Folder)_: |
Choose a folder where the OpenELEC image will downloaded to
| _(Config Folder)_: |
Choose a folder or type in a new name off of an existing folder to specify where OpenELEC will save configuration files. If you create multiple OpenELEC VMs, these Config Folders must be unique for each instance.
| _(CPU Mode)_: |
There are two CPU modes available to choose:
Host Passthrough
With this mode, the CPU visible to the guest should be exactly the same as the host CPU even in the aspects that libvirt does not understand. For the best possible performance, use this setting.Emulated
If you are having difficulties with Host Passthrough mode, you can try the emulated mode which doesn't expose the guest to host-based CPU features. This may impact the performance of your VM.
| _(Logical CPUs)_: |
$cpus = cpu_list();
foreach ($cpus as $pair) {
unset($cpu1,$cpu2);
[$cpu1, $cpu2] = my_preg_split('/[,-]/',$pair);
$extra = in_array($cpu1, $arrConfig['domain']['vcpu']) ? ($arrConfig['domain']['vcpus'] > 1 ? 'checked' : 'checked disabled') : '';
if (!$cpu2) {
echo "";
} else {
echo "";
$extra = in_array($cpu2, $arrConfig['domain']['vcpu']) ? ($arrConfig['domain']['vcpus'] > 1 ? 'checked' : 'checked disabled') : '';
echo "";
}
}
?>
|
The number of logical CPUs in your system is determined by multiplying the number of CPU cores on your processor(s) by the number of threads.
Select which logical CPUs you wish to allow your VM to use. (minimum 1).
| _(Initial)_ _(Memory)_: | _(Max)_ _(Memory)_: |
Select how much memory to allocate to the VM at boot.
For VMs where no PCI devices are being passed through (GPUs, sound, etc.), you can set different values to initial and max memory to allow for memory ballooning. If you are passing through a PCI device, only the initial memory value is used and the max memory value is ignored. For more information on KVM memory ballooning, see here.
| _(Machine)_: |
The machine type option primarily affects the success some users may have with various hardware and GPU pass through. For more information on the various QEMU machine types, see these links:
http://wiki.qemu.org/Documentation/Platforms/PC
http://wiki.qemu.org/Features/Q35
As a rule of thumb, try to get your configuration working with i440fx first and if that fails, try adjusting to Q35 to see if that changes anything.
| _(BIOS)_: | $usbboothidden = "hidden" ; if ($arrConfig['domain']['ovmf'] != '0') $usbboothidden = "" ; ?> >_(Enable USB boot)_: |
SeaBIOS
is the default virtual BIOS used to create virtual machines and is compatible with all guest operating systems (Windows, Linux, etc.).OVMF
(Open Virtual Machine Firmware) adds support for booting VMs using UEFI, but virtual machine guests must also support UEFI. Assigning graphics devices to a OVMF-based virtual machine requires that the graphics device also support UEFI.Once a VM is created this setting cannot be adjusted.
USB Boot
Adds support for booting from USB devices using UEFI. No device boot orders can be specified at the same time as this option.
| _(USB Controller)_: |
USB Controller
Select the USB Controller to emulate. Qemu XHCI is the same code base as Nec XHCI but without several hacks applied over the years. Recommended to try qemu XHCI before resorting to nec XHCI.
| _(Graphics Card)_: | |
| _(VM Console Protocol)_: | |
| _(VM Console AutoPort)_: | >_(VM Console Port)_: > >_(VM Console WS Port)_: > |
| _(Graphics ROM BIOS)_: |
}?> }?> $arrAudio) { $strLabel = ($i > 0) ? appendOrdinalSuffix($i + 1) : ''; ?>Graphics Card
Graphics Card
If you wish to assign a graphics card to the VM, select it from this list.
If you wish to assign a graphics card to the VM, select it from this list, otherwise leave it set to virtual.virtual video protocol VDC/SPICE
If you wish to assign a protocol type, specify one here.virtual auto port
Set it you want to specify a manual port for VNC or Spice. VNC needs two ports where Spice only requires one. Leave as auto yes for the system to set.Graphics ROM BIOS
1) {?>
If you wish to use a custom ROM BIOS for a Graphics card, specify one here.Additional devices can be added/removed by clicking the symbols to the left.
}?>
| _(Sound Card)_: |
}?> }?> $domain_bridge, 'mac' => "", 'model' => 'virtio-net' ] ; } foreach ($arrConfig['nic'] as $i => $arrNic) { $strLabel = ($i > 0) ? appendOrdinalSuffix($i + 1) : ''; ?>Select a sound device to assign to your VM. Most modern GPUs have a built-in audio device, but you can also select the on-board audio device(s) if present.
1) {?>Additional devices can be added/removed by clicking the symbols to the left.
}?>
| _(Network MAC)_: | |
| _(Network Source)_: | |
| _(Network Model)_: | |
| _(Boot Order)_: | style="width: 50px;" name="nic[=$i?>][boot]" title="_(Boot order)_" value="=$arrNic['boot']?>" > |
Network MAC
By default, a random MAC address will be assigned here that conforms to the standards for virtual network interface controllers. You can manually adjust this if desired.Network Source
The default libvirt managed network bridge (virbr0) will be used, otherwise you may specify an alternative name for a private network to the host.Network Model
Default and recommended is 'virtio-net', which gives improved stability. To improve performance 'virtio' can be selected, but this may lead to stability issues.Use boot order to set device as bootable and boot sequence.
Additional devices can be added/removed by clicking the symbols to the left.
| _(Select)_  _(Optional)_  _(Boot Order)_ | |
| _(USB Devices)_: |
if (!empty($arrVMUSBs)) {
foreach($arrVMUSBs as $i => $arrDev) {
?>
} } else { echo ""._('None available').""; } ?> |
If you wish to assign any USB devices to your guest, you can select them from this list.
Use boot order to set device as bootable and boot sequence.
Select optional if you want device to be ignored when VM starts if not present.
| _(Select)_  _(Boot Order)_ | |
| _(Other PCI Devices)_: |
$intAvailableOtherPCIDevices = 0;
if (!empty($arrValidOtherDevices)) {
foreach($arrValidOtherDevices as $i => $arrDev) {
$bootdisable = $extra = $pciboot = '';
if ($arrDev["typeid"] != "0108") $bootdisable = ' disabled="disabled"' ;
if (count($pcidevice=array_filter($arrConfig['pci'], function($arr) use ($arrDev) { return ($arr['id'] == $arrDev['id']); }))) {
$extra .= ' checked="checked"';
foreach ($pcidevice as $pcikey => $pcidev) $pciboot = $pcidev["boot"]; ;
} elseif (!in_array($arrDev['driver'], ['pci-stub', 'vfio-pci'])) {
//$extra .= ' disabled="disabled"';
continue;
}
$intAvailableOtherPCIDevices++;
?>
} } if (empty($intAvailableOtherPCIDevices)) { echo ""._('None available').""; } ?> |
If you wish to assign any other PCI devices to your guest, you can select them from this list.
Use boot order to set device as bootable and boot sequence. Only NVMe devices (PCI types 0108) supported for boot order.
|
} else {?>
}?> |
}?>Click Create to return to the Virtual Machines page where your new VM will be created.
|
} else {?>
}?> } else {?> }?> |