require_once('/usr/local/emhttp/webGui/include/Helpers.php'); require_once('/usr/local/emhttp/plugins/dynamix.vm.manager/classes/libvirt.php'); require_once('/usr/local/emhttp/plugins/dynamix.vm.manager/classes/libvirt_helpers.php'); $arrValidMachineTypes = getValidMachineTypes(); $arrValidGPUDevices = getValidGPUDevices(); $arrValidAudioDevices = getValidAudioDevices(); $arrValidOtherDevices = getValidOtherDevices(); $arrValidUSBDevices = getValidUSBDevices(); $arrValidDiskDrivers = getValidDiskDrivers(); $arrValidBridges = getNetworkBridges(); $strCPUModel = getHostCPUModel(); // 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 (array_key_exists('delete_version', $_POST)) { $arrDeleteOpenELEC = []; if (array_key_exists($_POST['delete_version'], $arrOpenELECVersions)) { $arrDeleteOpenELEC = $arrOpenELECVersions[$_POST['delete_version']]; } $arrResponse = []; if (empty($arrDeleteOpenELEC)) { $arrResponse = ['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); $arrResponse = ['status' => 'ok']; } echo json_encode($arrResponse); exit; } if (array_key_exists('download_path', $_POST)) { $arrDownloadOpenELEC = []; if (array_key_exists($_POST['download_version'], $arrOpenELECVersions)) { $arrDownloadOpenELEC = $arrOpenELECVersions[$_POST['download_version']]; } if (empty($arrDownloadOpenELEC)) { $arrResponse = ['error' => 'Unknown version: ' . $_POST['download_version']]; } else if (empty($_POST['download_path'])) { $arrResponse = ['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) { $arrResponse = [ 'error' => 'Not enough free space, need at least ' . ceil($arrDownloadOpenELEC['size']/1000000).'MB' ]; echo json_encode($arrResponse); 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); $arrResponse = []; if (file_exists($strExtractedFile)) { if (!file_exists($strTempFile)) { // Status = done $arrResponse['status'] = 'Done'; $arrResponse['localpath'] = $strExtractedFile; $arrResponse['localfolder'] = dirname($strExtractedFile); } else { if (pgrep($strExtractPgrep)) { // Status = running extract $arrResponse['status'] = 'Extracting ... '; } else { // Status = cleanup $arrResponse['status'] = 'Cleanup ... '; } } } else if (file_exists($strTempFile)) { if (pgrep($strDownloadPgrep)) { // Get Download percent completed $intSize = filesize($strTempFile); $strPercent = 0; if ($intSize > 0) { $strPercent = round(($intSize / $arrDownloadOpenELEC['size']) * 100); } $arrResponse['status'] = 'Downloading ... ' . $strPercent . '%'; } else if (pgrep($strVerifyPgrep)) { // Status = running md5 check $arrResponse['status'] = 'Verifying ... '; } else if (file_exists($strMD5StatusFile)) { // Status = running extract $arrResponse['status'] = 'Extracting ... '; if (!pgrep($strExtractPgrep)) { // Examine md5 status $strMD5StatusContents = file_get_contents($strMD5StatusFile); if (strpos($strMD5StatusContents, ': FAILED') !== false) { // ERROR: MD5 check failed unset($arrResponse['status']); $arrResponse['error'] = 'MD5 verification failed, your download is incomplete or corrupted.'; } } } else if (!file_exists($strMD5File)) { // Status = running md5 check $arrResponse['status'] = 'Downloading ... 100%'; if (!pgrep($strInstallScriptPgrep) && !$boolCheckOnly) { // Run all commands file_put_contents($strInstallScript, $strAllCmd); chmod($strInstallScript, 0777); exec($strInstallScript . ' >/dev/null 2>&1 &'); } } } else if (!$boolCheckOnly) { if (!pgrep($strInstallScriptPgrep)) { // Run all commands file_put_contents($strInstallScript, $strAllCmd); chmod($strInstallScript, 0777); exec($strInstallScript . ' >/dev/null 2>&1 &'); } $arrResponse['status'] = 'Downloading ... '; } $arrResponse['pid'] = pgrep($strInstallScriptPgrep); } echo json_encode($arrResponse); 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 ] ], 'gpu' => [ [ 'id' => '', 'mode' => 'qxl', 'keymap' => 'en-us' ] ], 'audio' => [ [ 'id' => '' ] ], 'pci' => [], 'nic' => [ [ 'network' => $domain_bridge, 'mac' => $lv->generate_random_mac_addr() ] ], 'usb' => [], 'shares' => [ [ 'source' => (is_dir('/mnt/user/appdata') ? '/mnt/user/appdata/OpenELEC/' : ''), 'target' => 'appconfig' ] ] ]; // Merge in any default values from the VM template if (!empty($arrAllTemplates[$strSelectedTemplate]) && !empty($arrAllTemplates[$strSelectedTemplate]['overrides'])) { $arrConfigDefaults = array_replace_recursive($arrConfigDefaults, $arrAllTemplates[$strSelectedTemplate]['overrides']); } // If we are editing a existing VM load it's existing configuration details $arrExistingConfig = (!empty($_GET['uuid']) ? domain_to_config($_GET['uuid']) : []); // Active config for this page $arrConfig = array_replace_recursive($arrConfigDefaults, $arrExistingConfig); if (array_key_exists($arrConfig['template']['openelec'], $arrOpenELECVersions)) { $arrConfigDefaults['disk'][0]['image'] = $arrOpenELECVersions[$arrConfig['template']['openelec']]['localpath']; } $boolNew = empty($arrExistingConfig); $boolRunning = (!empty($arrConfig['domain']['state']) && $arrConfig['domain']['state'] == 'running'); if (array_key_exists('createvm', $_POST)) { //DEBUG file_put_contents('/tmp/debug_libvirt_postparams.txt', print_r($_POST, true)); file_put_contents('/tmp/debug_libvirt_newxml.xml', $lv->config_to_xml($_POST)); if (!empty($_POST['shares'][0]['source'])) { @mkdir($_POST['shares'][0]['source'], 0777, true); } $tmp = $lv->domain_new($_POST); if (!$tmp){ $arrResponse = ['error' => $lv->get_last_error()]; } else { $arrResponse = ['success' => true]; } echo json_encode($arrResponse); exit; } if (array_key_exists('updatevm', $_POST)) { //DEBUG file_put_contents('/tmp/debug_libvirt_postparams.txt', print_r($_POST, true)); file_put_contents('/tmp/debug_libvirt_updatexml.xml', $lv->config_to_xml($_POST)); if (!empty($_POST['shares'][0]['source'])) { @mkdir($_POST['shares'][0]['source'], 0777, true); } // Backup xml for existing domain in ram $strOldXML = ''; $boolOldAutoStart = false; $dom = $lv->domain_get_domain_by_uuid($_POST['domain']['uuid']); if ($dom) { $strOldXML = $lv->domain_get_xml($dom); $boolOldAutoStart = $lv->domain_get_autostart($dom); $strOldName = $lv->domain_get_name($dom); $strNewName = $_POST['domain']['name']; if (!empty($strOldName) && !empty($strNewName) && is_dir($domain_cfg['DOMAINDIR'].$strOldName.'/') && !is_dir($domain_cfg['DOMAINDIR'].$strNewName.'/')) { // mv domain/vmname folder if (rename($domain_cfg['DOMAINDIR'].$strOldName, $domain_cfg['DOMAINDIR'].$strNewName)) { // replace all disk paths in xml foreach ($_POST['disk'] as &$arrDisk) { if (!empty($arrDisk['new'])) { $arrDisk['new'] = str_replace($domain_cfg['DOMAINDIR'].$strOldName.'/', $domain_cfg['DOMAINDIR'].$strNewName.'/', $arrDisk['new']); } if (!empty($arrDisk['image'])) { $arrDisk['image'] = str_replace($domain_cfg['DOMAINDIR'].$strOldName.'/', $domain_cfg['DOMAINDIR'].$strNewName.'/', $arrDisk['image']); } } } } //DEBUG file_put_contents('/tmp/debug_libvirt_oldxml.xml', $strOldXML); } // Remove existing domain $lv->nvram_backup($_POST['domain']['uuid']); $lv->domain_undefine($dom); $lv->nvram_restore($_POST['domain']['uuid']); // Save new domain $tmp = $lv->domain_new($_POST); if (!$tmp){ $strLastError = $lv->get_last_error(); // Failure -- try to restore existing domain $tmp = $lv->domain_define($strOldXML); if ($tmp) $lv->domain_set_autostart($tmp, $boolOldAutoStart); $arrResponse = ['error' => $strLastError]; } else { $lv->domain_set_autostart($tmp, $_POST['domain']['autostart'] == 1); $arrResponse = ['success' => true]; } echo json_encode($arrResponse); exit; } ?>
| 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: |
|
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: |
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.
| Graphics Card: |
} ?> } ?> foreach ($arrConfig['audio'] as $i => $arrAudio) { $strLabel = ($i > 0) ? appendOrdinalSuffix($i + 1) : ''; ?>Graphics Card
if (count($arrValidGPUDevices) > 1) { ?>
If you wish to assign a graphics card to the VM, select it from this list.Additional devices can be added/removed by clicking the symbols to the left.
} ?>
| Sound Card: |
} ?> } ?> 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.
if (count($arrValidAudioDevices) > 1) { ?>Additional devices can be added/removed by clicking the symbols to the left.
} ?>
| Network MAC: | |
| Network Bridge: |
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 Bridge
The default libvirt managed network bridge (virbr0) will be used, otherwise you may specify an alternative name for a private network bridge to the host.Additional devices can be added/removed by clicking the symbols to the left.
| USB Devices: |
$arrDev) {
?>
None available"; } ?> |
If you wish to assign any USB devices to your guest, you can select them from this list.
NOTE: USB hotplug support is not yet implemented, so devices must be attached before the VM is started to use them.
| USB Mode: |
USB Mode
Select the USB Mode to emulate.
| Other PCI Devices: |
$intAvailableOtherPCIDevices = 0;
if (!empty($arrValidOtherDevices)) {
foreach($arrValidOtherDevices as $i => $arrDev) {
$extra = '';
if (count(array_filter($arrConfig['pci'], function($arr) use ($arrDev) { return ($arr['id'] == $arrDev['id']); }))) {
$extra .= ' checked="checked"';
} else if (!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.
|
if (!$boolRunning) { ?>
if (!$boolNew) { ?>
} else { ?>
} ?> } else { ?> } ?> |
} ?>Click Create to return to the Virtual Machines page where your new VM will be created.