Local modifications to ClusterLabs/Anvil by Alteeve
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2217 lines
73 KiB

#!/usr/bin/perl
#
# This manages the DHCP, PXE, tftp daemons, kickstart files and and the install sources on Striker
# dashboards. Collectively, this provides the ability for other machines in the Anvil! to be built or rebuilt
# without access to the Internet.
#
# The initial configuration and the ability to get updates does check for an Internet connection.
# Alternatively, /mnt/files/ will be checked for a
#
# Examples;
# - Call with '--enable' to enable the install target.
# - Call with '--disable' to disable the install target.
# - Call with '--refresh' to check for updates from upstream repositories.
#
# Exit codes;
# 0 = Normal exit.
# 1 = Alteeve repo not installed and not installable.
# 2 = BCN IP not found, unable to configure yet.
# 3 = Failed to write or update a configuration file.
# 4 = Not a striker dashboard (or isn't yet).
# 5 = Not running as the 'root' user.
# 6 = The 'comps,xml' file provided by anvil-striker-extra was not found.
# 7 = A package failed to download to our repo
# 8 = No database connection available.
# 9 = The system isn't configured yet.
# 10 = Failed to start dhcpd.
# 11 = Failed to stop dhcpd.
#
# Switchs
#
# --check - Normally, '--status' will exit after checking to see if it's running or not. This switch
# tells it to proceed with a check of the configuration (and possibly update the RPM repo)
# after reporting the status.
# --disable - Disable the "Install Manifest" feature.
# --enable - Enable the "Install Manifest" feature.
# --force - This is the same as '--refresh', forcing the update of the RPM repo.
# --job-uuid - If set, the UUID passed will be read and updated as the update runs.
# --no-refresh - If a refresh of the RPM repository would have been called, this switch will prevent it from
# happening.
# --refresh - This tells this run to refresh the RPM repository, regardless of whether it would have run
# otherwise.
# --status - This checks to see if dhcpd is running or not, then exits (effectively checking if the
# "Install Target" feature is running).
#
#
# TODO:
# - Support building the install target by mounting the ISO and checking /mnt/shared/incoming for needed
# files. We can dump the files into a temporary directory, createrepo it, then use the same logic as we use
# for internet loads. We can use '/mnt/shared/incoming' for handling updated RPMs to updating air-gapped
# systems, too.
use strict;
use warnings;
use Anvil::Tools;
# Disable buffering
$| = 1;
my $THIS_FILE = ($0 =~ /^.*\/(.*)$/)[0];
my $running_directory = ($0 =~ /^(.*?)\/$THIS_FILE$/)[0];
if (($running_directory =~ /^\./) && ($ENV{PWD}))
{
$running_directory =~ s/^\./$ENV{PWD}/;
}
my $anvil = Anvil::Tools->new();
$anvil->Log->level({set => 2});
$anvil->Log->secure({set => 1});
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 3, secure => 0, key => "log_0115", variables => { program => $THIS_FILE }});
# Read switches
$anvil->data->{switches}{check} = 0;
$anvil->data->{switches}{disable} = "";
$anvil->data->{switches}{enable} = "";
$anvil->data->{switches}{force} = "";
$anvil->data->{switches}{'job-uuid'} = "";
$anvil->data->{switches}{'no-refresh'} = 0;
$anvil->data->{switches}{refresh} = 0;
$anvil->data->{switches}{status} = "";
$anvil->Get->switches();
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
'switches::check' => $anvil->data->{switches}{check},
'switches::disable' => $anvil->data->{switches}{disable},
'switches::enable' => $anvil->data->{switches}{enable},
'switches::force' => $anvil->data->{switches}{force},
'switches::job-uuid' => $anvil->data->{switches}{'job-uuid'},
'switches::no-refresh' => $anvil->data->{switches}{'no-refresh'},
'switches::refresh' => $anvil->data->{switches}{refresh},
'switches::status' => $anvil->data->{switches}{status},
}});
# Make sure we're running as 'root'
# $< == real UID, $> == effective UID
if (($< != 0) && ($> != 0))
{
# Not root
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, key => "error_0005"});
$anvil->nice_exit({code => 5});
}
# If the user just wants a status, check and exit.
if ($anvil->data->{switches}{status})
{
my $dhcpd_running = $anvil->System->check_daemon({daemon => $anvil->data->{sys}{daemon}{dhcpd}});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { dhcpd_running => $dhcpd_running }});
if ($dhcpd_running)
{
print $anvil->Words->string({key => "message_0123"})."\n";
print "status=1\n";
if (not $anvil->data->{switches}{check})
{
$anvil->nice_exit({code => 0});
}
}
else
{
print $anvil->Words->string({key => "message_0124"})."\n";
print "status=0\n";
if (not $anvil->data->{switches}{check})
{
$anvil->nice_exit({code => 0});
}
}
}
# Connect to the database(s).
$anvil->Database->connect;
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 3, secure => 0, key => "log_0132"});
if (not $anvil->data->{sys}{database}{connections})
{
# No databases, exit.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, key => "error_0003"});
$anvil->nice_exit({exit_code => 8});
}
update_progress($anvil, 0, "clear");
update_progress($anvil, 1, "log_0239");
# If we're being asked to disable, just do so and exit.
if ($anvil->data->{switches}{disable})
{
my $exit_code = 0;
my $job_message = "message_0125";
my $return_code = $anvil->System->stop_daemon({daemon => $anvil->data->{sys}{daemon}{dhcpd}});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { return_code => $return_code }});
if ($return_code)
{
# non-0 means something went wrong.
$job_message = "message_0126";
$exit_code = 8;
print $anvil->Words->string({key => "error_0047", variables => { rc => $return_code }})."\n";
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, secure => 0, key => "error_0048", variables => { rc => $return_code }});
}
else
{
# Success!
print $anvil->Words->string({key => "message_0122"})."\n";
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, secure => 0, key => "message_0122"});
}
# Record the new state
$anvil->Database->insert_or_update_variables({
variable_name => "install-target::enabled",
variable_source_uuid => $anvil->Get->host_uuid,
variable_source_table => "hosts",
variable_value => "disabled",
variable_default => "unavailable",
variable_description => "striker_0110",
variable_section => "system",
});
### NOTE: There is no complex job processing here. If we've been asked to enable or disable, it only
### takes a second to do it.
update_progress($anvil, 100, $job_message);
$anvil->nice_exit({exit_code => $exit_code});
}
# Exit if we're not configured yet
my $configured = $anvil->System->check_if_configured;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { configured => $configured }});
if (not $configured)
{
print $anvil->Words->string({key => "error_0046"})."\n";
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, secure => 0, key => "error_0046"});
update_progress($anvil, 100, "log_0240");
$anvil->nice_exit({exit_code => 9});
}
# Figure out what this machine is
my ($os_type, $os_arch) = $anvil->System->get_os_type();
$anvil->data->{host_os}{os_type} = $os_type;
$anvil->data->{host_os}{os_arch} = $os_arch;
$anvil->data->{host_os}{version} = ($os_type =~ /(\d+)$/)[0];
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
"host_os::os_type" => $anvil->data->{host_os}{os_type},
"host_os::os_arch" => $anvil->data->{host_os}{os_arch},
"host_os::version" => $anvil->data->{host_os}{version},
}});
# If this isn't a Striker dashboard, exit.
if ($anvil->_short_hostname !~ /striker/)
{
print $anvil->Words->string({key => "error_0044"})."\n";
update_progress($anvil, 100, "error_0044");
$anvil->nice_exit({code => 4});
}
# Calling with 'refresh' takes time, so we only do it when asked.
print $anvil->Words->string({key => "message_0102"})."\n";
load_packages($anvil);
# Setup PXE/tftp/dhcpd config.
setup_boot_environment($anvil);
# Store the RPMs in repo directory.
update_install_source($anvil);
# If we've been asked to enable or disable the install target, do so now.
if ($anvil->data->{switches}{enable})
{
my $exit_code = 0;
my $job_message = "message_0127";
my $return_code = $anvil->System->start_daemon({daemon => $anvil->data->{sys}{daemon}{dhcpd}});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { return_code => $return_code }});
if ($return_code)
{
# non-0 means something went wrong.
$exit_code = 8;
$job_message = "message_0128";
print $anvil->Words->string({key => "error_0047", variables => { rc => $return_code }})."\n";
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, secure => 0, key => "error_0047", variables => { rc => $return_code }});
}
else
{
# Success!
print $anvil->Words->string({key => "message_0121"})."\n";
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, secure => 0, key => "message_0121"});
}
# Record the new state
$anvil->Database->insert_or_update_variables({
variable_name => "install-target::enabled",
variable_source_uuid => $anvil->Get->host_uuid,
variable_source_table => "hosts",
variable_value => "enabled",
variable_default => "unavailable",
variable_description => "striker_0110",
variable_section => "system",
});
### NOTE: There is no complex job processing here. If we've been asked to enable or disable, it only
### takes a second to do it.
update_progress($anvil, 90, $job_message);
if ($exit_code)
{
update_progress($anvil, 100, "");
$anvil->nice_exit({exit_code => $exit_code});
}
}
# We're done
print $anvil->Words->string({key => "message_0025"})."\n";
update_progress($anvil, 100, "message_0025");
$anvil->nice_exit({exit_code => 0});
#############################################################################################################
# Private functions. #
#############################################################################################################
# If this is being called as a job, this will allow the progress to be updated.
sub update_progress
{
my ($anvil, $progress, $message) = @_;
if (not $anvil->data->{switches}{'job-uuid'})
{
return(0);
}
$anvil->Job->update_progress({
progress => $progress,
message => $message,
job_uuid => $anvil->data->{switches}{'job-uuid'},
});
return(0);
}
sub check_refresh
{
my ($anvil) = @_;
# If '--no-refresh' is passed, don't refresh
if ($anvil->data->{switches}{'no-refresh'})
{
return(0);
update_progress($anvil, 90, "");
}
# Setup the packages directory
$anvil->data->{path}{directories}{packages} = "/var/www/html/".$anvil->data->{host_os}{os_type}."/".$anvil->data->{host_os}{os_arch}."/os/Packages";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { "path::directories::packages" => $anvil->data->{path}{directories}{packages} }});
# Default to 'no'
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
"switches::refresh" => $anvil->data->{switches}{refresh},
"switches::force" => $anvil->data->{switches}{force},
}});
# If refresh isn't set, we're out.
if (not $anvil->data->{switches}{refresh})
{
return(0);
}
# It it's forced, the user is insisting on it.
if ($anvil->data->{switches}{force})
{
return(0);
}
# If it's been disabled in anvil.conf, exit.
$anvil->data->{'install-manifest'}{'refresh-packages'} = 1 if not defined $anvil->data->{'install-manifest'}{'refresh-packages'};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { "install-manifest::refresh-packages" => $anvil->data->{'install-manifest'}{'refresh-packages'} }});
if (not $anvil->data->{'install-manifest'}{'refresh-packages'})
{
# We're out.
$anvil->data->{switches}{refresh} = 0;
print $anvil->Words->string({key => "log_0235"})."\n";
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 3, key => "log_0235"});
return(0);
}
# If the Packages directory doesn't exist, we'll need to refresh.
if (not -e $anvil->data->{path}{directories}{packages})
{
# Source isn't configured, set it up
$anvil->data->{switches}{refresh} = 1;
print $anvil->Words->string({key => "log_0237"})."\n";
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 3, key => "log_0237"});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { "switches::refresh" => $anvil->data->{switches}{refresh} }});
return(0);
}
# See when the last time we refreshed the local package cache
my ($unixtime, $variable_uuid, $modified_date) = $anvil->Database->read_variable({
variable_name => "install-target::refreshed",
variable_source_uuid => $anvil->Get->host_uuid,
variable_source_table => "hosts",
});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
unixtime => $unixtime,
variable_uuid => $variable_uuid,
modified_date => $modified_date,
}});
if (($unixtime eq "") or ($unixtime =~ /\D/))
{
$unixtime = 0;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { unixtime => $unixtime }});
}
### TODO: Allow the user to set a "refresh time" that will wait until the local time is after a
### certain time before refreshing. Also, allow the user to disable auto-refresh entirely.
# If the database variable 'install-target::refresh' is not set, or is more than 24 hours old,
# refresh.
$anvil->data->{'install-manifest'}{'refresh-period'} = 86400 if not defined $anvil->data->{'install-manifest'}{'refresh-period'};
$anvil->data->{'install-manifest'}{'refresh-period'} =~ s/,//g;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
'install-manifest::refresh-period' => $anvil->data->{'install-manifest'}{'refresh-period'},
}});
if ($anvil->data->{'install-manifest'}{'refresh-period'} =~ /\D/)
{
$anvil->data->{'install-manifest'}{'refresh-period'} = 86400;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
'install-manifest::refresh-period' => $anvil->data->{'install-manifest'}{'refresh-period'},
}});
}
my $time_now = time;
my $next_scan = $unixtime + $anvil->data->{'install-manifest'}{'refresh-period'};
my $difference = ($next_scan - $time_now);
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
's1:time_now' => $time_now,
's2:next_scan' => $next_scan,
's3:difference' => $difference,
}});
if ((not $variable_uuid) or ($unixtime !~ /^\d+/) or ($difference < 0))
{
# It's been long enough (or it's the first time), refresh.
$anvil->data->{switches}{refresh} = 1;
my $variables = { seconds => $anvil->Convert->add_commas({number => $anvil->data->{'install-manifest'}{'refresh-period'}}) };
print $anvil->Words->string({key => "log_0239", variables => $variables})."\n";
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 3, key => "log_0239", variables => $variables});
return(0);
}
elsif ($difference > 0)
{
# Log when the next scan will happen
$anvil->data->{switches}{refresh} = 0;
my $variables = { next_refresh => $anvil->Convert->add_commas({number => $difference}) };
print $anvil->Words->string({key => "log_0236", variables => $variables})."\n";
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 3, key => "log_0236", variables => $variables});
}
# If the refresh fails, we'll update the last unixtime to be 24 hours from now, so that we can try
# again. We intentionally ignore 'install-manifest::refresh-period', that's only used when the
# refresh succeeded.
if ($unixtime =~ /^\d+$/)
{
$anvil->data->{sys}{retry_time} = $unixtime + 86400;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { "sys::retry_time" => $anvil->data->{sys}{retry_time} }});
}
else
{
# No previous time, so start with the current time
$anvil->data->{sys}{retry_time} = time;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { "sys::retry_time" => $anvil->data->{sys}{retry_time} }});
}
return(0);
}
sub setup_boot_environment
{
my ($anvil) = @_;
### TODO: We don't yet support UEFI, though we will need to soon. Note that the 'shim.efi' and
### 'grubx64.efi' files should be added to 'anvil-striker-extras'. We'll also need to create
### '/var/lib/tftpboot/pxelinux/uefi' as well.
### We;re going to need the '.treeinfo' as well in anvil-striker-extra
### - https://docs.fedoraproject.org/en-US/fedora/f28/install-guide/advanced/Network_based_Installations/
# Get my BCN IP and active OS.
$anvil->System->get_ips();
my $bcn_interface = "";
foreach my $interface (sort {$a cmp $b} keys %{$anvil->data->{sys}{network}{interface}})
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { interface => $interface }});
next if $interface !~ /^bcn/;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { "sys::network::${interface}::ip" => $anvil->data->{sys}{network}{interface}{$interface}{ip} }});
if ($anvil->Validate->is_ipv4({ip => $anvil->data->{sys}{network}{interface}{$interface}{ip} }))
{
$bcn_interface = $interface;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { bcn_interface => $bcn_interface }});
}
last if $bcn_interface;
}
if (not $bcn_interface)
{
# Can't set this up yet.
print $anvil->Words->string({key => "error_0042"})."\n";
update_progress($anvil, 100, "error_0042");
$anvil->nice_exit({code => 2});
}
my $bcn_ip = $anvil->data->{sys}{network}{interface}{$bcn_interface}{ip};
my $bcn_subnet = $anvil->data->{sys}{network}{interface}{$bcn_interface}{subnet};
my $bcn_network = $anvil->Get->network({ip => $bcn_ip, subnet => $bcn_subnet});
my $dns = $anvil->data->{sys}{network}{interface}{$bcn_interface}{dns} ? $anvil->data->{sys}{network}{interface}{$bcn_interface}{dns} : $anvil->data->{defaults}{network}{dns};
my $domain = "localdomain";
my $base_url = "http://".$bcn_ip."/".$anvil->data->{host_os}{os_type}."/".$anvil->data->{host_os}{os_arch};
if ($anvil->_hostname =~ /\./)
{
$domain = $anvil->_hostname;
$domain =~ s/^.*?\.//;
}
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
bcn_interface => $bcn_interface,
bcn_ip => $bcn_ip,
bcn_subnet => $bcn_subnet,
bcn_network => $bcn_network,
dns => $dns,
domain => $domain,
base_url => $base_url,
}});
### NOTE: The DNS range is a bit tricky, so for now, we'll assume that the BCN is always a /16
### network. Someday this might change, and if so, we'll need to make this a lot smarter.
my $striker_number = ($anvil->_short_hostname =~ /striker(\d+)/)[0];
$striker_number = 1 if not $striker_number;
$striker_number =~ s/^0//;
my $third_octet = (10 * $striker_number) + 4;
$third_octet = 254 if $third_octet > 254;
my $first_part = ($bcn_network =~ /^(\d+\.\d+)\./)[0].".".$third_octet;
my $range = $first_part.".10 ".$first_part.".250";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
's1:striker_number' => $striker_number,
's2:third_octet' => $third_octet,
's3:first_part' => $first_part,
's4:range' => $range,
}});
### DHCP server config
my $dhcpd_conf_body = $anvil->Template->get({file => "pxe.txt", show_name => 0, name => "dhcpd_conf", variables => {
dns => $dns,
domain => $domain,
network => $bcn_network,
range => $range,
router => $bcn_ip,
}});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { dhcpd_conf_body => $dhcpd_conf_body }});
# Return code if '1' means the file was changed, '2' indicates it didn't change. '0' means something
# went wrong.
my $dhcpd_conf_success = $anvil->Storage->update_file({
body => $dhcpd_conf_body,
file => $anvil->data->{path}{configs}{'dhcpd.conf'},
});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { dhcpd_conf_success => $dhcpd_conf_success }});
if (not $dhcpd_conf_success)
{
# Failed.
print $anvil->Words->string({key => "error_0043", variables => { file => $anvil->data->{path}{configs}{'dhcpd.conf'} }})."\n";
update_progress($anvil, 100, "error_0043");
$anvil->nice_exit({code => 3});
}
elsif ($dhcpd_conf_success eq "1")
{
# Restart dhcpd.
print $anvil->Words->string({key => "message_0095", variables => {
daemon => "dhcpd",
file => $anvil->data->{path}{configs}{'dhcpd.conf'},
}})."\n";
update_progress($anvil, 10, "message_0095,!!daemon!dhcpd!!,!!file!".$anvil->data->{path}{configs}{'dhcpd.conf'}."!!");
$anvil->System->restart_daemon({daemon => $anvil->data->{sys}{daemon}{dhcpd}});
}
elsif ($dhcpd_conf_success eq "2")
{
# Update not needed.
print $anvil->Words->string({key => "message_0096", variables => { file => $anvil->data->{path}{configs}{'dhcpd.conf'} }})."\n";
update_progress($anvil, 10, "message_0096,!!file!".$anvil->data->{path}{configs}{'dhcpd.conf'}."!!");
}
### PXE BIOS 'default' file.
my $bios_default_body = $anvil->Template->get({file => "pxe.txt", show_name => 0, name => "tftp_bios", variables => { base_url => $base_url }});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { bios_default_body => $bios_default_body }});
# Return code if '1' means the file was changed, '2' indicates it didn't change. '0' means something
# went wrong.
my $bios_default_success = $anvil->Storage->update_file({
body => $bios_default_body,
file => $anvil->data->{path}{configs}{pxe_default},
});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { bios_default_success => $bios_default_success }});
if (not $bios_default_success)
{
# Failed.
print $anvil->Words->string({key => "error_0043", variables => { file => $anvil->data->{path}{configs}{pxe_default} }})."\n";
update_progress($anvil, 100, "error_0043,!!file!".$anvil->data->{path}{configs}{pxe_default}."!!");
$anvil->nice_exit({code => 3});
}
elsif ($dhcpd_conf_success eq "1")
{
# Updated
print $anvil->Words->string({key => "message_0097", variables => { file => $anvil->data->{path}{configs}{pxe_default} }})."\n";
}
elsif ($dhcpd_conf_success eq "2")
{
# Update not needed.
print $anvil->Words->string({key => "message_0096", variables => { file => $anvil->data->{path}{configs}{pxe_default} }})."\n";
}
### Generate kickstart files.
my $progress = 10;
foreach my $type ("striker", "node", "dr")
{
$progress += 2;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { type => $type, progress => $progress }});
my $say_type = "#!string!message_0115!#";
if ($type eq "node")
{
$say_type = "#!string!message_0116!#";
}
elsif ($type eq "dr")
{
$say_type = "#!string!message_0117!#";
}
my $kickstart_body = $anvil->Template->get({file => "pxe.txt", show_name => 0, name => "kickstart", variables => {
type => $type,
say_type => $say_type,
hostname => "new-".$type.".".$domain,
os => $anvil->data->{host_os}{os_type},
url => $base_url."/os/",
keyboard => $anvil->data->{kickstart}{keyboard} ? $anvil->data->{kickstart}{keyboard} : $anvil->data->{defaults}{kickstart}{keyboard},
timezone => $anvil->data->{kickstart}{timezone} ? $anvil->data->{kickstart}{timezone} : $anvil->data->{defaults}{kickstart}{timezone},
password => $anvil->data->{kickstart}{password} ? $anvil->data->{kickstart}{password} : $anvil->data->{defaults}{kickstart}{password},
debug => 0, # This isn't the same as the rest of our code, just '1' or '0'.
}});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { kickstart_body => $kickstart_body }});
# Return code if '1' means the file was changed, '2' indicates it didn't change. '0' means
# something went wrong.
my $kickstart_file = "/var/www/html/".$anvil->data->{host_os}{os_type}."/".$anvil->data->{host_os}{os_arch}."/kickstart/".$type.".ks";
my $kickstart_success = $anvil->Storage->update_file({
body => $kickstart_body,
file => $kickstart_file,
});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { kickstart_success => $kickstart_success }});
if (not $kickstart_success)
{
# Failed.
print $anvil->Words->string({key => "error_0043", variables => { file => $kickstart_file }})."\n";
update_progress($anvil, 100, "error_0043,!!file!".$kickstart_file."!!");
$anvil->nice_exit({code => 3});
}
elsif ($kickstart_success eq "1")
{
# Updated
$anvil->Storage->change_mode({path => $kickstart_file, mode => "0664"});
$anvil->Storage->change_owner({path => $kickstart_file, user => "apache", group => "apache" });
print $anvil->Words->string({key => "message_0097", variables => { file => $kickstart_file }})."\n";
update_progress($anvil, $progress, "message_0097,!!file!".$kickstart_file."!!");
}
elsif ($kickstart_success eq "2")
{
# Update not needed.
print $anvil->Words->string({key => "message_0096", variables => { file => $kickstart_file }})."\n";
update_progress($anvil, $progress, "message_0096,!!file!".$kickstart_file."!!");
}
# progress is '16' as it leaves this loop
}
# Configure apache to show hidden (dot) files.
my $old_autoindex_conf = $anvil->Storage->read_file({file => $anvil->data->{path}{configs}{'autoindex.conf'}});
my $new_autoindex_conf = "";
my $update_autoindex_conf = 0;
$progress = 18;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { old_autoindex_conf => $old_autoindex_conf }});
foreach my $line (split/\n/, $old_autoindex_conf)
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { line => $line }});
if (($line =~ /^IndexIgnore /) && ($line =~ / \.\?\?\* /))
{
$line =~ s/ \.\?\?\* / /;
$update_autoindex_conf = 1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
"<< line" => $line,
update_autoindex_conf => $update_autoindex_conf,
}});
}
if (($line =~ /^IndexOptions /) && ($line !~ / NameWidth=/))
{
# Allow long file names to show without being truncated
$line .= " NameWidth=*";
$update_autoindex_conf = 1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
"<< line" => $line,
update_autoindex_conf => $update_autoindex_conf,
}});
}
if (($line =~ /^IndexOptions /) && ($line !~ / FoldersFirst/))
{
# Sort folders before files
$line .= " FoldersFirst";
$update_autoindex_conf = 1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
"<< line" => $line,
update_autoindex_conf => $update_autoindex_conf,
}});
}
if (($line =~ /^IndexOptions /) && ($line !~ / IgnoreCase/))
{
# Sort filenames without putting capitalized first letters at the start of the list.
$line .= " IgnoreCase";
$update_autoindex_conf = 1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
"<< line" => $line,
update_autoindex_conf => $update_autoindex_conf,
}});
}
$new_autoindex_conf .= $line."\n";
}
if ($update_autoindex_conf)
{
# Update and reload apache
$anvil->Storage->backup({debug => 2, file => $anvil->data->{path}{configs}{'autoindex.conf'}});
my $return = $anvil->Storage->write_file({
debug => 2,
body => $new_autoindex_conf,
file => $anvil->data->{path}{configs}{'autoindex.conf'},
overwrite => 1,
mode => "0644",
user => "root",
group => "root"
});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { 'return' => $return }});
if ($return)
{
# Something went wrong.
print $anvil->Words->string({key => "log_0233", variables => { file => $anvil->data->{path}{configs}{'autoindex.conf'}, 'return' => $return }})."\n";
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0233", variables => { file => $anvil->data->{path}{configs}{'autoindex.conf'}, 'return' => $return }});
update_progress($anvil, "100", "log_0233,!!file!".$anvil->data->{path}{configs}{'autoindex.conf'}."!!,!!return!".$return."!!");
$anvil->nice_exit({code => 3});
}
print $anvil->Words->string({key => "message_0095", variables => { daemon => "httpd", file => $anvil->data->{path}{configs}{'autoindex.conf'} }})."\n";
$anvil->System->reload_daemon({daemon => $anvil->data->{sys}{daemon}{httpd}});
update_progress($anvil, $progress, "message_0095,!!daemon!httpd!!,!!file!".$anvil->data->{path}{configs}{'autoindex.conf'}."!!");
}
else
{
# Update not needed.
print $anvil->Words->string({key => "message_0096", variables => { file => $anvil->data->{path}{configs}{'autoindex.conf'} }})."\n";
update_progress($anvil, $progress, "message_0096,!!file!".$anvil->data->{path}{configs}{'autoindex.conf'}."!!");
}
# Update dnf to save downloaded files for later repo building efficiency and to skip upstream repos
# that aren't available at a given time.
my $keepcache_seen = 0;
my $skip_seen = 0;
my $old_dnf_conf = $anvil->Storage->read_file({file => $anvil->data->{path}{configs}{'dnf.conf'}});
my $new_dnf_conf = "";
$progress = 20;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { old_dnf_conf => $old_dnf_conf }});
foreach my $line (split/\n/, $old_dnf_conf)
{
### NOTE: We don't examin the value on purpose. If the user changes these, we'll respect it.
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { line => $line }});
if ($line =~ /^keepcache=/)
{
$keepcache_seen = 1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { keepcache_seen => $keepcache_seen }});
}
elsif ($line =~ /^skip_if_unavailable=/)
{
$skip_seen = 1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { skip_seen => $skip_seen }});
}
$new_dnf_conf .= $line."\n";
}
if ((not $keepcache_seen) or (not $skip_seen))
{
# Update needed.
if (not $keepcache_seen)
{
$new_dnf_conf .= "keepcache=1\n";
}
if (not $skip_seen)
{
$new_dnf_conf .= "skip_if_unavailable=1\n";
}
$anvil->Storage->backup({debug => 2, file => $anvil->data->{path}{configs}{'dnf.conf'}});
my $return = $anvil->Storage->write_file({
debug => 2,
body => $new_dnf_conf,
file => $anvil->data->{path}{configs}{'dnf.conf'},
overwrite => 1,
mode => "0644",
user => "root",
group => "root"
});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { 'return' => $return }});
if ($return)
{
# Something went wrong.
print $anvil->Words->string({key => "log_0233", variables => { file => $anvil->data->{path}{configs}{'dnf.conf'}, 'return' => $return }})."\n";
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0233", variables => { file => $anvil->data->{path}{configs}{'dnf.conf'}, 'return' => $return }});
update_progress($anvil, 100, "log_0023,!!file!".$anvil->data->{path}{configs}{'dnf.conf'}."!!,!!refresh!return!".$return."!!");
$anvil->nice_exit({code => 3});
}
print $anvil->Words->string({key => "message_0097", variables => { file => $anvil->data->{path}{configs}{'dnf.conf'} }})."\n";
update_progress($anvil, $progress, "message_0097,!!file!".$anvil->data->{path}{configs}{'dnf.conf'}."!!");
}
else
{
# Update not needed.
print $anvil->Words->string({key => "message_0096", variables => { file => $anvil->data->{path}{configs}{'dnf.conf'} }})."\n";
update_progress($anvil, $progress, "message_0096,!!file!".$anvil->data->{path}{configs}{'dnf.conf'}."!!");
}
### Check that daemons are enabled/disabled.
### NOTE: We don't manage dhcpd. We leave it off, but if the user enabled it, respect that.
# Make sure tftp is enabled.
$progress = 22;
if (-e $anvil->data->{path}{systemd}{tftp_enabled_symlink})
{
print $anvil->Words->string({key => "message_0099", variables => { daemon => $anvil->data->{sys}{daemon}{tftp} }})."\n";
update_progress($anvil, $progress, "message_0099,!!daemon!".$anvil->data->{sys}{daemon}{tftp}."!!");
}
else
{
print $anvil->Words->string({key => "message_0098", variables => { daemon => $anvil->data->{sys}{daemon}{tftp} }})."\n";
update_progress($anvil, $progress, "message_0098,!!daemon!".$anvil->data->{sys}{daemon}{tftp}."!!");
$anvil->System->enable_daemon({daemon => $anvil->data->{sys}{daemon}{tftp}});
$anvil->System->start_daemon({daemon => $anvil->data->{sys}{daemon}{tftp}});
}
$progress = 24;
if (-e $anvil->data->{path}{systemd}{httpd_enabled_symlink})
{
print $anvil->Words->string({key => "message_0099", variables => { daemon => $anvil->data->{sys}{daemon}{httpd} }})."\n";
update_progress($anvil, $progress, "message_0099,!!daemon!".$anvil->data->{sys}{daemon}{httpd}."!!");
}
else
{
print $anvil->Words->string({key => "message_0098", variables => { daemon => $anvil->data->{sys}{daemon}{httpd} }})."\n";
update_progress($anvil, $progress, "message_0098,!!daemon!".$anvil->data->{sys}{daemon}{httpd}."!!");
$anvil->System->enable_daemon({daemon => $anvil->data->{sys}{daemon}{httpd}});
$anvil->System->start_daemon({daemon => $anvil->data->{sys}{daemon}{httpd}});
}
# Make sure the syslinux files needed for creating the PXE boot menu are in place.
$progress = 26;
if (not -e $anvil->data->{path}{directories}{tftpboot}."/vesamenu.c32")
{
# Copy the syslinux files
print $anvil->Words->string({key => "message_0100"})."\n";
update_progress($anvil, $progress, "message_0100");
$anvil->Storage->rsync({
debug => 3,
source => $anvil->data->{path}{directories}{syslinux}."/*",
destination => $anvil->data->{path}{directories}{tftpboot}."/",
});
}
else
{
print $anvil->Words->string({key => "message_0101"})."\n";
update_progress($anvil, $progress, "message_0101");
}
### TODO: Add UEFI
# Copy the background splash image for the PXE boot images.
my $bios_splash = $anvil->data->{path}{directories}{skins}."/".$anvil->Template->skin."/images/bios-splash.jpg";
if (-e $bios_splash)
{
# We don't bother checking if this needs to be updated because rsync can figure it out more
# efficiently.
$anvil->Storage->rsync({
debug => 3,
source => $bios_splash,
destination => $anvil->data->{path}{directories}{tftpboot}."/splash.jpg",
});
}
return(0);
}
### NOTE: This will probably be removed...
sub check_alteeve_repo
{
my ($anvil) = @_;
my $repo_url = "";
my $repo_file = "";
### NOTE: This adds 'b' for the beta repo
if (1)
{
### TODO: Remove this and the check when the signed el8 repo is ready
$repo_file = "/etc/yum.repos.d/alteeve-el".$anvil->data->{host_os}{version}.".repo";
$repo_url = "https://alteeve.com/an-repo/el".$anvil->data->{host_os}{version}."b/alteeve-el".$anvil->data->{host_os}{version}."b-repo-latest.noarch.rpm";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
repo_file => $repo_file,
repo_url => $repo_url,
}});
}
else
{
### TODO: Update this when rhel8 is out
$repo_file = "/etc/yum.repos.d/alteeve-el".$anvil->data->{host_os}{version}."b.repo";
$repo_url = "https://alteeve.com/an-repo/el".$anvil->data->{host_os}{version}."/alteeve-el".$anvil->data->{host_os}{version}."-repo-latest.noarch.rpm";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
repo_file => $repo_file,
repo_url => $repo_url,
}});
}
# If the repo file doesn't exist, try to install it.
if (not -e $repo_file)
{
# Install the repo
my ($handle, $return_code) = $anvil->System->call({debug => 2, shell_call => $anvil->data->{path}{exe}{rpm}." -Uvh ".$repo_url });
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { handle => $handle, return_code => $return_code }});
}
# If it still doesn't exist, we're done.
if (not -e $repo_file)
{
# Die
print $anvil->Words->string({key => "error_0041"})."\n";
update_progress($anvil, 100, "error_0041");
$anvil->nice_exit({code => 1});
}
return(0);
}
# This uses yumdownloader to push the files into '/var/www/html/<os_name>/<arch>/os
sub update_install_source
{
my ($anvil) = @_;
# Job progress is at '26' coming into here
# Should we refresh the local repo?
check_refresh($anvil);
if (not $anvil->data->{switches}{refresh})
{
return(0);
update_progress($anvil, 90, "");
}
### TODO: Make sure this handles no internet access gracefully.
# Clear the dnf cache
my $success = 1;
my $progress = 30;
my ($output, $return_code) = $anvil->System->call({debug => 3, shell_call => $anvil->data->{path}{exe}{dnf}." clean expire-cache" });
my $packages = "/var/www/html/".$anvil->data->{host_os}{os_type}."/".$anvil->data->{host_os}{os_arch}."/os/Packages/*";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { output => $output, return_code => $return_code }});
print $anvil->Words->string({key => "message_0077", variables => { directory => $packages }})."\n";
update_progress($anvil, $progress, "message_0077,!!directory!".$packages."!!");
# Loop through each letter directory
foreach my $letter (sort {$a cmp $b} keys %{$anvil->data->{packages}})
{
$progress += 2;
$progress = 90 if $progress > 90;
my $download_path = "/var/www/html/".$anvil->data->{host_os}{os_type}."/".$anvil->data->{host_os}{os_arch}."/os/Packages/".$letter;
my $array_size = @{$anvil->data->{packages}{$letter}};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
letter => $letter,
download_path => $download_path,
array_size => $array_size,
progress => $progress,
}});
if (not -e $download_path)
{
$anvil->Storage->make_directory({debug => 2, directory => $download_path, mode => "0775"});
}
my $say_packages = $anvil->Convert->add_commas({number => $array_size});
print $anvil->Words->string({key => "message_0120", variables => {
directory => $download_path,
packages => $say_packages,
}})."\n";
update_progress($anvil, $progress, "message_0120,!!directory!".$download_path."!!,!!packages!".$say_packages."!!");
my $packages = "";
my $shell_call = $anvil->data->{path}{exe}{dnf}." download --destdir ".$download_path." ";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { shell_call => $shell_call }});
foreach my $package (sort {$a cmp $b} @{$anvil->data->{packages}{$letter}})
{
# Append the package to the active shell call.
$packages .= $package." ";
}
$packages =~ s/ $//;
$shell_call .= " ".$packages."; 2>&1 ".$anvil->data->{path}{exe}{'echo'}." return_code:\$?";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { shell_call => $shell_call }});
# Now call the command in the background, then we'll track the output.
my $stdout_file = "/tmp/".$THIS_FILE."_update_install_source.stdout";
if (-e $stdout_file)
{
unlink $stdout_file;
}
my ($handle, undef) = $anvil->System->call({
debug => 2,
shell_call => $shell_call,
background => 1,
stdout_file => $stdout_file,
});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { handle => $handle }});
# Now we'll loop, printing output, until the handle dies.
my $alive = 1;
my $last_stdout_line = 0;
my $error_out = "";
my $return_code = 255;
while($alive)
{
# Are we still alive?
$alive = $handle->poll();
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { alive => $alive }});
# Sleep (even if we're dead now, we want to give a second for the stdout file to be updated
# before processing it).
sleep 1;
# print any new STDOUT lines
my $stdout = $anvil->Storage->read_file({force_read => 1, file => $stdout_file});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { stdout => $stdout }});
my $this_stdout_line = 0;
foreach my $line (split/\n/, $stdout)
{
$this_stdout_line++;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
's1:this_stdout_line' => $this_stdout_line,
's2:last_stdout_line' => $last_stdout_line,
's3:line' => $line,
}});
if ($this_stdout_line > $last_stdout_line)
{
print $anvil->Words->string({key => "message_0078", variables => { line => $line }})."\n";
$last_stdout_line = $this_stdout_line;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { last_stdout_line => $last_stdout_line }});
# A none-zero return code indicates, likely, that a package failed to download.
if ($line =~ /^return_code:(\d+)$/)
{
$return_code = $1;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { return_code => $return_code }});
if ($return_code)
{
# Something went wrong.
$error_out .= $anvil->Words->string({key => "error_0063", variables => { packages => $packages, return_code => $return_code }})."\n";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, list => { error_out => $error_out }});
}
}
if ($line =~ /^Error: /)
{
$error_out .= $line."\n";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, list => { error_out => $error_out }});
}
}
}
}
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { error_out => $error_out }});
if ($error_out)
{
# Bump the last successful time by 24 hours.
$anvil->Database->insert_or_update_variables({
variable_name => "install-target::refreshed",
variable_value => $anvil->data->{sys}{retry_time},
variable_default => "",
variable_description => "striker_0106",
variable_section => "system",
variable_source_uuid => $anvil->Get->host_uuid,
variable_source_table => "hosts",
});
# Something went wrong, exit.
print $anvil->Words->string({key => "error_0045", variables => { error => $error_out }})."\n";
update_progress($anvil, 100, "error_0045,!!error!".$error_out."!!");
$anvil->nice_exit({code => 7});
}
# Progress is '82' leaving this loop
}
# Create the repodata
print $anvil->Words->string({key => "message_0118"})."\n";
my $repo_path = "/var/www/html/".$anvil->data->{host_os}{os_type}."/".$anvil->data->{host_os}{os_arch}."/os";
my $comps_xml = $repo_path."/comps.xml";
my $target = $repo_path."/repodata/comps.xml";
if (not -e $comps_xml)
{
# We can't install properly without the comps.xml file, it provides grouping needed by the
# guest OS install.
print $anvil->Words->string({key => "message_0119", variables => { comps_xml => $comps_xml }})."\n";
update_progress($anvil, 100, "message_0119,!!comps_xml!".$comps_xml."!!");
$anvil->nice_exit({code => 6});
}
$anvil->Storage->copy_file({
debug => 2,
source_file => $comps_xml,
target_file => $target,
overwrite => 1,
});
if (not -e $target)
{
# Something appears to have happened and it failed to copy.
print $anvil->Words->string({key => "message_0129", variables => { comps_xml => $comps_xml, target => $target }})."\n";
update_progress($anvil, 100, "message_0129,!!comps_xml!".$comps_xml."!!,!!target!".$target."!!");
$anvil->nice_exit({code => 6});
}
update_progress($anvil, 85, "");
$output = "";
($output, $return_code) = $anvil->System->call({debug => 2, shell_call => $anvil->data->{path}{exe}{createrepo}." -g ".$comps_xml." ".$repo_path });
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { output => $output, return_code => $return_code }});
print $anvil->Words->string({key => "message_0130"})."\n";
update_progress($anvil, 90, "message_0130");
# Update the refresh time to now.
$anvil->Database->insert_or_update_variables({
debug => 2,
variable_name => "install-target::refreshed",
variable_value => time,
variable_default => "",
variable_description => "striker_0106",
variable_section => "system",
variable_source_uuid => $anvil->Get->host_uuid,
variable_source_table => "hosts",
});
return(0);
}
#
sub load_packages
{
my ($anvil) = @_;
# Read in any packages the user wants us to add.
$anvil->data->{packages}{users} = [];
if ((exists $anvil->data->{striker}{repo}{'extra-packages'}) && ($anvil->data->{striker}{repo}{'extra-packages'}))
{
foreach my $package (split/,/, $anvil->data->{striker}{repo}{'extra-packages'})
{
$package =~ s/^\s+//;
$package =~ s/\s+$//;
next if $package eq "";
push @{$anvil->data->{packages}{users}}, $package
}
}
### TODO: These need to be sorted into groups by their first letter, and those groups need to go under Packages
### TODO: Update this for EL8
# This is the list of packages we need to download.
$anvil->data->{packages} = {
a => [
"abattis-cantarell-fonts.noarch",
"accountsservice-libs.x86_64",
"accountsservice.x86_64",
"acl.x86_64",
"adwaita-cursor-theme.noarch",
"adwaita-gtk2-theme.x86_64",
"adwaita-icon-theme.noarch",
"alsa-lib.x86_64",
"alteeve-el8-repo.noarch",
"anvil-core.noarch",
"anvil-dr.noarch",
"anvil-node.noarch",
"anvil-striker-extra.noarch",
"anvil-striker.noarch",
"apr-util-bdb.x86_64",
"apr-util-openssl.x86_64",
"apr-util.x86_64",
"apr.x86_64",
"at-spi2-atk.x86_64",
"at-spi2-core.x86_64",
"atk.x86_64",
"audit-libs.x86_64",
"audit.x86_64",
"augeas-libs.x86_64",
"authselect-libs.x86_64",
"authselect.x86_64",
"autogen-libopts.x86_64",
"avahi-glib.x86_64",
"avahi-libs.x86_64",
],
b => [
"basesystem.noarch",
"bash-completion.noarch",
"bash.x86_64",
"bind-export-libs.x86_64",
"bind-libs-lite.x86_64",
"bind-libs.x86_64",
"bind-license.noarch",
"bind-utils.x86_64",
"bluez-libs.x86_64",
"bluez-obexd.x86_64",
"bluez.x86_64",
"bolt.x86_64",
"boost-atomic.x86_64",
"boost-chrono.x86_64",
"boost-date-time.x86_64",
"boost-iostreams.x86_64",
"boost-program-options.x86_64",
"boost-random.x86_64",
"boost-regex.x86_64",
"boost-system.x86_64",
"boost-thread.x86_64",
"brotli.x86_64",
"bubblewrap.x86_64",
"bzip2-libs.x86_64",
"bzip2.x86_64",
],
c => [
"c-ares.x86_64",
"ca-certificates.noarch",
"cairo-gobject.x86_64",
"cairo.x86_64",
"celt051.x86_64",
"checkpolicy.x86_64",
"cheese-libs.x86_64",
"chkconfig.x86_64",
"chrony.x86_64",
"cifs-utils.x86_64",
"clufter-bin.x86_64",
"clufter-common.noarch",
"clutter-gst3.x86_64",
"clutter-gtk.x86_64",
"clutter.x86_64",
"cockpit-bridge.x86_64",
"cockpit-dashboard.noarch",
"cockpit-machines.noarch",
"cockpit-packagekit.noarch",
"cockpit-system.noarch",
"cockpit-ws.x86_64",
"cockpit.x86_64",
"cogl.x86_64",
"color-filesystem.noarch",
"colord-gtk.x86_64",
"colord-libs.x86_64",
"colord.x86_64",
"coreutils-common.x86_64",
"coreutils.x86_64",
"corosync.x86_64",
"corosynclib.x86_64",
"cpio.x86_64",
"cracklib-dicts.x86_64",
"cracklib.x86_64",
"createrepo_c-libs.x86_64",
"createrepo_c.x86_64",
"cronie-anacron.x86_64",
"cronie.x86_64",
"crontabs.noarch",
"crypto-policies.noarch",
"cryptsetup-libs.x86_64",
"cups-libs.x86_64",
"cups-pk-helper.x86_64",
"curl.x86_64",
"cyrus-sasl-gssapi.x86_64",
"cyrus-sasl-lib.x86_64",
"cyrus-sasl.x86_64",
],
d => [
"dbus-common.noarch",
"dbus-daemon.x86_64",
"dbus-glib.x86_64",
"dbus-libs.x86_64",
"dbus-tools.x86_64",
"dbus-x11.x86_64",
"dbus.x86_64",
"dconf.x86_64",
"desktop-file-utils.x86_64",
"device-mapper-event-libs.x86_64",
"device-mapper-event.x86_64",
"device-mapper-libs.x86_64",
"device-mapper-multipath-libs.x86_64",
"device-mapper-multipath.x86_64",
"device-mapper-persistent-data.x86_64",
"device-mapper.x86_64",
"dhcp-client.x86_64",
"dhcp-common.noarch",
"dhcp-libs.x86_64",
"dhcp-server.x86_64",
"diffutils.x86_64",
"dmidecode.x86_64",
"dnf-data.noarch",
"dnf-plugin-subscription-manager.x86_64",
"dnf-plugins-core.noarch",
"dnf-utils.noarch",
"dnf.noarch",
"dnsmasq.x86_64",
"dracut-config-rescue.x86_64",
"dracut-network.x86_64",
"dracut-squash.x86_64",
"dracut.x86_64",
"drbd90-utils.x86_64",
"drpm.x86_64",
],
e => [
"e2fsprogs-libs.x86_64",
"e2fsprogs.x86_64",
"elfutils-default-yama-scope.noarch",
"elfutils-libelf.x86_64",
"elfutils-libs.x86_64",
"emacs-filesystem.noarch",
"enchant.x86_64",
"ethtool.x86_64",
"evolution-data-server-langpacks.noarch",
"evolution-data-server.x86_64",
"expat.x86_64",
],
f => [
"fence-agents-all.x86_64",
"fence-agents-amt-ws.noarch",
"fence-agents-apc-snmp.noarch",
"fence-agents-apc.noarch",
"fence-agents-bladecenter.noarch",
"fence-agents-brocade.noarch",
"fence-agents-cisco-mds.noarch",
"fence-agents-cisco-ucs.noarch",
"fence-agents-common.noarch",
"fence-agents-compute.noarch",
"fence-agents-drac5.noarch",
"fence-agents-eaton-snmp.noarch",
"fence-agents-emerson.noarch",
"fence-agents-eps.noarch",
"fence-agents-heuristics-ping.noarch",
"fence-agents-hpblade.noarch",
"fence-agents-ibmblade.noarch",
"fence-agents-ifmib.noarch",
"fence-agents-ilo-moonshot.noarch",
"fence-agents-ilo-mp.noarch",
"fence-agents-ilo-ssh.noarch",
"fence-agents-ilo2.noarch",
"fence-agents-intelmodular.noarch",
"fence-agents-ipdu.noarch",
"fence-agents-ipmilan.noarch",
"fence-agents-kdump.x86_64",
"fence-agents-mpath.noarch",
"fence-agents-rhevm.noarch",
"fence-agents-rsa.noarch",
"fence-agents-rsb.noarch",
"fence-agents-sbd.noarch",
"fence-agents-scsi.noarch",
"fence-agents-virsh.noarch",
"fence-agents-vmware-rest.noarch",
"fence-agents-vmware-soap.noarch",
"fence-agents-wti.noarch",
"fence-virt.x86_64",
"file-libs.x86_64",
"file.x86_64",
"filesystem.x86_64",
"findutils.x86_64",
"fipscheck-lib.x86_64",
"fipscheck.x86_64",
"firefox.x86_64",
"firewalld-filesystem.noarch",
"firewalld.noarch",
"flac-libs.x86_64",
"fontconfig.x86_64",
"fontpackages-filesystem.noarch",
"freetype.x86_64",
"fribidi.x86_64",
"fuse-libs.x86_64",
],
g => [
"gawk.x86_64",
"GConf2.x86_64",
"gcr.x86_64",
"gdbm-libs.x86_64",
"gdbm.x86_64",
"gdk-pixbuf2-modules.x86_64",
"gdk-pixbuf2.x86_64",
"gdm.x86_64",
"genisoimage.x86_64",
"geoclue2-libs.x86_64",
"geoclue2.x86_64",
"geocode-glib.x86_64",
"geolite2-city.noarch",
"geolite2-country.noarch",
"gettext-libs.x86_64",
"gettext.x86_64",
"gjs.x86_64",
"glib-networking.x86_64",
"glib2.x86_64",
"glibc-common.x86_64",
"glibc-langpack-en.x86_64",
"glibc.x86_64",
"glx-utils.x86_64",
"gmp.x86_64",
"gnome-bluetooth-libs.x86_64",
"gnome-bluetooth.x86_64",
"gnome-control-center-filesystem.noarch",
"gnome-control-center.x86_64",
"gnome-desktop3.x86_64",
"gnome-keyring-pam.x86_64",
"gnome-keyring.x86_64",
"gnome-online-accounts.x86_64",
"gnome-session-wayland-session.x86_64",
"gnome-session-xsession.x86_64",
"gnome-session.x86_64",
"gnome-settings-daemon.x86_64",
"gnome-shell.x86_64",
"gnome-themes-standard.x86_64",
"gnupg2-smime.x86_64",
"gnupg2.x86_64",
"gnutls-dane.x86_64",
"gnutls-utils.x86_64",
"gnutls.x86_64",
"gobject-introspection.x86_64",
"gpgme.x86_64",
"gpm-libs.x86_64",
"gpm.x86_64",
"graphite2.x86_64",
"grep.x86_64",
"grilo.x86_64",
"groff-base.x86_64",
"grub2-common.noarch",
"grub2-pc-modules.noarch",
"grub2-pc.x86_64",
"grub2-tools-extra.x86_64",
"grub2-tools-minimal.x86_64",
"grub2-tools.x86_64",
"grubby.x86_64",
"gsettings-desktop-schemas.x86_64",
"gsm.x86_64",
"gssproxy.x86_64",
"gstreamer1-plugins-base.x86_64",
"gstreamer1.x86_64",
"gtk-update-icon-cache.x86_64",
"gtk-vnc2.x86_64",
"gtk2.x86_64",
"gtk3.x86_64",
"gvnc.x86_64",
"gzip.x86_64",
],
h => [
"hardlink.x86_64",
"harfbuzz-icu.x86_64",
"harfbuzz.x86_64",
"hdparm.x86_64",
"hicolor-icon-theme.noarch",
"hostname.x86_64",
"htop.x86_64",
"httpd-filesystem.noarch",
"httpd-tools.x86_64",
"httpd.x86_64",
"hunspell-en-GB.noarch",
"hunspell-en-US.noarch",
"hunspell-en.noarch",
"hunspell.x86_64",
"hwdata.noarch",
"hyphen.x86_64",
],
i => [
"ibus-gtk2.x86_64",
"ibus-gtk3.x86_64",
"ibus-libs.x86_64",
"ibus-setup.noarch",
"ibus.x86_64",
"iio-sensor-proxy.x86_64",
"ima-evm-utils.x86_64",
"info.x86_64",
"initscripts.x86_64",
"ipcalc.x86_64",
"ipmitool.x86_64",
"iproute-tc.x86_64",
"iproute.x86_64",
"iprutils.x86_64",
"ipset-libs.x86_64",
"ipset.x86_64",
"iptables-ebtables.x86_64",
"iptables-libs.x86_64",
"iptables.x86_64",
"iputils.x86_64",
"ipxe-roms-qemu.noarch",
"irqbalance.x86_64",
"iscsi-initiator-utils-iscsiuio.x86_64",
"iscsi-initiator-utils.x86_64",
"isns-utils-libs.x86_64",
"iso-codes.noarch",
"iwl100-firmware.noarch",
"iwl1000-firmware.noarch",
"iwl105-firmware.noarch",
"iwl135-firmware.noarch",
"iwl2000-firmware.noarch",
"iwl2030-firmware.noarch",
"iwl3160-firmware.noarch",
"iwl3945-firmware.noarch",
"iwl4965-firmware.noarch",
"iwl5000-firmware.noarch",
"iwl5150-firmware.noarch",
"iwl6000-firmware.noarch",
"iwl6000g2a-firmware.noarch",
"iwl6050-firmware.noarch",
"iwl7260-firmware.noarch",
],
j => [
"jansson.x86_64",
"jasper-libs.x86_64",
"jbigkit-libs.x86_64",
"json-c.x86_64",
"json-glib.x86_64",
],
k => [
"kbd-legacy.noarch",
"kbd-misc.noarch",
"kbd.x86_64",
"kernel-core.x86_64",
"kernel-modules.x86_64",
"kernel-tools-libs.x86_64",
"kernel-tools.x86_64",
"kernel.x86_64",
"kexec-tools.x86_64",
"keyutils-libs.x86_64",
"keyutils.x86_64",
"kmod-drbd90.x86_64",
"kmod-libs.x86_64",
"kmod.x86_64",
"kpartx.x86_64",
"krb5-libs.x86_64",
],
l => [
"langpacks-en.noarch",
"lcms2.x86_64",
"less.x86_64",
"libICE.x86_64",
"libSM.x86_64",
"libX11-common.noarch",
"libX11-xcb.x86_64",
"libX11.x86_64",
"libXau.x86_64",
"libXcomposite.x86_64",
"libXcursor.x86_64",
"libXdamage.x86_64",
"libXdmcp.x86_64",
"libXext.x86_64",
"libXfixes.x86_64",
"libXfont2.x86_64",
"libXft.x86_64",
"libXi.x86_64",
"libXinerama.x86_64",
"libXmu.x86_64",
"libXrandr.x86_64",
"libXrender.x86_64",
"libXt.x86_64",
"libXtst.x86_64",
"libXv.x86_64",
"libXxf86misc.x86_64",
"libXxf86vm.x86_64",
"libacl.x86_64",
"libaio.x86_64",
"libappstream-glib.x86_64",
"libarchive.x86_64",
"libassuan.x86_64",
"libasyncns.x86_64",
"libattr.x86_64",
"libbasicobjects.x86_64",
"libblkid.x86_64",
"libcacard.x86_64",
"libcanberra-gtk3.x86_64",
"libcanberra.x86_64",
"libcap-ng.x86_64",
"libcap.x86_64",
"libcgroup.x86_64",
"libcollection.x86_64",
"libcom_err.x86_64",
"libcomps.x86_64",
"libcroco.x86_64",
"libcurl.x86_64",
"libdaemon.x86_64",
"libdatrie.x86_64",
"libdb-utils.x86_64",
"libdb.x86_64",
"libdhash.x86_64",
"libdnf.x86_64",
"libdrm.x86_64",
"libedit.x86_64",
"libepoxy.x86_64",
"liberation-fonts-common.noarch",
"liberation-sans-fonts.noarch",
"libestr.x86_64",
"libevdev.x86_64",
"libevent.x86_64",
"libfastjson.x86_64",
"libfdisk.x86_64",
"libffi.x86_64",
"libfontenc.x86_64",
"libgcc.x86_64",
"libgcrypt.x86_64",
"libgdata.x86_64",
"libglvnd-egl.x86_64",
"libglvnd-gles.x86_64",
"libglvnd-glx.x86_64",
"libglvnd.x86_64",
"libgnomekbd.x86_64",
"libgomp.x86_64",
"libgpg-error.x86_64",
"libgtop2.x86_64",
"libgudev.x86_64",
"libgusb.x86_64",
"libgweather.x86_64",
"libibumad.x86_64",
"libibverbs.x86_64",
"libical.x86_64",
"libicu.x86_64",
"libidn2.x86_64",
"libimobiledevice.x86_64",
"libini_config.x86_64",
"libinput.x86_64",
"libiscsi.x86_64",
"libjpeg-turbo.x86_64",
"libkcapi-hmaccalc.x86_64",
"libkcapi.x86_64",
"libknet1.x86_64",
"libksba.x86_64",
"libldb.x86_64",
"libmaxminddb.x86_64",
"libmcpp.x86_64",
"libmetalink.x86_64",
"libmnl.x86_64",
"libmodman.x86_64",
"libmodulemd.x86_64",
"libmodulemd1.x86_64",
"libmount.x86_64",
"libndp.x86_64",
"libnetfilter_conntrack.x86_64",
"libnfnetlink.x86_64",
"libnfsidmap.x86_64",
"libnftnl.x86_64",
"libnghttp2.x86_64",
"libnl3-cli.x86_64",
"libnl3.x86_64",
"libnma.x86_64",
"libnotify.x86_64",
"libnsl2.x86_64",
"liboauth.x86_64",
"libogg.x86_64",
"libosinfo.x86_64",
"libpath_utils.x86_64",
"libpcap.x86_64",
"libpciaccess.x86_64",
"libpipeline.x86_64",
"libpkgconf.x86_64",
"libplist.x86_64",
"libpng.x86_64",
"libpq.x86_64",
"libproxy.x86_64",
"libpsl.x86_64",
"libpwquality.x86_64",
"libqb.x86_64",
"libquvi-scripts.noarch",
"libquvi.x86_64",
"librados2.x86_64",
"librbd1.x86_64",
"librdmacm.x86_64",
"libref_array.x86_64",
"librepo.x86_64",
"libreport-filesystem.x86_64",
"librhsm.x86_64",
"librsvg2.x86_64",
"libseccomp.x86_64",
"libsecret.x86_64",
"libselinux-utils.x86_64",
"libselinux.x86_64",
"libsemanage.x86_64",
"libsepol.x86_64",
"libsigsegv.x86_64",
"libsmartcols.x86_64",
"libsmbclient.x86_64",
"libsndfile.x86_64",
"libsolv.x86_64",
"libsoup.x86_64",
"libss.x86_64",
"libssh.x86_64",
"libssh2.x86_64",
"libsss_autofs.x86_64",
"libsss_certmap.x86_64",
"libsss_idmap.x86_64",
"libsss_nss_idmap.x86_64",
"libsss_sudo.x86_64",
"libstdc++.x86_64",
"libstemmer.x86_64",
"libsysfs.x86_64",
"libtalloc.x86_64",
"libtasn1.x86_64",
"libtdb.x86_64",
"libteam.x86_64",
"libtevent.x86_64",
"libthai.x86_64",
"libtheora.x86_64",
"libtiff.x86_64",
"libtirpc.x86_64",
"libtool-ltdl.x86_64",
"libunistring.x86_64",
"libusal.x86_64",
"libusbmuxd.x86_64",
"libusbx.x86_64",
"libuser.x86_64",
"libutempter.x86_64",
"libuuid.x86_64",
"libverto-libevent.x86_64",
"libverto.x86_64",
"libvirt-bash-completion.x86_64",
"libvirt-client.x86_64",
"libvirt-daemon-config-network.x86_64",
"libvirt-daemon-config-nwfilter.x86_64",
"libvirt-daemon-driver-interface.x86_64",
"libvirt-daemon-driver-network.x86_64",
"libvirt-daemon-driver-nodedev.x86_64",
"libvirt-daemon-driver-nwfilter.x86_64",
"libvirt-daemon-driver-qemu.x86_64",
"libvirt-daemon-driver-secret.x86_64",
"libvirt-daemon-driver-storage-core.x86_64",
"libvirt-daemon-driver-storage-disk.x86_64",
"libvirt-daemon-driver-storage-iscsi.x86_64",
"libvirt-daemon-driver-storage-logical.x86_64",
"libvirt-daemon-driver-storage-mpath.x86_64",
"libvirt-daemon-driver-storage-rbd.x86_64",
"libvirt-daemon-driver-storage-scsi.x86_64",
"libvirt-daemon-driver-storage.x86_64",
"libvirt-daemon-kvm.x86_64",
"libvirt-daemon.x86_64",
"libvirt-docs.x86_64",
"libvirt-glib.x86_64",
"libvirt-libs.x86_64",
"libvirt.x86_64",
"libvisual.x86_64",
"libvorbis.x86_64",
"libwacom-data.noarch",
"libwacom.x86_64",
"libwayland-client.x86_64",
"libwayland-cursor.x86_64",
"libwayland-egl.x86_64",
"libwayland-server.x86_64",
"libwbclient.x86_64",
"libwebp.x86_64",
"libwsman1.x86_64",
"libxcb.x86_64",
"libxcrypt.x86_64",
"libxkbcommon-x11.x86_64",
"libxkbcommon.x86_64",
"libxkbfile.x86_64",
"libxklavier.x86_64",
"libxml2.x86_64",
"libxshmfence.x86_64",
"libxslt.x86_64",
"libyaml.x86_64",
"linux-firmware.noarch",
"linuxconsoletools.x86_64",
"llvm-libs.x86_64",
"logrotate.x86_64",
"lshw.x86_64",
"lsscsi.x86_64",
"lua-expat.x86_64",
"lua-json.noarch",
"lua-libs.x86_64",
"lua-lpeg.x86_64",
"lua-socket.x86_64",
"lua.x86_64",
"lvm2-libs.x86_64",
"lvm2.x86_64",
"lz4-libs.x86_64",
"lzo.x86_64",
"lzop.x86_64",
],
'm' => [
"mailcap.noarch",
"man-db.x86_64",
"mcpp.x86_64",
"mesa-dri-drivers.x86_64",
"mesa-filesystem.x86_64",
"mesa-libEGL.x86_64",
"mesa-libGL.x86_64",
"mesa-libgbm.x86_64",
"mesa-libglapi.x86_64",
"microcode_ctl.x86_64",
"mlocate.x86_64",
"mobile-broadband-provider-info.noarch",
"mod_http2.x86_64",
"ModemManager-glib.x86_64",
"mozilla-filesystem.x86_64",
"mozjs52.x86_64",
"mpfr.x86_64",
"mtdev.x86_64",
"mtools.x86_64",
"mutter.x86_64",
],
n => [
"ncurses-base.noarch",
"ncurses-libs.x86_64",
"ncurses.x86_64",
"net-snmp-libs.x86_64",
"net-snmp-utils.x86_64",
"net-tools.x86_64",
"netcf-libs.x86_64",
"nettle.x86_64",
"NetworkManager-libnm.x86_64",
"NetworkManager-team.x86_64",
"NetworkManager-tui.x86_64",
"NetworkManager-wifi.x86_64",
"NetworkManager.x86_64",
"newt.x86_64",
"nfs-utils.x86_64",
"nftables.x86_64",
"nm-connection-editor.x86_64",
"nmap-ncat.x86_64",
"nmap.x86_64",
"npth.x86_64",
"nspr.x86_64",
"nss-softokn-freebl.x86_64",
"nss-softokn.x86_64",
"nss-sysinit.x86_64",
"nss-util.x86_64",
"nss.x86_64",
"numactl-libs.x86_64",
"numad.x86_64",
],
o => [
"openldap.x86_64",
"openssh-clients.x86_64",
"openssh-server.x86_64",
"openssh.x86_64",
"openssl-libs.x86_64",
"openssl-pkcs11.x86_64",
"openssl.x86_64",
"openwsman-python3.x86_64",
"opus.x86_64",
"orc.x86_64",
"os-prober.x86_64",
"osinfo-db-tools.x86_64",
"osinfo-db.noarch",
"overpass-fonts.noarch",
],
p => [
"p11-kit-trust.x86_64",
"p11-kit.x86_64",
"pacemaker-cli.x86_64",
"pacemaker-cluster-libs.x86_64",
"pacemaker-libs.x86_64",
"pacemaker.x86_64",
"PackageKit-glib.x86_64",
"PackageKit.x86_64",
"pam.x86_64",
"pango.x86_64",
"parted.x86_64",
"passwd.x86_64",
"pciutils-libs.x86_64",
"pciutils.x86_64",
"pcre.x86_64",
"pcre2.x86_64",
"pcs.x86_64",
"perl-Algorithm-Diff.noarch",
"perl-B-Hooks-EndOfScope.noarch",
"perl-CGI.noarch",
"perl-Carp.noarch",
"perl-Class-Data-Inheritable.noarch",
"perl-Class-Method-Modifiers.noarch",
"perl-Compress-Raw-Bzip2.x86_64",
"perl-Compress-Raw-Zlib.x86_64",
"perl-DBD-Pg.x86_64",
"perl-DBI.x86_64",
"perl-Data-Dump.noarch",
"perl-Data-Dumper.x86_64",
"perl-Data-OptList.noarch",
"perl-Date-Manip.noarch",
"perl-Devel-Caller.x86_64",
"perl-Devel-GlobalDestruction.noarch",
"perl-Devel-LexAlias.x86_64",
"perl-Devel-StackTrace.noarch",
"perl-Digest-HMAC.noarch",
"perl-Digest-MD5.x86_64",
"perl-Digest-SHA.x86_64",
"perl-Digest-SHA1.x86_64",
"perl-Digest.noarch",
"perl-Dist-CheckConflicts.noarch",
"perl-Email-Date-Format.noarch",
"perl-Email-Find.noarch",
"perl-Email-Valid.noarch",
"perl-Encode-Locale.noarch",
"perl-Encode.x86_64",
"perl-Errno.x86_64",
"perl-Eval-Closure.noarch",
"perl-Exception-Class.noarch",
"perl-Exporter.noarch",
"perl-File-BaseDir",
"perl-File-DesktopEntry",
"perl-File-Listing.noarch",
"perl-File-DesktopEntry",
"perl-File-Path.noarch",
"perl-File-Temp.noarch",
"perl-Getopt-Long.noarch",
"perl-HTML-FromText.noarch",
"perl-HTML-Parser.x86_64",
"perl-HTML-Strip.x86_64",
"perl-HTML-Tagset.noarch",
"perl-HTTP-Cookies.noarch",
"perl-HTTP-Date.noarch",
"perl-HTTP-Message.noarch",
"perl-HTTP-Negotiate.noarch",
"perl-HTTP-Tiny.noarch",
"perl-IO-Compress.noarch",
"perl-IO-HTML.noarch",
"perl-IO-Socket-IP.noarch",
"perl-IO-Socket-SSL.noarch",
"perl-IO.x86_64",
"perl-IPC-SysV.x86_64",
"perl-JSON.noarch",
"perl-LWP-MediaTypes.noarch",
"perl-Log-Dispatch-FileRotate.noarch",
"perl-Log-Dispatch.noarch",
"perl-Log-Journald.x86_64",
"perl-Log-Log4perl.noarch",
"perl-MIME-Base64.x86_64",
"perl-MIME-Lite.noarch",
"perl-MIME-Types.noarch",
"perl-MRO-Compat.noarch",
"perl-Mail-Sender.noarch",
"perl-Mail-Sendmail.noarch",
"perl-MailTools.noarch",
"perl-Math-BigInt.noarch",
"perl-Math-Complex.noarch",
"perl-Module-Implementation.noarch",
"perl-Module-Runtime.noarch",
"perl-Mozilla-CA.noarch",
"perl-NTLM.noarch",
"perl-Net-HTTP.noarch",
"perl-Net-Netmask.noarch",
"perl-Net-SMTP-SSL.noarch",
"perl-Net-SSH2.x86_64",
"perl-Net-SSLeay.x86_64",
"perl-NetAddr-IP.x86_64",
"perl-Package-Generator.noarch",
"perl-Package-Stash-XS.x86_64",
"perl-Package-Stash.noarch",
"perl-PadWalker.x86_64",
"perl-Params-Util.x86_64",
"perl-Params-Validate.x86_64",
"perl-Params-ValidationCompiler.noarch",
"perl-PathTools.x86_64",
"perl-Pod-Escapes.noarch",
"perl-Pod-Perldoc.noarch",
"perl-Pod-Simple.noarch",
"perl-Pod-Usage.noarch",
"perl-Proc-Simple.noarch",
"perl-Ref-Util-XS.x86_64",
"perl-Ref-Util.noarch",
"perl-Role-Tiny.noarch",
"perl-Scalar-List-Utils.x86_64",
"perl-Socket.x86_64",
"perl-Socket6.x86_64",
"perl-Specio.noarch",
"perl-Storable.x86_64",
"perl-Sub-Exporter-Progressive.noarch",
"perl-Sub-Exporter.noarch",
"perl-Sub-Identify.x86_64",
"perl-Sub-Install.noarch",
"perl-Sys-Syslog.x86_64",
"perl-Term-ANSIColor.noarch",
"perl-Term-Cap.noarch",
"perl-TermReadKey.x86_64",
"perl-Test-Simple.noarch",
"perl-Text-Diff.noarch",
"perl-Text-ParseWords.noarch",
"perl-Text-Tabs+Wrap.noarch",
"perl-Time-HiRes.x86_64",
"perl-Time-Local.noarch",
"perl-TimeDate.noarch",
"perl-Try-Tiny.noarch",
"perl-URI.noarch",
"perl-UUID-Tiny.noarch",
"perl-Unicode-Normalize.x86_64",
"perl-Variable-Magic.x86_64",
"perl-WWW-RobotRules.noarch",
"perl-XML-NamespaceSupport.noarch",
"perl-XML-Parser.x86_64",
"perl-XML-SAX-Base.noarch",
"perl-XML-SAX.noarch",
"perl-XML-Simple.noarch",
"perl-constant.noarch",
"perl-interpreter.x86_64",
"perl-libnet.noarch",
"perl-libs.x86_64",
"perl-libwww-perl.noarch",
"perl-macros.x86_64",
"perl-namespace-autoclean.noarch",
"perl-namespace-clean.noarch",
"perl-parent.noarch",
"perl-podlators.noarch",
"perl-threads-shared.x86_64",
"perl-threads.x86_64",
"perl-version.x86_64",
"perltidy.noarch",
"pigz.x86_64",
"pinentry-gtk.x86_64",
"pinentry.x86_64",
"pipewire-libs.x86_64",
"pipewire.x86_64",
"pixman.x86_64",
"pkgconf-m4.noarch",
"pkgconf-pkg-config.x86_64",
"pkgconf.x86_64",
"platform-python.x86_64",
"platform-python-pip.noarch",
"platform-python-setuptools.noarch",
"plymouth-core-libs.x86_64",
"plymouth-scripts.x86_64",
"plymouth.x86_64",
"policycoreutils-python-utils.noarch",
"policycoreutils.x86_64",
"polkit-libs.x86_64",
"polkit-pkla-compat.x86_64",
"polkit.x86_64",
"popt.x86_64",
"postfix.x86_64",
"postgresql-contrib.x86_64",
"postgresql-plperl.x86_64",
"postgresql-server.x86_64",
"postgresql.x86_64",
"prefixdevname.x86_64",
"procps-ng.x86_64",
"psmisc.x86_64",
"publicsuffix-list-dafsa.noarch",
"pulseaudio-libs-glib2.x86_64",
"pulseaudio-libs.x86_64",
"pulseaudio-module-bluetooth.x86_64",
"pulseaudio.x86_64",
#"python3-IPy.noarch",
"python3-asn1crypto.noarch",
"python3-audit.x86_64",
"python3-bind.noarch",
"python3-cairo.x86_64",
"python3-cffi.x86_64",
"python3-chardet.noarch",
"python3-clufter.noarch",
"python3-configobj.noarch",
"python3-cryptography.x86_64",
"python3-dateutil.noarch",
"python3-dbus.x86_64",
"python3-decorator.noarch",
"python3-dmidecode.x86_64",
"python3-dnf-plugins-core.noarch",
"python3-dnf.noarch",
"python3-ethtool.x86_64",
"python3-firewall.noarch",
"python3-gobject-base.x86_64",
"python3-gobject.x86_64",
"python3-gpg.x86_64",
"python3-hawkey.x86_64",
"python3-html5lib.noarch",
"python3-idna.noarch",
"python3-iniparse.noarch",
"python3-inotify.noarch",
"python3-libcomps.x86_64",
"python3-libdnf.x86_64",
"python3-librepo.x86_64",
"python3-libs.x86_64",
"python3-libselinux.x86_64",
"python3-libsemanage.x86_64",
"python3-libvirt.x86_64",
"python3-libxml2.x86_64",
"python3-linux-procfs.noarch",
"python3-lxml.x86_64",
"python3-perf.x86_64",
"python3-pexpect.noarch",
"python3-pip.noarch",
"python3-ply.noarch",
"python3-policycoreutils.noarch",
"python3-ptyprocess.noarch",
"python3-pyOpenSSL.noarch",
"python3-pycparser.noarch",
"python3-pycurl.x86_64",
"python3-pysocks.noarch",
"python3-pyudev.noarch",
"python3-requests.noarch",
"python3-rpm.x86_64",
"python3-schedutils.x86_64",
"python3-setools.x86_64",
"python3-setuptools.noarch",
"python3-six.noarch",
"python3-slip-dbus.noarch",
"python3-slip.noarch",
"python3-subscription-manager-rhsm.x86_64",
"python3-suds.noarch",
"python3-syspurpose.x86_64",
"python3-systemd.x86_64",
"python3-unbound.x86_64",
"python3-urllib3.noarch",
"python3-webencodings.noarch",
],
'q' => [
"qemu-guest-agent.x86_64",
"qemu-img.x86_64",
"qemu-kvm-block-curl.x86_64",
"qemu-kvm-block-iscsi.x86_64",
"qemu-kvm-block-rbd.x86_64",
"qemu-kvm-block-ssh.x86_64",
"qemu-kvm-common.x86_64",
"qemu-kvm-core.x86_64",
"qemu-kvm.x86_64",
"quota-nls.noarch",
"quota.x86_64",
],
r => [
"radvd.x86_64",
"rdma-core.x86_64",
"readline.x86_64",
"redhat-backgrounds.noarch",
"redhat-indexhtml.noarch",
"redhat-logos-httpd.noarch",
"redhat-logos.x86_64",
"redhat-release.x86_64",
"resource-agents.x86_64",
"rest.x86_64",
"rootfiles.noarch",
"rpcbind.x86_64",
"rpm-build-libs.x86_64",
"rpm-libs.x86_64",
"rpm-plugin-selinux.x86_64",
"rpm-plugin-systemd-inhibit.x86_64",
"rpm.x86_64",
"rsync.x86_64",
"rsyslog.x86_64",
"rtkit.x86_64",
"ruby-irb.noarch",
"ruby-libs.x86_64",
"ruby.x86_64",
"rubygem-bigdecimal.x86_64",
"rubygem-did_you_mean.noarch",
"rubygem-io-console.x86_64",
"rubygem-json.x86_64",
"rubygem-openssl.x86_64",
"rubygem-psych.x86_64",
"rubygem-rdoc.noarch",
"rubygems.noarch",
],
's' => [
"samba-client-libs.x86_64",
"samba-common-libs.x86_64",
"samba-common.noarch",
"sbc.x86_64",
"sbd.x86_64",
"SDL.x86_64",
"seabios-bin.noarch",
"seavgabios-bin.noarch",
"sed.x86_64",
"selinux-policy-targeted.noarch",
"selinux-policy.noarch",
"setroubleshoot-plugins.noarch",
"setroubleshoot-server.x86_64",
"setup.noarch",
"sg3_utils-libs.x86_64",
"sg3_utils.x86_64",
"sgabios-bin.noarch",
"shadow-utils.x86_64",
"shared-mime-info.x86_64",
"slang.x86_64",
"snappy.x86_64",
"sound-theme-freedesktop.noarch",
"speexdsp.x86_64",
"spice-glib.x86_64",
"spice-gtk3.x86_64",
"spice-server.x86_64",
"sqlite-libs.x86_64",
"squashfs-tools.x86_64",
"sscg.x86_64",
"sssd-client.x86_64",
"sssd-common.x86_64",
"sssd-kcm.x86_64",
"sssd-nfs-idmap.x86_64",
"startup-notification.x86_64",
"subscription-manager-cockpit.noarch",
"subscription-manager-rhsm-certificates.x86_64",
"subscription-manager.x86_64",
"sudo.x86_64",
"switcheroo-control.x86_64",
"syslinux-nonlinux.noarch",
"syslinux.x86_64",
"systemd-container.x86_64",
"systemd-libs.x86_64",
"systemd-pam.x86_64",
"systemd-udev.x86_64",
"systemd.x86_64",
],
t => [
"teamd.x86_64",
"telnet.x86_64",
"tftp-server.x86_64",
"timedatex.x86_64",
"tmux.x86_64",
"totem-pl-parser.x86_64",
"trousers-lib.x86_64",
"trousers.x86_64",
"tuned.noarch",
"tzdata.noarch",
],
u => [
"unbound-libs.x86_64",
"upower.x86_64",
"usbredir.x86_64",
"usermode.x86_64",
"userspace-rcu.x86_64",
"util-linux.x86_64",
"uuid.x86_64",
],
v => [
"vim-common.x86_64",
"vim-enhanced.x86_64",
"vim-filesystem.noarch",
"vim-minimal.x86_64",
"vino.x86_64",
"virt-install.noarch",
"virt-manager-common.noarch",
"virt-manager.noarch",
"virt-what.x86_64",
"vte-profile.x86_64",
"vte291.x86_64",
],
w => [
"webkit2gtk3-jsc.x86_64",
"webkit2gtk3-plugin-process-gtk2.x86_64",
"webkit2gtk3.x86_64",
"webrtc-audio-processing.x86_64",
"which.x86_64",
"woff2.x86_64",
"wpa_supplicant.x86_64",
],
x => [
"xcb-util.x86_64",
"xfsprogs.x86_64",
"xkeyboard-config.noarch",
"xml-common.noarch",
"xorg-x11-server-Xwayland.x86_64",
"xorg-x11-server-common.x86_64",
"xorg-x11-server-utils.x86_64",
"xorg-x11-xauth.x86_64",
"xorg-x11-xinit.x86_64",
"xorg-x11-xkb-utils.x86_64",
"xz-libs.x86_64",
"xz.x86_64",
],
'y' => [
"yajl.x86_64",
"yum.noarch",
"zenity.x86_64",
],
z => [
"zlib.x86_64",
],
};
update_progress($anvil, 5, "log_0241");
return(0);
}