Add APT package hold management to Package Updates
- Add an APT-only “Held updates” view while keeping held packages excluded from normal and scheduled updates. - Allow administrators to hold or unhold packages from package details. - Allow explicitly selected held packages to be updated once, then restore all existing package holds. - Preserve configured `aptitude` behavior for regular installs while using `apt-get` for the held-package override. Fixes #2689
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
---- Changes since 2.641 ----
|
||||
Added a Held updates view with controls to hold, unhold or explicitly update APT-held packages.
|
||||
---- Changes since 1.490 ----
|
||||
First version of this module.
|
||||
---- Changes since 1.500 ----
|
||||
|
||||
+23
-12
@@ -9,6 +9,7 @@ if ($in{'clear'}) {
|
||||
$in{'search'} = '';
|
||||
}
|
||||
$has_repos = defined(&software::list_package_repos);
|
||||
$has_holds = &supports_package_holds();
|
||||
|
||||
# Start of mode tabs
|
||||
print &ui_tabs_start([ [ 'pkgs', $text{'index_tabpkgs'} ],
|
||||
@@ -23,7 +24,10 @@ $in{'mode'} ||= 'updates';
|
||||
|
||||
# Show mode selector (all, updates only, updates and new)
|
||||
@grid = ( );
|
||||
foreach $m ('current', 'updates', 'security', 'new') {
|
||||
@modes = ('current', 'updates');
|
||||
push(@modes, 'held') if ($has_holds);
|
||||
push(@modes, 'security', 'new');
|
||||
foreach $m (@modes) {
|
||||
$mmsg = $text{'index_mode_'.$m};
|
||||
if ($in{'mode'} eq $m) {
|
||||
push(@mlinks, "<b>$mmsg</b>");
|
||||
@@ -68,15 +72,21 @@ foreach $p (sort { $a->{'name'} cmp $b->{'name'} } (@current, @avail)) {
|
||||
$c = $current{$p->{'name'}."/".$p->{'system'}};
|
||||
$a = $avail{$p->{'name'}."/".$p->{'system'}};
|
||||
|
||||
if ($a && $c && (&compare_versions($a, $c) > 0 || $upmode)) {
|
||||
if ($a && $c && (&compare_versions($a, $c) > 0 || $upmode ||
|
||||
$in{'mode'} eq 'held')) {
|
||||
# An update is available
|
||||
$msg = "<b><font color=#00aa00>".
|
||||
&text('index_new', $a->{'version'})."</font></b>";
|
||||
$need = 1;
|
||||
$msg = $a->{'held'} ?
|
||||
"<b><font color=#ffaa00>".
|
||||
&text('index_held', $c->{'version'}, $a->{'version'}).
|
||||
"</font></b>" :
|
||||
"<b><font color=#00aa00>".
|
||||
&text('index_new', $a->{'version'})."</font></b>";
|
||||
$need = $a->{'held'} ? 0 : 1;
|
||||
next if ($in{'mode'} eq 'security' && !$a->{'security'});
|
||||
next if ($in{'mode'} ne 'updates' &&
|
||||
$in{'mode'} ne 'current' &&
|
||||
$in{'mode'} ne 'security');
|
||||
$in{'mode'} ne 'security' &&
|
||||
$in{'mode'} ne 'held');
|
||||
}
|
||||
elsif ($a && !$c) {
|
||||
# Could be installed, but isn't currently
|
||||
@@ -143,18 +153,20 @@ if ($in{'mode'} eq 'new' && !$in{'search'}) {
|
||||
}
|
||||
else {
|
||||
# Show the packages, if any
|
||||
$update_label = $in{'mode'} eq 'new' ? $text{'index_install'} :
|
||||
$in{'mode'} eq 'held' ? $text{'index_updateheld'} :
|
||||
$text{'index_update'};
|
||||
if (@rows) {
|
||||
print &text('index_count', scalar(@rows)),"<br>\n";
|
||||
print &ui_form_start("update.cgi", "post");
|
||||
print &ui_submit($in{'mode'} eq 'new' ? $text{'index_install'}
|
||||
: $text{'index_update'}, "ok_top" );
|
||||
print &ui_submit($update_label, "ok_top" );
|
||||
print &ui_submit($text{'index_refresh'}, "refresh_top"), "<br>";
|
||||
}
|
||||
@buttons = ( [ "ok", $update_label ] );
|
||||
push(@buttons, [ "refresh", $text{'index_refresh'} ]);
|
||||
print &ui_form_columns_table(
|
||||
"",
|
||||
[ [ "ok", $in{'mode'} eq 'new' ? $text{'index_install'}
|
||||
: $text{'index_update'} ],
|
||||
[ "refresh", $text{'index_refresh'} ] ],
|
||||
\@buttons,
|
||||
1,
|
||||
undef,
|
||||
[ [ "mode", $in{'mode'} ],
|
||||
@@ -296,4 +308,3 @@ if ($has_repos) {
|
||||
print &ui_tabs_end(1);
|
||||
|
||||
&ui_print_footer("/", $text{'index'});
|
||||
|
||||
|
||||
@@ -6,8 +6,10 @@ index_source=Source
|
||||
index_bad2=Update to version $1 not yet available : $1
|
||||
index_bad=Update to version $1 advised : $2
|
||||
index_new=New version $1
|
||||
index_held=Held at version $1; version $2 is available
|
||||
index_ok=Running latest $1
|
||||
index_update=Update Selected Packages
|
||||
index_updateheld=Update Selected Held Packages
|
||||
index_install=Install Selected Packages
|
||||
index_return=package list
|
||||
index_header=Scheduled checking options
|
||||
@@ -39,6 +41,7 @@ index_webmintheme=Webmin theme $1
|
||||
index_mode=States to display:
|
||||
index_mode_current=Installed
|
||||
index_mode_updates=Only updates
|
||||
index_mode_held=Held updates
|
||||
index_mode_new=Only new
|
||||
index_mode_security=Only security updates
|
||||
index_allsel=Packages to show:
|
||||
@@ -46,6 +49,7 @@ index_all_0=Only Virtualmin related
|
||||
index_all_1=All packages
|
||||
index_none_all=No packages managed by an update system were found on your system!
|
||||
index_none_updates=No packages available to be updated were found.
|
||||
index_none_held=No held packages with available updates were found.
|
||||
index_none_both=No new packages or packages available to be updated were found.
|
||||
index_none_new=No new packages for installation were found.
|
||||
index_none_security=No packages available for security updates were found.
|
||||
@@ -100,6 +104,10 @@ update_rusure=Are you sure you wish to install the $1 packages listed below? Thi
|
||||
update_oldver=Current version
|
||||
update_newver=New version
|
||||
update_confirm=Install Now
|
||||
update_confirmheld=Update Held Packages
|
||||
update_heldnote=These packages are held. This action explicitly updates them once, and leaves them held for future updates.
|
||||
update_enotheld=Package $1 is not currently held by APT
|
||||
update_enoheldops=No update operation was found for the selected held packages. Refresh the package list and try again.
|
||||
update_none=None
|
||||
update_ops=Building complete list of packages ..
|
||||
update_rebootdesc=One of the installed packages requires a reboot to be fully applied.
|
||||
@@ -114,6 +122,8 @@ log_schedup=Background installed $1 updated packages
|
||||
log_sched=Enabled scheduled updates
|
||||
log_unsched=Disabled scheduled updates
|
||||
log_refresh=Refreshed available packages
|
||||
log_hold=Held updates for $1 packages
|
||||
log_unhold=Unheld updates for $1 packages
|
||||
log_enable_repos=Enabled $1 package repositories
|
||||
log_disable_repos=Disabled $1 package repositories
|
||||
log_delete_repos=Deleted $1 package repositories
|
||||
@@ -140,6 +150,10 @@ view_source=Installation source
|
||||
view_changelog=Changelog for available version
|
||||
view_software=Manage Package
|
||||
view_update=Update Package
|
||||
view_updateheld=Update Held Package
|
||||
view_hold=Hold Package
|
||||
view_unhold=Unhold Package
|
||||
view_held=Held at version $1
|
||||
view_install=Install Package
|
||||
|
||||
system_yum=YUM
|
||||
@@ -159,4 +173,12 @@ repos_title=Delete Repositories
|
||||
repos_rusure=Are you sure you want to delete the $1 selected package repositories? Packages installed from them will still be available, but may not be updatable.
|
||||
repos_ok=Delete Now
|
||||
|
||||
hold_enotsupported=The active package update system does not support package holds
|
||||
hold_enone=No packages were selected
|
||||
hold_enotinstalled=Package $1 is not installed
|
||||
hold_enotheld=Package $1 is not currently held
|
||||
hold_esystem=Package $1 is not managed by the active package update system
|
||||
hold_efailed=Failed to hold packages: $1
|
||||
unhold_efailed=Failed to unhold packages: $1
|
||||
|
||||
__norefs=1
|
||||
|
||||
@@ -21,6 +21,9 @@ elsif ($action eq 'update') {
|
||||
elsif ($action eq 'schedup') {
|
||||
return &text('log_schedup', $object);
|
||||
}
|
||||
elsif ($action eq 'hold' || $action eq 'unhold') {
|
||||
return &text('log_'.$action, $object);
|
||||
}
|
||||
elsif ($action eq 'sched') {
|
||||
return $text{$object ? 'log_sched' : 'log_unsched'};
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ eval "use WebminCore;";
|
||||
$available_cache_file = &cache_file_path("available.cache");
|
||||
$current_cache_file = &cache_file_path("current.cache");
|
||||
$updates_cache_file = &cache_file_path("updates.cache");
|
||||
$held_updates_cache_file = &cache_file_path("held-updates.cache");
|
||||
$cron_cmd = "$module_config_directory/update.pl";
|
||||
|
||||
$yum_cache_file = &cache_file_path("yumcache");
|
||||
@@ -308,30 +309,74 @@ sub supports_updates_available
|
||||
return defined(&software::update_system_updates);
|
||||
}
|
||||
|
||||
# updates_available(no-cache)
|
||||
# supports_package_holds()
|
||||
# Returns true if the current update system can list and change package holds.
|
||||
sub supports_package_holds
|
||||
{
|
||||
return defined(&software::list_update_system_holds) &&
|
||||
defined(&software::update_system_hold);
|
||||
}
|
||||
|
||||
# list_package_holds()
|
||||
# Returns the package names currently held by the update system.
|
||||
sub list_package_holds
|
||||
{
|
||||
return ( ) if (!&supports_package_holds());
|
||||
return &software::list_update_system_holds();
|
||||
}
|
||||
|
||||
# package_is_held(package, [holds])
|
||||
# Returns true if a package is in a supplied or freshly-read list of holds.
|
||||
sub package_is_held
|
||||
{
|
||||
my ($name, $holds) = @_;
|
||||
my @holds = $holds ? @$holds : &list_package_holds();
|
||||
return 1 if (grep { $_ eq $name } @holds);
|
||||
if ($software::update_system eq 'apt' &&
|
||||
defined(&software::strip_apt_package_arch)) {
|
||||
my $base = &software::strip_apt_package_arch($name);
|
||||
return 1 if (grep {
|
||||
&software::strip_apt_package_arch($_) eq $base
|
||||
} @holds);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
# update_package_holds(&packages, hold)
|
||||
# Holds or unholds packages. Returns undef on success, or an error message.
|
||||
sub update_package_holds
|
||||
{
|
||||
my ($packages, $hold) = @_;
|
||||
return $text{'hold_enotsupported'} if (!&supports_package_holds());
|
||||
return &software::update_system_hold($packages, $hold);
|
||||
}
|
||||
|
||||
# updates_available(no-cache, [include-held])
|
||||
# Returns an array of hash refs of package updates available, according to
|
||||
# the update system, with caching.
|
||||
sub updates_available
|
||||
{
|
||||
my ($nocache) = @_;
|
||||
if (!scalar(@updates_available_cache)) {
|
||||
if ($nocache || &cache_expired($updates_cache_file)) {
|
||||
my ($nocache, $include_held) = @_;
|
||||
my $cache_file = $include_held ? $held_updates_cache_file :
|
||||
$updates_cache_file;
|
||||
my $cache = $include_held ? \@held_updates_available_cache :
|
||||
\@updates_available_cache;
|
||||
if (!scalar(@$cache)) {
|
||||
if ($nocache || &cache_expired($cache_file)) {
|
||||
# Get from original source
|
||||
@updates_available_cache = &software::update_system_updates();
|
||||
foreach my $a (@updates_available_cache) {
|
||||
@$cache = &software::update_system_updates($include_held);
|
||||
foreach my $a (@$cache) {
|
||||
$a->{'update'} = $a->{'name'};
|
||||
$a->{'system'} = $software::update_system;
|
||||
}
|
||||
&write_cache_file($updates_cache_file,
|
||||
\@updates_available_cache);
|
||||
&write_cache_file($cache_file, $cache);
|
||||
}
|
||||
else {
|
||||
# Use on-disk cache
|
||||
@updates_available_cache =
|
||||
&read_cache_file($updates_cache_file);
|
||||
@$cache = &read_cache_file($cache_file);
|
||||
}
|
||||
}
|
||||
return @updates_available_cache;
|
||||
return @$cache;
|
||||
}
|
||||
|
||||
# package_install(package-name, [system], [new-install], [flags])
|
||||
@@ -343,12 +388,14 @@ my ($name, $system, $install, $flags) = @_;
|
||||
$system ||= $software::update_system;
|
||||
my @rv;
|
||||
my $pkg;
|
||||
my $include_held = $system eq 'apt' && defined($flags) &&
|
||||
$flags eq '--allow-change-held-packages';
|
||||
|
||||
# First get from list of updates
|
||||
($pkg) = grep { $_->{'update'} eq $name &&
|
||||
($_->{'system'} eq $system || !$system) }
|
||||
sort { &compare_versions($b, $a) }
|
||||
&list_possible_updates(0);
|
||||
&list_possible_updates(0, 0, $include_held);
|
||||
if (!$pkg) {
|
||||
# Then try list of all available packages
|
||||
($pkg) = grep { $_->{'update'} eq $name &&
|
||||
@@ -458,14 +505,14 @@ if (defined(&software::update_system_operations)) {
|
||||
return ( );
|
||||
}
|
||||
|
||||
# list_possible_updates([nocache], [nocache-no-data])
|
||||
# list_possible_updates([nocache], [nocache-no-data], [include-held])
|
||||
# Returns a list of updates that are available. Each element in the array
|
||||
# is a hash ref containing a name, version, description and severity flag.
|
||||
# Intended for calling from themes. Nocache 0=cache everything, 1=flush all
|
||||
# caches, 2=flush only current. Nocache-no-data prohibits collecting data
|
||||
sub list_possible_updates
|
||||
{
|
||||
my ($nocache, $nocache_no_data) = @_;
|
||||
my ($nocache, $nocache_no_data, $include_held) = @_;
|
||||
my @rv;
|
||||
return @rv if ($nocache_no_data);
|
||||
my @current = &list_current($nocache);
|
||||
@@ -476,9 +523,10 @@ if (&supports_updates_available()) {
|
||||
foreach my $c (@current) {
|
||||
$currentmap{$c->{'name'},$c->{'system'}} ||= $c;
|
||||
}
|
||||
foreach my $a (&updates_available($nocache == 1)) {
|
||||
foreach my $a (&updates_available($nocache == 1, $include_held)) {
|
||||
my $c = $currentmap{$a->{'name'},$a->{'system'}};
|
||||
next if (!$c);
|
||||
next if ($a->{'held'} && !$include_held);
|
||||
next if ($a->{'version'} eq $c->{'version'} &&
|
||||
$a->{'epoch'} eq $c->{'epoch'});
|
||||
push(@rv, { 'name' => $a->{'name'},
|
||||
@@ -489,6 +537,7 @@ if (&supports_updates_available()) {
|
||||
'epoch' => $a->{'epoch'},
|
||||
'oldepoch' => $c->{'epoch'},
|
||||
'security' => $a->{'security'},
|
||||
'held' => $a->{'held'},
|
||||
'source' => $a->{'source'},
|
||||
'desc' => $c->{'desc'} || $a->{'desc'} });
|
||||
}
|
||||
@@ -700,10 +749,13 @@ sub flush_package_caches
|
||||
{
|
||||
unlink($current_cache_file);
|
||||
unlink($updates_cache_file);
|
||||
unlink($held_updates_cache_file);
|
||||
unlink($available_cache_file);
|
||||
unlink($available_cache_file.'0');
|
||||
unlink($available_cache_file.'1');
|
||||
@packages_available_cache = ( );
|
||||
@updates_available_cache = ( );
|
||||
@held_updates_available_cache = ( );
|
||||
%read_cache_file_cache = ( );
|
||||
}
|
||||
|
||||
@@ -713,6 +765,8 @@ unlink($available_cache_file.'1');
|
||||
sub list_for_mode
|
||||
{
|
||||
my ($mode, $nocache) = @_;
|
||||
return grep { $_->{'held'} }
|
||||
&list_possible_updates($nocache, 0, 1) if ($mode eq 'held');
|
||||
return $mode eq 'updates' || $mode eq 'security' ?
|
||||
&list_possible_updates($nocache) : &list_available($nocache);
|
||||
}
|
||||
|
||||
@@ -8,8 +8,14 @@ if ($in{'software'}) {
|
||||
&redirect("../software/edit_pack.cgi?package=".&urlize($in{'name'}).
|
||||
"&version=".&urlize($in{'version'}));
|
||||
}
|
||||
else {
|
||||
elsif ($in{'hold'} || $in{'unhold'}) {
|
||||
$action = $in{'hold'} ? "hold" : "unhold";
|
||||
&redirect("update.cgi?u=".&urlize($in{'name'}."/".$in{'system'}).
|
||||
"&all=$in{'all'}&mode=$in{'mode'}");
|
||||
"&$action=1&mode=".&urlize($in{'mode'}));
|
||||
}
|
||||
else {
|
||||
$mode = $in{'held'} ? "held" : $in{'mode'};
|
||||
&redirect("update.cgi?u=".&urlize($in{'name'}."/".$in{'system'}).
|
||||
"&all=$in{'all'}&mode=".&urlize($mode));
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,38 @@ else {
|
||||
$redir =~ /\?/ ? "$redir&tab=pkgs" : "$redir?tab=pkgs";
|
||||
}
|
||||
|
||||
if ($in{'refresh'} || $in{'refresh_top'}) {
|
||||
$hold_action = $in{'hold'} ? 1 : $in{'unhold'} ? 0 : undef;
|
||||
if (defined($hold_action)) {
|
||||
# Hold or unhold selected packages
|
||||
&supports_package_holds() || &error($text{'hold_enotsupported'});
|
||||
@holdpkgs = split(/\0/, $in{'u'});
|
||||
@holdpkgs || &error($text{'hold_enone'});
|
||||
@current = &list_current(1);
|
||||
%current = map { $_->{'name'}."/".$_->{'system'}, 1 } @current;
|
||||
@held = &list_package_holds();
|
||||
@holdnames = ( );
|
||||
foreach $ps (@holdpkgs) {
|
||||
($p, $s) = split(/\//, $ps, 2);
|
||||
$current{$p."/".$s} || &error(&text('hold_enotinstalled', $p));
|
||||
$s eq $software::update_system ||
|
||||
&error(&text('hold_esystem', $p));
|
||||
if (!$hold_action && !&package_is_held($p, \@held)) {
|
||||
&error(&text('hold_enotheld', $p));
|
||||
}
|
||||
push(@holdnames, $p);
|
||||
}
|
||||
@holdnames = &unique(@holdnames);
|
||||
$err = &update_package_holds(\@holdnames, $hold_action);
|
||||
&error(&text($hold_action ? 'hold_efailed' : 'unhold_efailed', $err))
|
||||
if ($err);
|
||||
&flush_package_caches();
|
||||
$logaction = $hold_action ? 'hold' : 'unhold';
|
||||
&webmin_log($logaction, "packages", scalar(@holdnames),
|
||||
{ 'packages' => \@holdnames });
|
||||
&redirect("index.cgi?mode=".&urlize($in{'mode'}).
|
||||
"&search=".&urlize($in{'search'}));
|
||||
}
|
||||
elsif ($in{'refresh'} || $in{'refresh_top'}) {
|
||||
&ui_print_unbuffered_header(undef, $text{'refresh_title'}, "");
|
||||
|
||||
# Clear all caches
|
||||
@@ -40,6 +71,21 @@ else {
|
||||
# Upgrade some packages
|
||||
my @pkgs = split(/\0/, $in{'u'});
|
||||
@pkgs || &error($text{'update_enone'});
|
||||
$allow_held = 0;
|
||||
if ($in{'mode'} eq 'held') {
|
||||
# The held-updates page is the only UI that can explicitly
|
||||
# override an APT hold for a single update transaction.
|
||||
&supports_package_holds() || &error($text{'hold_enotsupported'});
|
||||
@held = &list_package_holds();
|
||||
foreach $ps (@pkgs) {
|
||||
($p, $s) = split(/\//, $ps, 2);
|
||||
$s eq 'apt' && &package_is_held($p, \@held) ||
|
||||
&error(&text('update_enotheld', $p));
|
||||
}
|
||||
$allow_held = 1;
|
||||
}
|
||||
$install_flags = $allow_held ? '--allow-change-held-packages' :
|
||||
$in{'flags'};
|
||||
&ui_print_unbuffered_header(undef,
|
||||
$in{'mode'} eq 'new' ? $text{'update_title2'} : $text{'update_title'}, "");
|
||||
|
||||
@@ -57,6 +103,7 @@ else {
|
||||
push(@pkgnames, $p);
|
||||
}
|
||||
@ops = &list_package_operations(join(" ", @pkgnames), $s);
|
||||
&error($text{'update_enoheldops'}) if (!@ops && $allow_held);
|
||||
}
|
||||
|
||||
if (@ops) {
|
||||
@@ -74,9 +121,14 @@ else {
|
||||
foreach $ps (@pkgs) {
|
||||
$confform .= &ui_hidden("u", $ps);
|
||||
}
|
||||
$confform .= &ui_alert_box($text{'update_heldnote'},
|
||||
'warn', undef, undef, '')
|
||||
if ($allow_held && !$bottom);
|
||||
$confform .= &text('update_rusure', scalar(@ops)),"<p>\n"
|
||||
if (!$bottom);
|
||||
$confform .= &ui_form_end([ [ "confirm", $text{'update_confirm'} ] ]);
|
||||
$confform .= &ui_form_end([ [ "confirm",
|
||||
$allow_held ? $text{'update_confirmheld'} :
|
||||
$text{'update_confirm'} ] ]);
|
||||
};
|
||||
print &$getconfform();
|
||||
|
||||
@@ -127,7 +179,7 @@ else {
|
||||
"<br>\n";
|
||||
print "<ul data-package-updates='1'>\n";
|
||||
@got = &package_install_multiple(
|
||||
\@pkgnames, $pkgsystem, $in{'mode'} eq 'new', $in{'flags'});
|
||||
\@pkgnames, $pkgsystem, $in{'mode'} eq 'new', $install_flags);
|
||||
print "</ul><br>\n";
|
||||
}
|
||||
else {
|
||||
@@ -138,7 +190,7 @@ else {
|
||||
print &text($msg, "<tt>@{[&html_escape($p)]}</tt>"),"<br>\n";
|
||||
print "<ul data-package-updates='2'>\n";
|
||||
@pgot = &package_install(
|
||||
$p, $s, $in{'mode'} eq 'new', $in{'flags'});
|
||||
$p, $s, $in{'mode'} eq 'new', $install_flags);
|
||||
foreach $g (@pgot) {
|
||||
$donedep{$g}++;
|
||||
}
|
||||
|
||||
@@ -13,12 +13,17 @@ require './package-updates-lib.pl';
|
||||
($c) = grep { $_->{'name'} eq $in{'name'} &&
|
||||
$_->{'system'} eq $in{'system'} } @current;
|
||||
$p = $a || $c;
|
||||
$has_holds = &supports_package_holds();
|
||||
$held = $has_holds && $c &&
|
||||
$c->{'system'} eq $software::update_system &&
|
||||
&package_is_held($p->{'name'});
|
||||
|
||||
print &ui_form_start("save_view.cgi");
|
||||
print &ui_hidden("name", $p->{'name'});
|
||||
print &ui_hidden("system", $p->{'system'});
|
||||
print &ui_hidden("version", $p->{'version'});
|
||||
print &ui_hidden("mode", $in{'mode'});
|
||||
print &ui_hidden("held", $held);
|
||||
print &ui_table_start($text{'view_header'}, undef, 2);
|
||||
|
||||
# Package name and type
|
||||
@@ -29,6 +34,11 @@ print &ui_table_row($text{'view_desc'}, $p->{'desc'});
|
||||
|
||||
# Current state
|
||||
print &ui_table_row($text{'view_state'},
|
||||
$held && $a && $c && &compare_versions($a, $c) > 0 ?
|
||||
"<font color=#ffaa00>".
|
||||
&text('index_held', $c->{'version'}, $a->{'version'})."</font>" :
|
||||
$held && $c ? "<font color=#ffaa00>".
|
||||
&text('view_held', $c->{'version'})."</font>" :
|
||||
$a && !$c ? "<font color=#00aa00>$text{'index_caninstall'}</font>" :
|
||||
!$a && $c ? "<font color=#ffaa00>".
|
||||
&text('index_noupdate', $c->{'version'})."</font>" :
|
||||
@@ -69,11 +79,16 @@ if ($c && &foreign_available("software") && $c->{'software'}) {
|
||||
push(@buts, [ "software", $text{'view_software'} ]);
|
||||
}
|
||||
if ($a && $c && &compare_versions($a, $c) > 0) {
|
||||
push(@buts, [ "update", $text{'view_update'} ]);
|
||||
push(@buts, [ "update", $held ? $text{'view_updateheld'} :
|
||||
$text{'view_update'} ]);
|
||||
}
|
||||
elsif ($a && !$c) {
|
||||
push(@buts, [ "update", $text{'view_install'} ]);
|
||||
}
|
||||
if ($c && $has_holds && $c->{'system'} eq $software::update_system) {
|
||||
push(@buts, [ $held ? "unhold" : "hold",
|
||||
$held ? $text{'view_unhold'} : $text{'view_hold'} ]);
|
||||
}
|
||||
print &ui_form_end(\@buts);
|
||||
|
||||
&ui_print_footer("index.cgi?mode=$in{'mode'}&search=".
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---- Changes since 2.641 ----
|
||||
Fix Alpine Linux mysql/mariadb package installs names due missing server utils (means at least Alpine Linux package installation is supported since Alpine linux v 3.16 up to edge)
|
||||
Added APT functions for listing, holding, unholding and explicitly updating held packages.
|
||||
---- Changes since 1.130 ----
|
||||
Packages can now be installed directly from yum, if installed.
|
||||
The entire system can also be upgraded from yum.
|
||||
|
||||
+93
-31
@@ -20,20 +20,33 @@ $name =~ s/:[A-Za-z0-9][A-Za-z0-9._-]*$//;
|
||||
return $name;
|
||||
}
|
||||
|
||||
# update_system_install([package], [&in], [no-force])
|
||||
# update_system_install([package], [&in], [no-force], [flags])
|
||||
# Install some package with apt
|
||||
sub update_system_install
|
||||
{
|
||||
local $update = $_[0] || $in{'update'};
|
||||
local $force = !$_[2];
|
||||
local $flags = $_[3];
|
||||
local (@rv, @newpacks);
|
||||
|
||||
# Only accept the one flag needed for an explicit update of held packages.
|
||||
# Other update systems use this argument for their own package-manager flags,
|
||||
# but APT historically ignored it.
|
||||
local $holdflag = defined($flags) &&
|
||||
$flags eq '--allow-change-held-packages'
|
||||
? ' --allow-change-held-packages'
|
||||
: '';
|
||||
local $install_command = $holdflag ? 'apt-get' : $apt_get_command;
|
||||
local @rehold = $holdflag ? &list_update_system_holds() : ( );
|
||||
|
||||
# Build the command to run
|
||||
$ENV{'UCF_FORCE_CONFFOLD'} = 'YES';
|
||||
$ENV{'DEBIAN_FRONTEND'} = 'noninteractive';
|
||||
local $uicmd = "$apt_get_command -y ".($force ? " -f" : "")." install $update";
|
||||
local $uicmd = "$install_command -y".$holdflag.
|
||||
($force ? " -f" : "")." install $update";
|
||||
$update = join(" ", map { quotemeta($_) } split(/\s+/, $update));
|
||||
local $cmd = "$apt_get_command -y ".($force ? " -f" : "")." install $update";
|
||||
local $cmd = "$install_command -y".$holdflag.
|
||||
($force ? " -f" : "")." install $update";
|
||||
print &text('apt_install', "<tt>".&html_escape($uicmd)."</tt>"),"\n";
|
||||
print "<pre data-installer>";
|
||||
&additional_log('exec', undef, $cmd);
|
||||
@@ -68,14 +81,27 @@ while(<CMD>) {
|
||||
print &html_escape("$_");
|
||||
}
|
||||
close(CMD);
|
||||
local $status = $?;
|
||||
|
||||
# Restore holds after --allow-change-held-packages, which applies to the whole
|
||||
# transaction and can clear holds on selected packages or dependencies.
|
||||
if (@rehold) {
|
||||
local $rehold_error = &update_system_hold(\@rehold, 1);
|
||||
if ($rehold_error) {
|
||||
print &text('apt_reholdfailed',
|
||||
"<tt>".&html_escape(join(" ", @rehold))."</tt>",
|
||||
&html_escape($rehold_error)),"<p>\n";
|
||||
}
|
||||
}
|
||||
&reset_environment();
|
||||
if (!@rv && $config{'package_system'} ne 'debian' && !$?) {
|
||||
if (!@rv && $config{'package_system'} ne 'debian' && !$status) {
|
||||
# Other systems don't list the packages installed!
|
||||
@rv = @newpacks;
|
||||
}
|
||||
print "</pre>\n";
|
||||
if ($?) { print "$text{'apt_failed'}<p>\n"; }
|
||||
if ($status) { print "$text{'apt_failed'}<p>\n"; }
|
||||
else { print "$text{'apt_ok'}<p>\n"; }
|
||||
$? = $status;
|
||||
return @rv;
|
||||
}
|
||||
|
||||
@@ -239,21 +265,13 @@ return @rv;
|
||||
# Returns a list of available package updates
|
||||
sub update_system_updates
|
||||
{
|
||||
my ($include_holds) = @_;
|
||||
&execute_command("$apt_get_command update");
|
||||
|
||||
# Find held packages by dpkg
|
||||
local %holds;
|
||||
if ($config{'package_system'} eq 'debian') {
|
||||
&clean_language();
|
||||
&open_execute_command(HOLDS, "dpkg --get-selections", 1, 1);
|
||||
while(<HOLDS>) {
|
||||
if (/^(\S+)\s+hold/) {
|
||||
$holds{$1}++;
|
||||
}
|
||||
}
|
||||
close(HOLDS);
|
||||
&reset_environment();
|
||||
}
|
||||
# Find held packages. By default these remain excluded, but callers can ask
|
||||
# for them so that a dedicated held-updates view can display them.
|
||||
local %holds = map { &strip_apt_package_arch($_), 1 }
|
||||
&list_update_system_holds();
|
||||
|
||||
if (&has_command("apt-show-versions")) {
|
||||
# This awesome command can give us all updates in one hit, and takes
|
||||
@@ -264,7 +282,7 @@ if (&has_command("apt-show-versions")) {
|
||||
&open_execute_command(PKGS, "apt-show-versions 2>/dev/null", 1, 1);
|
||||
while(<PKGS>) {
|
||||
if (/^(\S+)\/(\S+)\s+upgradeable\s+from\s+(\S+)\s+to\s+(\S+)/ &&
|
||||
!$holds{$1}) {
|
||||
($include_holds || !$holds{&strip_apt_package_arch($1)})) {
|
||||
# Old format
|
||||
local $pkg = { 'name' => $1,
|
||||
'source' => $2,
|
||||
@@ -272,9 +290,12 @@ if (&has_command("apt-show-versions")) {
|
||||
if ($pkg->{'version'} =~ s/^(\S+)://) {
|
||||
$pkg->{'epoch'} = $1;
|
||||
}
|
||||
$pkg->{'held'} = 1
|
||||
if ($holds{&strip_apt_package_arch($pkg->{'name'})});
|
||||
push(@rv, $pkg);
|
||||
}
|
||||
elsif (/^(\S+):(\S+)\/(\S+)\s+(\S+)\s+upgradeable\s+to\s+(\S+)/ && !$holds{$1}) {
|
||||
elsif (/^(\S+):(\S+)\/(\S+)\s+(\S+)\s+upgradeable\s+to\s+(\S+)/ &&
|
||||
($include_holds || !$holds{$1})) {
|
||||
# New format, like
|
||||
# libgomp1:i386/unstable 4.8.2-2 upgradeable to 4.8.2-4
|
||||
local $pkg = { 'name' => $1,
|
||||
@@ -284,12 +305,14 @@ if (&has_command("apt-show-versions")) {
|
||||
if ($pkg->{'version'} =~ s/^(\S+)://) {
|
||||
$pkg->{'epoch'} = $1;
|
||||
}
|
||||
$pkg->{'held'} = 1 if ($holds{$pkg->{'name'}});
|
||||
push(@rv, $pkg);
|
||||
}
|
||||
}
|
||||
close(PKGS);
|
||||
&reset_environment();
|
||||
@rv = &filter_held_packages(@rv);
|
||||
@rv = grep { !$holds{&strip_apt_package_arch($_->{'name'})} } @rv
|
||||
if (!$include_holds);
|
||||
foreach my $pkg (@rv) {
|
||||
$pkg->{'security'} = 1 if ($pkg->{'source'} =~ /security/i);
|
||||
}
|
||||
@@ -301,7 +324,8 @@ elsif (&has_command("apt")) {
|
||||
&clean_language();
|
||||
&open_execute_command(PKGS, "apt list --upgradable 2>/dev/null", 1, 1);
|
||||
while(<PKGS>) {
|
||||
if (/^(\S+)\/(\S+)\s+(\S+)\s+(\S+)\s+\[upgradable\s+from:\s+(\S+)\]/ && !$holds{$1}) {
|
||||
if (/^(\S+)\/(\S+)\s+(\S+)\s+(\S+)\s+\[upgradable\s+from:\s+(\S+)\]/ &&
|
||||
($include_holds || !$holds{&strip_apt_package_arch($1)})) {
|
||||
local $pkg = { 'name' => $1,
|
||||
'source' => $2,
|
||||
'version' => $3,
|
||||
@@ -310,12 +334,15 @@ elsif (&has_command("apt")) {
|
||||
$pkg->{'epoch'} = $1;
|
||||
}
|
||||
$pkg->{'source'} =~ s/,.*$//;
|
||||
$pkg->{'held'} = 1
|
||||
if ($holds{&strip_apt_package_arch($pkg->{'name'})});
|
||||
push(@rv, $pkg);
|
||||
}
|
||||
}
|
||||
close(PKGS);
|
||||
&reset_environment();
|
||||
@rv = &filter_held_packages(@rv);
|
||||
@rv = grep { !$holds{&strip_apt_package_arch($_->{'name'})} } @rv
|
||||
if (!$include_holds);
|
||||
foreach my $pkg (@rv) {
|
||||
$pkg->{'security'} = 1 if ($pkg->{'source'} =~ /security/i);
|
||||
}
|
||||
@@ -334,7 +361,8 @@ else {
|
||||
$currentmap{$pkg->{'name'}} ||= $pkg;
|
||||
}
|
||||
local @rv;
|
||||
local @names = grep { !$holds{$_} } keys %currentmap;
|
||||
local @names = $include_holds ? keys %currentmap :
|
||||
grep { !$holds{$_} } keys %currentmap;
|
||||
while(scalar(@names)) {
|
||||
local @somenames;
|
||||
if (scalar(@names) > 100) {
|
||||
@@ -371,6 +399,8 @@ else {
|
||||
&compare_versions($pkg->{'version'},
|
||||
$pkg->{'oldversion'});
|
||||
if ($newer > 0) {
|
||||
$pkg->{'held'} = 1
|
||||
if ($holds{$pkg->{'name'}});
|
||||
push(@rv, $pkg);
|
||||
}
|
||||
}
|
||||
@@ -378,7 +408,8 @@ else {
|
||||
close(PKGS);
|
||||
&reset_environment();
|
||||
}
|
||||
@rv = &filter_held_packages(@rv);
|
||||
@rv = grep { !$holds{&strip_apt_package_arch($_->{'name'})} } @rv
|
||||
if (!$include_holds);
|
||||
&set_pinned_versions(\@rv);
|
||||
return @rv;
|
||||
}
|
||||
@@ -411,11 +442,10 @@ close(PKGS);
|
||||
&reset_environment();
|
||||
}
|
||||
|
||||
# filter_held_packages(package, ...)
|
||||
# Returns a list of package updates, minus those that are held
|
||||
sub filter_held_packages
|
||||
# list_update_system_holds()
|
||||
# Returns the unique names of all packages currently held by APT or dpkg.
|
||||
sub list_update_system_holds
|
||||
{
|
||||
my @pkgs = @_;
|
||||
my %hold;
|
||||
|
||||
# Get holds from dpkg
|
||||
@@ -447,15 +477,47 @@ if (&has_command("apt-mark")) {
|
||||
&clean_language();
|
||||
&open_execute_command(PKGS, "apt-mark showhold 2>/dev/null", 1, 1);
|
||||
while(<PKGS>) {
|
||||
if (/^([^:\s]+)/) {
|
||||
if (/^(\S+)/) {
|
||||
$hold{$1} = 1;
|
||||
}
|
||||
}
|
||||
close(PKGS);
|
||||
&reset_environment();
|
||||
}
|
||||
return sort keys %hold;
|
||||
}
|
||||
|
||||
return grep { !$hold{$_->{'name'}} } @pkgs;
|
||||
# update_system_hold(&packages, hold)
|
||||
# Holds or unholds a list of packages. Returns undef on success, or an error.
|
||||
sub update_system_hold
|
||||
{
|
||||
my ($packages, $hold) = @_;
|
||||
return "The apt-mark command is not installed"
|
||||
if (!&has_command("apt-mark"));
|
||||
my @packages = &unique(@$packages);
|
||||
return "No packages were specified" if (!@packages);
|
||||
my $action = $hold ? 'hold' : 'unhold';
|
||||
my $cmd = "apt-mark $action ".
|
||||
join(" ", map { quotemeta($_) } @packages);
|
||||
my $out;
|
||||
&clean_language();
|
||||
my $status = &execute_command_logged($cmd, undef, \$out, \$out);
|
||||
&reset_environment();
|
||||
if ($status) {
|
||||
$out = &trim($out);
|
||||
return $out || "apt-mark $action failed";
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
# filter_held_packages(package, ...)
|
||||
# Returns a list of package updates, minus those that are held
|
||||
sub filter_held_packages
|
||||
{
|
||||
my @pkgs = @_;
|
||||
my %hold = map { &strip_apt_package_arch($_), 1 }
|
||||
&list_update_system_holds();
|
||||
return grep { !$hold{&strip_apt_package_arch($_->{'name'})} } @pkgs;
|
||||
}
|
||||
|
||||
# list_package_repos()
|
||||
|
||||
@@ -235,6 +235,7 @@ apt_input=Package from APT
|
||||
apt_install=Installing package(s) with command $1 ..
|
||||
apt_ok=.. install complete
|
||||
apt_failed=.. install failed!
|
||||
apt_reholdfailed=.. failed to restore the hold on $1 : $2
|
||||
apt_form=Upgrade All Packages
|
||||
apt_header=APT package upgrade options
|
||||
apt_update=Resynchronize package list (<tt>update</tt>)
|
||||
|
||||
+110
-2
@@ -8,6 +8,7 @@ use File::Spec;
|
||||
use Cwd qw(abs_path);
|
||||
|
||||
our %config;
|
||||
our $apt_get_command;
|
||||
|
||||
my $root = abs_path(File::Spec->catdir(dirname(__FILE__), '..'));
|
||||
chdir($root) or die "chdir($root): $!";
|
||||
@@ -39,11 +40,21 @@ is($ops[0]->{'name'}, 'libtinfo6',
|
||||
no warnings qw(once redefine);
|
||||
my $apt_output = "Setting up libtinfo6:amd64 (6.3-2ubuntu0.2) ...\n";
|
||||
my $yes_input = "";
|
||||
my $executed_command = "";
|
||||
my @reheld;
|
||||
local *additional_log = sub { };
|
||||
local *backquote_logged = sub { return ""; };
|
||||
local *clean_language = sub { };
|
||||
local *html_escape = sub { return $_[0]; };
|
||||
local *reset_environment = sub { };
|
||||
local *list_update_system_holds = sub {
|
||||
return ('libtinfo6:amd64', 'held-dependency:i386');
|
||||
};
|
||||
local *update_system_hold = sub {
|
||||
my ($packages, $hold) = @_;
|
||||
@reheld = @$packages if ($hold);
|
||||
return undef;
|
||||
};
|
||||
local *text = sub { return $_[0]; };
|
||||
local *transname = sub { return "/tmp/software-apt-test-yes"; };
|
||||
local *open_tempfile = sub {
|
||||
@@ -64,19 +75,116 @@ local *close_tempfile = sub {
|
||||
close(ref($fh) ? $fh : \*{$fh});
|
||||
};
|
||||
local *open_execute_command = sub {
|
||||
my ($fh) = @_;
|
||||
my ($fh, $command) = @_;
|
||||
$executed_command = $command;
|
||||
no strict 'refs';
|
||||
open(ref($fh) ? $fh : \*{$fh}, "<", \$apt_output)
|
||||
or die "open simulated apt output: $!";
|
||||
};
|
||||
local $config{'package_system'} = 'debian';
|
||||
local $apt_get_command = 'aptitude';
|
||||
|
||||
my $printed = "";
|
||||
open(my $stdout, ">", \$printed) or die "open captured stdout: $!";
|
||||
local *STDOUT = $stdout;
|
||||
my @installed = update_system_install('libtinfo6', undef, 1);
|
||||
my @installed = update_system_install(
|
||||
'libtinfo6', undef, 1, '--allow-change-held-packages');
|
||||
is_deeply(\@installed, [ 'libtinfo6' ],
|
||||
'normalizes package names returned by apt install output');
|
||||
like($executed_command, qr/apt-get -y --allow-change-held-packages install/,
|
||||
'uses apt-get for the held-package override even in aptitude mode');
|
||||
is_deeply(\@reheld, [ 'libtinfo6:amd64', 'held-dependency:i386' ],
|
||||
'restores all exact holds after explicitly updating a held package');
|
||||
|
||||
$executed_command = "";
|
||||
@reheld = ( );
|
||||
update_system_install('libtinfo6', undef, 1);
|
||||
like($executed_command, qr/^aptitude -y install/,
|
||||
'continues using configured aptitude mode for regular installs');
|
||||
is_deeply(\@reheld, [ ], 'does not reapply holds after a regular install');
|
||||
}
|
||||
|
||||
{
|
||||
no warnings qw(once redefine);
|
||||
local *clean_language = sub { };
|
||||
local *reset_environment = sub { };
|
||||
local *has_command = sub {
|
||||
return $_[0] eq 'aptitude' || $_[0] eq 'apt-mark';
|
||||
};
|
||||
local *open_execute_command = sub {
|
||||
my ($fh, $command) = @_;
|
||||
my $output = $command =~ /^dpkg / ?
|
||||
"alpha hold\ndelta:amd64 hold\n" :
|
||||
$command =~ /^aptitude / ?
|
||||
".h beta 1.0 installed\n" :
|
||||
$command =~ /^apt-mark / ?
|
||||
"gamma\ndelta:amd64\n" : "";
|
||||
no strict 'refs';
|
||||
open(ref($fh) ? $fh : \*{$fh}, '<', \$output)
|
||||
or die "open simulated holds: $!";
|
||||
};
|
||||
|
||||
is_deeply([ list_update_system_holds() ],
|
||||
[ qw(alpha beta delta:amd64 gamma) ],
|
||||
'combines held packages without discarding architecture qualifiers');
|
||||
}
|
||||
|
||||
{
|
||||
no warnings qw(once redefine);
|
||||
my $apt_output =
|
||||
"Listing...\n".
|
||||
"held-pkg/stable 2.0 amd64 [upgradable from: 1.0]\n".
|
||||
"regular-pkg/stable 3.0 amd64 [upgradable from: 2.0]\n";
|
||||
local *clean_language = sub { };
|
||||
local *reset_environment = sub { };
|
||||
local *execute_command = sub { return 0; };
|
||||
local *list_update_system_holds = sub { return ('held-pkg'); };
|
||||
local *has_command = sub { return $_[0] eq 'apt'; };
|
||||
local *open_execute_command = sub {
|
||||
my ($fh, $command) = @_;
|
||||
my $output = $command =~ /^apt list / ? $apt_output : "";
|
||||
no strict 'refs';
|
||||
open(ref($fh) ? $fh : \*{$fh}, '<', \$output)
|
||||
or die "open simulated updates: $!";
|
||||
};
|
||||
|
||||
my @normal = update_system_updates(0);
|
||||
is_deeply([ map { $_->{'name'} } @normal ], [ 'regular-pkg' ],
|
||||
'default APT updates still exclude held packages');
|
||||
my @with_holds = update_system_updates(1);
|
||||
is_deeply([ map { $_->{'name'} } @with_holds ],
|
||||
[ 'held-pkg', 'regular-pkg' ],
|
||||
'held-update query includes regular and held packages');
|
||||
ok($with_holds[0]->{'held'}, 'marks the held update');
|
||||
ok(!$with_holds[1]->{'held'}, 'does not mark a regular update as held');
|
||||
}
|
||||
|
||||
{
|
||||
no warnings qw(once redefine);
|
||||
my $command;
|
||||
local *has_command = sub { return '/usr/bin/apt-mark'; };
|
||||
local *unique = sub {
|
||||
my %seen;
|
||||
return grep { !$seen{$_}++ } @_;
|
||||
};
|
||||
local *trim = sub {
|
||||
my ($value) = @_;
|
||||
$value =~ s/^\s+|\s+$//g;
|
||||
return $value;
|
||||
};
|
||||
local *clean_language = sub { };
|
||||
local *reset_environment = sub { };
|
||||
local *execute_command_logged = sub {
|
||||
my ($cmd, undef, $stdout) = @_;
|
||||
$command = $cmd;
|
||||
$$stdout = "";
|
||||
return 0;
|
||||
};
|
||||
|
||||
is(update_system_hold([ 'webmin-virtual-server', 'webmin-virtual-server' ], 1),
|
||||
undef, 'holds packages successfully');
|
||||
is($command, 'apt-mark hold webmin\-virtual\-server',
|
||||
'builds a quoted apt-mark hold command without duplicates');
|
||||
}
|
||||
|
||||
done_testing();
|
||||
|
||||
Reference in New Issue
Block a user