]>
git.proxmox.com Git - pve-manager.git/blob - PVE/API2/APT.pm
1 package PVE
::API2
::APT
;
14 use PVE
::Tools
qw(extract_param);
20 use PVE
::RPCEnvironment
;
24 use PVE
::JSONSchema
qw(get_standard_option);
27 use AptPkg
::PkgRecords
;
30 my $get_apt_cache = sub {
32 my $apt_cache = AptPkg
::Cache-
>new() || die "unable to initialize AptPkg::Cache\n";
37 use base
qw(PVE::RESTHandler);
39 __PACKAGE__-
>register_method({
43 description
=> "Directory index for apt (Advanced Package Tool).",
48 additionalProperties
=> 0,
50 node
=> get_standard_option
('pve-node'),
58 id
=> { type
=> 'string' },
61 links
=> [ { rel
=> 'child', href
=> "{id}" } ],
67 { id
=> 'changelog' },
75 my $get_pkgfile = sub {
78 foreach my $verfile (@{$veriter->{FileList
}}) {
79 my $pkgfile = $verfile->{File
};
80 next if !$pkgfile->{Origin
};
87 my $get_changelog_url =sub {
88 my ($pkgname, $info, $pkgver, $origin, $component) = @_;
91 my $base = dirname
($info->{FileName
});
92 if ($origin && $base) {
93 $pkgver =~ s/^\d+://; # strip epoch
94 my $srcpkg = $info->{SourcePkg
} || $pkgname;
95 if ($origin eq 'Debian') {
96 $base =~ s!pool/updates/!pool/!; # for security channel
97 $changelog_url = "http://packages.debian.org/changelogs/$base/" .
98 "${srcpkg}_${pkgver}/changelog";
99 } elsif ($origin eq 'Proxmox') {
100 if ($component eq 'pve-enterprise') {
101 $changelog_url = "https://enterprise.proxmox.com/debian/$base/" .
102 "${pkgname}_${pkgver}.changelog";
104 $changelog_url = "http://download.proxmox.com/debian/$base/" .
105 "${pkgname}_${pkgver}.changelog";
110 return $changelog_url;
113 my $assemble_pkginfo = sub {
114 my ($pkgname, $info, $current_ver, $candidate_ver) = @_;
117 Package
=> $info->{Name
},
118 Title
=> $info->{ShortDesc
},
122 if (my $pkgfile = &$get_pkgfile($candidate_ver)) {
123 $data->{Origin
} = $pkgfile->{Origin
};
124 if (my $changelog_url = &$get_changelog_url($pkgname, $info, $candidate_ver->{VerStr
},
125 $pkgfile->{Origin
}, $pkgfile->{Component
})) {
126 $data->{ChangeLogUrl
} = $changelog_url;
130 if (my $desc = $info->{LongDesc
}) {
131 $desc =~ s/^.*\n\s?//; # remove first line
133 $data->{Description
} = $desc;
136 foreach my $k (qw(Section Arch Priority)) {
137 $data->{$k} = $candidate_ver->{$k};
140 $data->{Version
} = $candidate_ver->{VerStr
};
141 $data->{OldVersion
} = $current_ver->{VerStr
} if $current_ver;
146 # we try to cache results
147 my $pve_pkgstatus_fn = "/var/lib/pve-manager/pkgupdates";
149 my $read_cached_pkgstatus = sub {
152 my $jsonstr = PVE
::Tools
::file_get_contents
($pve_pkgstatus_fn, 5*1024*1024);
153 $data = decode_json
($jsonstr);
156 warn "error reading cached package status in $pve_pkgstatus_fn\n";
161 my $update_pve_pkgstatus = sub {
163 syslog
('info', "update new package list: $pve_pkgstatus_fn");
165 my $notify_status = {};
166 my $oldpkglist = &$read_cached_pkgstatus();
167 foreach my $pi (@$oldpkglist) {
168 $notify_status->{$pi->{Package
}} = $pi->{NotifyStatus
};
173 my $cache = &$get_apt_cache();
174 my $policy = $cache->policy;
175 my $pkgrecords = $cache->packages();
177 foreach my $pkgname (keys %$cache) {
178 my $p = $cache->{$pkgname};
179 next if !$p->{SelectedState
} || ($p->{SelectedState
} ne 'Install');
180 my $current_ver = $p->{CurrentVer
} || next;
181 my $candidate_ver = $policy->candidate($p) || next;
183 if ($current_ver->{VerStr
} ne $candidate_ver->{VerStr
}) {
184 my $info = $pkgrecords->lookup($pkgname);
185 my $res = &$assemble_pkginfo($pkgname, $info, $current_ver, $candidate_ver);
186 push @$pkglist, $res;
188 # also check if we need any new package
189 # Note: this is just a quick hack (not recursive as it should be), because
190 # I found no way to get that info from AptPkg
191 if (my $deps = $candidate_ver->{DependsList
}) {
195 if ($d->{DepType
} eq 'Depends') {
196 $found = $d->{TargetPkg
}->{SelectedState
} eq 'Install' if !$found;
197 $req = $d->{TargetPkg
} if !$req;
199 if (!($d->{CompType
} & AptPkg
::Dep
::Or
)) {
200 if (!$found && $req) { # New required Package
201 my $tpname = $req->{Name
};
202 my $tpinfo = $pkgrecords->lookup($tpname);
203 my $tpcv = $policy->candidate($req);
204 if ($tpinfo && $tpcv) {
205 my $res = &$assemble_pkginfo($tpname, $tpinfo, undef, $tpcv);
206 push @$pkglist, $res;
218 # keep notification status (avoid sending mails abou new packages more than once)
219 foreach my $pi (@$pkglist) {
220 if (my $ns = $notify_status->{$pi->{Package
}}) {
221 $pi->{NotifyStatus
} = $ns if $ns eq $pi->{Version
};
225 PVE
::Tools
::file_set_contents
($pve_pkgstatus_fn, encode_json
($pkglist));
230 __PACKAGE__-
>register_method({
231 name
=> 'list_updates',
234 description
=> "List available updates.",
236 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
241 additionalProperties
=> 0,
243 node
=> get_standard_option
('pve-node'),
256 if (my $st1 = File
::stat::stat($pve_pkgstatus_fn)) {
257 my $st2 = File
::stat::stat("/var/cache/apt/pkgcache.bin");
258 my $st3 = File
::stat::stat("/var/lib/dpkg/status");
260 if ($st2 && $st3 && $st2->mtime <= $st1->mtime && $st3->mtime <= $st1->mtime) {
261 if (my $data = &$read_cached_pkgstatus()) {
267 my $pkglist = &$update_pve_pkgstatus();
272 __PACKAGE__-
>register_method({
273 name
=> 'update_database',
276 description
=> "This is used to resynchronize the package index files from their sources (apt-get update).",
278 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
283 additionalProperties
=> 0,
285 node
=> get_standard_option
('pve-node'),
288 description
=> "Send notification mail about new packages (to email address specified for user 'root\@pam').",
294 description
=> "Only produces output suitable for logging, omitting progress indicators.",
306 my $rpcenv = PVE
::RPCEnvironment
::get
();
308 my $authuser = $rpcenv->get_user();
313 # setup proxy for apt
314 my $dcconf = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
316 my $aptconf = "// no proxy configured\n";
317 if ($dcconf->{http_proxy
}) {
318 $aptconf = "Acquire::http::Proxy \"$dcconf->{http_proxy}\";\n";
320 my $aptcfn = "/etc/apt/apt.conf.d/76pveproxy";
321 PVE
::Tools
::file_set_contents
($aptcfn, $aptconf);
323 my $cmd = ['apt-get', 'update'];
325 print "starting apt-get update\n" if !$param->{quiet
};
327 if ($param->{quiet
}) {
328 PVE
::Tools
::run_command
($cmd, outfunc
=> sub {}, errfunc
=> sub {});
330 PVE
::Tools
::run_command
($cmd);
333 my $pkglist = &$update_pve_pkgstatus();
335 if ($param->{notify
} && scalar(@$pkglist)) {
337 my $usercfg = PVE
::Cluster
::cfs_read_file
("user.cfg");
338 my $rootcfg = $usercfg->{users
}->{'root@pam'} || {};
339 my $mailto = $rootcfg->{email
};
342 my $hostname = `hostname -f` || PVE
::INotify
::nodename
();
344 my $mailfrom = $dcconf->{email_from
} || "root";
346 my $data = "Content-Type: text/plain;charset=\"UTF8\"\n";
347 $data .= "Content-Transfer-Encoding: 8bit\n";
348 $data .= "FROM: <$mailfrom>\n";
349 $data .= "TO: $mailto\n";
350 $data .= "SUBJECT: New software packages available ($hostname)\n";
353 $data .= "The following updates are available:\n\n";
356 foreach my $p (sort {$a->{Package
} cmp $b->{Package
} } @$pkglist) {
357 next if $p->{NotifyStatus
} && $p->{NotifyStatus
} eq $p->{Version
};
359 if ($p->{OldVersion
}) {
360 $data .= "$p->{Package}: $p->{OldVersion} ==> $p->{Version}\n";
362 $data .= "$p->{Package}: $p->{Version} (new)\n";
368 my $fh = IO
::File-
>new("|sendmail -B 8BITMIME -f $mailfrom $mailto") ||
369 die "unable to open 'sendmail' - $!";
373 $fh->close() || die "unable to close 'sendmail' - $!";
375 foreach my $pi (@$pkglist) {
376 $pi->{NotifyStatus
} = $pi->{Version
};
378 PVE
::Tools
::file_set_contents
($pve_pkgstatus_fn, encode_json
($pkglist));
385 return $rpcenv->fork_worker('aptupdate', undef, $authuser, $realcmd);
389 __PACKAGE__-
>register_method({
393 description
=> "Get package changelogs.",
395 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
399 additionalProperties
=> 0,
401 node
=> get_standard_option
('pve-node'),
403 description
=> "Package name.",
407 description
=> "Package version.",
419 my $pkgname = $param->{name
};
421 my $cache = &$get_apt_cache();
422 my $policy = $cache->policy;
423 my $p = $cache->{$pkgname} || die "no such package '$pkgname'\n";
424 my $pkgrecords = $cache->packages();
427 if ($param->{version
}) {
428 if (my $available = $p->{VersionList
}) {
429 for my $v (@$available) {
430 if ($v->{VerStr
} eq $param->{version
}) {
436 die "package '$pkgname' version '$param->{version}' is not avalable\n" if !$ver;
438 $ver = $policy->candidate($p) || die "no installation candidate for package '$pkgname'\n";
441 my $info = $pkgrecords->lookup($pkgname);
443 my $pkgfile = &$get_pkgfile($ver);
446 die "changelog for '${pkgname}_$ver->{VerStr}' not available\n"
447 if !($pkgfile && ($url = &$get_changelog_url($pkgname, $info, $ver->{VerStr
}, $pkgfile->{Origin
}, $pkgfile->{Component
})));
451 my $dccfg = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
452 my $proxy = $dccfg->{http_proxy
};
454 my $ua = LWP
::UserAgent-
>new;
455 $ua->agent("PVE/1.0");
457 $ua->max_size(1024*1024);
458 $ua->ssl_opts(verify_hostname
=> 0); # don't care for changelogs
461 $ua->proxy(['http', 'https'], $proxy);
469 if ($pkgfile->{Origin
} eq 'Proxmox' && $pkgfile->{Component
} eq 'pve-enterprise') {
470 my $info = PVE
::INotify
::read_file
('subscription');
471 if ($info->{status
} eq 'Active') {
472 $username = $info->{key
};
473 $pw = PVE
::API2Tools
::get_hwaddress
();
474 $ua->credentials("enterprise.proxmox.com:443", 'pve-enterprise-repository',
479 my $response = $ua->get($url);
481 if ($response->is_success) {
482 $data = $response->decoded_content;
484 PVE
::Exception
::raise
($response->message, code
=> $response->code);
490 __PACKAGE__-
>register_method({
495 description
=> "Get package information for important Proxmox packages.",
497 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
500 additionalProperties
=> 0,
502 node
=> get_standard_option
('pve-node'),
515 my $cache = &$get_apt_cache();
516 my $policy = $cache->policy;
517 my $pkgrecords = $cache->packages();
519 # order most important things first
520 my @list = qw(proxmox-ve pve-manager);
522 my $aptver = $AptPkg::System
::_system-
>versioning();
523 my $byver = sub { $aptver->compare($cache->{$b}->{CurrentVer
}->{VerStr
}, $cache->{$a}->{CurrentVer
}->{VerStr
}) };
524 push @list, sort $byver grep { /^pve-kernel-/ && $cache->{$_}->{CurrentState
} eq 'Installed' } keys %$cache;
529 libpve-apiclient-perl
542 libpve-access-control
544 libpve-guest-common-perl
545 libpve-http-server-perl
552 proxmox-widget-toolkit
570 # add the rest ordered by name, easier to find for humans
571 push @list, (sort @pkgs, @opt_pack);
573 my (undef, undef, $kernel_release) = POSIX
::uname
();
574 my $pvever = PVE
::pvecfg
::version_text
();
577 foreach my $pkgname (@list) {
578 my $p = $cache->{$pkgname};
579 my $info = $pkgrecords->lookup($pkgname);
580 my $candidate_ver = defined($p) ?
$policy->candidate($p) : undef;
582 if (my $current_ver = $p->{CurrentVer
}) {
583 $res = &$assemble_pkginfo($pkgname, $info, $current_ver,
584 $candidate_ver || $current_ver);
585 } elsif ($candidate_ver) {
586 $res = &$assemble_pkginfo($pkgname, $info, $candidate_ver,
588 delete $res->{OldVersion
};
592 $res->{CurrentState
} = $p->{CurrentState
};
594 # hack: add some useful information (used by 'pveversion -v')
595 if ($pkgname eq 'pve-manager') {
596 $res->{ManagerVersion
} = $pvever;
597 } elsif ($pkgname eq 'proxmox-ve') {
598 $res->{RunningKernel
} = $kernel_release;
600 if (grep( /^$pkgname$/, @opt_pack)) {
601 next if $res->{CurrentState
} eq 'NotInstalled';
604 push @$pkglist, $res;