Skip to content

Commit 4917175

Browse files
committed
WIP P2V migration + restore
Signed-off-by: Tiziano Bacocco <tizbac2@gmail.com>
1 parent da2a049 commit 4917175

9 files changed

Lines changed: 247 additions & 95 deletions

File tree

directorybackup/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ func main() {
284284

285285
func backup_stream(client *pbscommon.PBSClient, newchunk, reusechunk *atomic.Uint64, filename string, stream io.Reader) error {
286286
knownChunks := hashmap.New[string, bool]()
287-
client.Connect(false)
287+
client.Connect(false, "host")
288288
previousDidx, err := client.DownloadPreviousToBytes(filename)
289289
if err != nil {
290290
return err
@@ -345,7 +345,7 @@ func backup_stream(client *pbscommon.PBSClient, newchunk, reusechunk *atomic.Uin
345345
}
346346

347347
func backup_real(client *pbscommon.PBSClient, newchunk, reusechunk *atomic.Uint64, pxarOut string, backupdir string) error {
348-
client.Connect(false)
348+
client.Connect(false, "host")
349349
knownChunks := hashmap.New[string, bool]()
350350

351351
archive := &pbscommon.PXARArchive{}

machinebackup/config.go

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,14 @@ type Config struct {
3535
Datastore string `json:"datastore"`
3636
Namespace string `json:"namespace"`
3737
BackupID string `json:"backup-id"`
38-
BackupDevice string `json:"backupdev"`
38+
BackupDevices []string `json:"backupdev"`
3939
SMTP *SMTPConfig `json:"smtp"`
4040
SysTray bool `json:"systray"`
41+
BackupType string `json:"backuptype"`
4142
}
4243

4344
func (c *Config) valid() bool {
44-
baseValid := c.BaseURL != "" && c.AuthID != "" && c.Secret != "" && c.Datastore != "" && c.BackupDevice != ""
45+
baseValid := c.BaseURL != "" && c.AuthID != "" && c.Secret != "" && c.Datastore != "" && len(c.BackupDevices) > 0
4546
if !baseValid {
4647
return baseValid
4748
}
@@ -60,16 +61,31 @@ func (c *Config) valid() bool {
6061
return true
6162
}
6263

64+
type arrayFlags []string
65+
66+
func (i *arrayFlags) String() string {
67+
return fmt.Sprintf("%v", *i)
68+
}
69+
70+
// Set is an implementation of the flag.Value interface
71+
func (i *arrayFlags) Set(value string) error {
72+
*i = append(*i, value)
73+
return nil
74+
}
6375
func loadConfig() *Config {
6476
// Define flags
77+
78+
var backupdevs arrayFlags
79+
6580
baseURLFlag := flag.String("baseurl", "", "Base URL for the proxmox backup server, example: https://192.168.1.10:8007")
6681
certFingerprintFlag := flag.String("certfingerprint", "", "Certificate fingerprint for SSL connection, example: ea:7d:06:f9...")
6782
authIDFlag := flag.String("authid", "", "Authentication ID (PBS Api token)")
6883
secretFlag := flag.String("secret", "", "Secret for authentication")
6984
datastoreFlag := flag.String("datastore", "", "Datastore name")
7085
namespaceFlag := flag.String("namespace", "", "Namespace (optional)")
7186
backupIDFlag := flag.String("backup-id", "", "Backup ID (optional - if not specified, the hostname is used as the default)")
72-
backupDEVFlag := flag.String("backupdev", "", "Backup device file ( On windows it can be \\\\.\\PhysicalDriveN , in that case VSS will be leveraged to take consistent snapshot), on linux can be /dev/sdX or whatever but not consistent for now unless it being an LVM snapshot or ZFS")
87+
backupTypeFlag := flag.String("type", "", "host|vm , vm will allow to restore as VM inside proxmox VE (Physical to Virtual), also it enables to use file restore feature, use numeric backupid")
88+
flag.Var(&backupdevs, "backupdev", "Can be specified multiple times,Backup device file ( On windows it can be \\\\.\\PhysicalDriveN , in that case VSS will be leveraged to take consistent snapshot), on linux can be /dev/sdX or whatever but not consistent for now unless it being an LVM snapshot or ZFS")
7389
sysTrayFlag := flag.Bool("systray", false, "Enable systray( Note it can cause issues when running with no user logged in )")
7490
mailHostFlag := flag.String("mail-host", "", "mail notification system: mail server host(optional)")
7591
mailPortFlag := flag.String("mail-port", "", "mail notification system: mail server port(optional)")
@@ -86,7 +102,9 @@ func loadConfig() *Config {
86102
// Parse command line flags
87103
flag.Parse()
88104

89-
config := &Config{}
105+
config := &Config{
106+
BackupType: "host",
107+
}
90108
if *configFile != "" {
91109
file, err := os.ReadFile(*configFile)
92110
if err != nil {
@@ -121,13 +139,15 @@ func loadConfig() *Config {
121139
if *backupIDFlag != "" {
122140
config.BackupID = *backupIDFlag
123141
}
124-
if *backupDEVFlag != "" {
125-
config.BackupDevice = *backupDEVFlag
126-
}
142+
config.BackupDevices = backupdevs
127143
if *sysTrayFlag {
128144
config.SysTray = true
129145
}
130146

147+
if *backupTypeFlag != "" {
148+
config.BackupType = *backupTypeFlag
149+
}
150+
131151
initSmtpConfigIfNeeded := func() {
132152
if config.SMTP == nil {
133153
config.SMTP = &SMTPConfig{}

machinebackup/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.24.4
44

55
require (
66
github.com/go-ole/go-ole v1.2.6 // indirect
7+
github.com/google/uuid v1.6.0 // indirect
78
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
89
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
910
github.com/yusufpapurcu/wmi v1.2.4 // indirect

machinebackup/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
22
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
3+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
4+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
35
github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
46
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
57
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=

machinebackup/main.go

Lines changed: 79 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"bytes"
45
"crypto/sha256"
56
"encoding/hex"
67
"flag"
@@ -12,6 +13,7 @@ import (
1213
"strconv"
1314
"strings"
1415
"sync"
16+
"text/template"
1517

1618
"clientcommon"
1719
"fmt"
@@ -21,6 +23,7 @@ import (
2123
"sync/atomic"
2224

2325
"github.com/cornelk/hashmap"
26+
"github.com/google/uuid"
2427
"github.com/tawesoft/golib/v2/dialog"
2528
)
2629

@@ -260,6 +263,11 @@ func backupFileDevice(client *pbscommon.PBSClient, filename string) error {
260263
return uploadWorker(client, slug+".fidx", uint64(size), ch)
261264
}
262265

266+
type BackupDisk struct {
267+
Index int
268+
Size int64
269+
}
270+
263271
func main() {
264272

265273
cfg := loadConfig()
@@ -307,22 +315,84 @@ func main() {
307315
},
308316
}
309317

310-
//Physycal drive paths will be like "\\\\.\\PhysicalDrive0"
311-
client.Connect(false)
312-
if strings.HasPrefix(cfg.BackupDevice, "\\\\.\\PhysicalDrive") {
318+
//Physical drive paths will be like "\\\\.\\PhysicalDrive0"
319+
client.Connect(false, cfg.BackupType)
320+
disks := make([]BackupDisk, 0)
321+
322+
for _, dev := range cfg.BackupDevices {
323+
if strings.HasPrefix(dev, "\\\\.\\PhysicalDrive") {
313324

314-
re := regexp.MustCompile(`PhysicalDrive(\d+)$`)
315-
matches := re.FindStringSubmatch(cfg.BackupDevice)
316-
idx, _ := strconv.ParseInt(matches[1], 10, 32)
317-
err := backupWindowsDisk(client, int(idx))
325+
re := regexp.MustCompile(`PhysicalDrive(\d+)$`)
326+
matches := re.FindStringSubmatch(dev)
327+
idx, _ := strconv.ParseInt(matches[1], 10, 32)
328+
size, err := backupWindowsDisk(client, int(idx))
329+
if err != nil {
330+
panic(err)
331+
}
332+
disks = append(disks, BackupDisk{
333+
Index: int(idx),
334+
Size: size,
335+
})
336+
} else {
337+
err := backupFileDevice(client, dev)
338+
if err != nil {
339+
panic(err)
340+
}
341+
}
342+
}
343+
344+
if cfg.BackupType == "vm" {
345+
type ConfigTemplate struct {
346+
VMGenId string
347+
VMID int64
348+
VMName string
349+
Disks []BackupDisk
350+
OS string
351+
SMBIOS string
352+
}
353+
354+
tmpl, err := template.New("qemuconfig").Parse(`boot: order=sata0
355+
cores: 4
356+
machine: q35
357+
memory: 2048
358+
name: {{.VMName}}
359+
numa: 0
360+
onboot: 0
361+
ostype: {{.OS}}
362+
scsihw: virtio-scsi-single
363+
smbios1: uuid={{.SMBIOS}}
364+
sockets: 1
365+
{{range .Disks}}
366+
sata{{.Index}}: local:{{.VMID}}/vm-{{.VMID}}-disk-{{.Index}}.raw,cache=writeback,discard=on,iothread=1,size={{.Size}}
367+
{{end}}
368+
vmgenid: {{.VMGenId}}
369+
`)
318370
if err != nil {
319371
panic(err)
320372
}
321-
} else {
322-
err := backupFileDevice(client, cfg.BackupDevice)
373+
vmid, err := strconv.ParseInt(cfg.BackupID, 10, 32)
374+
if err != nil {
375+
panic(err)
376+
}
377+
hostname, err := os.Hostname()
323378
if err != nil {
324379
panic(err)
325380
}
381+
wr := bytes.Buffer{}
382+
cfgt := ConfigTemplate{
383+
VMGenId: uuid.New().String(),
384+
VMID: vmid,
385+
Disks: disks,
386+
VMName: hostname,
387+
SMBIOS: uuid.New().String(), //TODO extract from real machine
388+
}
389+
if runtime.GOOS == "windows" { // TODO Improve
390+
cfgt.OS = "win11"
391+
} else {
392+
cfgt.OS = "l26"
393+
}
394+
tmpl.Execute(&wr, cfgt)
395+
client.UploadBlob("qemu-server.conf.blob", wr.Bytes())
326396
}
327397

328398
err := client.UploadManifest()

machinebackup/nonwin.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import (
88
"pbscommon"
99
)
1010

11-
func backupWindowsDisk(client *pbscommon.PBSClient, index int) error {
12-
return fmt.Errorf("Not supported on this platform")
11+
func backupWindowsDisk(client *pbscommon.PBSClient, index int) (int64, error) {
12+
return 0, fmt.Errorf("Not supported on this platform")
1313
}
1414

1515
func sysTraySetup() {

machinebackup/windows.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ func GetDiskLength(path string) (int64, error) {
247247
return lengthInfo.Length, nil
248248
}
249249

250-
func backupWindowsDisk(client *pbscommon.PBSClient, index int) error {
250+
func backupWindowsDisk(client *pbscommon.PBSClient, index int) (int64, error) {
251251
parts := make([]Partition, 0)
252252
ch := make(chan []byte)
253253
diskdev := fmt.Sprintf("\\\\.\\PhysicalDrive%d", index)
@@ -352,10 +352,10 @@ func backupWindowsDisk(client *pbscommon.PBSClient, index int) error {
352352

353353
total, err := GetDiskLength(diskdev)
354354
if err != nil {
355-
return err
355+
return 0, err
356356
}
357357

358-
return snapshot.CreateVSSSnapshot(snapshot_paths, func(snapshots map[string]snapshot.SnapShot) error {
358+
return total, snapshot.CreateVSSSnapshot(snapshot_paths, func(snapshots map[string]snapshot.SnapShot) error {
359359

360360
/*hostname, err := os.Hostname()
361361
if err != nil {
@@ -509,7 +509,7 @@ func backupWindowsDisk(client *pbscommon.PBSClient, index int) error {
509509
close(ch)
510510
}()
511511

512-
return uploadWorker(client, fmt.Sprintf("windisk%d.fidx", index), uint64(total), ch)
512+
return uploadWorker(client, fmt.Sprintf("drive-sata%d.img.fidx", index), uint64(total), ch)
513513

514514
})
515515
}

0 commit comments

Comments
 (0)