]>
git.proxmox.com Git - pve-manager.git/blob - PVE/API2/APT.pm
f73535e158ae9ffb0c3db168367525a82d187152
1 package PVE
::API2
::APT
;
13 use Proxmox
::RS
::APT
::Repositories
;
16 use PVE
::Tools
qw(extract_param);
18 use PVE
::DataCenterConfig
;
23 use PVE
::RPCEnvironment
;
27 use PVE
::JSONSchema
qw(get_standard_option);
30 use AptPkg
::PkgRecords
;
33 my $get_apt_cache = sub {
35 my $apt_cache = AptPkg
::Cache-
>new() || die "unable to initialize AptPkg::Cache\n";
40 use base
qw(PVE::RESTHandler);
42 __PACKAGE__-
>register_method({
46 description
=> "Directory index for apt (Advanced Package Tool).",
51 additionalProperties
=> 0,
53 node
=> get_standard_option
('pve-node'),
61 id
=> { type
=> 'string' },
64 links
=> [ { rel
=> 'child', href
=> "{id}" } ],
70 { id
=> 'changelog' },
71 { id
=> 'repositories' },
79 my $get_pkgfile = sub {
82 foreach my $verfile (@{$veriter->{FileList
}}) {
83 my $pkgfile = $verfile->{File
};
84 next if !$pkgfile->{Origin
};
91 my $get_changelog_url =sub {
92 my ($pkgname, $info, $pkgver, $pkgfile) = @_;
95 $base = dirname
($info->{FileName
}) if defined($info->{FileName
});
97 my $origin = $pkgfile->{Origin
};
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");
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}/;
115 return $repo->{URIs
}[0] . "/$base/${pkgname}_${pkgver}.changelog";
120 return; # none found, with our heuristic that is..
123 my $assemble_pkginfo = sub {
124 my ($pkgname, $info, $current_ver, $candidate_ver) = @_;
127 Package
=> $info->{Name
},
128 Title
=> $info->{ShortDesc
},
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);
136 $data->{ChangeLogUrl
} = $changelog_url if $changelog_url;
139 if (my $desc = $info->{LongDesc
}) {
140 $desc =~ s/^.*\n\s?//; # remove first line
142 $data->{Description
} = $desc;
145 foreach my $k (qw(Section Arch Priority)) {
146 $data->{$k} = $candidate_ver->{$k};
149 $data->{Version
} = $candidate_ver->{VerStr
};
150 $data->{OldVersion
} = $current_ver->{VerStr
} if $current_ver;
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 $@;
163 my $update_pve_pkgstatus = sub {
164 syslog
('info', "update new package list: $pve_pkgstatus_fn");
166 my $oldpkglist = &$read_cached_pkgstatus();
167 my $notify_status = { map { $_->{Package
} => $_->{NotifyStatus
} } $oldpkglist->@* };
171 my $cache = &$get_apt_cache();
172 my $policy = $cache->policy;
173 my $pkgrecords = $cache->packages();
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
};
182 my $info = $pkgrecords->lookup($pkgname);
183 my $res = &$assemble_pkginfo($pkgname, $info, $current_ver, $candidate_ver);
184 push @$pkglist, $res;
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;
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';
202 $req = $d->{TargetPkg
} if !$req;
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;
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
};
228 PVE
::Tools
::file_set_contents
($pve_pkgstatus_fn, encode_json
($pkglist));
233 __PACKAGE__-
>register_method({
234 name
=> 'list_updates',
237 description
=> "List available updates.",
239 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
244 additionalProperties
=> 0,
246 node
=> get_standard_option
('pve-node'),
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");
263 if ($st2 && $st3 && $st2->mtime <= $st1->mtime && $st3->mtime <= $st1->mtime) {
264 if (my $data = &$read_cached_pkgstatus()) {
270 my $pkglist = &$update_pve_pkgstatus();
275 __PACKAGE__-
>register_method({
276 name
=> 'update_database',
279 description
=> "This is used to resynchronize the package index files from their sources (apt-get update).",
281 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
286 additionalProperties
=> 0,
288 node
=> get_standard_option
('pve-node'),
291 description
=> "Send notification mail about new packages (to email address specified for user 'root\@pam').",
297 description
=> "Only produces output suitable for logging, omitting progress indicators.",
309 my $rpcenv = PVE
::RPCEnvironment
::get
();
311 my $authuser = $rpcenv->get_user();
316 # setup proxy for apt
317 my $dcconf = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
319 my $aptconf = "// no proxy configured\n";
320 if ($dcconf->{http_proxy
}) {
321 $aptconf = "Acquire::http::Proxy \"$dcconf->{http_proxy}\";\n";
323 my $aptcfn = "/etc/apt/apt.conf.d/76pveproxy";
324 PVE
::Tools
::file_set_contents
($aptcfn, $aptconf);
326 my $cmd = ['apt-get', 'update'];
328 print "starting apt-get update\n" if !$param->{quiet
};
330 if ($param->{quiet
}) {
331 PVE
::Tools
::run_command
($cmd, outfunc
=> sub {}, errfunc
=> sub {});
333 PVE
::Tools
::run_command
($cmd);
336 my $pkglist = &$update_pve_pkgstatus();
338 if ($param->{notify
} && scalar(@$pkglist)) {
340 my $usercfg = PVE
::Cluster
::cfs_read_file
("user.cfg");
341 my $rootcfg = $usercfg->{users
}->{'root@pam'} || {};
342 my $mailto = $rootcfg->{email
};
345 my $hostname = `hostname -f` || PVE
::INotify
::nodename
();
347 my $mailfrom = $dcconf->{email_from
} || "root";
348 my $subject = "New software packages available ($hostname)";
350 my $data = "The following updates are available:\n\n";
353 foreach my $p (sort {$a->{Package
} cmp $b->{Package
} } @$pkglist) {
354 next if $p->{NotifyStatus
} && $p->{NotifyStatus
} eq $p->{Version
};
356 if ($p->{OldVersion
}) {
357 $data .= "$p->{Package}: $p->{OldVersion} ==> $p->{Version}\n";
359 $data .= "$p->{Package}: $p->{Version} (new)\n";
365 PVE
::Tools
::sendmail
($mailto, $subject, $data, undef, $mailfrom, '');
367 foreach my $pi (@$pkglist) {
368 $pi->{NotifyStatus
} = $pi->{Version
};
370 PVE
::Tools
::file_set_contents
($pve_pkgstatus_fn, encode_json
($pkglist));
377 return $rpcenv->fork_worker('aptupdate', undef, $authuser, $realcmd);
381 __PACKAGE__-
>register_method({
385 description
=> "Get package changelogs.",
387 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
391 additionalProperties
=> 0,
393 node
=> get_standard_option
('pve-node'),
395 description
=> "Package name.",
399 description
=> "Package version.",
411 my $pkgname = $param->{name
};
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();
419 if ($param->{version
}) {
420 if (my $available = $p->{VersionList
}) {
421 for my $v (@$available) {
422 if ($v->{VerStr
} eq $param->{version
}) {
428 die "package '$pkgname' version '$param->{version}' is not available\n" if !$ver;
430 $ver = $policy->candidate($p) || die "no installation candidate for package '$pkgname'\n";
433 my $info = $pkgrecords->lookup($pkgname);
435 my $pkgfile = $get_pkgfile->($ver) or die "couldn't find package info file for ${pkgname}=$ver->{VerStr}\n";
437 my $url = $get_changelog_url->($pkgname, $info, $ver->{VerStr
}, $pkgfile)
438 or die "changelog for '${pkgname}_$ver->{VerStr}' not available\n";
440 my $ua = LWP
::UserAgent-
>new();
441 $ua->agent("PVE/1.0");
443 $ua->max_size(1024 * 1024);
444 $ua->ssl_opts(verify_hostname
=> 0); # don't care for changelogs
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);
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);
461 my $response = $ua->get($url);
463 if ($response->is_success) {
464 return $response->decoded_content;
466 PVE
::Exception
::raise
($response->message, code
=> $response->code);
471 __PACKAGE__-
>register_method({
472 name
=> 'repositories',
473 path
=> 'repositories',
476 description
=> "Get APT repository information.",
478 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
481 additionalProperties
=> 0,
483 node
=> get_standard_option
('pve-node'),
488 description
=> "Result from parsing the APT repository files in /etc/apt/.",
492 description
=> "List of parsed repository files.",
498 description
=> "Path to the problematic file.",
502 enum
=> [ 'list', 'sources' ],
503 description
=> "Format of the file.",
507 description
=> "The parsed repositories.",
513 description
=> "List of package types.",
516 enum
=> [ 'deb', 'deb-src' ],
520 description
=> "List of repository URIs.",
528 description
=> "List of package distribuitions",
535 description
=> "List of repository components",
536 optional
=> 1, # not present if suite is absolute
543 description
=> "Additional options",
562 description
=> "Associated comment",
567 enum
=> [ 'list', 'sources' ],
568 description
=> "Format of the defining file.",
572 description
=> "Whether the repository is enabled or not",
579 description
=> "Digest of the file as bytes.",
589 description
=> "List of problematic repository files.",
595 description
=> "Path to the problematic file.",
599 description
=> "The error message",
606 description
=> "Common digest of all files.",
610 description
=> "Additional information/warnings for APT repositories.",
616 description
=> "Path to the associated file.",
620 description
=> "Index of the associated repository within the file.",
624 description
=> "Property from which the info originates.",
629 description
=> "Kind of the information (e.g. warning).",
633 description
=> "Information message.",
638 'standard-repos' => {
640 description
=> "List of standard repositories and their configuration status",
646 description
=> "Handle to identify the repository.",
650 description
=> "Full name of the repository.",
655 description
=> "Indicating enabled/disabled status, if the " .
656 "repository is configured.",
666 return Proxmox
::RS
::APT
::Repositories
::repositories
("pve");
669 __PACKAGE__-
>register_method({
670 name
=> 'add_repository',
671 path
=> 'repositories',
673 description
=> "Add a standard repository to the configuration",
675 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
680 additionalProperties
=> 0,
682 node
=> get_standard_option
('pve-node'),
685 description
=> "Handle that identifies a repository.",
689 description
=> "Digest to detect modifications.",
701 Proxmox
::RS
::APT
::Repositories
::add_repository
($param->{handle
}, "pve", $param->{digest
});
704 __PACKAGE__-
>register_method({
705 name
=> 'change_repository',
706 path
=> 'repositories',
708 description
=> "Change the properties of a repository. Currently only allows enabling/disabling.",
710 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
715 additionalProperties
=> 0,
717 node
=> get_standard_option
('pve-node'),
720 description
=> "Path to the containing file.",
724 description
=> "Index within the file (starting from 0).",
728 description
=> "Whether the repository should be enabled or not.",
733 description
=> "Digest to detect modifications.",
747 my $enabled = $param->{enabled
};
748 $options->{enabled
} = int($enabled) if defined($enabled);
750 Proxmox
::RS
::APT
::Repositories
::change_repository
(
752 int($param->{index}),
758 __PACKAGE__-
>register_method({
763 description
=> "Get package information for important Proxmox packages.",
765 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
768 additionalProperties
=> 0,
770 node
=> get_standard_option
('pve-node'),
783 my $cache = &$get_apt_cache();
784 my $policy = $cache->policy;
785 my $pkgrecords = $cache->packages();
787 # order most important things first
788 my @list = qw(proxmox-ve pve-manager);
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;
802 libpve-apiclient-perl
805 proxmox-backup-file-restore
806 proxmox-kernel-helper
807 proxmox-offline-mirror-helper
819 libproxmox-backup-qemu0
821 libpve-access-control
823 libpve-guest-common-perl
824 libpve-http-server-perl
833 proxmox-backup-client
835 proxmox-mini-journalreader
836 proxmox-widget-toolkit
854 # add the rest ordered by name, easier to find for humans
855 push @list, (sort @pkgs, @opt_pack);
857 my (undef, undef, $kernel_release) = POSIX
::uname
();
858 my $pvever = PVE
::pvecfg
::version_text
();
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;
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
};
874 $res->{CurrentState
} = $p->{CurrentState
};
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;
882 if (grep( /^$pkgname$/, @opt_pack)) {
883 next if $res->{CurrentState
} eq 'NotInstalled';
886 push @$pkglist, $res;