From 4ae30eb155e2ceb695f1d21d7e10b4d603bf5676 Mon Sep 17 00:00:00 2001 From: Arthur Lu Date: Mon, 21 Jul 2025 22:25:54 +0000 Subject: [PATCH] fix issue in qemu backup restore --- src/routes/cluster/backup.js | 103 ++++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/src/routes/cluster/backup.js b/src/routes/cluster/backup.js index f2d6888..caf0b2a 100644 --- a/src/routes/cluster/backup.js +++ b/src/routes/cluster/backup.js @@ -103,6 +103,7 @@ router.post("/", async (req, res) => { * - node: string - vm host node id * - type: string - vm type (lxc, qemu) * - vmid: number - vm id number + * - volid: volid of the backup to be deleted * - notes: notes template string * responses: * - 200: PVE Task Object @@ -150,7 +151,12 @@ router.post("/notes", async (req, res) => { notes: params.notes }; const result = await global.pve.requestPVE(`/nodes/${params.node}/storage/${storage}/content/${params.volid}`, "PUT", { token: true }, body); - res.status(result.status).send(result.data.data); + if (result.status === 200) { + res.status(result.status).send(); + } + else { + res.status(result.status).send({ error: result.statusText }); + } }); /** @@ -204,3 +210,98 @@ router.delete("/", async (req, res) => { const result = await global.pve.requestPVE(`/nodes/${params.node}/storage/${storage}/content/${params.volid}?delay=5`, "DELETE", { token: true }); res.status(result.status).send(result.data.data); }); + +/** + * POST - restore instance using backup file. Ideally, PBS should be used instead so that individual disk level restore can be done. + * request: + * - node: string - vm host node id + * - type: string - vm type (lxc, qemu) + * - vmid: number - vm id number + * - volid: volid of the backup to be deleted + * responses: + * - 200: PVE Task Object + * - 401: {auth: false, path: string} + * - 500: {error: string} + * - 500: PVE Task Object + */ +router.post("/restore", async (req, res) => { + const params = { + node: req.params.node, + type: req.params.type, + vmid: req.params.vmid, + volid: req.body.volid + }; + + // check auth for specific instance + const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`; + const auth = await checkAuth(req.cookies, res, vmpath); + if (!auth) { + return; + } + + // check if the specified volid is a backup for the instance + // for whatever reason, calling /nodes/node/storage/content/volid does not return the vmid number whereas /nodes/storage/content?... does + const storage = global.config.backups.storage; + const backups = await global.pve.requestPVE(`/nodes/${params.node}/storage/${storage}/content?content=backup&vmid=${params.vmid}`, "GET", { token: true }); + if (backups.status !== 200) { + res.status(backups.status).send({ error: backups.statusText }); + return; + } + let found = false; + for (const volume of backups.data.data) { + if (volume.subtype === params.type && String(volume.vmid) === params.vmid && volume.content === "backup" && volume.volid === params.volid) { + found = true; + } + } + if (!found) { + res.status(500).send({ error: `Did not find backup volume ${params.volid} for ${params.node}.${params.vmid}` }); + return; + } + + // container restore + // need to use "advanced" mode to specify the storage used for each disk, so we also need to read the container's config + // for whatever reason, this will wipe disks that are not included in the backup !!! + if (params.type === "lxc") { + const body = { + vmid: params.vmid, + force: 1, + ostemplate: params.volid, + restore: 1 + }; + + const instance = await global.pve.getInstance(params.node, params.vmid); + for (const v in instance.volumes) { + const volume = instance.volumes[v]; + if (volume.type === "mp") { + body[v] = `${volume.storage}:${volume.size / 1024 ** 3},mp=${volume.mp},backup=1`; + } + else if (volume.type === "rootfs") { + body[v] = `${volume.storage}:${volume.size / 1024 ** 3}`; + } + } + + const result = await global.pve.requestPVE(`/nodes/${params.node}/${params.type}/`, "POST", { token: true }, body); + console.log(result); + if (result.status === 200) { + res.status(result.status).send(); + } + else { + res.status(result.status).send({ error: result.statusText }); + } + } + // VM restore, unlike the container restore, this should not affect disks which are not in the backup + else if (params.type === "qemu") { // vm restore + const body = { + vmid: params.vmid, + force: 1, + archive: params.volid + }; + const result = await global.pve.requestPVE(`/nodes/${params.node}/${params.type}/`, "POST", { token: true }, body); + if (result.status === 200) { + res.status(result.status).send(); + } + else { + res.status(result.status).send({ error: result.statusText }); + } + } +});