From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id 4716D1FF0E0 for ; Thu, 09 Jul 2026 10:22:28 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 452D221629; Thu, 09 Jul 2026 10:20:52 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1783366858; x=1783971658; darn=lists.proxmox.com; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=056OWqSYOi10nOvpVVGM1WmdvLKF6x7HiQ+0f6bQ0Vw=; b=mUiiVzQksF7H2At5/k/Bxv8xeKO9Z2vzj+2C/mCkEpeK/oDZiKz9A2+TmPk8H8h1U7 I4Mtg2C0ORmmfLgp6StfeEgogmozFcmkUMM2v+IKGHTLMCkWDMJaitqiinsObkjyzNPK lYMO/J5z/g4OrU4kFhjJQC3wT1ET2sUyjR2E8bW5esQwituQxd4m4pggiExXRBZNXw4M pF637pUCO2YEUE8eeEJmNr88Uq7XJLi2oXBt4OeUQmq9xc8/+ecJh5aJkFFvbF5bib+3 m7DcIb8NyzQqV3lnYou8oIgEODH9MDVC1LSjkwmTo1b2swhcRBj4eTN2BUTZQaZoSlpu Wh8Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783366858; x=1783971658; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=056OWqSYOi10nOvpVVGM1WmdvLKF6x7HiQ+0f6bQ0Vw=; b=SejLuEIRmTGHzWRUCgYPt5l8hw9BDe70wPaS+ormYaMvo4Ob6TmBzchQd/mVJkO/wz IGgpprBUUPz9k/HSTrKxi7+7LX1XsrVgQm6hSJZVCPquzS8AM1eFZoHaz+VtB1aHnqbX jUf0wdLLDZ/ap6SpU693bRWT3EaYqGXPFZuGAfP+U6gvBJqAl/hEE04GkZdEAb5/knx6 PgAtfQZSfjSmRJJ+qWpDaDWbxWAPXWCDkZ4IumlD9L8l54NJASyFJOtKpDWOvjnHdEhh 3w1xUocUEe4kJKzNCGNcXgtZaFVT3nCnh87d+OhkSItQeciP0MbtwUVbcyWeb/Nd6s6V eFBg== X-Gm-Message-State: AOJu0Yx4iNy624KkpqDPZu53/AkoUYZoV6PNjffehnDJTfACqSxwkNYu 23VEYY9WjsjaMKxRUIg8Yd94zzkgta8ApX2z2pfKXnbAQwkcfUgogHDZap6AUmSp X-Gm-Gg: AfdE7clnDUVDwI+J8p/j91SMxzRLoGky2NhGduDhoKv/lFMVzj1m5rk7QPPbp1mm7jN tRAnEA+y8fWSCSTnkPZPFImOgiEVS3OfzxQIjUiU8IIQaPmiJZRK0Y54QOL771/RCuxGlV2a1ap GXee+6b1CprnY3i67ru08be20avYqmHoM1aRYRYrWA6l6Tp0j7nfqhkqkFISuOjwecH6fRuaXTG EGRkxWnmuJQEwmSpFkVu5/GJOOA4UbCkNSCXYf5SiBk9E/RFuj0vhjua+KW2HAEpL+mnRGI4Whb 9n8Yak/h3mlHT1vfx3dDOdtX01uE0cRXvJVedmb2Es8wSqPDMlCLwn1vOPY9qyPI3uftD0yoYcG piy6gMwgasrCgz0q3UJ8/0YVe5TWhIvjZdyUEtPkJx/5oCaVO8lB0lXBOFdOgy26YZpTubd0CHP 9SXstNvrGHCa6uSA== X-Received: by 2002:adf:e00c:0:10b0:46f:1b89:999 with SMTP id ffacd0b85a97d-479378bd497mr13533844f8f.30.1783366857555; Mon, 06 Jul 2026 12:40:57 -0700 (PDT) From: copystring To: pve-devel@lists.proxmox.com subject: SPAM: [RFC PATCH pve-manager] lxc: add OCI rootfs replacement window Date: Mon, 6 Jul 2026 21:39:33 +0200 Message-ID: <20260706193934.279992-6-copystring@gmail.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260706193934.279992-1-copystring@gmail.com> References: <20260706193934.279992-1-copystring@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 4 AWL -0.225 Adjusted score from AWL reputation of From: address DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain DMARC_PASS -0.1 DMARC pass policy FORGED_GMAIL_RCVD 1 'From' gmail.com does not match 'Received' headers FREEMAIL_FROM 0.001 Sender email is commonly abused enduser mail provider RCVD_IN_DNSWL_NONE -0.0001 Sender listed at https://www.dnswl.org/, no trust RCVD_IN_SBL_CSS 3.558 Received via a relay in Spamhaus SBL-CSS SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record X-MailFrom: copystring@gmail.com X-Mailman-Rule-Hits: nonmember-moderation X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation Message-ID-Hash: 3RPJS74D6KYMJANO4IEVMTNLDXW4X36U X-Message-ID-Hash: 3RPJS74D6KYMJANO4IEVMTNLDXW4X36U X-Mailman-Approved-At: Thu, 09 Jul 2026 10:20:43 +0200 CC: copystring X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Add a guarded UI flow for replacing a stopped container rootfs from an OCI image archive. The action requires VM.Config.Disk, keeps the submit button disabled until the current config digest is loaded and the user confirms the rootfs replacement, and calls the new backend endpoint. Signed-off-by: copystring --- www/manager6/Makefile | 1 + www/manager6/lxc/CmdMenu.js | 14 +++ www/manager6/lxc/Config.js | 22 ++++ www/manager6/lxc/RootfsOciReplace.js | 149 +++++++++++++++++++++++++++ 4 files changed, 186 insertions(+) create mode 100644 www/manager6/lxc/RootfsOciReplace.js diff --git a/www/manager6/Makefile b/www/manager6/Makefile index d4dd3f35..4f7206b9 100644 --- a/www/manager6/Makefile +++ b/www/manager6/Makefile @@ -207,6 +207,7 @@ JSSRC= \ dc/PCIMapView.js \ dc/USBMapView.js \ dc/DirMapView.js \ + lxc/RootfsOciReplace.js \ lxc/CmdMenu.js \ lxc/Config.js \ lxc/CreateWizard.js \ diff --git a/www/manager6/lxc/CmdMenu.js b/www/manager6/lxc/CmdMenu.js index a7940e04..93bea07f 100644 --- a/www/manager6/lxc/CmdMenu.js +++ b/www/manager6/lxc/CmdMenu.js @@ -134,6 +134,20 @@ Ext.define('PVE.lxc.CmdMenu', { }, }, { xtype: 'menuseparator' }, + { + text: gettext('Replace Rootfs from OCI'), + iconCls: 'fa fa-fw fa-refresh', + hidden: !caps.vms['VM.Config.Disk'], + disabled: me.isTemplate || !stopped, + handler: () => { + Ext.create('PVE.lxc.RootfsOciReplace', { + nodename: info.node, + vmid: info.vmid, + canUpdateOptions: caps.vms['VM.Config.Options'], + autoShow: true, + }); + }, + }, { text: gettext('Take Snapshot'), iconCls: 'fa fa-fw fa-history', diff --git a/www/manager6/lxc/Config.js b/www/manager6/lxc/Config.js index 3a99d6a7..9058a287 100644 --- a/www/manager6/lxc/Config.js +++ b/www/manager6/lxc/Config.js @@ -147,6 +147,22 @@ Ext.define('PVE.lxc.Config', { }); }, }, + { + text: gettext('Replace Rootfs from OCI'), + itemId: 'replaceRootfsFromOciBtn', + iconCls: 'fa fa-fw fa-refresh', + hidden: !caps.vms['VM.Config.Disk'], + disabled: template || running, + handler: function () { + Ext.create('PVE.lxc.RootfsOciReplace', { + nodename: nodename, + vmid: vmid, + canUpdateOptions: caps.vms['VM.Config.Options'], + reload: () => me.statusStore.load(), + autoShow: true, + }); + }, + }, { iconCls: 'fa fa-heartbeat ', hidden: !caps.nodes['Sys.Console'], @@ -424,6 +440,12 @@ Ext.define('PVE.lxc.Config', { startBtn.setDisabled(!caps.vms['VM.PowerMgmt'] || status === 'running' || template); shutdownBtn.setDisabled(!caps.vms['VM.PowerMgmt'] || status !== 'running'); me.down('#removeBtn').setDisabled(!caps.vms['VM.Allocate'] || status !== 'stopped'); + let replaceRootfsBtn = me.down('#replaceRootfsFromOciBtn'); + if (replaceRootfsBtn) { + replaceRootfsBtn.setDisabled( + !caps.vms['VM.Config.Disk'] || template || status !== 'stopped', + ); + } consoleBtn.setDisabled(template); if (prevStatus === 'stopped' && status === 'running') { diff --git a/www/manager6/lxc/RootfsOciReplace.js b/www/manager6/lxc/RootfsOciReplace.js new file mode 100644 index 00000000..2b6ffe78 --- /dev/null +++ b/www/manager6/lxc/RootfsOciReplace.js @@ -0,0 +1,149 @@ +Ext.define('PVE.lxc.RootfsOciReplace', { + extend: 'Proxmox.window.Edit', + alias: 'widget.pveLxcRootfsOciReplace', + + title: gettext('Replace Rootfs from OCI Image'), + isCreate: true, + method: 'POST', + submitText: gettext('Replace Rootfs'), + showTaskViewer: true, + autoLoad: true, + width: 760, + fieldDefaults: { + labelWidth: 130, + }, + + initComponent: function () { + let me = this; + + if (!me.nodename) { + throw 'no node name specified'; + } + if (!me.vmid) { + throw 'no VM ID specified'; + } + + me.url = '/nodes/' + me.nodename + '/lxc/' + me.vmid + '/replace_rootfs_from_oci'; + me.loadUrl = '/nodes/' + me.nodename + '/lxc/' + me.vmid + '/config'; + + let canUpdateOptions = !!me.canUpdateOptions; + let warningHtml = [ + `${Ext.String.htmlEncode(gettext('Rootfs replacement is destructive.'))}`, + Ext.String.htmlEncode(gettext('The current rootfs / will be replaced by the OCI image.')), + Ext.String.htmlEncode(gettext('Separate mount points such as mp0, mp1, ... are preserved.')), + Ext.String.htmlEncode( + gettext( + 'Data stored only on / will not be part of the new active rootfs. The old rootfs is kept as an unused volume.', + ), + ), + ].join('
'); + + let templateStorageSelector = Ext.create('PVE.form.StorageSelector', { + name: 'tmplstorage', + fieldLabel: gettext('Image Storage'), + nodename: me.nodename, + storageContent: 'vztmpl', + autoSelect: true, + allowBlank: false, + submitValue: false, + }); + + let templateSelector = Ext.create('PVE.form.FileSelector', { + name: 'ostemplate', + fieldLabel: gettext('OCI Image'), + nodename: me.nodename, + storageContent: 'vztmpl', + allowBlank: false, + }); + + templateStorageSelector.on('change', function (_field, storage) { + templateSelector.setStorage(storage, me.nodename); + }); + + me.items = [ + { + xtype: 'inputpanel', + border: false, + onGetValues: function (values) { + delete values.tmplstorage; + delete values.confirmRootfsReplace; + + values['confirm-rootfs-replace'] = 1; + values['update-oci-config'] = values['update-oci-config'] ? 1 : 0; + + if (!values.storage) { + delete values.storage; + } + if (!values.size) { + delete values.size; + } + + return values; + }, + columnT: [ + { + xtype: 'displayfield', + fieldLabel: gettext('Warning'), + userCls: 'pmx-hint', + value: warningHtml, + }, + templateStorageSelector, + templateSelector, + ], + column1: [ + { + xtype: 'pveStorageSelector', + name: 'storage', + fieldLabel: gettext('Target Storage'), + nodename: me.nodename, + storageContent: 'rootdir', + allowBlank: true, + autoSelect: false, + emptyText: gettext('Current rootfs storage'), + }, + { + xtype: 'proxmoxtextfield', + name: 'size', + fieldLabel: gettext('Root Disk Size'), + allowBlank: true, + emptyText: gettext('Current size'), + regex: /^\d+(\.\d+)?[KMGT]?$/, + regexText: gettext('Invalid size'), + }, + ], + column2: [ + { + xtype: 'proxmoxcheckbox', + name: 'update-oci-config', + fieldLabel: gettext('Update OCI config'), + checked: canUpdateOptions, + disabled: !canUpdateOptions, + uncheckedValue: 0, + }, + { + xtype: 'proxmoxcheckbox', + name: 'confirmRootfsReplace', + fieldLabel: gettext('Confirmation'), + boxLabel: gettext( + 'I understand that the current rootfs will be replaced and that data stored only on / will no longer be active.', + ), + submitValue: false, + uncheckedValue: 0, + getErrors: function () { + return this.getValue() ? [] : [gettext('Confirmation required')]; + }, + listeners: { + change: function (field) { + field.validate(); + }, + }, + }, + ], + }, + ]; + + me.callParent(); + + me.on('destroy', () => Ext.isFunction(me.reload) && me.reload()); + }, +}); -- 2.55.0.windows.2