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