]> git.proxmox.com Git - pve-manager.git/blob - PVE/API2/APT.pm
handle pve-kernel -> proxmox-kernel rename
[pve-manager.git] / PVE / API2 / APT.pm
1 package PVE::API2::APT;
2
3 use strict;
4 use warnings;
5
6 use POSIX;
7 use File::stat ();
8 use IO::File;
9 use File::Basename;
10
11 use LWP::UserAgent;
12
13 use Proxmox::RS::APT::Repositories;
14
15 use PVE::pvecfg;
16 use PVE::Tools qw(extract_param);
17 use PVE::Cluster;
18 use PVE::DataCenterConfig;
19 use PVE::SafeSyslog;
20 use PVE::INotify;
21 use PVE::Exception;
22 use PVE::RESTHandler;
23 use PVE::RPCEnvironment;
24 use PVE::API2Tools;
25
26 use JSON;
27 use PVE::JSONSchema qw(get_standard_option);
28
29 use AptPkg::Cache;
30 use AptPkg::PkgRecords;
31 use AptPkg::System;
32
33 my $get_apt_cache = sub {
34
35 my $apt_cache = AptPkg::Cache->new() || die "unable to initialize AptPkg::Cache\n";
36
37 return $apt_cache;
38 };
39
40 use base qw(PVE::RESTHandler);
41
42 __PACKAGE__->register_method({
43 name => 'index',
44 path => '',
45 method => 'GET',
46 description => "Directory index for apt (Advanced Package Tool).",
47 permissions => {
48 user => 'all',
49 },
50 parameters => {
51 additionalProperties => 0,
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
69 my $res = [
70 { id => 'changelog' },
71 { id => 'repositories' },
72 { id => 'update' },
73 { id => 'versions' },
74 ];
75
76 return $res;
77 }});
78
79 my $get_pkgfile = sub {
80 my ($veriter) = @_;
81
82 foreach my $verfile (@{$veriter->{FileList}}) {
83 my $pkgfile = $verfile->{File};
84 next if !$pkgfile->{Origin};
85 return $pkgfile;
86 }
87
88 return undef;
89 };
90
91 my $get_changelog_url =sub {
92 my ($pkgname, $info, $pkgver, $pkgfile) = @_;
93
94 my $base;
95 $base = dirname($info->{FileName}) if defined($info->{FileName});
96
97 my $origin = $pkgfile->{Origin};
98
99 if ($origin && $base) {
100 $pkgver =~ s/^\d+://; # strip epoch
101 my $srcpkg = $info->{SourcePkg} || $pkgname;
102 if ($origin eq 'Debian' || $origin eq 'Debian Backports') {
103 $base =~ s!pool/updates/!pool/!; # for security channel
104 return "http://packages.debian.org/changelogs/$base/${srcpkg}_${pkgver}/changelog";
105 } elsif ($origin eq 'Proxmox') {
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
108 my $data = Proxmox::RS::APT::Repositories::repositories("pve");
109
110 for my $file ($data->{files}->@*) {
111 for my $repo (grep { $_->{Enabled} } $file->{repositories}->@*) {
112 next if !grep(/$pkgfile->{Component}/, $repo->{Components}->@*);
113 next if !$repo->{URIs}[0] =~ m/$pkgfile->{Site}/;
114
115 return $repo->{URIs}[0] . "/$base/${pkgname}_${pkgver}.changelog";
116 }
117 }
118 }
119 }
120 return; # none found, with our heuristic that is..
121 };
122
123 my $assemble_pkginfo = sub {
124 my ($pkgname, $info, $current_ver, $candidate_ver) = @_;
125
126 my $data = {
127 Package => $info->{Name},
128 Title => $info->{ShortDesc},
129 Origin => 'unknown',
130 };
131
132 if (my $pkgfile = &$get_pkgfile($candidate_ver)) {
133 $data->{Origin} = $pkgfile->{Origin};
134 my $changelog_url = $get_changelog_url->($pkgname, $info, $candidate_ver->{VerStr}, $pkgfile);
135
136 $data->{ChangeLogUrl} = $changelog_url if $changelog_url;
137 }
138
139 if (my $desc = $info->{LongDesc}) {
140 $desc =~ s/^.*\n\s?//; # remove first line
141 $desc =~ s/\n / /g;
142 $data->{Description} = $desc;
143 }
144
145 foreach my $k (qw(Section Arch Priority)) {
146 $data->{$k} = $candidate_ver->{$k};
147 }
148
149 $data->{Version} = $candidate_ver->{VerStr};
150 $data->{OldVersion} = $current_ver->{VerStr} if $current_ver;
151
152 return $data;
153 };
154
155 # we try to cache results
156 my $pve_pkgstatus_fn = "/var/lib/pve-manager/pkgupdates";
157 my $read_cached_pkgstatus = sub {
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 $@;
160 return $data;
161 };
162
163 my $update_pve_pkgstatus = sub {
164 syslog('info', "update new package list: $pve_pkgstatus_fn");
165
166 my $oldpkglist = &$read_cached_pkgstatus();
167 my $notify_status = { map { $_->{Package} => $_->{NotifyStatus} } $oldpkglist->@* };
168
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};
177 next if !$p->{SelectedState} || ($p->{SelectedState} ne 'Install');
178 my $current_ver = $p->{CurrentVer} || next;
179 my $candidate_ver = $policy->candidate($p) || next;
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;
212 }
213 }
214 undef $found;
215 undef $req;
216 }
217 }
218 }
219 }
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
228 PVE::Tools::file_set_contents($pve_pkgstatus_fn, encode_json($pkglist));
229
230 return $pkglist;
231 };
232
233 __PACKAGE__->register_method({
234 name => 'list_updates',
235 path => 'update',
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 => {
244 additionalProperties => 0,
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
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");
262
263 if ($st2 && $st3 && $st2->mtime <= $st1->mtime && $st3->mtime <= $st1->mtime) {
264 if (my $data = &$read_cached_pkgstatus()) {
265 return $data;
266 }
267 }
268 }
269
270 my $pkglist = &$update_pve_pkgstatus();
271
272 return $pkglist;
273 }});
274
275 __PACKAGE__->register_method({
276 name => 'update_database',
277 path => 'update',
278 method => 'POST',
279 description => "This is used to resynchronize the package index files from their sources (apt-get update).",
280 permissions => {
281 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
282 },
283 protected => 1,
284 proxyto => 'node',
285 parameters => {
286 additionalProperties => 0,
287 properties => {
288 node => get_standard_option('pve-node'),
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 },
295 quiet => {
296 type => 'boolean',
297 description => "Only produces output suitable for logging, omitting progress indicators.",
298 optional => 1,
299 default => 0,
300 },
301 },
302 },
303 returns => {
304 type => 'string',
305 },
306 code => sub {
307 my ($param) = @_;
308
309 my $rpcenv = PVE::RPCEnvironment::get();
310
311 my $authuser = $rpcenv->get_user();
312
313 my $realcmd = sub {
314 my $upid = shift;
315
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
326 my $cmd = ['apt-get', 'update'];
327
328 print "starting apt-get update\n" if !$param->{quiet};
329
330 if ($param->{quiet}) {
331 PVE::Tools::run_command($cmd, outfunc => sub {}, errfunc => sub {});
332 } else {
333 PVE::Tools::run_command($cmd);
334 }
335
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;
347 my $mailfrom = $dcconf->{email_from} || "root";
348 my $subject = "New software packages available ($hostname)";
349
350 my $data = "The following updates are available:\n\n";
351
352 my $count = 0;
353 foreach my $p (sort {$a->{Package} cmp $b->{Package} } @$pkglist) {
354 next if $p->{NotifyStatus} && $p->{NotifyStatus} eq $p->{Version};
355 $count++;
356 if ($p->{OldVersion}) {
357 $data .= "$p->{Package}: $p->{OldVersion} ==> $p->{Version}\n";
358 } else {
359 $data .= "$p->{Package}: $p->{Version} (new)\n";
360 }
361 }
362
363 return if !$count;
364
365 PVE::Tools::sendmail($mailto, $subject, $data, undef, $mailfrom, '');
366
367 foreach my $pi (@$pkglist) {
368 $pi->{NotifyStatus} = $pi->{Version};
369 }
370 PVE::Tools::file_set_contents($pve_pkgstatus_fn, encode_json($pkglist));
371 }
372 }
373
374 return;
375 };
376
377 return $rpcenv->fork_worker('aptupdate', undef, $authuser, $realcmd);
378
379 }});
380
381 __PACKAGE__->register_method({
382 name => 'changelog',
383 path => 'changelog',
384 method => 'GET',
385 description => "Get package changelogs.",
386 permissions => {
387 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
388 },
389 proxyto => 'node',
390 parameters => {
391 additionalProperties => 0,
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,
402 },
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 }
428 die "package '$pkgname' version '$param->{version}' is not available\n" if !$ver;
429 } else {
430 $ver = $policy->candidate($p) || die "no installation candidate for package '$pkgname'\n";
431 }
432
433 my $info = $pkgrecords->lookup($pkgname);
434
435 my $pkgfile = $get_pkgfile->($ver) or die "couldn't find package info file for ${pkgname}=$ver->{VerStr}\n";
436
437 my $url = $get_changelog_url->($pkgname, $info, $ver->{VerStr}, $pkgfile)
438 or die "changelog for '${pkgname}_$ver->{VerStr}' not available\n";
439
440 my $ua = LWP::UserAgent->new();
441 $ua->agent("PVE/1.0");
442 $ua->timeout(10);
443 $ua->max_size(1024 * 1024);
444 $ua->ssl_opts(verify_hostname => 0); # don't care for changelogs
445
446 my $datacenter_cfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
447 if (my $proxy = $datacenter_cfg->{http_proxy}) {
448 $ua->proxy(['http', 'https'], $proxy);
449 } else {
450 $ua->env_proxy;
451 }
452
453 if ($pkgfile->{Origin} eq 'Proxmox' && $pkgfile->{Component} eq 'pve-enterprise') {
454 my $info = PVE::API2::Subscription::read_etc_subscription();
455 if ($info->{status} eq 'active') {
456 my $pw = PVE::API2Tools::get_hwaddress();
457 $ua->credentials("enterprise.proxmox.com:443", 'pve-enterprise-repository', $info->{key}, $pw);
458 }
459 }
460
461 my $response = $ua->get($url);
462
463 if ($response->is_success) {
464 return $response->decoded_content;
465 } else {
466 PVE::Exception::raise($response->message, code => $response->code);
467 }
468 return '';
469 }});
470
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
666 return Proxmox::RS::APT::Repositories::repositories("pve");
667 }});
668
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
701 Proxmox::RS::APT::Repositories::add_repository($param->{handle}, "pve", $param->{digest});
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
745 my $options = {};
746
747 my $enabled = $param->{enabled};
748 $options->{enabled} = int($enabled) if defined($enabled);
749
750 Proxmox::RS::APT::Repositories::change_repository(
751 $param->{path},
752 int($param->{index}),
753 $options,
754 $param->{digest}
755 );
756 }});
757
758 __PACKAGE__->register_method({
759 name => 'versions',
760 path => 'versions',
761 method => 'GET',
762 proxyto => 'node',
763 description => "Get package information for important Proxmox packages.",
764 permissions => {
765 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
766 },
767 parameters => {
768 additionalProperties => 0,
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
783 my $cache = &$get_apt_cache();
784 my $policy = $cache->policy;
785 my $pkgrecords = $cache->packages();
786
787 # order most important things first
788 my @list = qw(proxmox-ve pve-manager);
789
790 my $aptver = $AptPkg::System::_system->versioning();
791 my $byver = sub { $aptver->compare($cache->{$b}->{CurrentVer}->{VerStr}, $cache->{$a}->{CurrentVer}->{VerStr}) };
792 push @list, sort $byver grep { /^(?:pve|proxmox)-kernel-/ && $cache->{$_}->{CurrentState} eq 'Installed' } keys %$cache;
793
794 my @opt_pack = qw(
795 ceph
796 criu
797 gfs2-utils
798 ifupdown
799 ifupdown2
800 ksm-control-daemon
801 ksmtuned
802 libpve-apiclient-perl
803 libpve-network-perl
804 openvswitch-switch
805 proxmox-backup-file-restore
806 proxmox-kernel-helper
807 proxmox-offline-mirror-helper
808 pve-zsync
809 zfsutils-linux
810 );
811
812 my @pkgs = qw(
813 ceph-fuse
814 corosync
815 glusterfs-client
816 libjs-extjs
817 libknet1
818 libproxmox-acme-perl
819 libproxmox-backup-qemu0
820 libproxmox-rs-perl
821 libpve-access-control
822 libpve-common-perl
823 libpve-guest-common-perl
824 libpve-http-server-perl
825 libpve-rs-perl
826 libpve-storage-perl
827 libqb0
828 libspice-server1
829 lvm2
830 lxc-pve
831 lxcfs
832 novnc-pve
833 proxmox-backup-client
834 proxmox-mail-forward
835 proxmox-mini-journalreader
836 proxmox-widget-toolkit
837 pve-cluster
838 pve-container
839 pve-docs
840 pve-edk2-firmware
841 pve-firewall
842 pve-firmware
843 pve-ha-manager
844 pve-i18n
845 pve-qemu-kvm
846 pve-xtermjs
847 qemu-server
848 smartmontools
849 spiceterm
850 swtpm
851 vncterm
852 );
853
854 # add the rest ordered by name, easier to find for humans
855 push @list, (sort @pkgs, @opt_pack);
856
857 my (undef, undef, $kernel_release) = POSIX::uname();
858 my $pvever = PVE::pvecfg::version_text();
859
860 my $pkglist = [];
861 foreach my $pkgname (@list) {
862 my $p = $cache->{$pkgname};
863 my $info = $pkgrecords->lookup($pkgname);
864 my $candidate_ver = defined($p) ? $policy->candidate($p) : undef;
865 my $res;
866 if (my $current_ver = $p->{CurrentVer}) {
867 $res = $assemble_pkginfo->($pkgname, $info, $current_ver, $candidate_ver || $current_ver);
868 } elsif ($candidate_ver) {
869 $res = $assemble_pkginfo->($pkgname, $info, $candidate_ver, $candidate_ver);
870 delete $res->{OldVersion};
871 } else {
872 next;
873 }
874 $res->{CurrentState} = $p->{CurrentState};
875
876 # hack: add some useful information (used by 'pveversion -v')
877 if ($pkgname eq 'pve-manager') {
878 $res->{ManagerVersion} = $pvever;
879 } elsif ($pkgname eq 'proxmox-ve') {
880 $res->{RunningKernel} = $kernel_release;
881 }
882 if (grep( /^$pkgname$/, @opt_pack)) {
883 next if $res->{CurrentState} eq 'NotInstalled';
884 }
885
886 push @$pkglist, $res;
887 }
888
889 return $pkglist;
890 }});
891
892 1;