]>
git.proxmox.com Git - pmg-api.git/blob - src/PMG/API2/APT.pm
bcd749b245febe6523af4e47ad9522507b223269
1 package PMG
::API2
::APT
;
13 use PVE
::Tools
qw(extract_param);
18 use PVE
::JSONSchema
qw(get_standard_option);
20 use PMG
::RESTEnvironment
;
24 use Proxmox
::RS
::APT
::Repositories
;
28 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' },
68 { id
=> 'repositories' },
76 my $get_pkgfile = sub {
79 foreach my $verfile (@{$veriter->{FileList
}}) {
80 my $pkgfile = $verfile->{File
};
81 next if !$pkgfile->{Origin
};
88 my $get_changelog_url =sub {
89 my ($pkgname, $info, $pkgver, $origin, $component) = @_;
92 my $base = dirname
($info->{FileName
});
93 if ($origin && $base) {
94 $pkgver =~ s/^\d+://; # strip epoch
95 my $srcpkg = $info->{SourcePkg
} || $pkgname;
96 if ($origin eq 'Debian') {
97 $base =~ s!pool/updates/!pool/!; # for security channel
98 $changelog_url = "http://packages.debian.org/changelogs/$base/" .
99 "${srcpkg}_${pkgver}/changelog";
100 } elsif ($origin eq 'Proxmox') {
101 if ($component eq 'pmg-enterprise') {
102 $changelog_url = "https://enterprise.proxmox.com/debian/pmg/$base/" .
103 "${pkgname}_${pkgver}.changelog";
105 $changelog_url = "http://download.proxmox.com/debian/pmg/$base/" .
106 "${pkgname}_${pkgver}.changelog";
111 return $changelog_url;
114 my $assemble_pkginfo = sub {
115 my ($pkgname, $info, $current_ver, $candidate_ver) = @_;
118 Package
=> $info->{Name
},
119 Title
=> $info->{ShortDesc
},
123 if (my $pkgfile = &$get_pkgfile($candidate_ver)) {
124 $data->{Origin
} = $pkgfile->{Origin
};
125 if (my $changelog_url = &$get_changelog_url($pkgname, $info, $candidate_ver->{VerStr
},
126 $pkgfile->{Origin
}, $pkgfile->{Component
})) {
127 $data->{ChangeLogUrl
} = $changelog_url;
131 if (my $desc = $info->{LongDesc
}) {
132 $desc =~ s/^.*\n\s?//; # remove first line
134 $data->{Description
} = $desc;
137 foreach my $k (qw(Section Arch Priority)) {
138 $data->{$k} = $candidate_ver->{$k};
141 $data->{Version
} = $candidate_ver->{VerStr
};
142 $data->{OldVersion
} = $current_ver->{VerStr
} if $current_ver;
147 # we try to cache results
148 my $pmg_pkgstatus_fn = "/var/lib/pmg/pkgupdates";
150 my $read_cached_pkgstatus = sub {
153 my $jsonstr = PVE
::Tools
::file_get_contents
($pmg_pkgstatus_fn, 5*1024*1024);
154 $data = decode_json
($jsonstr);
157 warn "error reading cached package status in $pmg_pkgstatus_fn\n";
162 my $update_pmg_pkgstatus = sub {
164 syslog
('info', "update new package list: $pmg_pkgstatus_fn");
166 my $notify_status = {};
167 my $oldpkglist = &$read_cached_pkgstatus();
168 foreach my $pi (@$oldpkglist) {
169 $notify_status->{$pi->{Package
}} = $pi->{NotifyStatus
};
174 my $cache = &$get_apt_cache();
175 my $policy = $cache->policy;
176 my $pkgrecords = $cache->packages();
178 foreach my $pkgname (keys %$cache) {
179 my $p = $cache->{$pkgname};
180 next if !$p->{SelectedState
} || ($p->{SelectedState
} ne 'Install');
181 my $current_ver = $p->{CurrentVer
} || next;
182 my $candidate_ver = $policy->candidate($p) || next;
184 if ($current_ver->{VerStr
} ne $candidate_ver->{VerStr
}) {
185 my $info = $pkgrecords->lookup($pkgname);
186 my $res = &$assemble_pkginfo($pkgname, $info, $current_ver, $candidate_ver);
187 push @$pkglist, $res;
189 # also check if we need any new package
190 # Note: this is just a quick hack (not recursive as it should be), because
191 # I found no way to get that info from AptPkg
192 if (my $deps = $candidate_ver->{DependsList
}) {
196 if ($d->{DepType
} eq 'Depends') {
197 $found = $d->{TargetPkg
}->{SelectedState
} eq 'Install' if !$found;
198 $req = $d->{TargetPkg
} if !$req;
200 if (!($d->{CompType
} & AptPkg
::Dep
::Or
)) {
201 if (!$found && $req) { # New required Package
202 my $tpname = $req->{Name
};
203 my $tpinfo = $pkgrecords->lookup($tpname);
204 my $tpcv = $policy->candidate($req);
205 if ($tpinfo && $tpcv) {
206 my $res = &$assemble_pkginfo($tpname, $tpinfo, undef, $tpcv);
207 push @$pkglist, $res;
219 # keep notification status (avoid sending mails abou new packages more than once)
220 foreach my $pi (@$pkglist) {
221 if (my $ns = $notify_status->{$pi->{Package
}}) {
222 $pi->{NotifyStatus
} = $ns if $ns eq $pi->{Version
};
226 PVE
::Tools
::file_set_contents
($pmg_pkgstatus_fn, encode_json
($pkglist));
231 __PACKAGE__-
>register_method({
232 name
=> 'list_updates',
235 description
=> "List available updates.",
238 permissions
=> { check
=> [ 'admin', 'audit' ] },
240 additionalProperties
=> 0,
242 node
=> get_standard_option
('pve-node'),
255 if (my $st1 = File
::stat::stat($pmg_pkgstatus_fn)) {
256 my $st2 = File
::stat::stat("/var/cache/apt/pkgcache.bin");
257 my $st3 = File
::stat::stat("/var/lib/dpkg/status");
259 if ($st2 && $st3 && $st2->mtime <= $st1->mtime && $st3->mtime <= $st1->mtime) {
260 if (my $data = &$read_cached_pkgstatus()) {
266 my $pkglist = &$update_pmg_pkgstatus();
271 __PACKAGE__-
>register_method({
272 name
=> 'update_database',
275 description
=> "This is used to resynchronize the package index files from their sources (apt-get update).",
278 permissions
=> { check
=> [ 'admin' ] },
280 additionalProperties
=> 0,
282 node
=> get_standard_option
('pve-node'),
285 description
=> "Send notification mail about new packages (to email address specified for user 'root\@pam').",
291 description
=> "Only produces output suitable for logging, omitting progress indicators.",
303 my $rpcenv = PMG
::RESTEnvironment-
>get();
305 my $authuser = $rpcenv->get_user();
310 my $pmg_cfg = PMG
::Config-
>new();
312 my $http_proxy = $pmg_cfg->get('admin', 'http_proxy');
313 my $aptconf = "// no proxy configured\n";
315 $aptconf = "Acquire::http::Proxy \"${http_proxy}\";\n";
317 my $aptcfn = "/etc/apt/apt.conf.d/76pmgproxy";
318 PVE
::Tools
::file_set_contents
($aptcfn, $aptconf);
320 my $cmd = ['apt-get', 'update'];
322 print "starting apt-get update\n" if !$param->{quiet
};
324 if ($param->{quiet
}) {
325 PVE
::Tools
::run_command
($cmd, outfunc
=> sub {}, errfunc
=> sub {});
327 PVE
::Tools
::run_command
($cmd);
330 my $pkglist = &$update_pmg_pkgstatus();
332 if ($param->{notify
} && scalar(@$pkglist)) {
334 my $mailfrom = "root";
336 if (my $mailto = $pmg_cfg->get('admin', 'email', 1)) {
338 my $text .= "The following updates are available:\n\n";
341 foreach my $p (sort {$a->{Package
} cmp $b->{Package
} } @$pkglist) {
342 next if $p->{NotifyStatus
} && $p->{NotifyStatus
} eq $p->{Version
};
344 if ($p->{OldVersion
}) {
345 $text .= "$p->{Package}: $p->{OldVersion} ==> $p->{Version}\n";
347 $text .= "$p->{Package}: $p->{Version} (new)\n";
353 my $hostname = `hostname -f` || PVE
::INotify
::nodename
();
356 my $subject = "New software packages available ($hostname)";
357 PVE
::Tools
::sendmail
($mailto, $subject, $text, undef,
358 $mailfrom, 'Proxmox Mail Gateway');
360 foreach my $pi (@$pkglist) {
361 $pi->{NotifyStatus
} = $pi->{Version
};
364 PVE
::Tools
::file_set_contents
($pmg_pkgstatus_fn, encode_json
($pkglist));
371 return $rpcenv->fork_worker('aptupdate', undef, $authuser, $realcmd);
375 __PACKAGE__-
>register_method({
379 description
=> "Get package changelogs.",
381 permissions
=> { check
=> [ 'admin', 'audit' ] },
383 additionalProperties
=> 0,
385 node
=> get_standard_option
('pve-node'),
387 description
=> "Package name.",
391 description
=> "Package version.",
403 my $pkgname = $param->{name
};
405 my $cache = &$get_apt_cache();
406 my $policy = $cache->policy;
407 my $p = $cache->{$pkgname} || die "no such package '$pkgname'\n";
408 my $pkgrecords = $cache->packages();
411 if ($param->{version
}) {
412 if (my $available = $p->{VersionList
}) {
413 for my $v (@$available) {
414 if ($v->{VerStr
} eq $param->{version
}) {
420 die "package '$pkgname' version '$param->{version}' is not available\n" if !$ver;
422 $ver = $policy->candidate($p) || die "no installation candidate for package '$pkgname'\n";
425 my $info = $pkgrecords->lookup($pkgname);
427 my $pkgfile = &$get_pkgfile($ver);
430 die "changelog for '${pkgname}_$ver->{VerStr}' not available\n"
431 if !($pkgfile && ($url = &$get_changelog_url($pkgname, $info, $ver->{VerStr
}, $pkgfile->{Origin
}, $pkgfile->{Component
})));
435 my $pmg_cfg = PMG
::Config-
>new();
436 my $proxy = $pmg_cfg->get('admin', 'http_proxy');
438 my $ua = LWP
::UserAgent-
>new;
439 $ua->agent("PMG/1.0");
441 $ua->max_size(1024*1024);
442 $ua->ssl_opts(verify_hostname
=> 0); # don't care for changelogs
445 $ua->proxy(['http', 'https'], $proxy);
453 if ($pkgfile->{Origin
} eq 'Proxmox' && $pkgfile->{Component
} eq 'pmg-enterprise') {
454 my $info = PMG
::API2
::Subscription
::read_etc_subscription
();
455 if ($info->{status
} eq 'active') {
456 $username = $info->{key
};
457 $pw = PMG
::Utils
::get_hwaddress
();
458 $ua->credentials("enterprise.proxmox.com:443", 'pmg-enterprise-repository',
463 syslog
('info', "GET $url\n");
464 my $response = $ua->get($url);
466 if ($response->is_success) {
467 $data = $response->decoded_content;
469 PVE
::Exception
::raise
($response->message, code
=> $response->code);
475 __PACKAGE__-
>register_method({
476 name
=> 'repositories',
477 path
=> 'repositories',
480 description
=> "Get APT repository information.",
481 permissions
=> { check
=> [ 'admin', 'audit' ] },
483 additionalProperties
=> 0,
485 node
=> get_standard_option
('pve-node'),
490 description
=> "Result from parsing the APT repository files in /etc/apt/.",
494 description
=> "List of parsed repository files.",
500 description
=> "Path to the problematic file.",
504 enum
=> [ 'list', 'sources' ],
505 description
=> "Format of the file.",
509 description
=> "The parsed repositories.",
515 description
=> "List of package types.",
518 enum
=> [ 'deb', 'deb-src' ],
522 description
=> "List of repository URIs.",
530 description
=> "List of package distribuitions",
537 description
=> "List of repository components",
538 optional
=> 1, # not present if suite is absolute
545 description
=> "Additional options",
564 description
=> "Associated comment",
569 enum
=> [ 'list', 'sources' ],
570 description
=> "Format of the defining file.",
574 description
=> "Whether the repository is enabled or not",
581 description
=> "Digest of the file as bytes.",
591 description
=> "List of problematic repository files.",
597 description
=> "Path to the problematic file.",
601 description
=> "The error message",
608 description
=> "Common digest of all files.",
612 description
=> "Additional information/warnings for APT repositories.",
618 description
=> "Path to the associated file.",
622 description
=> "Index of the associated repository within the file.",
626 description
=> "Property from which the info originates.",
631 description
=> "Kind of the information (e.g. warning).",
635 description
=> "Information message.",
640 'standard-repos' => {
642 description
=> "List of standard repositories and their configuration status",
648 description
=> "Handle to identify the repository.",
652 description
=> "Display name of the repository.",
656 description
=> "Description of the repository.",
661 description
=> "Indicating enabled/disabled status, if the " .
662 "repository is configured.",
672 return Proxmox
::RS
::APT
::Repositories
::repositories
("pmg");
675 __PACKAGE__-
>register_method({
676 name
=> 'add_repository',
677 path
=> 'repositories',
679 description
=> "Add a standard repository to the configuration",
680 permissions
=> { check
=> [ 'admin' ] },
684 additionalProperties
=> 0,
686 node
=> get_standard_option
('pve-node'),
689 description
=> "Handle that identifies a repository.",
693 description
=> "Digest to detect modifications.",
705 Proxmox
::RS
::APT
::Repositories
::add_repository
($param->{handle
}, "pmg", $param->{digest
});
708 __PACKAGE__-
>register_method({
709 name
=> 'change_repository',
710 path
=> 'repositories',
712 description
=> "Change the properties of a repository. Currently only allows enabling/disabling.",
713 permissions
=> { check
=> [ 'admin' ] },
717 additionalProperties
=> 0,
719 node
=> get_standard_option
('pve-node'),
722 description
=> "Path to the containing file.",
726 description
=> "Index within the file (starting from 0).",
730 description
=> "Whether the repository should be enabled or not.",
735 description
=> "Digest to detect modifications.",
749 my $enabled = $param->{enabled
};
750 $options->{enabled
} = int($enabled) if defined($enabled);
752 Proxmox
::RS
::APT
::Repositories
::change_repository
(
754 int($param->{index}),
760 __PACKAGE__-
>register_method({
765 description
=> "Get package information for important Proxmox packages.",
766 permissions
=> { check
=> [ 'admin', '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-mailgateway pmg-api pmg-gui);
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;
797 libpve-apiclient-perl
798 proxmox-mailgateway-container
799 proxmox-offline-mirror-helper
810 libpve-http-server-perl
812 libproxmox-acme-plugins
819 proxmox-mini-journalreader
821 proxmox-widget-toolkit
826 push @list, (sort @pkgs, @opt_pack);
828 my (undef, undef, $kernel_release) = POSIX
::uname
();
829 my $pmgver = PMG
::pmgcfg
::version_text
();
832 foreach my $pkgname (@list) {
833 my $p = $cache->{$pkgname};
834 my $info = $pkgrecords->lookup($pkgname);
835 my $candidate_ver = defined($p) ?
$policy->candidate($p) : undef;
837 if (my $current_ver = $p->{CurrentVer
}) {
838 $res = &$assemble_pkginfo($pkgname, $info, $current_ver,
839 $candidate_ver || $current_ver);
840 } elsif ($candidate_ver) {
841 $res = &$assemble_pkginfo($pkgname, $info, $candidate_ver,
843 delete $res->{OldVersion
};
847 $res->{CurrentState
} = $p->{CurrentState
};
849 if (grep( /^$pkgname$/, @opt_pack)) {
850 next if $res->{CurrentState
} eq 'NotInstalled';
853 # hack: add some useful information (used by 'pmgversion -v')
854 if ($pkgname =~ /^proxmox-mailgateway(-container)?$/) {
855 $res->{ManagerVersion
} = $pmgver;
856 $res->{RunningKernel
} = $kernel_release;
857 if ($pkgname eq 'proxmox-mailgateway-container') {
858 # another hack: replace proxmox-mailgateway with CT meta pkg
860 unshift @$pkglist, $res;
865 push @$pkglist, $res;