Refactor plugin + scripts; survive pve-manager upgrades
- pve-trunks-plugin.js: убран мёртвый код (origHandler), идемпотентность (не добавлять поле дважды), null-safety при смене вёрстки PVE, вынос regex в trunksRegex, отдельный addTrunksField(); логика onGetValues/me.network сверена с реальной PVE.qemu.NetworkInputPanel (PVE 9.2). - install.sh: бэкап index.html.tpl, проверка </body>, идемпотентность, и переживание apt upgrade: persistent-копия + pve-trunks-reapply.sh + apt-hook (рестарт pveproxy только при реальном изменении), cache-busting ?ver. - uninstall.sh: снимает плагин и всю reapply-машинерию, бэкап шаблона. - README: разделы про сериализацию и переживание обновлений. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>main
parent
b3942f919d
commit
8bb44d526a
16
README.md
16
README.md
|
|
@ -72,10 +72,20 @@ ip link add link eth0 name eth0.200 type vlan id 200
|
|||
|
||||
## Как работает
|
||||
|
||||
Плагин использует ExtJS `override` на `PVE.qemu.NetworkInputPanel` и добавляет поле Trunks в секцию Advanced. Оригинальные файлы Proxmox не модифицируются — загружается только новый JS-файл через index template.
|
||||
Плагин использует ExtJS `override` на `PVE.qemu.NetworkInputPanel` и добавляет поле Trunks в секцию Advanced. Сериализацию делает штатный `PVE.Parser.printQemuNetwork` (он уже умеет `trunks=`), плагин лишь поддерживает `me.network.trunks`. Исходные файлы pvemanagerlib не правятся — добавляется только новый JS-файл, подключённый в index template после `pvemanagerlib.js`.
|
||||
|
||||
### Переживание обновлений
|
||||
|
||||
JS-файл (`/usr/share/pve-manager/js/`) и `index.html.tpl` принадлежат пакету `pve-manager`, поэтому `apt upgrade` их затирает. Установщик поэтому:
|
||||
|
||||
- кладёт копию плагина в `/usr/local/share/pve-trunks-plugin/`;
|
||||
- ставит идемпотентный `/usr/local/sbin/pve-trunks-reapply.sh` (восстанавливает JS и тег в шаблоне, перезапускает `pveproxy` только при реальном изменении);
|
||||
- регистрирует apt-hook `/etc/apt/apt.conf.d/99-pve-trunks-plugin`, дёргающий reapply в конце apt-транзакции.
|
||||
|
||||
`install.sh` и `uninstall.sh` идемпотентны; перед правкой `index.html.tpl` делается бэкап `index.html.tpl.pve-trunks-bak`.
|
||||
|
||||
## Файлы
|
||||
|
||||
- `pve-trunks-plugin.js` — ExtJS override плагин
|
||||
- `install.sh` — установщик
|
||||
- `uninstall.sh` — удаление
|
||||
- `install.sh` — установщик (+ reapply-hook для апгрейдов)
|
||||
- `uninstall.sh` — удаление (вместе с reapply-машинерией)
|
||||
|
|
|
|||
93
install.sh
93
install.sh
|
|
@ -1,42 +1,93 @@
|
|||
#!/bin/bash
|
||||
# PVE Trunks Plugin - Installer
|
||||
# Adds "Trunks" field to VM Network Device editor in Proxmox VE UI
|
||||
# Adds a "Trunks" field to the VM Network Device editor in the Proxmox VE UI.
|
||||
#
|
||||
# Usage: bash install.sh
|
||||
#
|
||||
# Both the plugin JS and the index template belong to the `pve-manager`
|
||||
# package, so an `apt upgrade` wipes them. This installer therefore also sets
|
||||
# up a reapply hook so the plugin survives Proxmox updates.
|
||||
|
||||
set -e
|
||||
set -euo pipefail
|
||||
|
||||
PLUGIN_JS="pve-trunks-plugin.js"
|
||||
PVE_JS_DIR="/usr/share/pve-manager/js"
|
||||
INDEX_TPL="/usr/share/pve-manager/index.html.tpl"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SCRIPT_TAG='<script type="text/javascript" src="/pve2/js/pve-trunks-plugin.js"></script>'
|
||||
|
||||
# Check running as root
|
||||
# Persistent copy + reapply machinery (so upgrades can restore the plugin).
|
||||
STORE_DIR="/usr/local/share/pve-trunks-plugin"
|
||||
REAPPLY="/usr/local/sbin/pve-trunks-reapply.sh"
|
||||
APT_HOOK="/etc/apt/apt.conf.d/99-pve-trunks-plugin"
|
||||
SCRIPT_TAG='<script type="text/javascript" src="/pve2/js/pve-trunks-plugin.js?ver=2"></script>'
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "Error: run as root"
|
||||
echo "Error: run as root" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check Proxmox
|
||||
if [ ! -d "$PVE_JS_DIR" ]; then
|
||||
echo "Error: Proxmox VE not found ($PVE_JS_DIR missing)"
|
||||
if [ ! -d "$PVE_JS_DIR" ] || [ ! -f "$INDEX_TPL" ]; then
|
||||
echo "Error: Proxmox VE not found ($PVE_JS_DIR / $INDEX_TPL missing)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Copy plugin JS
|
||||
cp "$SCRIPT_DIR/$PLUGIN_JS" "$PVE_JS_DIR/$PLUGIN_JS"
|
||||
echo "Installed $PVE_JS_DIR/$PLUGIN_JS"
|
||||
|
||||
# Patch index template if not already patched
|
||||
if ! grep -q "pve-trunks-plugin" "$INDEX_TPL"; then
|
||||
# Insert before closing </body>
|
||||
sed -i "s|</body>|${SCRIPT_TAG}\n</body>|" "$INDEX_TPL"
|
||||
echo "Patched $INDEX_TPL"
|
||||
else
|
||||
echo "Index template already patched, skipping"
|
||||
if [ ! -f "$SCRIPT_DIR/$PLUGIN_JS" ]; then
|
||||
echo "Error: $PLUGIN_JS not found next to installer" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Restart web UI
|
||||
systemctl restart pveproxy
|
||||
# 1) Persist the plugin source for the reapply hook.
|
||||
install -d "$STORE_DIR"
|
||||
install -m 0644 "$SCRIPT_DIR/$PLUGIN_JS" "$STORE_DIR/$PLUGIN_JS"
|
||||
|
||||
# 2) Install the reapply script (idempotent; restarts pveproxy only on change).
|
||||
cat > "$REAPPLY" <<EOF
|
||||
#!/bin/bash
|
||||
# Reapply PVE Trunks Plugin (after pve-manager upgrade or manual run). Idempotent.
|
||||
set -u
|
||||
PVE_JS_DIR="$PVE_JS_DIR"
|
||||
INDEX_TPL="$INDEX_TPL"
|
||||
STORE_DIR="$STORE_DIR"
|
||||
PLUGIN_JS="$PLUGIN_JS"
|
||||
SCRIPT_TAG='$SCRIPT_TAG'
|
||||
[ -d "\$PVE_JS_DIR" ] && [ -f "\$INDEX_TPL" ] || exit 0
|
||||
[ -f "\$STORE_DIR/\$PLUGIN_JS" ] || exit 0
|
||||
|
||||
changed=0
|
||||
|
||||
# Restore the JS file if missing or outdated.
|
||||
if ! cmp -s "\$STORE_DIR/\$PLUGIN_JS" "\$PVE_JS_DIR/\$PLUGIN_JS" 2>/dev/null; then
|
||||
install -m 0644 "\$STORE_DIR/\$PLUGIN_JS" "\$PVE_JS_DIR/\$PLUGIN_JS"
|
||||
changed=1
|
||||
fi
|
||||
|
||||
# Re-add the <script> tag if missing (must sit before </body>).
|
||||
if ! grep -q "pve-trunks-plugin" "\$INDEX_TPL"; then
|
||||
if grep -q "</body>" "\$INDEX_TPL"; then
|
||||
cp -a "\$INDEX_TPL" "\$INDEX_TPL.pve-trunks-bak"
|
||||
awk -v tag="\$SCRIPT_TAG" '/<\/body>/ && !done { print tag; done=1 } { print }' \
|
||||
"\$INDEX_TPL" > "\$INDEX_TPL.tmp" && mv "\$INDEX_TPL.tmp" "\$INDEX_TPL"
|
||||
changed=1
|
||||
else
|
||||
logger -t pve-trunks-plugin "no </body> in \$INDEX_TPL; cannot patch"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "\$changed" -eq 1 ]; then
|
||||
systemctl try-restart pveproxy
|
||||
logger -t pve-trunks-plugin "plugin reapplied"
|
||||
fi
|
||||
EOF
|
||||
chmod 0755 "$REAPPLY"
|
||||
|
||||
# 3) apt hook: reapply at the end of any apt/dpkg transaction (e.g. pve-manager upgrade).
|
||||
cat > "$APT_HOOK" <<EOF
|
||||
// pve-trunks-plugin: restore UI plugin after pve-manager upgrades
|
||||
DPkg::Post-Invoke { "test -x $REAPPLY && $REAPPLY >/dev/null 2>&1 || true"; };
|
||||
EOF
|
||||
|
||||
# 4) Apply now.
|
||||
"$REAPPLY"
|
||||
|
||||
echo "Installed. Reapply hook: $APT_HOOK -> $REAPPLY"
|
||||
echo "Done. Reload the Proxmox web UI (Ctrl+Shift+R)."
|
||||
|
|
|
|||
|
|
@ -1,15 +1,24 @@
|
|||
/*
|
||||
* Proxmox VE - VLAN Trunks UI Plugin
|
||||
* Adds "Trunks" field to QEMU VM Network Device editor
|
||||
* Adds a "Trunks" field to the QEMU VM Network Device editor.
|
||||
*
|
||||
* Install: place in /usr/share/pve-manager/js/ and patch index template
|
||||
* See install.sh for automated installation
|
||||
* The PVE network parser/printer (PVE.Parser) already understands the
|
||||
* `trunks=` option; only the UI field is missing. This override adds it.
|
||||
*
|
||||
* Install: place in /usr/share/pve-manager/js/ and load it after
|
||||
* pvemanagerlib.js via the index template (see install.sh).
|
||||
*/
|
||||
|
||||
Ext.define('PVE.patch.QemuNetworkTrunks', {
|
||||
override: 'PVE.qemu.NetworkInputPanel',
|
||||
|
||||
onGetValues: function(values) {
|
||||
// Keep in sync with the parser regex in pvemanagerlib.js (print/parse_net).
|
||||
trunksRegex: /^\d+(?:-\d+)?(?:;\d+(?:-\d+)?)*$/,
|
||||
|
||||
// The original onGetValues builds `me.network` and serializes it with
|
||||
// PVE.Parser.printQemuNetwork(), which appends `,trunks=` when set.
|
||||
// So we just maintain me.network.trunks and let the parent serialize.
|
||||
onGetValues: function (values) {
|
||||
var me = this;
|
||||
|
||||
if (values.trunks) {
|
||||
|
|
@ -17,38 +26,47 @@ Ext.define('PVE.patch.QemuNetworkTrunks', {
|
|||
} else {
|
||||
delete me.network.trunks;
|
||||
}
|
||||
delete values.trunks;
|
||||
delete values.trunks; // not a real form field for the parent
|
||||
|
||||
return me.callParent([values]);
|
||||
},
|
||||
|
||||
initComponent: function() {
|
||||
initComponent: function () {
|
||||
var me = this;
|
||||
|
||||
me.callParent(arguments);
|
||||
|
||||
// Add trunks field after MTU in advanced column
|
||||
var mtuFields = me.query('[name=mtu]');
|
||||
if (mtuFields.length > 0) {
|
||||
var container = mtuFields[0].ownerCt;
|
||||
if (container) {
|
||||
container.add({
|
||||
xtype: 'textfield',
|
||||
name: 'trunks',
|
||||
fieldLabel: 'Trunks',
|
||||
emptyText: 'e.g. 100;200;300-400',
|
||||
allowBlank: true,
|
||||
regex: /^\d+(?:-\d+)?(?:;\d+(?:-\d+)?)*$/,
|
||||
regexText: 'VLAN IDs separated by semicolons, e.g. 100;200;300-400',
|
||||
});
|
||||
}
|
||||
me.addTrunksField();
|
||||
},
|
||||
|
||||
// Insert the Trunks field right after MTU in the advanced column.
|
||||
addTrunksField: function () {
|
||||
var me = this;
|
||||
|
||||
if (me.down('[name=trunks]')) {
|
||||
return; // already added
|
||||
}
|
||||
|
||||
// Handle "No network device" checkbox in wizard
|
||||
var mtuField = me.down('[name=mtu]');
|
||||
var container = mtuField && mtuField.ownerCt;
|
||||
if (!container) {
|
||||
return; // layout changed in this PVE version; fail safe
|
||||
}
|
||||
|
||||
container.add({
|
||||
xtype: 'textfield',
|
||||
name: 'trunks',
|
||||
fieldLabel: 'Trunks',
|
||||
emptyText: 'e.g. 100;200;300-400',
|
||||
allowBlank: true,
|
||||
regex: me.trunksRegex,
|
||||
regexText: 'VLAN IDs separated by semicolons, e.g. 100;200;300-400',
|
||||
});
|
||||
|
||||
// In the wizard, disable Trunks together with "No network device".
|
||||
var noNetworkCb = me.down('[name=nonetwork]');
|
||||
if (noNetworkCb) {
|
||||
var origHandler = noNetworkCb.listeners && noNetworkCb.listeners.change;
|
||||
noNetworkCb.on('change', function(cb, value) {
|
||||
noNetworkCb.on('change', function (cb, value) {
|
||||
var trunksField = me.down('[name=trunks]');
|
||||
if (trunksField) {
|
||||
trunksField.setDisabled(value);
|
||||
|
|
|
|||
26
uninstall.sh
26
uninstall.sh
|
|
@ -1,24 +1,32 @@
|
|||
#!/bin/bash
|
||||
# PVE Trunks Plugin - Uninstaller
|
||||
|
||||
set -e
|
||||
set -euo pipefail
|
||||
|
||||
PVE_JS_DIR="/usr/share/pve-manager/js"
|
||||
INDEX_TPL="/usr/share/pve-manager/index.html.tpl"
|
||||
PLUGIN_JS="pve-trunks-plugin.js"
|
||||
STORE_DIR="/usr/local/share/pve-trunks-plugin"
|
||||
REAPPLY="/usr/local/sbin/pve-trunks-reapply.sh"
|
||||
APT_HOOK="/etc/apt/apt.conf.d/99-pve-trunks-plugin"
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "Error: run as root"
|
||||
echo "Error: run as root" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Remove plugin JS
|
||||
# Remove reapply machinery first, so it can't restore the plugin afterwards.
|
||||
rm -f "$APT_HOOK" "$REAPPLY"
|
||||
rm -rf "$STORE_DIR"
|
||||
|
||||
# Remove the plugin JS.
|
||||
rm -f "$PVE_JS_DIR/$PLUGIN_JS"
|
||||
echo "Removed $PVE_JS_DIR/$PLUGIN_JS"
|
||||
|
||||
# Remove script tag from index template
|
||||
sed -i '/pve-trunks-plugin/d' "$INDEX_TPL"
|
||||
echo "Cleaned $INDEX_TPL"
|
||||
# Remove the <script> tag line(s) from the index template.
|
||||
if grep -q "pve-trunks-plugin" "$INDEX_TPL"; then
|
||||
cp -a "$INDEX_TPL" "$INDEX_TPL.pve-trunks-bak"
|
||||
sed -i '/pve-trunks-plugin/d' "$INDEX_TPL"
|
||||
fi
|
||||
|
||||
systemctl restart pveproxy
|
||||
echo "Done. Reload the Proxmox web UI (Ctrl+Shift+R)."
|
||||
systemctl try-restart pveproxy
|
||||
echo "Removed. Reload the Proxmox web UI (Ctrl+Shift+R)."
|
||||
|
|
|
|||
Loading…
Reference in New Issue