mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-07-31 23:01:21 +08:00
🎨 Sync 2026-07-29 20:42:56
This commit is contained in:
parent
fc21fd8c5e
commit
93288fe344
@ -5,8 +5,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ddns-go
|
||||
PKG_VERSION:=6.17.2
|
||||
PKG_RELEASE:=19
|
||||
PKG_VERSION:=6.17.3
|
||||
PKG_RELEASE:=20
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/jeessy2/ddns-go/tar.gz/v$(PKG_VERSION)?
|
||||
|
||||
@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
|
||||
-include $(dir $(lastword $(MAKEFILE_LIST)))../version.mk
|
||||
|
||||
PKG_VERSION:=$(or $(EASYTIER_VERSION),2.6.4)
|
||||
PKG_RELEASE:=11
|
||||
PKG_RELEASE:=12
|
||||
|
||||
LUCI_TITLE:=LuCI support for EasyTier
|
||||
LUCI_DEPENDS:=+kmod-tun +easytier +luci-compat
|
||||
|
||||
@ -181,9 +181,11 @@ check_config() {
|
||||
LOG_SIZE=\$(ls -l /tmp/easytier.log | awk '{print int(\$5/1024)}')
|
||||
if [ \$LOG_SIZE -gt 5120 ]; then
|
||||
tail -n 500 /tmp/easytier.log > /tmp/easytier.log.tmp
|
||||
mv /tmp/easytier.log.tmp /tmp/easytier.log
|
||||
: > /tmp/easytier.log
|
||||
cat /tmp/easytier.log.tmp >> /tmp/easytier.log
|
||||
rm -f /tmp/easytier.log.tmp
|
||||
fi
|
||||
sleep 300
|
||||
sleep 60
|
||||
done
|
||||
} &
|
||||
LOG_MANAGEMENT_PID=\$!
|
||||
@ -604,9 +606,11 @@ EOF
|
||||
LOG_SIZE=\$(ls -l /tmp/easytier.log | awk '{print int(\$5/1024)}')
|
||||
if [ \$LOG_SIZE -gt 5120 ]; then
|
||||
tail -n 500 /tmp/easytier.log > /tmp/easytier.log.tmp
|
||||
mv /tmp/easytier.log.tmp /tmp/easytier.log
|
||||
: > /tmp/easytier.log
|
||||
cat /tmp/easytier.log.tmp >> /tmp/easytier.log
|
||||
rm -f /tmp/easytier.log.tmp
|
||||
fi
|
||||
sleep 300
|
||||
sleep 60
|
||||
done
|
||||
} &
|
||||
LOG_MANAGEMENT_PID=\$!
|
||||
@ -753,9 +757,11 @@ start_web() {
|
||||
LOG_SIZE=\$(ls -l /tmp/easytierweb.log | awk '{print int(\$5/1024)}')
|
||||
if [ \$LOG_SIZE -gt 5120 ]; then
|
||||
tail -n 500 /tmp/easytierweb.log > /tmp/easytierweb.log.tmp
|
||||
mv /tmp/easytierweb.log.tmp /tmp/easytierweb.log
|
||||
: > /tmp/easytierweb.log
|
||||
cat /tmp/easytierweb.log.tmp >> /tmp/easytierweb.log
|
||||
rm -f /tmp/easytierweb.log.tmp
|
||||
fi
|
||||
sleep 300
|
||||
sleep 60
|
||||
done
|
||||
} &
|
||||
LOG_MANAGEMENT_PID=\$!
|
||||
|
||||
@ -4,7 +4,7 @@ LUCI_TITLE:=Web UI for QEMU Virtual Machine Simple (VMS)
|
||||
LUCI_DEPENDS:=@TARGET_x86_64 +qemu-vms +ttyd +usbutils +pciutils
|
||||
PKG_LICENSE:=GPLv3
|
||||
PKG_VERSION:=0.0.1
|
||||
PKG_RELEASE:=3
|
||||
PKG_RELEASE:=4
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
|
||||
@ -2,17 +2,30 @@
|
||||
'require form';
|
||||
'require view';
|
||||
'require uci';
|
||||
'require rpc';
|
||||
|
||||
var callListNetDrivers = rpc.declare({
|
||||
object: 'luci.qemu-vms',
|
||||
method: 'list_net_drivers',
|
||||
params: []
|
||||
});
|
||||
|
||||
function randomMac() {
|
||||
// OUI 52:54:00:31 is our virtual-cluster prefix (matches manually
|
||||
// assigned MACs used elsewhere in this config).
|
||||
var b1 = Math.floor(Math.random() * 256).toString(16).padStart(2, '0');
|
||||
var b2 = Math.floor(Math.random() * 256).toString(16).padStart(2, '0');
|
||||
return '52:54:00:31:%s:%s'.format(b1, b2);
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
render: function() {
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
callListNetDrivers(),
|
||||
uci.load('qemu-vms')
|
||||
]);
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
var drivers = data[0].drivers || [];
|
||||
var m, s;
|
||||
|
||||
m = new form.Map('qemu-vms', _('Virtual network segments'),
|
||||
@ -26,10 +39,9 @@ return view.extend({
|
||||
s.nodescriptions = true;
|
||||
|
||||
var mac = s.option(form.Value, 'mac', _('MAC address'));
|
||||
//mac.placeholder = _('leave empty to auto-generate 52:54:00:31:xx:xx');
|
||||
mac.validate = function(section_id, value) {
|
||||
if (!value)
|
||||
return true; // filled in by write() below if left empty
|
||||
return true;
|
||||
if (!/^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$/.test(value))
|
||||
return _('Invalid MAC address format (expected xx:xx:xx:xx:xx:xx)');
|
||||
return true;
|
||||
@ -41,7 +53,6 @@ return view.extend({
|
||||
};
|
||||
mac.rmempty = false;
|
||||
mac.value(randomMac(), _('Automatically assigned'));
|
||||
//mac.editable = true;
|
||||
|
||||
var ifname = s.option(form.Value, 'ifname', _('tap ifname'));
|
||||
ifname.placeholder = 'tap0';
|
||||
@ -55,6 +66,20 @@ return view.extend({
|
||||
var bridge = s.option(form.Value, 'bridge', _('Bridge (optional)'));
|
||||
bridge.placeholder = _('leave empty to keep the interface standalone');
|
||||
|
||||
// --- Поле driver с динамическим списком ---
|
||||
var driver = s.option(form.ListValue, 'driver', _('Network driver'));
|
||||
// Добавляем опции из загруженного списка
|
||||
if (drivers.length) {
|
||||
drivers.forEach(function(d) {
|
||||
driver.value(d, d);
|
||||
});
|
||||
} else {
|
||||
driver.value('', _('No drivers found'));
|
||||
}
|
||||
driver.default = 'virtio-net-pci';
|
||||
driver.rmempty = true;
|
||||
driver.description = _('Choose the emulated network card model. If empty, virtio-net-pci is used.');
|
||||
|
||||
return m.render();
|
||||
}
|
||||
});
|
||||
|
||||
@ -12,6 +12,18 @@ var callStatus = rpc.declare({
|
||||
params: ['vm_name']
|
||||
});
|
||||
|
||||
var callListMachines = rpc.declare({
|
||||
object: 'luci.qemu-vms',
|
||||
method: 'list_machines',
|
||||
params: []
|
||||
});
|
||||
|
||||
var callListCpus = rpc.declare({
|
||||
object: 'luci.qemu-vms',
|
||||
method: 'list_cpus',
|
||||
params: []
|
||||
});
|
||||
|
||||
var callStart = rpc.declare({ object: 'luci.qemu-vms', method: 'start', params: ['vm_name'] });
|
||||
var callStop = rpc.declare({ object: 'luci.qemu-vms', method: 'stop', params: ['vm_name'] });
|
||||
var callRestart = rpc.declare({ object: 'luci.qemu-vms', method: 'restart', params: ['vm_name'] });
|
||||
@ -36,6 +48,83 @@ function widenModal() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ДЛЯ ПОСТРОЕНИЯ СЕЛЕКТОВ -----
|
||||
|
||||
function buildMachineSelect(vm, machines) {
|
||||
var select = E('select', { 'class': 'cbi-input-select', 'id': 'edit-machine' });
|
||||
machines.forEach(function(m) {
|
||||
var opt = E('option', { 'value': m });
|
||||
if (m === (vm.machine || 'q35')) opt.selected = true;
|
||||
opt.textContent = m;
|
||||
select.appendChild(opt);
|
||||
});
|
||||
if (vm.machine && !machines.includes(vm.machine)) {
|
||||
var opt = E('option', { 'value': vm.machine, 'selected': true });
|
||||
opt.textContent = vm.machine + ' (custom)';
|
||||
select.appendChild(opt);
|
||||
}
|
||||
return select;
|
||||
}
|
||||
|
||||
function buildCpuSelect(vm, cpus) {
|
||||
var baseCpus = ['host', 'max', 'qemu64', 'Haswell', 'Skylake-Client'];
|
||||
var select = E('select', { 'class': 'cbi-input-select', 'id': 'edit-cpu' });
|
||||
|
||||
baseCpus.forEach(function(c) {
|
||||
var opt = E('option', { 'value': c });
|
||||
if (c === (vm.cpu || 'host')) opt.selected = true;
|
||||
opt.textContent = c;
|
||||
select.appendChild(opt);
|
||||
});
|
||||
|
||||
cpus.forEach(function(c) {
|
||||
if (baseCpus.indexOf(c) === -1) {
|
||||
var opt = E('option', { 'value': c });
|
||||
if (c === vm.cpu) opt.selected = true;
|
||||
opt.textContent = c;
|
||||
select.appendChild(opt);
|
||||
}
|
||||
});
|
||||
|
||||
if (vm.cpu && !baseCpus.includes(vm.cpu) && !cpus.includes(vm.cpu)) {
|
||||
var opt = E('option', { 'value': vm.cpu, 'selected': true });
|
||||
opt.textContent = vm.cpu + ' (custom)';
|
||||
select.appendChild(opt);
|
||||
}
|
||||
return select;
|
||||
}
|
||||
|
||||
function buildMachineSelectFallback(vm) {
|
||||
var select = E('select', { 'class': 'cbi-input-select', 'id': 'edit-machine' }, [
|
||||
E('option', { 'value': 'q35', 'selected': (vm.machine || 'q35') === 'q35' || null }, 'q35 (recommended)'),
|
||||
E('option', { 'value': 'pc', 'selected': vm.machine === 'pc' || null }, 'pc (legacy)')
|
||||
]);
|
||||
if (vm.machine && vm.machine !== 'q35' && vm.machine !== 'pc') {
|
||||
var opt = E('option', { 'value': vm.machine, 'selected': true });
|
||||
opt.textContent = vm.machine + ' (custom)';
|
||||
select.appendChild(opt);
|
||||
}
|
||||
return select;
|
||||
}
|
||||
|
||||
function buildCpuSelectFallback(vm) {
|
||||
var baseCpus = ['host', 'max', 'qemu64', 'Haswell', 'Skylake-Client'];
|
||||
var select = E('select', { 'class': 'cbi-input-select', 'id': 'edit-cpu' });
|
||||
baseCpus.forEach(function(c) {
|
||||
var opt = E('option', { 'value': c });
|
||||
if (c === (vm.cpu || 'host')) opt.selected = true;
|
||||
opt.textContent = c;
|
||||
select.appendChild(opt);
|
||||
});
|
||||
if (vm.cpu && !baseCpus.includes(vm.cpu)) {
|
||||
var opt = E('option', { 'value': vm.cpu, 'selected': true });
|
||||
opt.textContent = vm.cpu + ' (custom)';
|
||||
select.appendChild(opt);
|
||||
}
|
||||
return select;
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
@ -283,233 +372,166 @@ return view.extend({
|
||||
},
|
||||
|
||||
handleEdit: function(name, ev) {
|
||||
var self = this;
|
||||
var isNew = !name;
|
||||
var existingNameForReset = isNew ? null : name;
|
||||
var vm = isNew ? {} : (uci.get('qemu-vms', name) || {});
|
||||
|
||||
var allNetworks = uci.sections('qemu-vms', 'network').map(function(s) { return s['.name']; });
|
||||
var allPci = uci.sections('qemu-vms', 'pci-passthrough').map(function(s) { return s['.name']; });
|
||||
var allUsb = uci.sections('qemu-vms', 'usb-passthrough').map(function(s) { return s['.name']; });
|
||||
var attachedNetworks = [].concat(vm.network || []);
|
||||
var attachedUsb = [].concat(vm.usb_passthrough || []);
|
||||
function showEditModal(machineSelect, cpuSelect) {
|
||||
var allNetworks = uci.sections('qemu-vms', 'network').map(function(s) { return s['.name']; });
|
||||
var allPci = uci.sections('qemu-vms', 'pci-passthrough').map(function(s) { return s['.name']; });
|
||||
var allUsb = uci.sections('qemu-vms', 'usb-passthrough').map(function(s) { return s['.name']; });
|
||||
var attachedNetworks = [].concat(vm.network || []);
|
||||
var attachedUsb = [].concat(vm.usb_passthrough || []);
|
||||
|
||||
var nameInput = E('input', {
|
||||
'class': 'cbi-input-text',
|
||||
'id': 'edit-name',
|
||||
'value': name || '',
|
||||
'disabled': isNew ? null : true,
|
||||
'placeholder': isNew ? _('e.g. ap, test, vlan') : null
|
||||
});
|
||||
var nameInput = E('input', { 'class': 'cbi-input-text', 'id': 'edit-name', 'value': name || '', 'disabled': isNew ? null : true, 'placeholder': isNew ? _('e.g. ap, test, vlan') : null });
|
||||
var enabledInput = E('input', { 'type': 'checkbox', 'id': 'edit-enabled', 'checked': (vm.enabled !== '0') || null });
|
||||
var memoryInput = E('input', { 'class': 'cbi-input-text', 'id': 'edit-memory', 'type': 'number', 'min': '32', 'step': '32', 'value': vm.memory || '128' });
|
||||
var smpInput = E('input', { 'class': 'cbi-input-text', 'id': 'edit-smp', 'type': 'number', 'min': '1', 'max': '16', 'step': '1', 'value': vm.smp || '1' });
|
||||
// cpuSelect и machineSelect переданы как аргументы
|
||||
|
||||
var enabledInput = E('input', {
|
||||
'type': 'checkbox',
|
||||
'id': 'edit-enabled',
|
||||
'checked': (vm.enabled !== '0') || null
|
||||
});
|
||||
|
||||
var memoryInput = E('input', {
|
||||
'class': 'cbi-input-text',
|
||||
'id': 'edit-memory',
|
||||
'type': 'number',
|
||||
'min': '32',
|
||||
'step': '32',
|
||||
'value': vm.memory || '128'
|
||||
});
|
||||
var smpInput = E('input', {
|
||||
'class': 'cbi-input-text',
|
||||
'id': 'edit-smp',
|
||||
'type': 'number',
|
||||
'min': '1',
|
||||
'max': '16',
|
||||
'step': '1',
|
||||
'value': vm.smp || '1'
|
||||
});
|
||||
|
||||
var cpuSelect = E('select', { 'class': 'cbi-input-select', 'id': 'edit-cpu' }, [
|
||||
E('option', { 'value': 'host', 'selected': (vm.cpu || 'host') === 'host' || null }, 'host'),
|
||||
E('option', { 'value': 'max', 'selected': vm.cpu === 'max' || null }, 'max'),
|
||||
E('option', { 'value': 'qemu64', 'selected': vm.cpu === 'qemu64' || null }, 'qemu64'),
|
||||
E('option', { 'value': 'Haswell', 'selected': vm.cpu === 'Haswell' || null }, 'Haswell'),
|
||||
E('option', { 'value': 'Skylake-Client', 'selected': vm.cpu === 'Skylake-Client' || null }, 'Skylake-Client')
|
||||
]);
|
||||
|
||||
var imageInput = E('input', {
|
||||
'class': 'cbi-input-text',
|
||||
'id': 'edit-image',
|
||||
'value': vm.image || '',
|
||||
'placeholder': '/mnt/disk/vm/name.img'
|
||||
});
|
||||
|
||||
var diskBusSelect = E('select', { 'class': 'cbi-input-select', 'id': 'edit-disk-bus' }, [
|
||||
E('option', { 'value': 'virtio', 'selected': (vm.disk_bus || 'virtio') === 'virtio' || null }, 'virtio (default, best performance, needs guest drivers)'),
|
||||
E('option', { 'value': 'ide', 'selected': vm.disk_bus === 'ide' || null }, 'IDE (widest compatibility \u2014 old Windows/DOS/legacy kernels)'),
|
||||
E('option', { 'value': 'sata', 'selected': vm.disk_bus === 'sata' || null }, 'SATA/AHCI'),
|
||||
E('option', { 'value': 'scsi', 'selected': vm.disk_bus === 'scsi' || null }, 'virtio-SCSI')
|
||||
]);
|
||||
|
||||
var cdromInput = E('input', {
|
||||
'class': 'cbi-input-text',
|
||||
'id': 'edit-cdrom',
|
||||
'value': vm.cdrom || '',
|
||||
'placeholder': _('optional, e.g. /mnt/disk/iso/installer.iso')
|
||||
});
|
||||
|
||||
var existingCustomArgs = [].concat(vm.custom_arg || []);
|
||||
var customArgsInput = E('textarea', {
|
||||
'class': 'cbi-input-textarea',
|
||||
'id': 'edit-custom-args',
|
||||
'style': 'width: 100%; height: 6em; font-family: monospace;',
|
||||
'placeholder': _('one raw QEMU argument per line, e.g.\n-device usb-tablet\n-boot order=d')
|
||||
}, existingCustomArgs.join('\n'));
|
||||
|
||||
var displaySelect = E('select', { 'class': 'cbi-input-select', 'id': 'edit-display' }, [
|
||||
E('option', { 'value': 'serial', 'selected': (vm.display_type || 'serial') === 'serial' || null }, _('Serial console (ttyd)')),
|
||||
E('option', { 'value': 'vnc', 'selected': vm.display_type === 'vnc' || null }, _('VNC (noVNC)'))
|
||||
]);
|
||||
|
||||
var usedPorts = uci.sections('qemu-vms', 'vm')
|
||||
.map(function(s) { return parseInt(s.vnc_port, 10); })
|
||||
.filter(function(p) { return !isNaN(p); });
|
||||
var suggestedPort = usedPorts.length ? Math.max.apply(null, usedPorts) + 1 : 5901;
|
||||
|
||||
var vncPortInput = E('input', {
|
||||
'class': 'cbi-input-text',
|
||||
'id': 'edit-vnc-port',
|
||||
'type': 'number',
|
||||
'min': '5900',
|
||||
'value': vm.vnc_port || suggestedPort
|
||||
});
|
||||
|
||||
var vncPortRow = E('div', { 'class': 'cbi-value', 'id': 'edit-vnc-port-row' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('VNC port (RFB, 5900+)')),
|
||||
E('div', { 'class': 'cbi-value-field' }, [
|
||||
vncPortInput,
|
||||
E('p', { 'class': 'cbi-value-description' },
|
||||
_('Serves noVNC over websocket on port %s.').format('+1000'))
|
||||
])
|
||||
]);
|
||||
|
||||
displaySelect.addEventListener('change', function() {
|
||||
vncPortRow.style.display = (displaySelect.value === 'vnc') ? '' : 'none';
|
||||
});
|
||||
vncPortRow.style.display = ((vm.display_type || 'serial') === 'vnc') ? '' : 'none';
|
||||
|
||||
var pciSelect = E('select', { 'class': 'cbi-input-select', 'id': 'edit-pci' }, [
|
||||
E('option', { 'value': '', 'selected': !vm.pci_passthrough || null }, _('-- none --'))
|
||||
].concat(allPci.map(function(p) {
|
||||
return E('option', { 'value': p, 'selected': vm.pci_passthrough === p || null }, p);
|
||||
})));
|
||||
|
||||
var networkChecks = allNetworks.map(function(net) {
|
||||
return E('div', {}, [
|
||||
E('label', {}, [
|
||||
E('input', {
|
||||
'type': 'checkbox',
|
||||
'class': 'edit-network-check',
|
||||
'value': net,
|
||||
'checked': attachedNetworks.indexOf(net) !== -1 || null
|
||||
}),
|
||||
' ' + net
|
||||
])
|
||||
var imageInput = E('input', { 'class': 'cbi-input-text', 'id': 'edit-image', 'value': vm.image || '', 'placeholder': '/mnt/disk/vm/name.img' });
|
||||
var diskBusSelect = E('select', { 'class': 'cbi-input-select', 'id': 'edit-disk-bus' }, [
|
||||
E('option', { 'value': 'virtio', 'selected': (vm.disk_bus || 'virtio') === 'virtio' || null }, 'virtio (default)'),
|
||||
E('option', { 'value': 'ide', 'selected': vm.disk_bus === 'ide' || null }, 'IDE'),
|
||||
E('option', { 'value': 'sata', 'selected': vm.disk_bus === 'sata' || null }, 'SATA/AHCI'),
|
||||
E('option', { 'value': 'scsi', 'selected': vm.disk_bus === 'scsi' || null }, 'virtio-SCSI')
|
||||
]);
|
||||
});
|
||||
var cdromInput = E('input', { 'class': 'cbi-input-text', 'id': 'edit-cdrom', 'value': vm.cdrom || '', 'placeholder': _('optional, e.g. /mnt/disk/iso/installer.iso') });
|
||||
var customArgsInput = E('textarea', { 'class': 'cbi-input-textarea', 'id': 'edit-custom-args', 'style': 'width: 100%; height: 6em; font-family: monospace;', 'placeholder': _('one raw QEMU argument per line') }, ([].concat(vm.custom_arg || [])).join('\n'));
|
||||
|
||||
if (!allNetworks.length)
|
||||
networkChecks = [E('em', {}, _('No networks defined yet \u2014 add some on the Networks tab.'))];
|
||||
|
||||
var usbSelect = E('div', {
|
||||
'style': 'display: flex; flex-wrap: wrap; gap: 0.3em 1.2em;'
|
||||
}, allUsb.map(function(usb) {
|
||||
return E('label', { 'style': 'white-space: nowrap; font-weight: normal;' }, [
|
||||
E('input', {
|
||||
'type': 'checkbox',
|
||||
'class': 'edit-usb-check',
|
||||
'value': usb,
|
||||
'checked': attachedUsb.indexOf(usb) !== -1 || null
|
||||
}),
|
||||
' ' + usb
|
||||
var displaySelect = E('select', { 'class': 'cbi-input-select', 'id': 'edit-display' }, [
|
||||
E('option', { 'value': 'serial', 'selected': (vm.display_type || 'serial') === 'serial' || null }, _('Serial console (ttyd)')),
|
||||
E('option', { 'value': 'vnc', 'selected': vm.display_type === 'vnc' || null }, _('VNC (noVNC)'))
|
||||
]);
|
||||
}));
|
||||
|
||||
if (!allUsb.length)
|
||||
usbSelect = E('em', {}, _('No USB passthrough sections defined yet \u2014 add some on the Hardware passthrough tab.'));
|
||||
|
||||
|
||||
ui.showModal(isNew ? _('Add VM') : _('Edit %s').format(name), [
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Name')),
|
||||
E('div', { 'class': 'cbi-value-field' }, nameInput)
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Enabled')),
|
||||
E('div', { 'class': 'cbi-value-field' }, enabledInput)
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Memory (MB)')),
|
||||
E('div', { 'class': 'cbi-value-field' }, memoryInput)
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('SMP (vCPUs)')),
|
||||
E('div', { 'class': 'cbi-value-field' }, smpInput)
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('CPU model')),
|
||||
E('div', { 'class': 'cbi-value-field' }, cpuSelect)
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Disk image path')),
|
||||
E('div', { 'class': 'cbi-value-field' }, imageInput)
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Disk bus')),
|
||||
E('div', { 'class': 'cbi-value-field' }, diskBusSelect)
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('CD-ROM (ISO)')),
|
||||
E('div', { 'class': 'cbi-value-field' }, cdromInput)
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Custom QEMU arguments')),
|
||||
var usedPorts = uci.sections('qemu-vms', 'vm').map(function(s) { return parseInt(s.vnc_port, 10); }).filter(function(p) { return !isNaN(p); });
|
||||
var suggestedPort = usedPorts.length ? Math.max.apply(null, usedPorts) + 1 : 5901;
|
||||
var vncPortInput = E('input', { 'class': 'cbi-input-text', 'id': 'edit-vnc-port', 'type': 'number', 'min': '5900', 'value': vm.vnc_port || suggestedPort });
|
||||
var vncPortRow = E('div', { 'class': 'cbi-value', 'id': 'edit-vnc-port-row' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('VNC port (RFB, 5900+)')),
|
||||
E('div', { 'class': 'cbi-value-field' }, [
|
||||
customArgsInput,
|
||||
E('p', { 'class': 'cbi-value-description' },
|
||||
_('Escape hatch for flags with no dedicated field \u2014 appended verbatim to the qemu-system-x86_64 command line.'))
|
||||
vncPortInput,
|
||||
E('p', { 'class': 'cbi-value-description' }, _('Serves noVNC over websocket on port %s.').format('+1000'))
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Console type')),
|
||||
E('div', { 'class': 'cbi-value-field' }, displaySelect)
|
||||
]),
|
||||
vncPortRow,
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('PCI passthrough')),
|
||||
E('div', { 'class': 'cbi-value-field' }, pciSelect)
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('USB passthrough')),
|
||||
E('div', { 'class': 'cbi-value-field' }, usbSelect)
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Attached networks')),
|
||||
E('div', { 'class': 'cbi-value-field' }, networkChecks)
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('p', { 'class': 'cbi-value-description' }, _('Changes will take effect after restarting the VM.'))
|
||||
]),
|
||||
E('div', { 'class': 'right', 'style': 'margin-top: 1em' }, [
|
||||
E('button', { 'class': 'btn', 'click': ui.hideModal }, _('Cancel')),
|
||||
' ',
|
||||
E('button', {
|
||||
'class': 'btn',
|
||||
'click': ui.createHandlerFn(this, 'handleEdit', existingNameForReset)
|
||||
}, _('Reset')),
|
||||
' ',
|
||||
E('button', {
|
||||
'class': 'btn cbi-button-positive',
|
||||
'click': ui.createHandlerFn(this, 'saveEdit', isNew ? null : name)
|
||||
}, _('Save'))
|
||||
])
|
||||
]);
|
||||
]);
|
||||
displaySelect.addEventListener('change', function() {
|
||||
vncPortRow.style.display = (displaySelect.value === 'vnc') ? '' : 'none';
|
||||
});
|
||||
vncPortRow.style.display = ((vm.display_type || 'serial') === 'vnc') ? '' : 'none';
|
||||
|
||||
var pciSelect = E('select', { 'class': 'cbi-input-select', 'id': 'edit-pci' }, [
|
||||
E('option', { 'value': '', 'selected': !vm.pci_passthrough || null }, _('-- none --'))
|
||||
].concat(allPci.map(function(p) {
|
||||
return E('option', { 'value': p, 'selected': vm.pci_passthrough === p || null }, p);
|
||||
})));
|
||||
|
||||
var networkChecks = allNetworks.map(function(net) {
|
||||
return E('div', {}, [
|
||||
E('label', {}, [
|
||||
E('input', { 'type': 'checkbox', 'class': 'edit-network-check', 'value': net, 'checked': attachedNetworks.indexOf(net) !== -1 || null }),
|
||||
' ' + net
|
||||
])
|
||||
]);
|
||||
});
|
||||
if (!allNetworks.length) networkChecks = [E('em', {}, _('No networks defined yet'))];
|
||||
|
||||
var usbSelect = E('div', { 'style': 'display: flex; flex-wrap: wrap; gap: 0.3em 1.2em;' }, allUsb.map(function(usb) {
|
||||
return E('label', { 'style': 'white-space: nowrap; font-weight: normal;' }, [
|
||||
E('input', { 'type': 'checkbox', 'class': 'edit-usb-check', 'value': usb, 'checked': attachedUsb.indexOf(usb) !== -1 || null }),
|
||||
' ' + usb
|
||||
]);
|
||||
}));
|
||||
if (!allUsb.length) usbSelect = E('em', {}, _('No USB passthrough sections defined yet'));
|
||||
|
||||
ui.showModal(isNew ? _('Add VM') : _('Edit %s').format(name), [
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Name')),
|
||||
E('div', { 'class': 'cbi-value-field' }, nameInput)
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Enabled')),
|
||||
E('div', { 'class': 'cbi-value-field' }, enabledInput)
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Memory (MB)')),
|
||||
E('div', { 'class': 'cbi-value-field' }, memoryInput)
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('SMP (vCPUs)')),
|
||||
E('div', { 'class': 'cbi-value-field' }, smpInput)
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('CPU model')),
|
||||
E('div', { 'class': 'cbi-value-field' }, cpuSelect)
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Machine type')),
|
||||
E('div', { 'class': 'cbi-value-field' }, machineSelect)
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Disk image path')),
|
||||
E('div', { 'class': 'cbi-value-field' }, imageInput)
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Disk bus')),
|
||||
E('div', { 'class': 'cbi-value-field' }, diskBusSelect)
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('CD-ROM (ISO)')),
|
||||
E('div', { 'class': 'cbi-value-field' }, cdromInput)
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Custom QEMU arguments')),
|
||||
E('div', { 'class': 'cbi-value-field' }, [
|
||||
customArgsInput,
|
||||
E('p', { 'class': 'cbi-value-description' }, _('Escape hatch for flags with no dedicated field'))
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Console type')),
|
||||
E('div', { 'class': 'cbi-value-field' }, displaySelect)
|
||||
]),
|
||||
vncPortRow,
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('PCI passthrough')),
|
||||
E('div', { 'class': 'cbi-value-field' }, pciSelect)
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('USB passthrough')),
|
||||
E('div', { 'class': 'cbi-value-field' }, usbSelect)
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title' }, _('Attached networks')),
|
||||
E('div', { 'class': 'cbi-value-field' }, networkChecks)
|
||||
]),
|
||||
E('p', { 'class': 'cbi-section-descr', 'style': 'margin-top: 1em;' }, _('Changes will take effect after restarting the VM.')),
|
||||
E('div', { 'class': 'right', 'style': 'margin-top: 1em' }, [
|
||||
E('button', { 'class': 'btn', 'click': ui.hideModal }, _('Cancel')),
|
||||
' ',
|
||||
E('button', { 'class': 'btn', 'click': ui.createHandlerFn(self, 'handleEdit', existingNameForReset) }, _('Reset')),
|
||||
' ',
|
||||
E('button', { 'class': 'btn cbi-button-positive', 'click': ui.createHandlerFn(self, 'saveEdit', isNew ? null : name) }, _('Save'))
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
Promise.all([callListMachines(), callListCpus()])
|
||||
.then(function(results) {
|
||||
var machines = results[0].machines || [];
|
||||
var cpus = results[1].cpus || [];
|
||||
|
||||
var machineSelect = buildMachineSelect(vm, machines);
|
||||
var cpuSelect = buildCpuSelect(vm, cpus);
|
||||
|
||||
showEditModal(machineSelect, cpuSelect);
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.warn('Failed to load machine/CPU lists, using fallback:', err.message);
|
||||
var machineSelect = buildMachineSelectFallback(vm);
|
||||
var cpuSelect = buildCpuSelectFallback(vm);
|
||||
showEditModal(machineSelect, cpuSelect);
|
||||
});
|
||||
},
|
||||
|
||||
saveEdit: function(existingName, ev) {
|
||||
@ -555,6 +577,7 @@ return view.extend({
|
||||
uci.set('qemu-vms', name, 'memory', String(memory));
|
||||
uci.set('qemu-vms', name, 'smp', String(smp));
|
||||
uci.set('qemu-vms', name, 'cpu', document.getElementById('edit-cpu').value);
|
||||
uci.set('qemu-vms', name, 'machine', document.getElementById('edit-machine').value || 'q35');
|
||||
uci.set('qemu-vms', name, 'image', document.getElementById('edit-image').value);
|
||||
uci.set('qemu-vms', name, 'disk_bus', document.getElementById('edit-disk-bus').value);
|
||||
|
||||
@ -605,8 +628,8 @@ return view.extend({
|
||||
// .map(function(el) { return el.value; });
|
||||
//uci.set('qemu-vms', name, 'network', nets);
|
||||
|
||||
var newUsb = Array.prototype.slice.call(document.querySelectorAll('.edit-usb-check:checked'))
|
||||
.map(function(el) { return el.value; });
|
||||
//var newUsb = Array.prototype.slice.call(document.querySelectorAll('.edit-usb-check:checked'))
|
||||
// .map(function(el) { return el.value; });
|
||||
|
||||
var newNets = Array.prototype.slice.call(document.querySelectorAll('.edit-network-check:checked'))
|
||||
.map(function(el) { return el.value; });
|
||||
@ -635,3 +658,4 @@ return view.extend({
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -309,7 +309,10 @@ case "$1" in
|
||||
"readlog": { "vm_name": "", "lines": 0 },
|
||||
"console_open": { "vm_name": "" },
|
||||
"console_close": { "vm_name": "" },
|
||||
"list_cpus": { },
|
||||
"list_hardware": { },
|
||||
"list_machines": { },
|
||||
"list_net_drivers": { },
|
||||
"delete_vm": { "vm_name": "" }
|
||||
}'
|
||||
;;
|
||||
@ -366,6 +369,39 @@ case "$1" in
|
||||
list_hardware)
|
||||
list_hardware
|
||||
;;
|
||||
list_machines)
|
||||
machines=$(/usr/bin/qemu-system-x86_64 -M help 2>/dev/null | grep -E '^[a-zA-Z0-9_-]+' | grep -v Supported | awk '{print $1}')
|
||||
json_init
|
||||
json_add_array "machines"
|
||||
for m in $machines; do
|
||||
json_add_string "" "$m"
|
||||
done
|
||||
json_close_array
|
||||
json_dump
|
||||
;;
|
||||
list_cpus)
|
||||
cpus=$(/usr/bin/qemu-system-x86_64 -cpu help 2>/dev/null | \
|
||||
awk '/^[[:space:]]*$/ || /Recognized/ {exit} {print $1}')
|
||||
json_init
|
||||
json_add_array "cpus"
|
||||
for c in $cpus; do
|
||||
json_add_string "" "$c"
|
||||
done
|
||||
json_close_array
|
||||
json_dump
|
||||
;;
|
||||
list_net_drivers)
|
||||
drivers=$(/usr/bin/qemu-system-x86_64 -device help 2>/dev/null | \
|
||||
awk '/^Network devices:/{flag=1; next} /^[[:space:]]*$/{if(flag) exit} flag && /^name "[^"]+"/{print $2}' | \
|
||||
sed -e 's/"//g' -e 's/,//g' )
|
||||
json_init
|
||||
json_add_array "drivers"
|
||||
for d in $drivers; do
|
||||
json_add_string "" "$d"
|
||||
done
|
||||
json_close_array
|
||||
json_dump
|
||||
;;
|
||||
delete_vm)
|
||||
stop_vm_instance "$vm_name"
|
||||
console_close "$vm_name" >/dev/null
|
||||
|
||||
@ -19,6 +19,9 @@
|
||||
"restart",
|
||||
"console_open",
|
||||
"console_close",
|
||||
"list_machines",
|
||||
"list_cpus",
|
||||
"list_net_drivers",
|
||||
"delete_vm"
|
||||
]
|
||||
},
|
||||
|
||||
@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=qemu-vms
|
||||
PKG_VERSION:=0.0.1
|
||||
PKG_RELEASE:=3
|
||||
PKG_RELEASE:=4
|
||||
PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
@ -56,6 +56,7 @@ _add_net() {
|
||||
config_get mac "$netcfg" mac
|
||||
config_get ifname "$netcfg" ifname
|
||||
config_get bridge "$netcfg" bridge
|
||||
config_get driver "$netcfg" driver "virtio-net-pci"
|
||||
|
||||
local id="net${idx}"
|
||||
|
||||
@ -78,7 +79,7 @@ _add_net() {
|
||||
|
||||
local mac_opt=""
|
||||
[ -n "$mac" ] && mac_opt=",mac=$mac"
|
||||
device_opts="$device_opts -device virtio-net-pci${mac_opt},netdev=$id"
|
||||
device_opts="$device_opts -device ${driver}${mac_opt},netdev=$id"
|
||||
idx=$((idx + 1))
|
||||
}
|
||||
|
||||
@ -101,25 +102,57 @@ _add_usb() {
|
||||
usb_needed=1
|
||||
}
|
||||
|
||||
#_unbind_usb_host() {
|
||||
# local usbcfg="$1"
|
||||
# local vendor_id product_id
|
||||
# config_get vendor_id "$usbcfg" vendor_id
|
||||
# config_get product_id "$usbcfg" product_id
|
||||
# [ -n "$vendor_id" ] && [ -n "$product_id" ] || return 0
|
||||
# for dev in /sys/bus/usb/devices/*; do
|
||||
# if [ -f "$dev/idVendor" ] && [ -f "$dev/idProduct" ]; then
|
||||
# local v=$(cat "$dev/idVendor") p=$(cat "$dev/idProduct")
|
||||
# if [ "$v" = "$vendor_id" ] && [ "$p" = "$product_id" ]; then
|
||||
# if [ -L "$dev/driver" ]; then
|
||||
# local driver_path=$(readlink -f "$dev/driver")
|
||||
# local driver_name=$(basename "$driver_path")
|
||||
# echo -n "$(basename "$dev")" > "$driver_path/unbind" 2>/dev/null
|
||||
# fi
|
||||
# fi
|
||||
# fi
|
||||
# done
|
||||
#}
|
||||
|
||||
_unbind_usb_host() {
|
||||
local usbcfg="$1"
|
||||
local vendor_id product_id
|
||||
config_get vendor_id "$usbcfg" vendor_id
|
||||
config_get product_id "$usbcfg" product_id
|
||||
[ -n "$vendor_id" ] && [ -n "$product_id" ] || return 0
|
||||
for dev in /sys/bus/usb/devices/*; do
|
||||
if [ -f "$dev/idVendor" ] && [ -f "$dev/idProduct" ]; then
|
||||
local v=$(cat "$dev/idVendor") p=$(cat "$dev/idProduct")
|
||||
if [ "$v" = "$vendor_id" ] && [ "$p" = "$product_id" ]; then
|
||||
if [ -L "$dev/driver" ]; then
|
||||
local driver_path=$(readlink -f "$dev/driver")
|
||||
local driver_name=$(basename "$driver_path")
|
||||
echo -n "$(basename "$dev")" > "$driver_path/unbind" 2>/dev/null
|
||||
host_log "$cfg" "USB: unbound $(basename "$dev") from $driver_name"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
local usbcfg="$1"
|
||||
local vendor_id product_id
|
||||
config_get vendor_id "$usbcfg" vendor_id
|
||||
config_get product_id "$usbcfg" product_id
|
||||
logger -t qemu-vms "DEBUG unbind: usbcfg=$usbcfg vendor=$vendor_id product=$product_id"
|
||||
[ -n "$vendor_id" ] && [ -n "$product_id" ] || return 0
|
||||
for dev in /sys/bus/usb/devices/*; do
|
||||
if [ -f "$dev/idVendor" ] && [ -f "$dev/idProduct" ]; then
|
||||
local v=$(cat "$dev/idVendor") p=$(cat "$dev/idProduct")
|
||||
if [ "$v" = "$vendor_id" ] && [ "$p" = "$product_id" ]; then
|
||||
local devname=$(basename "$dev")
|
||||
if [ -L "$dev/driver" ]; then
|
||||
local driver_path=$(readlink -f "$dev/driver")
|
||||
echo -n "$devname" > "$driver_path/unbind" 2>/dev/null
|
||||
host_log "$cfg" "USB: unbound $devname from $(basename "$driver_path")"
|
||||
fi
|
||||
# interfaces are SIBLINGS of $dev, not children -
|
||||
# glob at the /sys/bus/usb/devices/ level, matching "$devname:*"
|
||||
for iface in /sys/bus/usb/devices/"$devname":*; do
|
||||
[ -e "$iface" ] || continue
|
||||
if [ -L "$iface/driver" ]; then
|
||||
local ifname=$(basename "$iface")
|
||||
local idriver_path=$(readlink -f "$iface/driver")
|
||||
echo -n "$ifname" > "$idriver_path/unbind" 2>/dev/null
|
||||
host_log "$cfg" "USB: unbound $ifname from $(basename "$idriver_path")"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
build_customargs() {
|
||||
@ -209,6 +242,7 @@ start_vm() {
|
||||
config_get pci_passthrough "$cfg" pci_passthrough
|
||||
config_get display_type "$cfg" display_type "serial"
|
||||
config_get vnc_port "$cfg" vnc_port
|
||||
config_get machine "$cfg" machine "q35"
|
||||
|
||||
local console_sock="/var/run/qemu-$cfg.sock"
|
||||
local console_log="/var/log/qemu-$cfg-console.log"
|
||||
@ -242,7 +276,6 @@ start_vm() {
|
||||
fi
|
||||
fi
|
||||
|
||||
# Test version PCI Pass
|
||||
#local pci_id="" pci_opts=""
|
||||
#if [ -n "$pci_passthrough" ]; then
|
||||
# config_get pci_id "$pci_passthrough" pci_id
|
||||
@ -299,7 +332,7 @@ start_vm() {
|
||||
$disk_controller_opts $disk_opts \
|
||||
$device_opts $netdev_opts $pci_opts $cdrom_opts $usb_controller_opts $usb_opts $vnc_opts $custom_opts \
|
||||
-device virtio-rng-pci \
|
||||
-machine q35 \
|
||||
-machine $machine \
|
||||
-chardev socket,id=con0,path="$console_sock",server=on,wait=off,logfile="$console_log",logappend=on \
|
||||
-serial chardev:con0 \
|
||||
-chardev socket,id=qmp0,path="$qmp_sock",server=on,wait=off \
|
||||
|
||||
@ -5,8 +5,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=sing-box
|
||||
PKG_VERSION:=1.13.14
|
||||
PKG_RELEASE:=22
|
||||
PKG_VERSION:=1.13.15
|
||||
PKG_RELEASE:=23
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/SagerNet/sing-box/tar.gz/v$(PKG_VERSION)?
|
||||
|
||||
Loading…
Reference in New Issue
Block a user