]>
git.proxmox.com Git - pve-manager.git/blob - PVE/API2/APT.pm
1 package PVE
::API2
::APT
;
13 use Proxmox
::RS
::APT
::Repositories
;
16 use PVE
::Tools
qw(extract_param);
18 use PVE
::DataCenterConfig
;
24 use PVE
::RPCEnvironment
;
28 use PVE
::JSONSchema
qw(get_standard_option);
31 use AptPkg
::PkgRecords
;
34 my $get_apt_cache = sub {
36 my $apt_cache = AptPkg
::Cache-
>new() || die "unable to initialize AptPkg::Cache\n";
41 use base
qw(PVE::RESTHandler);
43 __PACKAGE__-
>register_method({
47 description
=> "Directory index for apt (Advanced Package Tool).",
52 additionalProperties
=> 0,
54 node
=> get_standard_option
('pve-node'),
62 id
=> { type
=> 'string' },
65 links
=> [ { rel
=> 'child', href
=> "{id}" } ],
71 { id
=> 'changelog' },
72 { id
=> 'repositories' },
80 my $get_pkgfile = sub {
83 foreach my $verfile (@{$veriter->{FileList
}}) {
84 my $pkgfile = $verfile->{File
};
85 next if !$pkgfile->{Origin
};
92 my $assemble_pkginfo = sub {
93 my ($pkgname, $info, $current_ver, $candidate_ver) = @_;
96 Package
=> $info->{Name
},
97 Title
=> $info->{ShortDesc
},
101 if (my $pkgfile = &$get_pkgfile($candidate_ver)) {
102 $data->{Origin
} = $pkgfile->{Origin
};
105 if (my $desc = $info->{LongDesc
}) {
106 $desc =~ s/^.*\n\s?//; # remove first line
108 $data->{Description
} = $desc;
111 foreach my $k (qw(Section Arch Priority)) {
112 $data->{$k} = $candidate_ver->{$k};
115 $data->{Version
} = $candidate_ver->{VerStr
};
116 $data->{OldVersion
} = $current_ver->{VerStr
} if $current_ver;
121 # we try to cache results
122 my $pve_pkgstatus_fn = "/var/lib/pve-manager/pkgupdates";
123 my $read_cached_pkgstatus = sub {
124 my $data = eval { decode_json
(PVE
::Tools
::file_get_contents
($pve_pkgstatus_fn, 5*1024*1024)) } // [];
125 warn "error reading cached package status in '$pve_pkgstatus_fn' - $@\n" if $@;
129 my $update_pve_pkgstatus = sub {
130 syslog
('info', "update new package list: $pve_pkgstatus_fn");
132 my $oldpkglist = &$read_cached_pkgstatus();
133 my $notify_status = { map { $_->{Package
} => $_->{NotifyStatus
} } $oldpkglist->@* };
137 my $cache = &$get_apt_cache();
138 my $policy = $cache->policy;
139 my $pkgrecords = $cache->packages();
141 foreach my $pkgname (keys %$cache) {
142 my $p = $cache->{$pkgname};
143 next if !$p->{SelectedState
} || ($p->{SelectedState
} ne 'Install');
144 my $current_ver = $p->{CurrentVer
} || next;
145 my $candidate_ver = $policy->candidate($p) || next;
146 next if $current_ver->{VerStr
} eq $candidate_ver->{VerStr
};
148 my $info = $pkgrecords->lookup($pkgname);
149 my $res = &$assemble_pkginfo($pkgname, $info, $current_ver, $candidate_ver);
150 push @$pkglist, $res;
152 # also check if we need any new package
153 # Note: this is just a quick hack (not recursive as it should be), because
154 # I found no way to get that info from AptPkg
155 my $deps = $candidate_ver->{DependsList
} || next;
159 if ($d->{DepType
} eq 'Depends') {
160 $found = $d->{TargetPkg
}->{SelectedState
} eq 'Install' if !$found;
161 # need to check ProvidesList for virtual packages
162 if (!$found && (my $provides = $d->{TargetPkg
}->{ProvidesList
})) {
163 for my $provide ($provides->@*) {
164 $found = $provide->{OwnerPkg
}->{SelectedState
} eq 'Install';
168 $req = $d->{TargetPkg
} if !$req;
170 if (!($d->{CompType
} & AptPkg
::Dep
::Or
)) {
171 if (!$found && $req) { # New required Package
172 my $tpname = $req->{Name
};
173 my $tpinfo = $pkgrecords->lookup($tpname);
174 my $tpcv = $policy->candidate($req);
175 if ($tpinfo && $tpcv) {
176 my $res = &$assemble_pkginfo($tpname, $tpinfo, undef, $tpcv);
177 push @$pkglist, $res;
187 # keep notification status (avoid sending mails abou new packages more than once)
188 foreach my $pi (@$pkglist) {
189 if (my $ns = $notify_status->{$pi->{Package
}}) {
190 $pi->{NotifyStatus
} = $ns if $ns eq $pi->{Version
};
194 PVE
::Tools
::file_set_contents
($pve_pkgstatus_fn, encode_json
($pkglist));
199 __PACKAGE__-
>register_method({
200 name
=> 'list_updates',
203 description
=> "List available updates.",
205 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
210 additionalProperties
=> 0,
212 node
=> get_standard_option
('pve-node'),
225 if (my $st1 = File
::stat::stat($pve_pkgstatus_fn)) {
226 my $st2 = File
::stat::stat("/var/cache/apt/pkgcache.bin");
227 my $st3 = File
::stat::stat("/var/lib/dpkg/status");
229 if ($st2 && $st3 && $st2->mtime <= $st1->mtime && $st3->mtime <= $st1->mtime) {
230 if (my $data = &$read_cached_pkgstatus()) {
236 my $pkglist = &$update_pve_pkgstatus();
241 my $updates_available_subject_template = "New software packages available ({{hostname}})";
242 my $updates_available_body_template = <<EOT;
243 The following updates are available:
247 __PACKAGE__-
>register_method({
248 name
=> 'update_database',
251 description
=> "This is used to resynchronize the package index files from their sources (apt-get update).",
253 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
258 additionalProperties
=> 0,
260 node
=> get_standard_option
('pve-node'),
263 description
=> "Send notification about new packages.",
269 description
=> "Only produces output suitable for logging, omitting progress indicators.",
281 my $rpcenv = PVE
::RPCEnvironment
::get
();
282 my $dcconf = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
284 my $authuser = $rpcenv->get_user();
289 # setup proxy for apt
291 my $aptconf = "// no proxy configured\n";
292 if ($dcconf->{http_proxy
}) {
293 $aptconf = "Acquire::http::Proxy \"$dcconf->{http_proxy}\";\n";
295 my $aptcfn = "/etc/apt/apt.conf.d/76pveproxy";
296 PVE
::Tools
::file_set_contents
($aptcfn, $aptconf);
298 my $cmd = ['apt-get', 'update'];
300 print "starting apt-get update\n" if !$param->{quiet
};
302 if ($param->{quiet
}) {
303 PVE
::Tools
::run_command
($cmd, outfunc
=> sub {}, errfunc
=> sub {});
305 PVE
::Tools
::run_command
($cmd);
308 my $pkglist = &$update_pve_pkgstatus();
310 if ($param->{notify
} && scalar(@$pkglist)) {
311 my $updates_table = {
319 label
=> "Old Version",
323 label
=> "New Version",
331 my $hostname = `hostname -f` || PVE
::INotify
::nodename
();
335 foreach my $p (sort {$a->{Package
} cmp $b->{Package
} } @$pkglist) {
336 next if $p->{NotifyStatus
} && $p->{NotifyStatus
} eq $p->{Version
};
339 push @{$updates_table->{data
}}, {
340 "package" => $p->{Package
},
341 "old-version" => $p->{OldVersion
},
342 "new-version" => $p->{Version
}
348 my $template_data = {
349 updates
=> $updates_table,
350 hostname
=> $hostname,
353 # Additional metadata fields that can be used in notification
355 my $metadata_fields = {
356 type
=> 'package-updates',
357 hostname
=> $hostname,
361 $updates_available_subject_template,
362 $updates_available_body_template,
367 foreach my $pi (@$pkglist) {
368 $pi->{NotifyStatus
} = $pi->{Version
};
370 PVE
::Tools
::file_set_contents
($pve_pkgstatus_fn, encode_json
($pkglist));
376 return $rpcenv->fork_worker('aptupdate', undef, $authuser, $realcmd);
382 __PACKAGE__-
>register_method({
386 description
=> "Get package changelogs.",
388 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
392 additionalProperties
=> 0,
394 node
=> get_standard_option
('pve-node'),
396 description
=> "Package name.",
400 description
=> "Package version.",
412 my $pkgname = $param->{name
};
414 my $cmd = ['apt-get', 'changelog', '-qq'];
415 if (my $version = $param->{version
}) {
416 push @$cmd, "$pkgname=$version";
418 push @$cmd, "$pkgname";
423 my $rc = PVE
::Tools
::run_command
(
428 $output .= "$line\n";
433 $output .= "RC: $rc" if $rc != 0;
438 __PACKAGE__-
>register_method({
439 name
=> 'repositories',
440 path
=> 'repositories',
443 description
=> "Get APT repository information.",
445 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
448 additionalProperties
=> 0,
450 node
=> get_standard_option
('pve-node'),
455 description
=> "Result from parsing the APT repository files in /etc/apt/.",
459 description
=> "List of parsed repository files.",
465 description
=> "Path to the problematic file.",
469 enum
=> [ 'list', 'sources' ],
470 description
=> "Format of the file.",
474 description
=> "The parsed repositories.",
480 description
=> "List of package types.",
483 enum
=> [ 'deb', 'deb-src' ],
487 description
=> "List of repository URIs.",
495 description
=> "List of package distribuitions",
502 description
=> "List of repository components",
503 optional
=> 1, # not present if suite is absolute
510 description
=> "Additional options",
529 description
=> "Associated comment",
534 enum
=> [ 'list', 'sources' ],
535 description
=> "Format of the defining file.",
539 description
=> "Whether the repository is enabled or not",
546 description
=> "Digest of the file as bytes.",
556 description
=> "List of problematic repository files.",
562 description
=> "Path to the problematic file.",
566 description
=> "The error message",
573 description
=> "Common digest of all files.",
577 description
=> "Additional information/warnings for APT repositories.",
583 description
=> "Path to the associated file.",
587 description
=> "Index of the associated repository within the file.",
591 description
=> "Property from which the info originates.",
596 description
=> "Kind of the information (e.g. warning).",
600 description
=> "Information message.",
605 'standard-repos' => {
607 description
=> "List of standard repositories and their configuration status",
613 description
=> "Handle to identify the repository.",
617 description
=> "Full name of the repository.",
622 description
=> "Indicating enabled/disabled status, if the " .
623 "repository is configured.",
633 return Proxmox
::RS
::APT
::Repositories
::repositories
("pve");
636 __PACKAGE__-
>register_method({
637 name
=> 'add_repository',
638 path
=> 'repositories',
640 description
=> "Add a standard repository to the configuration",
642 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
647 additionalProperties
=> 0,
649 node
=> get_standard_option
('pve-node'),
652 description
=> "Handle that identifies a repository.",
656 description
=> "Digest to detect modifications.",
668 Proxmox
::RS
::APT
::Repositories
::add_repository
($param->{handle
}, "pve", $param->{digest
});
671 __PACKAGE__-
>register_method({
672 name
=> 'change_repository',
673 path
=> 'repositories',
675 description
=> "Change the properties of a repository. Currently only allows enabling/disabling.",
677 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
682 additionalProperties
=> 0,
684 node
=> get_standard_option
('pve-node'),
687 description
=> "Path to the containing file.",
691 description
=> "Index within the file (starting from 0).",
695 description
=> "Whether the repository should be enabled or not.",
700 description
=> "Digest to detect modifications.",
714 my $enabled = $param->{enabled
};
715 $options->{enabled
} = int($enabled) if defined($enabled);
717 Proxmox
::RS
::APT
::Repositories
::change_repository
(
719 int($param->{index}),
725 __PACKAGE__-
>register_method({
730 description
=> "Get package information for important Proxmox packages.",
732 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
735 additionalProperties
=> 0,
737 node
=> get_standard_option
('pve-node'),
750 my $cache = &$get_apt_cache();
751 my $policy = $cache->policy;
752 my $pkgrecords = $cache->packages();
754 # order most important things first
755 my @list = qw(proxmox-ve pve-manager);
757 my $aptver = $AptPkg::System
::_system-
>versioning();
758 my $byver = sub { $aptver->compare($cache->{$b}->{CurrentVer
}->{VerStr
}, $cache->{$a}->{CurrentVer
}->{VerStr
}) };
759 push @list, sort $byver grep { /^(?:pve|proxmox)-kernel-/ && $cache->{$_}->{CurrentState
} eq 'Installed' } keys %$cache;
773 libpve-apiclient-perl
776 proxmox-backup-file-restore
778 proxmox-kernel-helper
779 proxmox-offline-mirror-helper
780 pve-esxi-import-tools
792 libproxmox-backup-qemu0
794 libpve-access-control
795 libpve-cluster-api-perl
798 libpve-guest-common-perl
799 libpve-http-server-perl
809 proxmox-backup-client
811 proxmox-mini-journalreader
812 proxmox-widget-toolkit
830 # add the rest ordered by name, easier to find for humans
831 push @list, (sort @pkgs, @opt_pack);
833 my (undef, undef, $kernel_release) = POSIX
::uname
();
834 my $pvever = PVE
::pvecfg
::version_text
();
837 foreach my $pkgname (@list) {
838 my $p = $cache->{$pkgname};
839 my $info = $pkgrecords->lookup($pkgname);
840 my $candidate_ver = defined($p) ?
$policy->candidate($p) : undef;
842 if (my $current_ver = $p->{CurrentVer
}) {
843 $res = $assemble_pkginfo->($pkgname, $info, $current_ver, $candidate_ver || $current_ver);
844 } elsif ($candidate_ver) {
845 $res = $assemble_pkginfo->($pkgname, $info, $candidate_ver, $candidate_ver);
846 delete $res->{OldVersion
};
850 $res->{CurrentState
} = $p->{CurrentState
};
852 # hack: add some useful information (used by 'pveversion -v')
853 if ($pkgname eq 'pve-manager') {
854 $res->{ManagerVersion
} = $pvever;
855 } elsif ($pkgname eq 'proxmox-ve') {
856 $res->{RunningKernel
} = $kernel_release;
858 if (grep( /^$pkgname$/, @opt_pack)) {
859 next if $res->{CurrentState
} eq 'NotInstalled';
862 push @$pkglist, $res;