]> git.proxmox.com Git - pve-manager.git/blame - PVE/API2/APT.pm
api apt: fix spelling in hash to avoid undef access warnings
[pve-manager.git] / PVE / API2 / APT.pm
CommitLineData
21299915
DM
1package PVE::API2::APT;
2
3use strict;
4use warnings;
8794761f
DM
5
6use POSIX;
cd0bc36b 7use File::stat ();
a88002cf 8use IO::File;
d330e26f 9use File::Basename;
21299915 10
f5ed75de
DM
11use LWP::UserAgent;
12
9c767422
FE
13use Proxmox::RS::APT::Repositories;
14
8794761f 15use PVE::pvecfg;
21299915 16use PVE::Tools qw(extract_param);
f5ed75de 17use PVE::Cluster;
3ac3653e 18use PVE::DataCenterConfig;
21299915
DM
19use PVE::SafeSyslog;
20use PVE::INotify;
396c9e4a 21use PVE::Exception;
21299915
DM
22use PVE::RESTHandler;
23use PVE::RPCEnvironment;
446b9669 24use PVE::API2Tools;
21299915 25
cd0bc36b 26use JSON;
21299915
DM
27use PVE::JSONSchema qw(get_standard_option);
28
29use AptPkg::Cache;
21299915 30use AptPkg::PkgRecords;
ee94b16a 31use AptPkg::System;
21299915 32
21299915 33my $get_apt_cache = sub {
89e4fc8c 34
c06e9cc8 35 my $apt_cache = AptPkg::Cache->new() || die "unable to initialize AptPkg::Cache\n";
21299915
DM
36
37 return $apt_cache;
38};
39
40use base qw(PVE::RESTHandler);
41
42__PACKAGE__->register_method({
89e4fc8c
TL
43 name => 'index',
44 path => '',
21299915
DM
45 method => 'GET',
46 description => "Directory index for apt (Advanced Package Tool).",
47 permissions => {
48 user => 'all',
49 },
50 parameters => {
89e4fc8c 51 additionalProperties => 0,
21299915
DM
52 properties => {
53 node => get_standard_option('pve-node'),
54 },
55 },
56 returns => {
57 type => "array",
58 items => {
59 type => "object",
60 properties => {
61 id => { type => 'string' },
62 },
63 },
64 links => [ { rel => 'child', href => "{id}" } ],
65 },
66 code => sub {
67 my ($param) = @_;
68
89e4fc8c 69 my $res = [
21299915 70 { id => 'changelog' },
9005e0df 71 { id => 'repositories' },
8794761f
DM
72 { id => 'update' },
73 { id => 'versions' },
21299915
DM
74 ];
75
76 return $res;
77 }});
78
a972b69d
DM
79my $get_pkgfile = sub {
80 my ($veriter) = @_;
21299915 81
a972b69d 82 foreach my $verfile (@{$veriter->{FileList}}) {
00d48356 83 my $pkgfile = $verfile->{File};
a972b69d
DM
84 next if !$pkgfile->{Origin};
85 return $pkgfile;
86 }
87
88 return undef;
89};
90
91my $get_changelog_url =sub {
2bbfac1f 92 my ($pkgname, $info, $pkgver, $pkgfile) = @_;
a972b69d 93
b8122787
TL
94 my $base;
95 $base = dirname($info->{FileName}) if defined($info->{FileName});
2bbfac1f
LN
96
97 my $origin = $pkgfile->{Origin};
2bbfac1f 98
a972b69d
DM
99 if ($origin && $base) {
100 $pkgver =~ s/^\d+://; # strip epoch
101 my $srcpkg = $info->{SourcePkg} || $pkgname;
59263581 102 if ($origin eq 'Debian' || $origin eq 'Debian Backports') {
702ecae1 103 $base =~ s!pool/updates/!pool/!; # for security channel
a8cac426 104 return "http://packages.debian.org/changelogs/$base/${srcpkg}_${pkgver}/changelog";
09c67ebb 105 } elsif ($origin eq 'Proxmox') {
a8cac426
TL
106 # the product is just for getting the standard repos and is currently a required param,
107 # but we actually only care about `files`, which includes _all_ configured repos
2bbfac1f
LN
108 my $data = Proxmox::RS::APT::Repositories::repositories("pve");
109
110 for my $file ($data->{files}->@*) {
a8cac426 111 for my $repo (grep { $_->{Enabled} } $file->{repositories}->@*) {
a689d3c0 112 next if !grep(/$pkgfile->{Component}/, $repo->{Components}->@*);
a8cac426
TL
113 next if !$repo->{URIs}[0] =~ m/$pkgfile->{Site}/;
114
115 return $repo->{URIs}[0] . "/$base/${pkgname}_${pkgver}.changelog";
2bbfac1f 116 }
2ba6d822 117 }
00d48356
DM
118 }
119 }
a8cac426 120 return; # none found, with our heuristic that is..
b688d438
DM
121};
122
123my $assemble_pkginfo = sub {
124 my ($pkgname, $info, $current_ver, $candidate_ver) = @_;
00d48356 125
89e4fc8c 126 my $data = {
21299915
DM
127 Package => $info->{Name},
128 Title => $info->{ShortDesc},
a972b69d 129 Origin => 'unknown',
21299915
DM
130 };
131
a972b69d 132 if (my $pkgfile = &$get_pkgfile($candidate_ver)) {
2ba6d822 133 $data->{Origin} = $pkgfile->{Origin};
913d8933 134 my $changelog_url = $get_changelog_url->($pkgname, $info, $candidate_ver->{VerStr}, $pkgfile);
c3264e92
TL
135
136 $data->{ChangeLogUrl} = $changelog_url if $changelog_url;
b688d438 137 }
00d48356 138
21299915
DM
139 if (my $desc = $info->{LongDesc}) {
140 $desc =~ s/^.*\n\s?//; # remove first line
141 $desc =~ s/\n / /g;
142 $data->{Description} = $desc;
143 }
89e4fc8c 144
21299915
DM
145 foreach my $k (qw(Section Arch Priority)) {
146 $data->{$k} = $candidate_ver->{$k};
147 }
148
149 $data->{Version} = $candidate_ver->{VerStr};
93305404 150 $data->{OldVersion} = $current_ver->{VerStr} if $current_ver;
21299915
DM
151
152 return $data;
153};
154
4806bc69
DM
155# we try to cache results
156my $pve_pkgstatus_fn = "/var/lib/pve-manager/pkgupdates";
745d942d 157my $read_cached_pkgstatus = sub {
c3264e92
TL
158 my $data = eval { decode_json(PVE::Tools::file_get_contents($pve_pkgstatus_fn, 5*1024*1024)) } // [];
159 warn "error reading cached package status in '$pve_pkgstatus_fn' - $@\n" if $@;
745d942d
DM
160 return $data;
161};
162
4806bc69 163my $update_pve_pkgstatus = sub {
446f9217
DM
164 syslog('info', "update new package list: $pve_pkgstatus_fn");
165
745d942d 166 my $oldpkglist = &$read_cached_pkgstatus();
c3264e92 167 my $notify_status = { map { $_->{Package} => $_->{NotifyStatus} } $oldpkglist->@* };
745d942d 168
4806bc69
DM
169 my $pkglist = [];
170
171 my $cache = &$get_apt_cache();
172 my $policy = $cache->policy;
173 my $pkgrecords = $cache->packages();
174
175 foreach my $pkgname (keys %$cache) {
176 my $p = $cache->{$pkgname};
2051816c 177 next if !$p->{SelectedState} || ($p->{SelectedState} ne 'Install');
26f876ce
DM
178 my $current_ver = $p->{CurrentVer} || next;
179 my $candidate_ver = $policy->candidate($p) || next;
913d8933
TL
180 next if $current_ver->{VerStr} eq $candidate_ver->{VerStr};
181
182 my $info = $pkgrecords->lookup($pkgname);
183 my $res = &$assemble_pkginfo($pkgname, $info, $current_ver, $candidate_ver);
184 push @$pkglist, $res;
185
186 # also check if we need any new package
187 # Note: this is just a quick hack (not recursive as it should be), because
188 # I found no way to get that info from AptPkg
189 my $deps = $candidate_ver->{DependsList} || next;
190
191 my ($found, $req);
192 for my $d (@$deps) {
193 if ($d->{DepType} eq 'Depends') {
194 $found = $d->{TargetPkg}->{SelectedState} eq 'Install' if !$found;
195 # need to check ProvidesList for virtual packages
196 if (!$found && (my $provides = $d->{TargetPkg}->{ProvidesList})) {
197 for my $provide ($provides->@*) {
198 $found = $provide->{OwnerPkg}->{SelectedState} eq 'Install';
199 last if $found;
200 }
201 }
202 $req = $d->{TargetPkg} if !$req;
203
204 if (!($d->{CompType} & AptPkg::Dep::Or)) {
205 if (!$found && $req) { # New required Package
206 my $tpname = $req->{Name};
207 my $tpinfo = $pkgrecords->lookup($tpname);
208 my $tpcv = $policy->candidate($req);
209 if ($tpinfo && $tpcv) {
210 my $res = &$assemble_pkginfo($tpname, $tpinfo, undef, $tpcv);
211 push @$pkglist, $res;
93305404
DM
212 }
213 }
913d8933
TL
214 undef $found;
215 undef $req;
93305404
DM
216 }
217 }
4806bc69
DM
218 }
219 }
745d942d
DM
220
221 # keep notification status (avoid sending mails abou new packages more than once)
222 foreach my $pi (@$pkglist) {
223 if (my $ns = $notify_status->{$pi->{Package}}) {
224 $pi->{NotifyStatus} = $ns if $ns eq $pi->{Version};
225 }
226 }
227
4806bc69
DM
228 PVE::Tools::file_set_contents($pve_pkgstatus_fn, encode_json($pkglist));
229
230 return $pkglist;
231};
232
21299915 233__PACKAGE__->register_method({
89e4fc8c
TL
234 name => 'list_updates',
235 path => 'update',
21299915
DM
236 method => 'GET',
237 description => "List available updates.",
238 permissions => {
239 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
240 },
241 protected => 1,
242 proxyto => 'node',
243 parameters => {
89e4fc8c 244 additionalProperties => 0,
21299915
DM
245 properties => {
246 node => get_standard_option('pve-node'),
247 },
248 },
249 returns => {
250 type => "array",
251 items => {
252 type => "object",
253 properties => {},
254 },
255 },
256 code => sub {
257 my ($param) = @_;
258
cd0bc36b
DM
259 if (my $st1 = File::stat::stat($pve_pkgstatus_fn)) {
260 my $st2 = File::stat::stat("/var/cache/apt/pkgcache.bin");
261 my $st3 = File::stat::stat("/var/lib/dpkg/status");
89e4fc8c 262
446f9217 263 if ($st2 && $st3 && $st2->mtime <= $st1->mtime && $st3->mtime <= $st1->mtime) {
745d942d 264 if (my $data = &$read_cached_pkgstatus()) {
cd0bc36b
DM
265 return $data;
266 }
267 }
268 }
269
4806bc69
DM
270 my $pkglist = &$update_pve_pkgstatus();
271
272 return $pkglist;
273 }});
274
275__PACKAGE__->register_method({
89e4fc8c
TL
276 name => 'update_database',
277 path => 'update',
4806bc69 278 method => 'POST',
ecddd2e2 279 description => "This is used to resynchronize the package index files from their sources (apt-get update).",
4806bc69
DM
280 permissions => {
281 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
282 },
283 protected => 1,
284 proxyto => 'node',
285 parameters => {
89e4fc8c 286 additionalProperties => 0,
4806bc69
DM
287 properties => {
288 node => get_standard_option('pve-node'),
a88002cf
DM
289 notify => {
290 type => 'boolean',
291 description => "Send notification mail about new packages (to email address specified for user 'root\@pam').",
292 optional => 1,
293 default => 0,
294 },
2544e8d5
DM
295 quiet => {
296 type => 'boolean',
297 description => "Only produces output suitable for logging, omitting progress indicators.",
298 optional => 1,
299 default => 0,
300 },
4806bc69
DM
301 },
302 },
303 returns => {
304 type => 'string',
305 },
306 code => sub {
307 my ($param) = @_;
308
309 my $rpcenv = PVE::RPCEnvironment::get();
21299915 310
4806bc69 311 my $authuser = $rpcenv->get_user();
21299915 312
4806bc69
DM
313 my $realcmd = sub {
314 my $upid = shift;
21299915 315
19ed44c0
DM
316 # setup proxy for apt
317 my $dcconf = PVE::Cluster::cfs_read_file('datacenter.cfg');
318
319 my $aptconf = "// no proxy configured\n";
320 if ($dcconf->{http_proxy}) {
321 $aptconf = "Acquire::http::Proxy \"$dcconf->{http_proxy}\";\n";
322 }
323 my $aptcfn = "/etc/apt/apt.conf.d/76pveproxy";
324 PVE::Tools::file_set_contents($aptcfn, $aptconf);
325
ecddd2e2 326 my $cmd = ['apt-get', 'update'];
21299915 327
ecddd2e2 328 print "starting apt-get update\n" if !$param->{quiet};
89e4fc8c 329
f460dc12
DM
330 if ($param->{quiet}) {
331 PVE::Tools::run_command($cmd, outfunc => sub {}, errfunc => sub {});
332 } else {
333 PVE::Tools::run_command($cmd);
334 }
4806bc69 335
a88002cf
DM
336 my $pkglist = &$update_pve_pkgstatus();
337
338 if ($param->{notify} && scalar(@$pkglist)) {
339
340 my $usercfg = PVE::Cluster::cfs_read_file("user.cfg");
341 my $rootcfg = $usercfg->{users}->{'root@pam'} || {};
342 my $mailto = $rootcfg->{email};
343
344 if ($mailto) {
345 my $hostname = `hostname -f` || PVE::INotify::nodename();
346 chomp $hostname;
4b152656 347 my $mailfrom = $dcconf->{email_from} || "root";
d49b7037 348 my $subject = "New software packages available ($hostname)";
a88002cf 349
d49b7037 350 my $data = "The following updates are available:\n\n";
a88002cf 351
d97a4468 352 my $count = 0;
a88002cf 353 foreach my $p (sort {$a->{Package} cmp $b->{Package} } @$pkglist) {
d97a4468
DM
354 next if $p->{NotifyStatus} && $p->{NotifyStatus} eq $p->{Version};
355 $count++;
93305404
DM
356 if ($p->{OldVersion}) {
357 $data .= "$p->{Package}: $p->{OldVersion} ==> $p->{Version}\n";
358 } else {
359 $data .= "$p->{Package}: $p->{Version} (new)\n";
360 }
a88002cf
DM
361 }
362
d49b7037 363 return if !$count;
a88002cf 364
d49b7037 365 PVE::Tools::sendmail($mailto, $subject, $data, undef, $mailfrom, '');
745d942d
DM
366
367 foreach my $pi (@$pkglist) {
368 $pi->{NotifyStatus} = $pi->{Version};
369 }
370 PVE::Tools::file_set_contents($pve_pkgstatus_fn, encode_json($pkglist));
a88002cf
DM
371 }
372 }
4806bc69
DM
373
374 return;
375 };
376
c2d3fbe0 377 return $rpcenv->fork_worker('aptupdate', undef, $authuser, $realcmd);
cd0bc36b 378
21299915
DM
379 }});
380
b688d438 381__PACKAGE__->register_method({
89e4fc8c
TL
382 name => 'changelog',
383 path => 'changelog',
b688d438
DM
384 method => 'GET',
385 description => "Get package changelogs.",
386 permissions => {
387 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
388 },
a25fa6e9 389 proxyto => 'node',
b688d438 390 parameters => {
89e4fc8c 391 additionalProperties => 0,
b688d438
DM
392 properties => {
393 node => get_standard_option('pve-node'),
394 name => {
395 description => "Package name.",
396 type => 'string',
397 },
398 version => {
399 description => "Package version.",
400 type => 'string',
401 optional => 1,
89e4fc8c 402 },
b688d438
DM
403 },
404 },
405 returns => {
406 type => "string",
407 },
408 code => sub {
409 my ($param) = @_;
410
411 my $pkgname = $param->{name};
412
413 my $cache = &$get_apt_cache();
414 my $policy = $cache->policy;
415 my $p = $cache->{$pkgname} || die "no such package '$pkgname'\n";
416 my $pkgrecords = $cache->packages();
417
418 my $ver;
419 if ($param->{version}) {
420 if (my $available = $p->{VersionList}) {
421 for my $v (@$available) {
422 if ($v->{VerStr} eq $param->{version}) {
423 $ver = $v;
424 last;
425 }
426 }
427 }
76189130 428 die "package '$pkgname' version '$param->{version}' is not available\n" if !$ver;
b688d438
DM
429 } else {
430 $ver = $policy->candidate($p) || die "no installation candidate for package '$pkgname'\n";
431 }
432
433 my $info = $pkgrecords->lookup($pkgname);
434
ef2c6453 435 my $pkgfile = $get_pkgfile->($ver) or die "couldn't find package info file for ${pkgname}=$ver->{VerStr}\n";
a972b69d 436
ef2c6453
TL
437 my $url = $get_changelog_url->($pkgname, $info, $ver->{VerStr}, $pkgfile)
438 or die "changelog for '${pkgname}_$ver->{VerStr}' not available\n";
b688d438 439
ef2c6453 440 my $ua = LWP::UserAgent->new();
f5ed75de
DM
441 $ua->agent("PVE/1.0");
442 $ua->timeout(10);
ef2c6453 443 $ua->max_size(1024 * 1024);
2ba6d822
DM
444 $ua->ssl_opts(verify_hostname => 0); # don't care for changelogs
445
ef2c6453
TL
446 my $datacenter_cfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
447 if (my $proxy = $datacenter_cfg->{http_proxy}) {
f494187f 448 $ua->proxy(['http', 'https'], $proxy);
f5ed75de
DM
449 } else {
450 $ua->env_proxy;
451 }
452
2ba6d822 453 if ($pkgfile->{Origin} eq 'Proxmox' && $pkgfile->{Component} eq 'pve-enterprise') {
d017de1f
FG
454 my $info = PVE::API2::Subscription::read_etc_subscription();
455 if ($info->{status} eq 'active') {
ef2c6453
TL
456 my $pw = PVE::API2Tools::get_hwaddress();
457 $ua->credentials("enterprise.proxmox.com:443", 'pve-enterprise-repository', $info->{key}, $pw);
2ba6d822
DM
458 }
459 }
460
f5ed75de 461 my $response = $ua->get($url);
b688d438 462
ef2c6453
TL
463 if ($response->is_success) {
464 return $response->decoded_content;
465 } else {
396c9e4a 466 PVE::Exception::raise($response->message, code => $response->code);
ef2c6453
TL
467 }
468 return '';
b688d438
DM
469 }});
470
9005e0df
FE
471__PACKAGE__->register_method({
472 name => 'repositories',
473 path => 'repositories',
474 method => 'GET',
475 proxyto => 'node',
476 description => "Get APT repository information.",
477 permissions => {
478 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
479 },
480 parameters => {
481 additionalProperties => 0,
482 properties => {
483 node => get_standard_option('pve-node'),
484 },
485 },
486 returns => {
487 type => "object",
488 description => "Result from parsing the APT repository files in /etc/apt/.",
489 properties => {
490 files => {
491 type => "array",
492 description => "List of parsed repository files.",
493 items => {
494 type => "object",
495 properties => {
496 path => {
497 type => "string",
498 description => "Path to the problematic file.",
499 },
500 'file-type' => {
501 type => "string",
502 enum => [ 'list', 'sources' ],
503 description => "Format of the file.",
504 },
505 repositories => {
506 type => "array",
507 description => "The parsed repositories.",
508 items => {
509 type => "object",
510 properties => {
511 Types => {
512 type => "array",
513 description => "List of package types.",
514 items => {
515 type => "string",
516 enum => [ 'deb', 'deb-src' ],
517 },
518 },
519 URIs => {
520 description => "List of repository URIs.",
521 type => "array",
522 items => {
523 type => "string",
524 },
525 },
526 Suites => {
527 type => "array",
528 description => "List of package distribuitions",
529 items => {
530 type => "string",
531 },
532 },
533 Components => {
534 type => "array",
535 description => "List of repository components",
536 optional => 1, # not present if suite is absolute
537 items => {
538 type => "string",
539 },
540 },
541 Options => {
542 type => "array",
543 description => "Additional options",
544 optional => 1,
545 items => {
546 type => "object",
547 properties => {
548 Key => {
549 type => "string",
550 },
551 Values => {
552 type => "array",
553 items => {
554 type => "string",
555 },
556 },
557 },
558 },
559 },
560 Comment => {
561 type => "string",
562 description => "Associated comment",
563 optional => 1,
564 },
565 FileType => {
566 type => "string",
567 enum => [ 'list', 'sources' ],
568 description => "Format of the defining file.",
569 },
570 Enabled => {
571 type => "boolean",
572 description => "Whether the repository is enabled or not",
573 },
574 },
575 },
576 },
577 digest => {
578 type => "array",
579 description => "Digest of the file as bytes.",
580 items => {
581 type => "integer",
582 },
583 },
584 },
585 },
586 },
587 errors => {
588 type => "array",
589 description => "List of problematic repository files.",
590 items => {
591 type => "object",
592 properties => {
593 path => {
594 type => "string",
595 description => "Path to the problematic file.",
596 },
597 error => {
598 type => "string",
599 description => "The error message",
600 },
601 },
602 },
603 },
604 digest => {
605 type => "string",
606 description => "Common digest of all files.",
607 },
608 infos => {
609 type => "array",
610 description => "Additional information/warnings for APT repositories.",
611 items => {
612 type => "object",
613 properties => {
614 path => {
615 type => "string",
616 description => "Path to the associated file.",
617 },
618 index => {
619 type => "string",
620 description => "Index of the associated repository within the file.",
621 },
622 property => {
623 type => "string",
624 description => "Property from which the info originates.",
625 optional => 1,
626 },
627 kind => {
628 type => "string",
629 description => "Kind of the information (e.g. warning).",
630 },
631 message => {
632 type => "string",
633 description => "Information message.",
634 }
635 },
636 },
637 },
638 'standard-repos' => {
639 type => "array",
640 description => "List of standard repositories and their configuration status",
641 items => {
642 type => "object",
643 properties => {
644 handle => {
645 type => "string",
646 description => "Handle to identify the repository.",
647 },
648 name => {
649 type => "string",
650 description => "Full name of the repository.",
651 },
652 status => {
653 type => "boolean",
654 optional => 1,
655 description => "Indicating enabled/disabled status, if the " .
656 "repository is configured.",
657 },
658 },
659 },
660 },
661 },
662 },
663 code => sub {
664 my ($param) = @_;
665
9c767422 666 return Proxmox::RS::APT::Repositories::repositories("pve");
9005e0df
FE
667 }});
668
7e33f74b
FE
669__PACKAGE__->register_method({
670 name => 'add_repository',
671 path => 'repositories',
672 method => 'PUT',
673 description => "Add a standard repository to the configuration",
674 permissions => {
675 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
676 },
677 protected => 1,
678 proxyto => 'node',
679 parameters => {
680 additionalProperties => 0,
681 properties => {
682 node => get_standard_option('pve-node'),
683 handle => {
684 type => 'string',
685 description => "Handle that identifies a repository.",
686 },
687 digest => {
688 type => "string",
689 description => "Digest to detect modifications.",
690 maxLength => 80,
691 optional => 1,
692 },
693 },
694 },
695 returns => {
696 type => 'null',
697 },
698 code => sub {
699 my ($param) = @_;
700
9c767422 701 Proxmox::RS::APT::Repositories::add_repository($param->{handle}, "pve", $param->{digest});
7e33f74b
FE
702 }});
703
704__PACKAGE__->register_method({
705 name => 'change_repository',
706 path => 'repositories',
707 method => 'POST',
708 description => "Change the properties of a repository. Currently only allows enabling/disabling.",
709 permissions => {
710 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
711 },
712 protected => 1,
713 proxyto => 'node',
714 parameters => {
715 additionalProperties => 0,
716 properties => {
717 node => get_standard_option('pve-node'),
718 path => {
719 type => 'string',
720 description => "Path to the containing file.",
721 },
722 index => {
723 type => 'integer',
724 description => "Index within the file (starting from 0).",
725 },
726 enabled => {
727 type => 'boolean',
728 description => "Whether the repository should be enabled or not.",
729 optional => 1,
730 },
731 digest => {
732 type => "string",
733 description => "Digest to detect modifications.",
734 maxLength => 80,
735 optional => 1,
736 },
737 },
738 },
739 returns => {
740 type => 'null',
741 },
742 code => sub {
743 my ($param) = @_;
744
d3cd3c9c
FE
745 my $options = {};
746
747 my $enabled = $param->{enabled};
748 $options->{enabled} = int($enabled) if defined($enabled);
7e33f74b 749
9c767422 750 Proxmox::RS::APT::Repositories::change_repository(
7e33f74b 751 $param->{path},
d3cd3c9c 752 int($param->{index}),
7e33f74b
FE
753 $options,
754 $param->{digest}
755 );
756 }});
757
8794761f 758__PACKAGE__->register_method({
89e4fc8c
TL
759 name => 'versions',
760 path => 'versions',
8794761f 761 method => 'GET',
502a69ef 762 proxyto => 'node',
8794761f
DM
763 description => "Get package information for important Proxmox packages.",
764 permissions => {
765 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
766 },
767 parameters => {
89e4fc8c 768 additionalProperties => 0,
8794761f
DM
769 properties => {
770 node => get_standard_option('pve-node'),
771 },
772 },
773 returns => {
774 type => "array",
775 items => {
776 type => "object",
777 properties => {},
778 },
779 },
780 code => sub {
781 my ($param) = @_;
782
8794761f
DM
783 my $cache = &$get_apt_cache();
784 my $policy = $cache->policy;
785 my $pkgrecords = $cache->packages();
786
c861591b 787 # order most important things first
97f6e029 788 my @list = qw(proxmox-ve pve-manager);
8794761f 789
ee94b16a
FG
790 my $aptver = $AptPkg::System::_system->versioning();
791 my $byver = sub { $aptver->compare($cache->{$b}->{CurrentVer}->{VerStr}, $cache->{$a}->{CurrentVer}->{VerStr}) };
0459599d 792 push @list, sort $byver grep { /^pve-kernel-/ && $cache->{$_}->{CurrentState} eq 'Installed' } keys %$cache;
c861591b
TL
793
794 my @opt_pack = qw(
795 ceph
fcb80221 796 criu
c861591b 797 gfs2-utils
2e0f7840
TL
798 ifupdown
799 ifupdown2
fcb80221
TL
800 ksm-control-daemon
801 ksmtuned
c861591b 802 libpve-apiclient-perl
4047ea24 803 libpve-network-perl
c861591b 804 openvswitch-switch
8794bb15 805 proxmox-backup-file-restore
d088f89b 806 proxmox-offline-mirror-helper
a7a65b02 807 pve-zsync
c861591b
TL
808 zfsutils-linux
809 );
810
811 my @pkgs = qw(
fcb80221 812 ceph-fuse
c861591b 813 corosync
a7a65b02 814 libjs-extjs
c861591b 815 glusterfs-client
fcb80221 816 libknet1
c861591b
TL
817 libpve-access-control
818 libpve-common-perl
819 libpve-guest-common-perl
820 libpve-http-server-perl
821 libpve-storage-perl
74f7f7c1 822 libproxmox-acme-perl
7828eef6 823 libproxmox-backup-qemu0
c861591b 824 libqb0
4cfe5534 825 libspice-server1
c861591b
TL
826 lvm2
827 lxc-pve
828 lxcfs
829 novnc-pve
6f5c3b98 830 proxmox-backup-client
118f9076 831 proxmox-mail-forward
fcb80221 832 proxmox-mini-journalreader
a7a65b02 833 proxmox-widget-toolkit
c861591b
TL
834 pve-cluster
835 pve-container
836 pve-docs
5d4bb1f7 837 pve-edk2-firmware
c861591b
TL
838 pve-firewall
839 pve-firmware
840 pve-ha-manager
a7a65b02 841 pve-i18n
c861591b 842 pve-qemu-kvm
a7a65b02 843 pve-xtermjs
c861591b
TL
844 qemu-server
845 smartmontools
2b429650 846 swtpm
a7a65b02 847 spiceterm
c861591b
TL
848 vncterm
849 );
850
851 # add the rest ordered by name, easier to find for humans
852 push @list, (sort @pkgs, @opt_pack);
89e4fc8c 853
8794761f
DM
854 my (undef, undef, $kernel_release) = POSIX::uname();
855 my $pvever = PVE::pvecfg::version_text();
856
c861591b 857 my $pkglist = [];
8794761f
DM
858 foreach my $pkgname (@list) {
859 my $p = $cache->{$pkgname};
860 my $info = $pkgrecords->lookup($pkgname);
c0d696f9 861 my $candidate_ver = defined($p) ? $policy->candidate($p) : undef;
8794761f
DM
862 my $res;
863 if (my $current_ver = $p->{CurrentVer}) {
89e4fc8c 864 $res = $assemble_pkginfo->($pkgname, $info, $current_ver, $candidate_ver || $current_ver);
8794761f 865 } elsif ($candidate_ver) {
89e4fc8c 866 $res = $assemble_pkginfo->($pkgname, $info, $candidate_ver, $candidate_ver);
8794761f
DM
867 delete $res->{OldVersion};
868 } else {
869 next;
870 }
871 $res->{CurrentState} = $p->{CurrentState};
872
873 # hack: add some useful information (used by 'pveversion -v')
874 if ($pkgname eq 'pve-manager') {
875 $res->{ManagerVersion} = $pvever;
97f6e029 876 } elsif ($pkgname eq 'proxmox-ve') {
8794761f
DM
877 $res->{RunningKernel} = $kernel_release;
878 }
241c6f94
WL
879 if (grep( /^$pkgname$/, @opt_pack)) {
880 next if $res->{CurrentState} eq 'NotInstalled';
881 }
8794761f
DM
882
883 push @$pkglist, $res;
884 }
885
886 return $pkglist;
887 }});
888
21299915 8891;