$strLocalpath) { if (array_key_exists($strID, $arrOpenELECVersions)) { $arrOpenELECVersions[$strID]['localpath'] = $strLocalpath; if (file_exists($strLocalpath)) { $arrOpenELECVersions[$strID]['valid'] = '1'; } } } if ($_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 ($_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 ] ], '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' ] ] ]; $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 ($_POST['createvm']) { if ($_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 ($_POST['shares'][0]['source']) { @mkdir($_POST['shares'][0]['source'], 0777, true); } if ($lv->domain_new($_POST)){ $reply = ['success' => true]; } else { $reply = ['error' => $lv->get_last_error()]; } } echo json_encode($reply); exit; } // update existing VM if ($_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) { foreach ($arrExistingConfig['usb'] as $arrExistingUSB) { if ($strNewUSBID == $arrExistingUSB['id']) continue 2; } list($strVendor,$strProduct) = explode(':', $strNewUSBID); // hot-attach usb file_put_contents('/tmp/hotattach.tmp', ""); exec("virsh attach-device ".escapeshellarg($uuid)." /tmp/hotattach.tmp --live 2>&1", $arrOutput, $intReturnCode); unlink('/tmp/hotattach.tmp'); if ($intReturnCode != 0) { $arrErrors[] = implode(' ', $arrOutput); } } // hot-detach any old usb devices foreach ($arrExistingConfig['usb'] as $arrExistingUSB) { if (!in_array($arrExistingUSB['id'], $arrNewUSBIDs)) { list($strVendor, $strProduct) = explode(':', $arrExistingUSB['id']); file_put_contents('/tmp/hotdetach.tmp', ""); exec("virsh detach-device ".escapeshellarg($uuid)." /tmp/hotdetach.tmp --live 2>&1", $arrOutput, $intReturnCode); unlink('/tmp/hotdetach.tmp'); if ($intReturnCode != 0) $arrErrors[] = implode(' ',$arrOutput); } } $reply = !$arrErrors ? ['success' => true] : ['error' => implode(', ',$arrErrors)]; echo json_encode($reply); exit; } // backup xml for existing domain in ram if ($dom && !$_POST['xmldesc']) { $oldName = $lv->domain_get_name($dom); $newName = $_POST['domain']['name']; $oldDir = $domain_cfg['DOMAINDIR'].$oldName; $newDir = $domain_cfg['DOMAINDIR'].$newdName; if ($oldName && $newName && is_dir($oldDir) && !is_dir($newDir)) { // mv domain/vmname folder if (rename($oldDir, $newDir)) { // replace all disk paths in xml foreach ($_POST['disk'] as &$arrDisk) { if ($arrDisk['new']) $arrDisk['new'] = str_replace($oldDir, $newDir, $arrDisk['new']); if ($arrDisk['image']) $arrDisk['image'] = str_replace($oldDir, $newDir, $arrDisk['image']); } } } } // construct updated config if ($_POST['xmldesc']) { // XML view $xml = $_POST['xmldesc']; } else { // form view if ($_POST['shares'][0]['source']) { @mkdir($_POST['shares'][0]['source'], 0777, true); } $arrExistingConfig = custom::createArray('domain',$strXML); $arrUpdatedConfig = custom::createArray('domain',$lv->config_to_xml($_POST)); array_update_recursive($arrExistingConfig, $arrUpdatedConfig); $arrConfig = array_replace_recursive($arrExistingConfig, $arrUpdatedConfig); $xml = custom::createXML('domain',$arrConfig)->saveXML(); } // delete and create the VM $lv->nvram_backup($uuid); $lv->domain_undefine($dom); $lv->nvram_restore($uuid); $new = $lv->domain_define($xml); if ($new) { $lv->domain_set_autostart($new, $newAutoStart); $reply = ['success' => true]; } else { // Failure -- try to restore existing VM $reply = ['error' => $lv->get_last_error()]; $old = $lv->domain_define($strXML); if ($old) $lv->domain_set_autostart($old, $oldAutoStart); } echo json_encode($reply); exit; } if ($_GET['uuid']) { // edit an existing VM $uuid = $_GET['uuid']; $dom = $lv->domain_get_domain_by_uuid($uuid); $boolRunning = $lv->domain_get_state($dom)=='running'; $strXML = $lv->domain_get_xml($dom); $boolNew = false; $arrConfig = array_replace_recursive($arrConfigDefaults, domain_to_config($uuid)); } else { // edit new VM $boolRunning = false; $strXML = ''; $boolNew = true; $arrConfig = $arrConfigDefaults; } if (array_key_exists($arrConfig['template']['openelec'], $arrLibreELECVersions)) { $arrConfigDefaults['disk'][0]['image'] = $arrLibreELECVersions[$arrConfig['template']['openelec']]['localpath']; } ?>
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:
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:

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 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.

$arrGPU) { $strLabel = ($i > 0) ? appendOrdinalSuffix($i + 1) : ''; ?>
Graphics Card:
Graphics ROM BIOS:

Graphics Card
If you wish to assign a graphics card to the VM, select it from this list.

Graphics ROM BIOS
If you wish to use a custom ROM BIOS for a Graphics card, specify one here.

1) { ?>

Additional devices can be added/removed by clicking the symbols to the left.

$arrAudio) { $strLabel = ($i > 0) ? appendOrdinalSuffix($i + 1) : ''; ?>
Sound Card:

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.

$arrNic) { $strLabel = ($i > 0) ? appendOrdinalSuffix($i + 1) : ''; ?>
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.

Other PCI Devices:
$arrDev) { $extra = ''; if (count(array_filter($arrConfig['pci'], function($arr) use ($arrDev) { return ($arr['id'] == $arrDev['id']); }))) { $extra .= ' checked="checked"'; } elseif (!in_array($arrDev['driver'], ['pci-stub', 'vfio-pci'])) { //$extra .= ' disabled="disabled"'; continue; } $intAvailableOtherPCIDevices++; ?>
None available"; } ?>

If you wish to assign any other PCI devices to your guest, you can select them from this list.


Click Create to return to the Virtual Machines page where your new VM will be created.