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