+sub restore_proxmox_backup_archive {
+ my ($archive, $vmid, $user, $options) = @_;
+
+ my $storecfg = PVE::Storage::config();
+
+ my ($storeid, $volname) = PVE::Storage::parse_volume_id($archive);
+ my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+
+ my $server = $scfg->{server};
+ my $datastore = $scfg->{datastore};
+ my $username = $scfg->{username} // 'root@pam';
+ my $fingerprint = $scfg->{fingerprint};
+ my $keyfile = PVE::Storage::PBSPlugin::pbs_encryption_key_file_name($storecfg, $storeid);
+
+ my $repo = "$username\@$server:$datastore";
+
+ # This is only used for `pbs-restore`!
+ my $password = PVE::Storage::PBSPlugin::pbs_get_password($scfg, $storeid);
+ local $ENV{PBS_PASSWORD} = $password;
+ local $ENV{PBS_FINGERPRINT} = $fingerprint if defined($fingerprint);
+
+ my ($vtype, $pbs_backup_name, undef, undef, undef, undef, $format) =
+ PVE::Storage::parse_volname($storecfg, $archive);
+
+ die "got unexpected vtype '$vtype'\n" if $vtype ne 'backup';
+
+ die "got unexpected backup format '$format'\n" if $format ne 'pbs-vm';
+
+ my $tmpdir = "/var/tmp/vzdumptmp$$";
+ rmtree $tmpdir;
+ mkpath $tmpdir;
+
+ my $conffile = PVE::QemuConfig->config_file($vmid);
+ my $tmpfn = "$conffile.$$.tmp";
+ # disable interrupts (always do cleanups)
+ local $SIG{INT} =
+ local $SIG{TERM} =
+ local $SIG{QUIT} =
+ local $SIG{HUP} = sub { print STDERR "got interrupt - ignored\n"; };
+
+ # Note: $oldconf is undef if VM does not exists
+ my $cfs_path = PVE::QemuConfig->cfs_config_path($vmid);
+ my $oldconf = PVE::Cluster::cfs_read_file($cfs_path);
+
+ my $rpcenv = PVE::RPCEnvironment::get();
+ my $devinfo = {};
+
+ eval {
+ # enable interrupts
+ local $SIG{INT} =
+ local $SIG{TERM} =
+ local $SIG{QUIT} =
+ local $SIG{HUP} =
+ local $SIG{PIPE} = sub { die "interrupted by signal\n"; };
+
+ my $cfgfn = "$tmpdir/qemu-server.conf";
+ my $firewall_config_fn = "$tmpdir/fw.conf";
+ my $index_fn = "$tmpdir/index.json";
+
+ my $cmd = "restore";
+
+ my $param = [$pbs_backup_name, "index.json", $index_fn];
+ PVE::Storage::PBSPlugin::run_raw_client_cmd($scfg, $storeid, $cmd, $param);
+ my $index = PVE::Tools::file_get_contents($index_fn);
+ $index = decode_json($index);
+
+ # print Dumper($index);
+ foreach my $info (@{$index->{files}}) {
+ if ($info->{filename} =~ m/^(drive-\S+).img.fidx$/) {
+ my $devname = $1;
+ if ($info->{size} =~ m/^(\d+)$/) { # untaint size
+ $devinfo->{$devname}->{size} = $1;
+ } else {
+ die "unable to parse file size in 'index.json' - got '$info->{size}'\n";
+ }
+ }
+ }
+
+ my $is_qemu_server_backup = scalar(
+ grep { $_->{filename} eq 'qemu-server.conf.blob' } @{$index->{files}}
+ );
+ if (!$is_qemu_server_backup) {
+ die "backup does not look like a qemu-server backup (missing 'qemu-server.conf' file)\n";
+ }
+ my $has_firewall_config = scalar(grep { $_->{filename} eq 'fw.conf.blob' } @{$index->{files}});
+
+ $param = [$pbs_backup_name, "qemu-server.conf", $cfgfn];
+ PVE::Storage::PBSPlugin::run_raw_client_cmd($scfg, $storeid, $cmd, $param);
+
+ if ($has_firewall_config) {
+ $param = [$pbs_backup_name, "fw.conf", $firewall_config_fn];
+ PVE::Storage::PBSPlugin::run_raw_client_cmd($scfg, $storeid, $cmd, $param);
+
+ my $pve_firewall_dir = '/etc/pve/firewall';
+ mkdir $pve_firewall_dir; # make sure the dir exists
+ PVE::Tools::file_copy($firewall_config_fn, "${pve_firewall_dir}/$vmid.fw");
+ }
+
+ my $fh = IO::File->new($cfgfn, "r") ||
+ die "unable to read qemu-server.conf - $!\n";
+
+ my $virtdev_hash = $parse_backup_hints->($rpcenv, $user, $storecfg, $fh, $devinfo, $options);
+
+ # fixme: rate limit?
+
+ # create empty/temp config
+ PVE::Tools::file_set_contents($conffile, "memory: 128\nlock: create");
+
+ $restore_cleanup_oldconf->($storecfg, $vmid, $oldconf, $virtdev_hash) if $oldconf;
+
+ # allocate volumes
+ my $map = $restore_allocate_devices->($storecfg, $virtdev_hash, $vmid);
+
+ foreach my $virtdev (sort keys %$virtdev_hash) {
+ my $d = $virtdev_hash->{$virtdev};
+ next if $d->{is_cloudinit}; # no need to restore cloudinit
+
+ my $volid = $d->{volid};
+
+ my $path = PVE::Storage::path($storecfg, $volid);
+
+ # This is the ONLY user of the PBS_ env vars set on top of this function!
+ my $pbs_restore_cmd = [
+ '/usr/bin/pbs-restore',
+ '--repository', $repo,
+ $pbs_backup_name,
+ "$d->{devname}.img.fidx",
+ $path,
+ '--verbose',
+ ];
+
+ push @$pbs_restore_cmd, '--format', $d->{format} if $d->{format};
+ push @$pbs_restore_cmd, '--keyfile', $keyfile if -e $keyfile;
+
+ if (PVE::Storage::volume_has_feature($storecfg, 'sparseinit', $volid)) {
+ push @$pbs_restore_cmd, '--skip-zero';
+ }
+
+ my $dbg_cmdstring = PVE::Tools::cmd2string($pbs_restore_cmd);
+ print "restore proxmox backup image: $dbg_cmdstring\n";
+ run_command($pbs_restore_cmd);
+ }
+
+ $fh->seek(0, 0) || die "seek failed - $!\n";
+
+ my $outfd = new IO::File ($tmpfn, "w") ||
+ die "unable to write config for VM $vmid\n";
+
+ my $cookie = { netcount => 0 };
+ while (defined(my $line = <$fh>)) {
+ $restore_update_config_line->($outfd, $cookie, $vmid, $map, $line, $options->{unique});
+ }
+
+ $fh->close();
+ $outfd->close();
+ };
+ my $err = $@;
+
+ $restore_deactivate_volumes->($storecfg, $devinfo);
+
+ rmtree $tmpdir;
+
+ if ($err) {
+ unlink $tmpfn;
+ $restore_destroy_volumes->($storecfg, $devinfo);
+ die $err;
+ }
+
+ rename($tmpfn, $conffile) ||
+ die "unable to commit configuration file '$conffile'\n";
+
+ PVE::Cluster::cfs_update(); # make sure we read new file
+
+ eval { rescan($vmid, 1); };
+ warn $@ if $@;
+}
+