From: copystring <copystring@gmail.com>
To: pve-devel@lists.proxmox.com
Cc: copystring <copystring@gmail.com>
Subject: SPAM: [RFC PATCH pve-manager] lxc: add OCI rootfs replacement window
Date: Mon, 6 Jul 2026 21:40:58 +0200 [thread overview]
Message-ID: <20260706194059.280257-6-copystring@gmail.com> (raw)
In-Reply-To: <20260706194059.280257-1-copystring@gmail.com>
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 <copystring@gmail.com>
---
www/manager6/Makefile | 1 +
| 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 \
--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 = [
+ `<b>${Ext.String.htmlEncode(gettext('Rootfs replacement is destructive.'))}</b>`,
+ 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('<br>');
+
+ 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
next prev parent reply other threads:[~2026-07-09 8:21 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-06 19:40 SPAM: [RFC PATCH 0/4] lxc: add safe OCI rootfs replacement copystring
2026-07-06 19:40 ` SPAM: [RFC PATCH 1/4] lxc: create: add isolated OCI rootfs preparation copystring
2026-07-06 19:40 ` SPAM: [RFC PATCH 2/4] api: lxc: add OCI rootfs replacement endpoint copystring
2026-07-06 19:40 ` SPAM: [RFC PATCH 3/4] pct: add OCI rootfs replacement command copystring
2026-07-06 19:40 ` SPAM: [RFC PATCH 4/4] test: cover OCI rootfs replacement API transaction copystring
2026-07-06 19:40 ` copystring [this message]
2026-07-06 19:40 ` SPAM: [RFC PATCH pve-docs] pct: document OCI rootfs replacement semantics copystring
-- strict thread matches above, loose matches on Subject: below --
2026-07-06 19:39 SPAM: [RFC PATCH 0/4] lxc: add safe OCI rootfs replacement copystring
2026-07-06 19:39 ` SPAM: [RFC PATCH pve-manager] lxc: add OCI rootfs replacement window copystring
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260706194059.280257-6-copystring@gmail.com \
--to=copystring@gmail.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox