]>
git.proxmox.com Git - pmg-api.git/blob - PMG/API2/APT.pm
1 package PMG
::API2
::APT
;
13 use PVE
::Tools
qw(extract_param);
18 use PVE
::JSONSchema
qw(get_standard_option);
20 use PMG
::RESTEnvironment
;
26 use AptPkg
::PkgRecords
;
28 my $get_apt_cache = sub {
30 my $apt_cache = AptPkg
::Cache-
>new() || die "unable to initialize AptPkg::Cache\n";
35 use base
qw(PVE::RESTHandler);
37 __PACKAGE__-
>register_method({
41 description
=> "Directory index for apt (Advanced Package Tool).",
46 additionalProperties
=> 0,
48 node
=> get_standard_option
('pve-node'),
56 id
=> { type
=> 'string' },
59 links
=> [ { rel
=> 'child', href
=> "{id}" } ],
65 { id
=> 'changelog' },
73 my $get_pkgfile = sub {
76 foreach my $verfile (@{$veriter->{FileList
}}) {
77 my $pkgfile = $verfile->{File
};
78 next if !$pkgfile->{Origin
};
85 my $get_changelog_url =sub {
86 my ($pkgname, $info, $pkgver, $origin, $component) = @_;
89 my $base = dirname
($info->{FileName
});
90 if ($origin && $base) {
91 $pkgver =~ s/^\d+://; # strip epoch
92 my $srcpkg = $info->{SourcePkg
} || $pkgname;
93 if ($origin eq 'Debian') {
94 $base =~ s!pool/updates/!pool/!; # for security channel
95 $changelog_url = "http://packages.debian.org/changelogs/$base/" .
96 "${srcpkg}_${pkgver}/changelog";
97 } elsif ($origin eq 'Proxmox') {
98 if ($component eq 'pve-enterprise') {
99 $changelog_url = "https://enterprise.proxmox.com/debian/$base/" .
100 "${pkgname}_${pkgver}.changelog";
102 $changelog_url = "http://download.proxmox.com/debian/$base/" .
103 "${pkgname}_${pkgver}.changelog";
108 return $changelog_url;
111 my $assemble_pkginfo = sub {
112 my ($pkgname, $info, $current_ver, $candidate_ver) = @_;
115 Package
=> $info->{Name
},
116 Title
=> $info->{ShortDesc
},
120 if (my $pkgfile = &$get_pkgfile($candidate_ver)) {
121 $data->{Origin
} = $pkgfile->{Origin
};
122 if (my $changelog_url = &$get_changelog_url($pkgname, $info, $candidate_ver->{VerStr
},
123 $pkgfile->{Origin
}, $pkgfile->{Component
})) {
124 $data->{ChangeLogUrl
} = $changelog_url;
128 if (my $desc = $info->{LongDesc
}) {
129 $desc =~ s/^.*\n\s?//; # remove first line
131 $data->{Description
} = $desc;
134 foreach my $k (qw(Section Arch Priority)) {
135 $data->{$k} = $candidate_ver->{$k};
138 $data->{Version
} = $candidate_ver->{VerStr
};
139 $data->{OldVersion
} = $current_ver->{VerStr
} if $current_ver;
144 # we try to cache results
145 my $pmg_pkgstatus_fn = "/var/lib/pmg/pkgupdates";
147 my $read_cached_pkgstatus = sub {
150 my $jsonstr = PVE
::Tools
::file_get_contents
($pmg_pkgstatus_fn, 5*1024*1024);
151 $data = decode_json
($jsonstr);
154 warn "error reading cached package status in $pmg_pkgstatus_fn\n";
159 my $update_pmg_pkgstatus = sub {
161 syslog
('info', "update new package list: $pmg_pkgstatus_fn");
163 my $notify_status = {};
164 my $oldpkglist = &$read_cached_pkgstatus();
165 foreach my $pi (@$oldpkglist) {
166 $notify_status->{$pi->{Package
}} = $pi->{NotifyStatus
};
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;
181 if ($current_ver->{VerStr
} ne $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 if (my $deps = $candidate_ver->{DependsList
}) {
193 if ($d->{DepType
} eq 'Depends') {
194 $found = $d->{TargetPkg
}->{SelectedState
} eq 'Install' if !$found;
195 $req = $d->{TargetPkg
} if !$req;
197 if (!($d->{CompType
} & AptPkg
::Dep
::Or
)) {
198 if (!$found && $req) { # New required Package
199 my $tpname = $req->{Name
};
200 my $tpinfo = $pkgrecords->lookup($tpname);
201 my $tpcv = $policy->candidate($req);
202 if ($tpinfo && $tpcv) {
203 my $res = &$assemble_pkginfo($tpname, $tpinfo, undef, $tpcv);
204 push @$pkglist, $res;
216 # keep notification status (avoid sending mails abou new packages more than once)
217 foreach my $pi (@$pkglist) {
218 if (my $ns = $notify_status->{$pi->{Package
}}) {
219 $pi->{NotifyStatus
} = $ns if $ns eq $pi->{Version
};
223 PVE
::Tools
::file_set_contents
($pmg_pkgstatus_fn, encode_json
($pkglist));
228 __PACKAGE__-
>register_method({
229 name
=> 'list_updates',
232 description
=> "List available updates.",
236 additionalProperties
=> 0,
238 node
=> get_standard_option
('pve-node'),
251 if (my $st1 = File
::stat::stat($pmg_pkgstatus_fn)) {
252 my $st2 = File
::stat::stat("/var/cache/apt/pkgcache.bin");
253 my $st3 = File
::stat::stat("/var/lib/dpkg/status");
255 if ($st2 && $st3 && $st2->mtime <= $st1->mtime && $st3->mtime <= $st1->mtime) {
256 if (my $data = &$read_cached_pkgstatus()) {
262 my $pkglist = &$update_pmg_pkgstatus();
267 __PACKAGE__-
>register_method({
268 name
=> 'update_database',
271 description
=> "This is used to resynchronize the package index files from their sources (apt-get update).",
275 additionalProperties
=> 0,
277 node
=> get_standard_option
('pve-node'),
280 description
=> "Send notification mail about new packages (to email address specified for user 'root\@pam').",
286 description
=> "Only produces output suitable for logging, omitting progress indicators.",
298 my $rpcenv = PMG
::RESTEnvironment-
>get();
300 my $authuser = $rpcenv->get_user();
305 my $pmg_cfg = PMG
::Config-
>new();
307 my $http_proxy = $pmg_cfg->get('admin', 'http_proxy');
308 my $aptconf = "// no proxy configured\n";
310 $aptconf = "Acquire::http::Proxy \"${http_proxy}\";\n";
312 my $aptcfn = "/etc/apt/apt.conf.d/76pmgproxy";
313 PVE
::Tools
::file_set_contents
($aptcfn, $aptconf);
315 my $cmd = ['apt-get', 'update'];
317 print "starting apt-get update\n" if !$param->{quiet
};
319 if ($param->{quiet
}) {
320 PVE
::Tools
::run_command
($cmd, outfunc
=> sub {}, errfunc
=> sub {});
322 PVE
::Tools
::run_command
($cmd);
325 my $pkglist = &$update_pmg_pkgstatus();
327 if ($param->{notify
} && scalar(@$pkglist)) {
329 my $mailfrom = "root";
331 if (my $mailto = $pmg_cfg->get('admin', 'email', 1)) {
333 my $text .= "The following updates are available:\n\n";
336 foreach my $p (sort {$a->{Package
} cmp $b->{Package
} } @$pkglist) {
337 next if $p->{NotifyStatus
} && $p->{NotifyStatus
} eq $p->{Version
};
339 if ($p->{OldVersion
}) {
340 $text .= "$p->{Package}: $p->{OldVersion} ==> $p->{Version}\n";
342 $text .= "$p->{Package}: $p->{Version} (new)\n";
348 my $hostname = `hostname -f` || PVE
::INotify
::nodename
();
351 my $subject = "New software packages available ($hostname)";
352 PVE
::Tools
::sendmail
($mailto, $subject, $text, undef,
353 $mailfrom, 'Proxmox Mail Gateway');
355 foreach my $pi (@$pkglist) {
356 $pi->{NotifyStatus
} = $pi->{Version
};
359 PVE
::Tools
::file_set_contents
($pmg_pkgstatus_fn, encode_json
($pkglist));
366 return $rpcenv->fork_worker('aptupdate', undef, $authuser, $realcmd);
370 __PACKAGE__-
>register_method({
374 description
=> "Get package changelogs.",
377 additionalProperties
=> 0,
379 node
=> get_standard_option
('pve-node'),
381 description
=> "Package name.",
385 description
=> "Package version.",
397 my $pkgname = $param->{name
};
399 my $cache = &$get_apt_cache();
400 my $policy = $cache->policy;
401 my $p = $cache->{$pkgname} || die "no such package '$pkgname'\n";
402 my $pkgrecords = $cache->packages();
405 if ($param->{version
}) {
406 if (my $available = $p->{VersionList
}) {
407 for my $v (@$available) {
408 if ($v->{VerStr
} eq $param->{version
}) {
414 die "package '$pkgname' version '$param->{version}' is not avalable\n" if !$ver;
416 $ver = $policy->candidate($p) || die "no installation candidate for package '$pkgname'\n";
419 my $info = $pkgrecords->lookup($pkgname);
421 my $pkgfile = &$get_pkgfile($ver);
424 die "changelog for '${pkgname}_$ver->{VerStr}' not available\n"
425 if !($pkgfile && ($url = &$get_changelog_url($pkgname, $info, $ver->{VerStr
}, $pkgfile->{Origin
}, $pkgfile->{Component
})));
429 my $pmg_cfg = PMG
::Config-
>new();
430 my $proxy = $pmg_cfg->get('admin', 'http_proxy');
432 my $ua = LWP
::UserAgent-
>new;
433 $ua->agent("PMG/1.0");
435 $ua->max_size(1024*1024);
436 $ua->ssl_opts(verify_hostname
=> 0); # don't care for changelogs
439 $ua->proxy(['http', 'https'], $proxy);
447 if ($pkgfile->{Origin
} eq 'Proxmox' && $pkgfile->{Component
} eq 'pmg-enterprise') {
448 my $info = PVE
::INotify
::read_file
('subscription');
449 if ($info->{status
} eq 'Active') {
450 $username = $info->{key
};
451 $pw = PMG
::Utils
::get_hwaddress
();
452 $ua->credentials("enterprise.proxmox.com:443", 'pmg-enterprise-repository',
457 syslog
('info', "GET $url\n");
458 my $response = $ua->get($url);
460 if ($response->is_success) {
461 $data = $response->decoded_content;
463 PVE
::Exception
::raise
($response->message, code
=> $response->code);
469 __PACKAGE__-
>register_method({
474 description
=> "Get package information for important Proxmox packages.",
476 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
479 additionalProperties
=> 0,
481 node
=> get_standard_option
('pve-node'),
494 my $pkgname = $param->{name
};
496 my $cache = &$get_apt_cache();
497 my $policy = $cache->policy;
498 my $pkgrecords = $cache->packages();
500 # try to use a resonable ordering (most important things first)
501 my @list = qw(proxmox-mailgateway proxmox-mailgateway-gui proxmox-spamassassin proxmox-widget-toolkit);
503 foreach my $pkgname (keys %$cache) {
504 if ($pkgname =~ m/pve-kernel-/) {
505 my $p = $cache->{$pkgname};
506 push @list, $pkgname if $p && $p->{CurrentState
} eq 'Installed';
511 my @opt_pack = ('zfsutils-linux', 'libpve-apiclient-perl');
513 push @list, qw(libpve-http-server-perl lvm2 pve-firmware libpve-common-perl vncterm pmg-docs novnc-pve libarchive-perl libxdgmime-perl );
515 @list = (@list, @opt_pack);
518 my (undef, undef, $kernel_release) = POSIX
::uname
();
519 my $pmgver = PMG
::pmgcfg
::version_text
();
521 foreach my $pkgname (@list) {
522 my $p = $cache->{$pkgname};
523 my $info = $pkgrecords->lookup($pkgname);
524 my $candidate_ver = defined($p) ?
$policy->candidate($p) : undef;
526 if (my $current_ver = $p->{CurrentVer
}) {
527 $res = &$assemble_pkginfo($pkgname, $info, $current_ver,
528 $candidate_ver || $current_ver);
529 } elsif ($candidate_ver) {
530 $res = &$assemble_pkginfo($pkgname, $info, $candidate_ver,
532 delete $res->{OldVersion
};
536 $res->{CurrentState
} = $p->{CurrentState
};
538 # hack: add some useful information (used by 'pmgversion -v')
539 if ($pkgname eq 'proxmox-mailgateway-gui') {
540 $res->{ManagerVersion
} = $pmgver;
541 } elsif ($pkgname eq 'proxmox-mailgateway') {
542 $res->{RunningKernel
} = $kernel_release;
545 if (grep( /^$pkgname$/, @opt_pack)) {
546 next if $res->{CurrentState
} eq 'NotInstalled';
549 push @$pkglist, $res;