]>
Commit | Line | Data |
---|---|---|
a0208dbd SI |
1 | package PMG::API2::PBS::Job; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
6 | use POSIX qw(strftime); | |
7 | ||
8 | use PVE::JSONSchema qw(get_standard_option); | |
9 | use PVE::RESTHandler; | |
10 | use PVE::SafeSyslog; | |
11 | use PVE::Tools qw(extract_param); | |
12 | use PVE::PBSClient; | |
13 | ||
14 | use PMG::RESTEnvironment; | |
15 | use PMG::Backup; | |
16 | use PMG::PBSConfig; | |
7251cc51 | 17 | use PMG::PBSSchedule; |
a0208dbd SI |
18 | |
19 | use base qw(PVE::RESTHandler); | |
20 | ||
21 | __PACKAGE__->register_method ({ | |
22 | name => 'list', | |
23 | path => '', | |
24 | method => 'GET', | |
25 | description => "List all configured Proxmox Backup Server jobs.", | |
26 | permissions => { check => [ 'admin', 'audit' ] }, | |
27 | proxyto => 'node', | |
28 | protected => 1, | |
29 | parameters => { | |
30 | additionalProperties => 0, | |
31 | properties => { | |
32 | node => get_standard_option('pve-node'), | |
33 | }, | |
34 | }, | |
35 | returns => { | |
36 | type => "array", | |
37 | items => PMG::PBSConfig->createSchema(1), | |
38 | links => [ { rel => 'child', href => "{remote}" } ], | |
39 | }, | |
40 | code => sub { | |
41 | my ($param) = @_; | |
42 | ||
43 | my $res = []; | |
44 | ||
45 | my $conf = PMG::PBSConfig->new(); | |
46 | if (defined($conf)) { | |
47 | foreach my $remote (keys %{$conf->{ids}}) { | |
48 | my $d = $conf->{ids}->{$remote}; | |
49 | my $entry = { | |
50 | remote => $remote, | |
51 | server => $d->{server}, | |
52 | datastore => $d->{datastore}, | |
53 | }; | |
54 | push @$res, $entry; | |
55 | } | |
56 | } | |
57 | ||
58 | return $res; | |
59 | }}); | |
60 | ||
61 | __PACKAGE__->register_method ({ | |
62 | name => 'remote_index', | |
63 | path => '{remote}', | |
64 | method => 'GET', | |
65 | description => "Backup Job index.", | |
66 | parameters => { | |
67 | additionalProperties => 0, | |
68 | properties => { | |
69 | node => get_standard_option('pve-node'), | |
70 | remote => { | |
71 | description => "Proxmox Backup Server ID.", | |
72 | type => 'string', format => 'pve-configid', | |
73 | }, | |
74 | }, | |
75 | }, | |
76 | returns => { | |
77 | type => 'array', | |
78 | items => { | |
79 | type => "object", | |
80 | properties => { section => { type => 'string'} }, | |
81 | }, | |
82 | links => [ { rel => 'child', href => "{section}" } ], | |
83 | }, | |
84 | code => sub { | |
85 | my ($param) = @_; | |
86 | ||
87 | my $result = [ | |
88 | { section => 'snapshots' }, | |
89 | { section => 'backup' }, | |
90 | { section => 'restore' }, | |
91 | { section => 'timer' }, | |
92 | ]; | |
93 | return $result; | |
94 | }}); | |
95 | ||
96 | __PACKAGE__->register_method ({ | |
97 | name => 'get_snapshots', | |
98 | path => '{remote}/snapshots', | |
99 | method => 'GET', | |
100 | description => "Get snapshots stored on remote.", | |
101 | proxyto => 'node', | |
102 | protected => 1, | |
103 | permissions => { check => [ 'admin', 'audit' ] }, | |
104 | parameters => { | |
105 | additionalProperties => 0, | |
106 | properties => { | |
107 | node => get_standard_option('pve-node'), | |
108 | remote => { | |
109 | description => "Proxmox Backup Server ID.", | |
110 | type => 'string', format => 'pve-configid', | |
111 | }, | |
112 | }, | |
113 | }, | |
114 | returns => { | |
115 | type => 'array', | |
116 | items => { | |
117 | type => "object", | |
118 | properties => { | |
119 | time => { type => 'string'}, | |
120 | ctime => { type => 'string'}, | |
121 | size => { type => 'integer'}, | |
122 | }, | |
123 | }, | |
124 | links => [ { rel => 'child', href => "{time}" } ], | |
125 | }, | |
126 | code => sub { | |
127 | my ($param) = @_; | |
128 | ||
129 | my $remote = $param->{remote}; | |
130 | my $node = $param->{node}; | |
131 | ||
132 | my $conf = PMG::PBSConfig->new(); | |
133 | ||
134 | my $remote_config = $conf->{ids}->{$remote}; | |
135 | die "PBS remote '$remote' does not exist\n" if !$remote_config; | |
136 | ||
137 | return [] if $remote_config->{disable}; | |
138 | ||
139 | my $snap_param = { | |
140 | group => "host/$node", | |
141 | }; | |
142 | ||
143 | my $pbs = PVE::PBSClient->new($remote_config, $remote, $conf->{secret_dir}); | |
144 | my $snapshots = $pbs->get_snapshots($snap_param); | |
145 | my $res = []; | |
146 | foreach my $item (@$snapshots) { | |
147 | my $btype = $item->{"backup-type"}; | |
148 | my $bid = $item->{"backup-id"}; | |
149 | my $epoch = $item->{"backup-time"}; | |
150 | my $size = $item->{size} // 1; | |
151 | ||
152 | my @pxar = grep { $_->{filename} eq 'pmgbackup.pxar.didx' } @{$item->{files}}; | |
153 | die "unexpected number of pmgbackup archives in snapshot\n" if (scalar(@pxar) != 1); | |
154 | ||
155 | ||
156 | next if !($btype eq 'host'); | |
157 | next if !($bid eq $node); | |
158 | ||
159 | my $time = strftime("%FT%TZ", gmtime($epoch)); | |
160 | ||
161 | my $info = { | |
162 | time => $time, | |
163 | ctime => $epoch, | |
164 | size => $size, | |
165 | }; | |
166 | ||
167 | push @$res, $info; | |
168 | } | |
169 | ||
170 | return $res; | |
171 | }}); | |
172 | ||
173 | __PACKAGE__->register_method ({ | |
174 | name => 'forget_snapshot', | |
175 | path => '{remote}/snapshots/{time}', | |
176 | method => 'DELETE', | |
177 | description => "Forget a snapshot", | |
178 | proxyto => 'node', | |
179 | protected => 1, | |
180 | permissions => { check => [ 'admin', 'audit' ] }, | |
181 | parameters => { | |
182 | additionalProperties => 0, | |
183 | properties => { | |
184 | node => get_standard_option('pve-node'), | |
185 | remote => { | |
186 | description => "Proxmox Backup Server ID.", | |
187 | type => 'string', format => 'pve-configid', | |
188 | }, | |
189 | time => { | |
190 | description => "Backup time in RFC 3399 format", | |
191 | type => 'string', | |
192 | }, | |
193 | }, | |
194 | }, | |
195 | returns => {type => 'null' }, | |
196 | code => sub { | |
197 | my ($param) = @_; | |
198 | ||
199 | my $remote = $param->{remote}; | |
200 | my $node = $param->{node}; | |
201 | my $time = $param->{time}; | |
202 | ||
203 | my $snapshot = "host/$node/$time"; | |
204 | ||
205 | my $conf = PMG::PBSConfig->new(); | |
206 | ||
207 | my $remote_config = $conf->{ids}->{$remote}; | |
208 | die "PBS remote '$remote' does not exist\n" if !$remote_config; | |
209 | die "PBS remote '$remote' is disabled\n" if $remote_config->{disable}; | |
210 | ||
211 | my $pbs = PVE::PBSClient->new($remote_config, $remote, $conf->{secret_dir}); | |
212 | ||
213 | eval { | |
214 | $pbs->forget_snapshot($snapshot); | |
215 | }; | |
216 | die "Forgetting backup failed: $@" if $@; | |
217 | ||
218 | return; | |
219 | ||
220 | }}); | |
221 | ||
222 | __PACKAGE__->register_method ({ | |
223 | name => 'run_backup', | |
224 | path => '{remote}/backup', | |
225 | method => 'POST', | |
226 | description => "run backup and prune the backupgroup afterwards.", | |
227 | proxyto => 'node', | |
228 | protected => 1, | |
229 | permissions => { check => [ 'admin', 'audit' ] }, | |
230 | parameters => { | |
231 | additionalProperties => 0, | |
232 | properties => { | |
233 | node => get_standard_option('pve-node'), | |
234 | remote => { | |
235 | description => "Proxmox Backup Server ID.", | |
236 | type => 'string', format => 'pve-configid', | |
237 | }, | |
238 | }, | |
239 | }, | |
240 | returns => { type => "string" }, | |
241 | code => sub { | |
242 | my ($param) = @_; | |
243 | ||
244 | my $rpcenv = PMG::RESTEnvironment->get(); | |
245 | my $authuser = $rpcenv->get_user(); | |
246 | ||
247 | my $remote = $param->{remote}; | |
248 | my $node = $param->{node}; | |
249 | ||
250 | my $conf = PMG::PBSConfig->new(); | |
251 | ||
252 | my $remote_config = $conf->{ids}->{$remote}; | |
253 | die "PBS remote '$remote' does not exist\n" if !$remote_config; | |
254 | die "PBS remote '$remote' is disabled\n" if $remote_config->{disable}; | |
255 | ||
256 | my $pbs = PVE::PBSClient->new($remote_config, $remote, $conf->{secret_dir}); | |
257 | my $backup_dir = "/var/lib/pmg/backup/current"; | |
258 | ||
259 | my $worker = sub { | |
260 | my $upid = shift; | |
261 | ||
262 | print "starting update of current backup state\n"; | |
263 | ||
264 | -d $backup_dir || mkdir $backup_dir; | |
265 | PMG::Backup::pmg_backup($backup_dir, $param->{statistic}); | |
266 | my $pbs_opts = { | |
267 | type => 'host', | |
268 | id => $node, | |
269 | pxarname => 'pmgbackup', | |
270 | root => $backup_dir, | |
271 | }; | |
272 | ||
273 | $pbs->backup_tree($pbs_opts); | |
274 | ||
275 | print "backup finished\n"; | |
276 | ||
277 | my $group = "host/$node"; | |
278 | print "starting prune of $group\n"; | |
279 | my $prune_opts = $conf->prune_options($remote); | |
280 | my $res = $pbs->prune_group(undef, $prune_opts, $group); | |
281 | ||
282 | foreach my $pruned (@$res){ | |
283 | my $time = strftime("%FT%TZ", gmtime($pruned->{'backup-time'})); | |
284 | my $snap = $pruned->{'backup-type'} . '/' . $pruned->{'backup-id'} . '/' . $time; | |
285 | print "pruned snapshot: $snap\n"; | |
286 | } | |
287 | ||
288 | print "prune finished\n"; | |
289 | ||
290 | return; | |
291 | }; | |
292 | ||
293 | return $rpcenv->fork_worker('pbs_backup', undef, $authuser, $worker); | |
294 | ||
295 | }}); | |
296 | ||
297 | __PACKAGE__->register_method ({ | |
298 | name => 'restore', | |
299 | path => '{remote}/restore', | |
300 | method => 'POST', | |
301 | description => "Restore the system configuration.", | |
302 | permissions => { check => [ 'admin' ] }, | |
303 | proxyto => 'node', | |
304 | protected => 1, | |
305 | parameters => { | |
306 | additionalProperties => 0, | |
307 | properties => { | |
308 | PMG::Backup::get_restore_options(), | |
309 | remote => { | |
310 | description => "Proxmox Backup Server ID.", | |
311 | type => 'string', format => 'pve-configid', | |
312 | }, | |
313 | 'backup-time' => {description=> "backup-time to restore", | |
314 | optional => 1, type => 'string' | |
315 | }, | |
316 | 'backup-id' => {description => "backup-id (hostname) of backup snapshot", | |
317 | optional => 1, type => 'string' | |
318 | }, | |
319 | }, | |
320 | }, | |
321 | returns => { type => "string" }, | |
322 | code => sub { | |
323 | my ($param) = @_; | |
324 | ||
325 | my $rpcenv = PMG::RESTEnvironment->get(); | |
326 | my $authuser = $rpcenv->get_user(); | |
327 | ||
328 | my $remote = $param->{remote}; | |
329 | my $backup_id = $param->{'backup-id'} // $param->{node}; | |
330 | my $snapshot = "host/$backup_id"; | |
331 | $snapshot .= "/$param->{'backup-time'}" if defined($param->{'backup-time'}); | |
332 | ||
333 | my $conf = PMG::PBSConfig->new(); | |
334 | ||
335 | my $remote_config = $conf->{ids}->{$remote}; | |
336 | die "PBS remote '$remote' does not exist\n" if !$remote_config; | |
337 | die "PBS remote '$remote' is disabled\n" if $remote_config->{disable}; | |
338 | ||
339 | my $pbs = PVE::PBSClient->new($remote_config, $remote, $conf->{secret_dir}); | |
340 | ||
341 | my $time = time; | |
342 | my $dirname = "/tmp/proxrestore_$$.$time"; | |
343 | ||
344 | $param->{database} //= 1; | |
345 | ||
346 | die "nothing selected - please select what you want to restore (config or database?)\n" | |
347 | if !($param->{database} || $param->{config}); | |
348 | ||
349 | my $pbs_opts = { | |
350 | pxarname => 'pmgbackup', | |
351 | target => $dirname, | |
352 | snapshot => $snapshot, | |
353 | }; | |
354 | ||
355 | my $worker = sub { | |
356 | my $upid = shift; | |
357 | ||
358 | print "starting restore of $snapshot from $remote\n"; | |
359 | ||
360 | $pbs->restore_pxar($pbs_opts); | |
361 | print "starting restore of PMG config\n"; | |
362 | PMG::Backup::pmg_restore($dirname, $param->{database}, | |
363 | $param->{config}, $param->{statistic}); | |
364 | print "restore finished\n"; | |
365 | ||
366 | return; | |
367 | }; | |
368 | ||
369 | return $rpcenv->fork_worker('pbs_restore', undef, $authuser, $worker); | |
370 | }}); | |
371 | ||
7251cc51 SI |
372 | __PACKAGE__->register_method ({ |
373 | name => 'create_timer', | |
374 | path => '{remote}/timer', | |
375 | method => 'POST', | |
376 | description => "Create backup schedule", | |
377 | proxyto => 'node', | |
378 | protected => 1, | |
379 | permissions => { check => [ 'admin', 'audit' ] }, | |
380 | parameters => { | |
381 | additionalProperties => 0, | |
382 | properties => { | |
383 | node => get_standard_option('pve-node'), | |
384 | remote => { | |
385 | description => "Proxmox Backup Server ID.", | |
386 | type => 'string', format => 'pve-configid', | |
387 | }, | |
388 | schedule => { | |
389 | description => "Schedule for the backup (OnCalendar setting of the systemd.timer)", | |
390 | type => 'string', pattern => '[0-9a-zA-Z*.:,\-/ ]+', | |
391 | default => 'daily', optional => 1, | |
392 | }, | |
393 | delay => { | |
394 | description => "Randomized delay to add to the starttime (RandomizedDelaySec setting of the systemd.timer)", | |
395 | type => 'string', pattern => '[0-9a-zA-Z. ]+', | |
396 | default => 'daily', optional => 1, | |
397 | }, | |
398 | }, | |
399 | }, | |
400 | returns => { type => 'null' }, | |
401 | code => sub { | |
402 | my ($param) = @_; | |
403 | ||
404 | my $remote = $param->{remote}; | |
405 | my $schedule = $param->{schedule} // 'daily'; | |
406 | my $delay = $param->{delay} // '5min'; | |
407 | ||
408 | my $conf = PMG::PBSConfig->new(); | |
409 | ||
410 | my $remote_config = $conf->{ids}->{$remote}; | |
411 | die "PBS remote '$remote' does not exist\n" if !$remote_config; | |
412 | die "PBS remote '$remote' is disabled\n" if $remote_config->{disable}; | |
413 | ||
414 | PMG::PBSSchedule::create_schedule($remote, $schedule, $delay); | |
415 | ||
416 | }}); | |
417 | ||
418 | __PACKAGE__->register_method ({ | |
419 | name => 'delete_timer', | |
420 | path => '{remote}/timer', | |
421 | method => 'DELETE', | |
422 | description => "Delete backup schedule", | |
423 | proxyto => 'node', | |
424 | protected => 1, | |
425 | permissions => { check => [ 'admin', 'audit' ] }, | |
426 | parameters => { | |
427 | additionalProperties => 0, | |
428 | properties => { | |
429 | node => get_standard_option('pve-node'), | |
430 | remote => { | |
431 | description => "Proxmox Backup Server ID.", | |
432 | type => 'string', format => 'pve-configid', | |
433 | }, | |
434 | }, | |
435 | }, | |
436 | returns => { type => 'null' }, | |
437 | code => sub { | |
438 | my ($param) = @_; | |
439 | ||
440 | my $remote = $param->{remote}; | |
441 | ||
442 | PMG::PBSSchedule::delete_schedule($remote); | |
443 | ||
444 | }}); | |
445 | ||
446 | __PACKAGE__->register_method ({ | |
447 | name => 'list_timer', | |
448 | path => '{remote}/timer', | |
449 | method => 'GET', | |
450 | description => "Get timer specification", | |
451 | proxyto => 'node', | |
452 | protected => 1, | |
453 | permissions => { check => [ 'admin', 'audit' ] }, | |
454 | parameters => { | |
455 | additionalProperties => 0, | |
456 | properties => { | |
457 | node => get_standard_option('pve-node'), | |
458 | remote => { | |
459 | description => "Proxmox Backup Server ID.", | |
460 | type => 'string', format => 'pve-configid', | |
461 | }, | |
462 | }, | |
463 | }, | |
464 | returns => { type => 'object', properties => { | |
465 | remote => { | |
466 | description => "Proxmox Backup Server ID.", | |
467 | type => 'string', format => 'pve-configid', | |
468 | optional => 1, | |
469 | }, | |
470 | schedule => { | |
471 | description => "Schedule for the backup (OnCalendar setting of the systemd.timer)", | |
472 | type => 'string', pattern => '[0-9a-zA-Z*.:,\-/ ]+', | |
473 | default => 'daily', optional => 1, | |
474 | }, | |
475 | delay => { | |
476 | description => "Randomized delay to add to the starttime (RandomizedDelaySec setting of the systemd.timer)", | |
477 | type => 'string', pattern => '[0-9a-zA-Z. ]+', | |
478 | default => 'daily', optional => 1, | |
479 | }, | |
480 | unitfile => { | |
481 | description => "unit file for the systemd.timer unit", | |
482 | type => 'string', optional => 1, | |
483 | }, | |
484 | }}, | |
485 | code => sub { | |
486 | my ($param) = @_; | |
487 | ||
488 | my $remote = $param->{remote}; | |
489 | ||
490 | my $schedules = PMG::PBSSchedule::get_schedules(); | |
491 | my @data = grep {$_->{remote} eq $remote} @$schedules; | |
492 | ||
493 | my $res = {}; | |
494 | if (scalar(@data) == 1) { | |
495 | $res = $data[0]; | |
496 | } | |
497 | ||
498 | return $res | |
499 | }}); | |
500 | ||
a0208dbd | 501 | 1; |