#!/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 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.
# 12 = Failed to rename the /var/www/html/rhel8 to ./centos8
# 
# 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).
# 
# NOTE: This is currently broken. Re-enable when a solution to anvil-striker-extra is found.
# 
# 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();

# Read switches
$anvil->Get->switches({list => [
	"check", 
	"disable", 
	"enable", 
	"force", 
	"job-uuid", 
	"no-refresh", 
	"refresh", 
	"status"
], man => $THIS_FILE});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => $anvil->data->{switches}});
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, secure => 0, key => "log_0115", variables => { program => $THIS_FILE }});

# 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({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({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({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,!!job-uuid!".$anvil->data->{switches}{'job-uuid'}."!!");

# 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,!!job-uuid!".$anvil->data->{switches}{'job-uuid'}."!!");
	$anvil->nice_exit({exit_code => 9});
}

### TODO: Remove this when re-enabled.
print $anvil->Words->string({key => "message_0291"})."\n";
update_progress($anvil, 100, "message_0291");
$anvil->nice_exit({exit_code => 0});

# Figure out what this machine is.
load_os_info($anvil);

# If this isn't a Striker dashboard, exit.
if ($anvil->Get->host_type ne "striker")
{
	print $anvil->Words->string({key => "error_0044"})."\n";
	update_progress($anvil, 100, "error_0044");
	$anvil->nice_exit({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({debug => 2, daemon => $anvil->data->{sys}{daemon}{dhcpd}});
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, 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.                                                                                        #
#############################################################################################################

# This loads the OS info into hashes strings and templates will use later.
sub load_os_info
{
	my ($anvil) = @_;
	
	my ($os_type, $os_arch) = $anvil->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->data->{host_os}{os_name} = "";
	if ($os_type =~ /^rhel/)
	{
		$anvil->data->{host_os}{os_name} = "RHEL ".$anvil->data->{host_os}{version};
	}
	elsif ($os_type =~ /^centos-stream/)
	{
		$anvil->data->{host_os}{os_name} = "CentOS Stream ".$anvil->data->{host_os}{version};
	}
	elsif ($os_type =~ /^centos/)
	{
		$anvil->data->{host_os}{os_name} = "CentOS ".$anvil->data->{host_os}{version};
	}
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { 
		"host_os::os_arch" => $anvil->data->{host_os}{os_arch}, 
		"host_os::os_name" => $anvil->data->{host_os}{os_name}, 
		"host_os::os_type" => $anvil->data->{host_os}{os_type}, 
		"host_os::version" => $anvil->data->{host_os}{version}, 
	}});
	
	return(0);
}

# 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_0238", variables => $variables})."\n";
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 3, key => "log_0238", 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) = @_;
	
	# Get my BCN IP and active OS.
	$anvil->Network->get_ips();
	my $bcn_interface = "";
	my $local_host    = $anvil->Get->short_host_name();
	foreach my $interface (sort {$a cmp $b} keys %{$anvil->data->{network}{$local_host}{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 => { "network::local::${interface}::ip" => $anvil->data->{network}{$local_host}{interface}{$interface}{ip} }});
		if ($anvil->Validate->ipv4({ip => $anvil->data->{network}{$local_host}{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({exit_code => 2});
	}
	
	my $bcn_ip          = $anvil->data->{network}{$local_host}{interface}{$bcn_interface}{ip};
	my $bcn_subnet_mask = $anvil->data->{network}{$local_host}{interface}{$bcn_interface}{subnet_mask};
	my $bcn_network     = $anvil->Network->get_network({ip => $bcn_ip, subnet_mask => $bcn_subnet_mask});
	my $dns             = $anvil->data->{network}{$local_host}{interface}{$bcn_interface}{dns} ? $anvil->data->{network}{$local_host}{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->Get->host_name =~ /\./)
	{
		$domain =  $anvil->Get->host_name;
		$domain =~ s/^.*?\.//;
	}
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { 
		bcn_interface   => $bcn_interface,
		bcn_ip          => $bcn_ip,
		bcn_subnet_mask => $bcn_subnet_mask, 
		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->Get->short_host_name =~ /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({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({exit_code => 3});
	}
	elsif ($bios_default_success eq "1")
	{
		# Updated
		print $anvil->Words->string({key => "message_0097", variables => { file => $anvil->data->{path}{configs}{pxe_default} }})."\n";
	}
	elsif ($bios_default_success eq "2")
	{
		# Update not needed.
		print $anvil->Words->string({key => "message_0096", variables => { file => $anvil->data->{path}{configs}{pxe_default} }})."\n";
	}
	
	### PXE UEFI 'grub.cfg' file.
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { "host_os::os_type" => $anvil->data->{host_os}{os_type} }});
	my $say_os = "#!string!brand_0010!#";
	if ($anvil->data->{host_os}{os_type} eq "centos8")
	{
		$say_os = "#!string!brand_0011!#";
	}
	elsif ($anvil->data->{host_os}{os_type} eq "centos-stream8")
	{
		$say_os = "#!string!brand_0012!#";
	}
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { say_os => $say_os }});
	
	my $uefi_grub_body = $anvil->Template->get({file => "pxe.txt", show_name => 0, name => "tftp_grub", variables => { 
		base_url => $base_url,
		say_os   => $say_os,
	}});
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { uefi_grub_body => $uefi_grub_body }});
	
	# Return code if '1' means the file was changed, '2' indicates it didn't change. '0' means something 
	# went wrong.
	my $uefi_grub_success = $anvil->Storage->update_file({
		body => $uefi_grub_body, 
		file => $anvil->data->{path}{configs}{pxe_grub},
	});
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { uefi_grub_success => $uefi_grub_success }});
	if (not $uefi_grub_success)
	{
		# Failed.
		print $anvil->Words->string({key => "error_0043", variables => { file => $anvil->data->{path}{configs}{pxe_grub} }})."\n";
		update_progress($anvil, 100, "error_0043,!!file!".$anvil->data->{path}{configs}{pxe_grub}."!!");
		$anvil->nice_exit({exit_code => 3});
	}
	elsif ($uefi_grub_success eq "1")
	{
		# Updated
		print $anvil->Words->string({key => "message_0097", variables => { file => $anvil->data->{path}{configs}{pxe_grub} }})."\n";
	}
	elsif ($uefi_grub_success eq "2")
	{
		# Update not needed.
		print $anvil->Words->string({key => "message_0096", variables => { file => $anvil->data->{path}{configs}{pxe_grub} }})."\n";
	}
	
	### TODO: Add repos for all known strikers
	# Build the repository file body.
	my $repo_file = "/etc/yum.repos.d/".$anvil->Get->short_host_name.".repo";
	my $repo_body =  "[".$anvil->Get->short_host_name."-repo]\n";
	   $repo_body .= "name=".$anvil->Get->host_name." #!string!message_0153!#\n";
	# Add the IPs.
	$anvil->Network->get_ips({});
	my $first_line = 1;
	foreach my $in_iface (sort {$a cmp $b} keys %{$anvil->data->{network}{$local_host}{interface}})
	{
		my $ip = $anvil->data->{network}{$local_host}{interface}{$in_iface}{ip};
		if ($ip)
		{
			my $prefix     =  $first_line ? "baseurl=" : "        ";
			   $first_line =  0;
			   $repo_body  .= $prefix."http://".$ip."/".$anvil->data->{host_os}{os_type}."/".$anvil->data->{host_os}{os_arch}."/os/\n";
		}
	}
	$repo_body .= "enabled=1\n";
	$repo_body .= "gpgcheck=0\n";
	$repo_body .= "skip_if_unavailable=1\n";
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { 
		repo_file => $repo_file, 
		repo_body => $repo_body,
	}});
	
	### 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, 
			host_name => "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},
			repo_file => $repo_file,
			repo_body => $repo_body,
			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({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 => "striker-ui-api", group => "striker-ui-api" });

			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({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_0233,!!file!".$anvil->data->{path}{configs}{'dnf.conf'}."!!,!!return!".$return."!!");
			$anvil->nice_exit({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_file = "/etc/yum.repos.d/alteeve-anvil.repo";
	my $repo_url  = "https://www.alteeve.com/an-repo/m3/anvil-release-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 ($output, $return_code) = $anvil->System->call({debug => 2, shell_call => $anvil->data->{path}{exe}{dnf}." -y install ".$repo_url });
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
			output      => $output, 
			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({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, "");
	}
	
	# Test if there is internet access.
	my $domain = "redhat.com";
	if ($anvil->data->{host_os}{os_type} =~ /^centos/)
	{
		$domain = "mirrorlist.centos.org";
	}
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { domain => $domain }});
	
	$anvil->data->{sys}{internet} = $anvil->Network->check_internet({domains => [$domain]});
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { "sys::internet" => $anvil->data->{sys}{internet} }});
	
	# Loop through each letter directory
	if (not $anvil->data->{sys}{internet})
	{
		# No internet.
		$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, 'print' => 1, key => "warning_0031", variables => { domain => $domain }});
		update_progress($anvil, 50, "warning_0031,!!domain!".$domain."!!");
	}
	else
	{
		# 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."!!");
		
		# If this host is not a RHEL host, add the HA packages to the main packages. If it is RHEL, 
		# we'll pull the packages from a node.
		if ($anvil->data->{host_os}{os_type} ne "rhel8")
		{
			foreach my $letter (sort {$a cmp $b} keys %{$anvil->data->{ha_packages}})
			{
				foreach my $package (sort {$a cmp $b} @{$anvil->data->{packages}{$letter}})
				{
					# Push the package onto the normal array.
					push @{$anvil->data->{packages}{$letter}}, $package;
				}
			}
			delete $anvil->data->{ha_packages};
		}
		
		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 => 2, 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       => 3,
				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)
					{
						$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { line => $line }});
						print $anvil->Words->string({key => "message_0078", variables => { line => $line }})."\n";
						
						# In some cases, a bad local RPM can cause download failures. This 
						# checks for and removes them. It can take a few runs to clear out a
						# set of bad files, but it will clear out eventually.
						if ($line =~ / (.*?.rpm): Interrupted by header callback/i)
						{
							my $rpm_name = $1;
							my $rpm_path = $download_path."/".$rpm_name;
							$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, list => { 
								rpm_name => $rpm_name,
								rpm_path => $rpm_path, 
							}});
							
							if (-e $rpm_path)
							{
								$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, 'print' => 1, key => "log_0469", variables => { rpm_path => $rpm_path }});
								unlink $rpm_path;
								
								$error_out .= $anvil->Words->string({key => "log_0469", variables => { rpm_path => $rpm_path }})."\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({exit_code => 7});
			}
			# Progress is '82' leaving this loop
		}
		
		# If this is a RHEL host, we'll now look for a node to download HA packages from.
		if ($anvil->data->{host_os}{os_type} eq "rhel8")
		{
			# Try to find a node to download the RPMs on.
			update_progress($anvil, ++$progress, "message_0184");
			my $use_node_name         = "";
			my $use_node_ip           = "";
			my $use_password          = "";
			my $local_short_host_name = $anvil->Get->short_host_name;
			$anvil->Network->load_ips({
				debug => 3,
				host  => $local_short_host_name, 
			});

			my $query = "
SELECT 
    a.host_uuid, 
    a.host_name, 
    b.anvil_password 
FROM 
    hosts a, 
    anvils b 
WHERE 
    a.host_type = 'node' 
AND 
    (
        a.host_uuid = b.anvil_node1_host_uuid 
    OR 
        a.host_uuid = b.anvil_node2_host_uuid
    ) 
ORDER BY 
    a.host_name ASC
;";
			my $results = $anvil->Database->query({query => $query, source => $THIS_FILE, line => __LINE__});
			my $count   = @{$results};
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
				results => $results, 
				count   => $count,
			}});
			foreach my $row (@{$results})
			{
				my $host_uuid       = $row->[0];
				my $host_name       = $row->[1];
				my $anvil_password  = $row->[2];
				my $short_host_name = $host_name;
				$short_host_name =~ s/\..*$//;
				$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
					host_uuid       => $host_uuid, 
					host_name       => $host_name, 
					anvil_password  => $anvil->Log->is_secure($anvil_password), 
					short_host_name => $short_host_name, 
				}});
				$anvil->Network->load_ips({
					debug     => 2, 
					host_uuid => $host_uuid,
					host      => $short_host_name, 
				});

				my $access  = 0;
				my ($match) = $anvil->Network->find_matches({
					debug  => 2,
					first  => $local_short_host_name, 
					second => $short_host_name,
					source => $THIS_FILE, 
					line   => __LINE__,
				});
				$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { match => $match }});
				if (ref($match) eq "HASH")
				{
					my $keys = keys %{$match};
					$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 'keys' => $keys }});
				}
				
				if (ref($match) eq "HASH")
				{
					foreach my $interface (sort {$a cmp $b} keys %{$match->{$short_host_name}})
					{
						my $remote_ip               = $match->{$short_host_name}{$interface}{ip};
						my ($pinged, $average_time) = $anvil->Network->ping({
							ping  => $remote_ip, 
							count => 1,
						});
						$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
							remote_ip    => $remote_ip, 
							pinged       => $pinged,
							average_time => $average_time,
						}});
						if ($pinged)
						{
							my $access = $anvil->Remote->test_access({
								target   => $remote_ip,
								password => $anvil_password,
							});
							$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { access => $access }});
							if ($access)
							{
								my $internet = $anvil->Network->check_internet({
									debug    => 3,
									target   => $remote_ip, 
									password => $anvil_password, 
								});
								$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { internet => $internet }});
								if ($internet)
								{
									my ($os_type, $os_arch) = $anvil->Get->os_type({
										debug    => 3,
										target   => $remote_ip,
										password => $anvil_password, 
									});
									$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
										os_type => $os_type, 
										os_arch => $os_arch, 
									}});
									if (($anvil->data->{host_os}{os_type} eq $os_type) && ($os_arch eq $anvil->data->{host_os}{os_arch}))
									{
										update_progress($anvil, ++$progress, "message_0185,!!node_name!".$host_name."!!");
										$use_node_name = $host_name;
										$use_node_ip   = $remote_ip;
										$use_password  = $anvil_password;
										$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
											use_node_name => $use_node_name, 
											use_node_ip   => $use_node_ip,
											use_password  => $anvil->Log->is_secure($use_password), 
										}});
										last;
									}
								}
							}
						}
					}
				}
			}

			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { use_node_ip => $use_node_ip }});
			if ($use_node_ip)
			{
				foreach my $letter (sort {$a cmp $b} keys %{$anvil->data->{ha_packages}})
				{
					my $download_path = "/tmp/Packages/".$letter;
					my $local_path    = "/var/www/html/".$anvil->data->{host_os}{os_type}."/".$anvil->data->{host_os}{os_arch}."/os/Packages/".$letter;
					$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
						letter        => $letter, 
						download_path => $download_path, 
						local_path    => $local_path, 
					}});
					
					# This is the directory we'll download the packages to on the node.
					$anvil->Storage->make_directory({
						debug     => 3,
						directory => $download_path, 
						target    => $use_node_ip, 
						password  => $use_password, 
						mode      => "0775", 
					});
					
					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->{ha_packages}{$letter}})
					{
						# Append the package to the active shell call.
						$packages .= $package." ";
					}
					$packages   =~ s/ $//;
					$shell_call .= " ".$packages;
					$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }});
					
					# None of the HA packages are large os it's not worth trying to monitor the downlaods
					# in real time. As such, we'll make a standard remote call.
					my ($output, $error, $return_code) = $anvil->Remote->call({
						debug       => 3, 
						target      => $use_node_ip,
						password    => $use_password,
						shell_call  => $shell_call,
					});
					$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
						output      => $output,
						error       => $error, 
						return_code => $return_code, 
					}});
					
					if (not $return_code)
					{
						# Success! Copy the files.
						my $failed = $anvil->Storage->rsync({
							debug       => 3, 
							source      => "root\@".$use_node_ip.":".$download_path."/*",
							destination => $local_path."/",
							password    => $use_password, 
						});
						$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { failed => $failed }});
						if (not $failed)
						{
							update_progress($anvil, ++$progress, "message_0187,!!letter!".$letter."!!");
						}
					}
				}
				update_progress($anvil, ++$progress, "message_0188");
			}
			else
			{
				# No nodes found.
				update_progress($anvil, ++$progress, "message_0186");
			}
		}

	}
	
	# 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 $repo_data_path = $repo_path."/repodata";
	my $comps_xml      = $repo_path."/comps.xml";
	my $modules_yaml   = $repo_path."/modules.yaml";
	my $target_comps   = $repo_data_path."/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({exit_code => 6});
	}
	$anvil->Storage->copy_file({
		debug       => 2, 
		source_file => $comps_xml, 
		target_file => $target_comps,
		overwrite   => 1,
	});
	if (not -e $target_comps)
	{
		# Something appears to have happened and it failed to copy.
		print $anvil->Words->string({key => "message_0129", variables => { comps_xml => $comps_xml, target_comps => $target_comps }})."\n";
		update_progress($anvil, 100, "message_0129,!!comps_xml!".$comps_xml."!!,!!target_comps!".$target_comps."!!");
		$anvil->nice_exit({exit_code => 6});
	}
	update_progress($anvil, 85, "");
	
	my ($output, $return_code) = $anvil->System->call({debug => 2, shell_call => $anvil->data->{path}{exe}{createrepo_c}." -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");
	
	### NOTE: This doesn't work for libssh2 yet (haven't figured out how to add it to 'modules.yaml' 
	###       sourced from RHEL 8.1 ISO yet). Once that's fixed, remove 'module_hotfixes=1' from 
	###       Striker->get_local_repo().
	$output                 = "";
	($output, $return_code) = $anvil->System->call({debug => 2, shell_call => $anvil->data->{path}{exe}{modifyrepo_c}." --mdtype=modules ".$modules_yaml." ".$repo_data_path });
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { output => $output, return_code => $return_code }});
	print $anvil->Words->string({key => "message_0159"})."\n";
	update_progress($anvil, 95, "message_0159");
	
	# 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: Download 'alteeve-release.noarch' directly.
	
	### NOTE: If/when we support other archs, removing '.x86_64/.noarch' would cause all available archs 
	###       to be downloaded (including .ix86, which would waste space...). Decide if it's best to 
	###       explicitely declare archs vs using space/bandwidth to just grab all available.
	# 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",
			"annobin.x86_64",
			"anvil-core.noarch",
			"anvil-dr.noarch",
			"anvil-node.noarch",
			"anvil-striker.noarch",
			"apr-util-bdb.x86_64",
			"apr-util-openssl.x86_64",
			"apr-util.x86_64",
			"apr.x86_64",
			"aspell.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",
			"binutils.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",
			"bpg-dejavu-sans-fonts.noarch",
			"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",
			"clutter-gst3.x86_64",
			"clutter-gtk.x86_64",
			"clutter.x86_64",
			"cockpit-bridge.x86_64",
			"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",
			"cpio.x86_64",
			"cpp.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",
			"crypto-policies-scripts.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-md5.x86_64", 
			"cyrus-sasl-plain.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",
			"dbxtool.x86_64", 
			"dconf.x86_64",
			"dejavu-fonts-common.noarch",
			"dejavu-sans-fonts.noarch",
			"dejavu-sans-mono-fonts.noarch",
			"dejavu-serif-fonts.noarch",
			"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.noarch",
			"dnsmasq.x86_64",
			"dosfstools.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",
			"dwz.x86_64", 
		],
		e	=>	[
			"e2fsprogs-libs.x86_64",
			"e2fsprogs.x86_64",
			"edk2-ovmf.noarch",
			"efi-filesystem.noarch",
			"efi-srpm-macros.noarch",
			"efivar-libs.x86_64", 
			"efibootmgr.x86_64",
			"elfutils.x86_64",
			"elfutils-default-yama-scope.noarch",
			"elfutils-libelf.x86_64",
			"elfutils-libelf-devel.x86_64",
			"elfutils-libs.x86_64",
			"emacs-filesystem.noarch",
			"enchant.x86_64",
			"enchant2.x86_64 ",
			"ethtool.x86_64",
			"evolution-data-server-langpacks.noarch",
			"evolution-data-server.x86_64",
			"expat.x86_64",
			"expect.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-redfish.x86_64", 
			"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",
			"flatpak.x86_64",
			"fontconfig.x86_64",
			"fontpackages-filesystem.noarch",
			"freeipmi.x86_64", 
			"freetype.x86_64",
			"fribidi.x86_64",
			"fuse.x86_64",
			"fuse-libs.x86_64",
		],
		g	=>	[
			"gawk.x86_64",
			"GConf2.x86_64",
			"gc.x86_64",
			"gcc.x86_64", 
			"gcr.x86_64",
			"gdb-headless.x86_64",
			"gdbm-libs.x86_64",
			"gdbm.x86_64",
			"gdisk.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",
			"ghc-srpm-macros.noarch",
			"gjs.x86_64",
			"glib-networking.x86_64",
			"glib2.x86_64",
			"glibc-common.x86_64",
			"glibc-devel.x86_64", 
			"glibc-langpack-en.x86_64",
			"glibc.x86_64",
			"glibc-devel.x86_64", 
			"glibc-headers.x86_64", 
			"glusterfs.x86_64",
			"glusterfs-api.x86_64", 
			"glusterfs-cli.x86_64",
			"glusterfs-client-xlators.x86_64", 
			"glusterfs-libs.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-terminal.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",
			"go-srpm-macros.noarch",
			"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-efi-x64.x86_64",
			"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",
			"gtksourceview3.x86_64",
			"gtk-update-icon-cache.x86_64",
			"gtk-vnc2.x86_64",
			"gtk2.x86_64",
			"gtk3.x86_64",
			"guile.x86_64",
			"gvfs.x86_64",
			"gvfs-client.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",
			"isl.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-doc.noarch",
			"kernel-devel.x86_64", 
			"kernel-headers.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-drbd.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",
			"libatasmart.x86_64",
			"libatomic_ops.x86_64",
			"libattr.x86_64",
			"libbabeltrace.x86_64",
			"libbasicobjects.x86_64",
			"libblkid.x86_64",
			"libbytesize.x86_64",
			"libbluray.x86_64",
			"libcacard.x86_64",
			"libcanberra-gtk3.x86_64",
			"libcanberra.x86_64",
			"libcap-ng.x86_64",
			"libcap.x86_64",
			"libcdio.x86_64",
			"libcdio-paranoia.x86_64",
			"libcgroup.x86_64",
			"libblockdev.x86_64",
			"libblockdev-crypto.x86_64", 
			"libblockdev-fs.x86_64",
			"libblockdev-loop.x86_64",
			"libblockdev-mdraid.x86_64",
			"libblockdev-part.x86_64",
			"libblockdev-swap.x86_64",
			"libblockdev-utils.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",
			"libipt.x86_64",
			"libiscsi.x86_64",
			"libjpeg-turbo.x86_64",
			"libkcapi-hmaccalc.x86_64",
			"libkcapi.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",
			"libmpc.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",
			"libpmem.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",
			"libssh-config.noarch",
			"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",
			"libudisks2.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.x86_64", 
			"libvirt-daemon-driver-storage-core.x86_64",
			"libvirt-daemon-driver-storage-disk.x86_64",
			"libvirt-daemon-driver-storage-gluster.x86_64", 
			"libvirt-daemon-driver-storage-iscsi.x86_64",
			"libvirt-daemon-driver-storage-iscsi-direct.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-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",
			"libxcrypt-devel.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",
			"libzstd.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",
			"mailx.x86_64",
			"make.x86_64",
			"man-db.x86_64",
			"mcpp.x86_64",
			"mdadm.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",
			"mokutil.x86_64", 
			"mod_http2.x86_64",
			"ModemManager-glib.x86_64",
			"mozilla-filesystem.x86_64",
			"mozjs52.x86_64",
			"mozjs60.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.x86_64",
			"nss-softokn-freebl.x86_64",
			"nss-softokn.x86_64",
			"nss-sysinit.x86_64",
			"nss-util.x86_64",
			"numactl-libs.x86_64",
			"numad.x86_64",
			"nvme-cli.x86_64", 
		],
		o	=>	[
			"ocaml-srpm-macros.noarch",
			"openblas-srpm-macros.noarch",
			"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",
			"PackageKit-glib.x86_64",
			"PackageKit.x86_64",
			"pam.x86_64",
			"pango.x86_64",
			"parted.x86_64",
			"passwd.x86_64",
			"patch.x86_64",
			"pciutils-libs.x86_64",
			"pciutils.x86_64",
			"pcre.x86_64",
			"pcre2.x86_64",
			"perl-aliased.noarch", 
			"perl-Algorithm-C3.noarch", 
			"perl-Algorithm-Diff.noarch",
			"perl-Authen-SASL.noarch",
			"perl-B-Hooks-EndOfScope.noarch",
			"perl-CGI.noarch",
			"perl-Capture-Tiny.noarch", 
			"perl-Carp.noarch",
			"perl-Class-C3.noarch", 
			"perl-Class-Data-Inheritable.noarch",
			"perl-Class-Method-Modifiers.noarch",
			"perl-Compress-Raw-Bzip2.x86_64",
			"perl-Compress-Raw-Zlib.x86_64",
			"perl-Convert-ASN1.noarch",
			"perl-Curses.x86_64", 
			"perl-Curses-UI.noarch", 
			"perl-DBD-Pg.x86_64",
			"perl-DBI.x86_64",
			"perl-Data-Dump.noarch",
			"perl-Data-Dumper.x86_64",
			"perl-Data-Dumper-Concise.noarch",
			"perl-Data-OptList.noarch",
			"perl-Date-Manip.noarch",
			"perl-Data-Validate-Domain.noarch",
			"perl-Data-Validate-IP.noarch", 
			"perl-Devel-ArgNames.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-Eval-WithLexicals.noarch", 
			"perl-Exception-Class.noarch",
			"perl-Exporter.noarch",
			"perl-Exporter-Declare.noarch",
			"perl-File-BaseDir", 
			"perl-File-DesktopEntry", 
			"perl-File-Listing.noarch",
			"perl-File-MimeInfo.noarch", 
			"perl-File-Path.noarch",
			"perl-File-Temp.noarch",
			"perl-Filter.x86_64",
			"perl-Filter-Simple.noarch", 
			"perl-Future.noarch", 
			"perl-Getopt-Long.noarch",
			"perl-GSSAPI.x86_64",
			"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-Import-Into.noarch", 
			"perl-IO.x86_64",
			"perl-IO-Compress.noarch",
			"perl-IO-HTML.noarch",
			"perl-IO-Socket-IP.noarch",
			"perl-IO-Socket-SSL.noarch",
			"perl-IO-Tty.x86_64", 
			"perl-IPC-SysV.x86_64",
			"perl-JSON.noarch",
			"perl-JSON-PP.noarch",
			"perl-LDAP.noarch", 
			"perl-LWP-MediaTypes.noarch",
			"perl-Log-Contextual.noarch",
			"perl-Log-Dispatch-FileRotate.noarch",
			"perl-Log-Dispatch.noarch",
			"perl-Log-Journald.x86_64",
			"perl-Log-Log4perl.noarch",
			"perl-Meta-Builder.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-Moo.noarch",
			"perl-Mozilla-CA.noarch",
			"perl-NTLM.noarch",
			"perl-Net-Domain-TLD.noarch",
			"perl-Net-HTTP.noarch",
			"perl-Net-Netmask.noarch",
			"perl-Net-OpenSSH.noarch", 
			"perl-Net-SMTP-SSL.noarch",
			"perl-Net-SSH2.x86_64",
			"perl-Net-SSLeay.x86_64",
			"perl-NetAddr-IP.x86_64",
			"perl-Object-Remote.noarch", 
			"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-Mail-RFC822-Address.noarch",
			"perl-Role-Tiny.noarch",
			"perl-Scalar-List-Utils.x86_64",
			"perl-SelfLoader.noarch", 
			"perl-Socket.x86_64",
			"perl-Socket6.x86_64",
			"perl-Specio.noarch",
			"perl-srpm-macros.noarch",
			"perl-Storable.x86_64",
			"perl-strictures.noarch", 
			"perl-String-ShellQuote.noarch",
			"perl-Sub-Exporter-Progressive.noarch",
			"perl-Sub-Exporter.noarch",
			"perl-Sub-Identify.x86_64",
			"perl-Sub-Install.noarch",
			"perl-Sub-Quote.noarch", 
			"perl-Sys-Syslog.x86_64",
			"perl-Sys-Virt.x86_64",
			"perl-Term-ANSIColor.noarch",
			"perl-Term-Cap.noarch",
			"perl-Text-Balanced.noarch",
			"perl-TermReadKey.x86_64",
			"perl-Test-Simple.noarch",
			"perl-Text-Diff.noarch",
			"perl-Text-ParseWords.noarch",
			"perl-Text-Soundex.x86_64",
			"perl-Text-Tabs+Wrap.noarch",
			"perl-Text-Unidecode.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-LibXML.x86_64", 
			"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",
			"python-srpm-macros.noarch",
			"python3-asn1crypto.noarch",
			"python3-audit.x86_64",
			"python3-argcomplete.noarch",
			"python3-bind.noarch",
			"python3-cairo.x86_64",
			"python3-cffi.x86_64",
			"python3-chardet.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-nftables.x86_64",
			"python3-perf.x86_64",
			"python3-pexpect.noarch",
			"python3-pip.noarch",
			"python3-pip-wheel.noarch",
			"python3-ply.noarch",
			"python3-policycoreutils.noarch",
			"python3-ptyprocess.noarch",
			"python3-pyOpenSSL.noarch",
			"python3-pycparser.noarch",
			"python3-pycurl.x86_64",
			"python3-pyparsing.noarch", 
			"python3-pysocks.noarch",
			"python3-pyudev.noarch",
			"python3-pyyaml.x86_64",
			"python3-requests.noarch",
			"python3-rpm.x86_64",
			"python3-rpm-macros.noarch",
			"python3-schedutils.x86_64",
			"python3-setools.x86_64",
			"python3-setuptools.noarch",
			"python3-setuptools-wheel.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",
			"python36.x86_64",
		],
		'q'	=>	[
			"qemu-guest-agent.x86_64",
			"qemu-img.x86_64",
			"qemu-kvm.x86_64",
			"qemu-kvm-block-curl.x86_64",
			"qemu-kvm-block-gluster.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",
			"qt5-srpm-macros.noarch",
			"quota-nls.noarch",
			"quota.x86_64",
		],
		r	=>	[
			"radvd.x86_64",
			"rdma-core.x86_64",
			"readline.x86_64",
			"redhat-rpm-config.noarch",
			"rest.x86_64",
			"rootfiles.noarch",
			"rpcbind.x86_64",
			"rpm-build.x86_64",
			"rpm-build-libs.x86_64",
			"rpmdevtools.noarch",
			"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",
			"rust-srpm-macros.noarch",
		],
		's'	=>	[
			"samba-client-libs.x86_64",
			"samba-common-libs.x86_64",
			"samba-common.noarch",
			"sbc.x86_64",
			"sbd.x86_64",
			"screen.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",
			"shim-x64.x86_64", 
			"slang.x86_64",
			"smartmontools.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	=>	[
			"tar.x86_64",
			"tcl.x86_64", 
			"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	=>	[
			"udisks2.x86_64",
			"unbound-libs.x86_64",
			"unzip.x86_64",
			"upower.x86_64",
			"usbredir.x86_64",
			"usbutils.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-top.x86_64",
			"virt-what.x86_64",
			"volume_key-libs.x86_64",
			"vte-profile.x86_64",
			"vte291.x86_64",
		],
		w	=>	[
			"webkit2gtk3.x86_64",
			"webkit2gtk3-jsc.x86_64",
			"webrtc-audio-processing.x86_64",
			"wget.x86_64", 
			"which.x86_64",
			"woff2.x86_64",
			"wpa_supplicant.x86_64",
		],
		x	=>	[
			"xcb-util.x86_64",
			"xdg-desktop-portal.x86_64",
			"xdg-desktop-portal-gtk.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",
			"yum-utils.noarch",
		],
		z	=>	[
			"zenity.x86_64",
			"zlib.x86_64",
			"zlib-devel.x86_64",
			"zstd.x86_64",
		],
	};

	# These packages can't be downloaded on RHEL Striker dashboads as they usually are not entitled to 
	$anvil->data->{ha_packages} = {
		c	=>	[
			"clufter-bin.x86_64",
			"clufter-common.noarch", 
			"corosync.x86_64",
			"corosynclib.x86_64",
		],
		l	=>	[
			"libknet1.x86_64",
			"libknet1-compress-bzip2-plugin.x86_64",
			"libknet1-compress-lz4-plugin.x86_64",
			"libknet1-compress-lzma-plugin.x86_64",
			"libknet1-compress-lzo2-plugin.x86_64",
			"libknet1-compress-plugins-all.x86_64",
			"libknet1-compress-zlib-plugin.x86_64",
			"libknet1-crypto-nss-plugin.x86_64", 
			"libknet1-crypto-openssl-plugin.x86_64",
			"libknet1-crypto-plugins-all.x86_64",
			"libknet1-plugins-all.x86_64",
			"libnozzle1.x86_64",
		],
		p	=>	[
			"pacemaker.x86_64",
			"pacemaker-cli.x86_64",
			"pacemaker-cluster-libs.x86_64",
			"pacemaker-libs.x86_64",
			"pacemaker-schemas.noarch",
			"pcs.x86_64",
			"python3-clufter.noarch",
		],
		r	=>	[
			"resource-agents.x86_64",
		],
	};

	
	my ($os_type, $os_arch) = $anvil->Get->os_type();
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { 
		os_type => $os_type,
		os_arch => $os_arch, 
	}});
	if ($os_type eq "rhel8")
	{
		push @{$anvil->data->{packages}{c}}, "cockpit-dashboard.noarch";
		push @{$anvil->data->{packages}{r}}, "redhat-backgrounds.noarch";
		push @{$anvil->data->{packages}{r}}, "redhat-indexhtml.noarch";
		push @{$anvil->data->{packages}{r}}, "redhat-logos-httpd.noarch";
		push @{$anvil->data->{packages}{r}}, "redhat-logos.x86_64";
		push @{$anvil->data->{packages}{r}}, "redhat-release.x86_64";
	}
	elsif ($os_type eq "centos-stream8")
	{
		push @{$anvil->data->{packages}{c}}, "centos-backgrounds.noarch";
		push @{$anvil->data->{packages}{c}}, "centos-gpg-keys.noarch";
		push @{$anvil->data->{packages}{c}}, "centos-indexhtml.noarch";
		push @{$anvil->data->{packages}{c}}, "centos-logos-httpd.noarch";
		push @{$anvil->data->{packages}{c}}, "centos-logos.x86_64";
		push @{$anvil->data->{packages}{c}}, "centos-stream-release.noarch";
	}
	elsif ($os_type eq "centos8")
	{
		push @{$anvil->data->{packages}{c}}, "centos-backgrounds.noarch";
		push @{$anvil->data->{packages}{c}}, "centos-gpg-keys.noarch";
		push @{$anvil->data->{packages}{c}}, "centos-indexhtml.noarch";
		push @{$anvil->data->{packages}{c}}, "centos-logos-httpd.noarch";
		push @{$anvil->data->{packages}{c}}, "centos-logos.x86_64";
		push @{$anvil->data->{packages}{c}}, "centos-linux-release.noarch";
		push @{$anvil->data->{packages}{c}}, "cockpit-dashboard.noarch";
	}
	
	# Now see if the base OS has changed and, if so, rename directories
	my $rhel8_test_source          = $anvil->data->{path}{directories}{html}."/rhel8";
	my $centos8_test_source        = $anvil->data->{path}{directories}{html}."/centos8";
	my $centos_stream8_test_source = $anvil->data->{path}{directories}{html}."/centos-stream8";
	$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { 
		rhel8_test_source          => $rhel8_test_source,
		centos8_test_source        => $centos8_test_source,
		centos_stream8_test_source => $centos_stream8_test_source, 
	}});
	
	my $source = "";
	my $target = "";
	if ((-e $rhel8_test_source) && ($os_type ne "rhel8"))
	{
		# This isn't RHEL 8, we need to rename the directories
		$source = "rhel8";
		$target = $os_type;
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
			source => $source,
			target => $target, 
		}});
	}
	elsif ((-e $centos8_test_source) && ($os_type ne "centos8"))
	{
		# This isn't CentOS 8, we need to rename the directories
		$source = "centos8";
		$target = $os_type;
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
			source => $source,
			target => $target, 
		}});
	}
	elsif ((-e $centos_stream8_test_source) && ($os_type ne "centos-stream8"))
	{
		# This isn't CentOS Stream 8, rename the directories
		$source = "centos-stream8";
		$target = $os_type;
		$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
			source => $source,
			target => $target, 
		}});
	}
	
	# If 'source' is set, we need to rename.
	if ($source)
	{
		# While we're here, we will need to rename /var/www/html/rhel8 to /var/www/html/centos8, as
		# 'centos8' and '/var/lib/tftpboot/rhel8' as 'centos8'.
		foreach my $directory ("html", "tftpboot")
		{
			my $source_directory = $anvil->data->{path}{directories}{$directory}."/".$source;
			my $target_directory = $anvil->data->{path}{directories}{$directory}."/".$target;
			$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { 
				source_directory => $source_directory,
				target_directory => $target_directory,
			}});
			
			# We check to see if the target directory already exists. If it does, the user may be
			# supporting multiple distros and we don't want to clobber that
			if ((-e $source_directory) && (not -e $target_directory))
			{
				my $shell_call = $anvil->data->{path}{exe}{mv}." ".$source_directory." ".$target_directory;
				$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }});
				
				my ($handle, $return_code) = $anvil->System->call({debug => 2, shell_call => $shell_call});
				$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { handle => $handle, return_code => $return_code }});
				if (-e $target_directory)
				{
					# Success!
					$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, secure => 0, key => "log_0475", variables => {
						source => $source_directory,
						target => $target_directory,
					}});
				}
				else
				{
					# Failed :(
					$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, key => "error_0116", variables => {
						source => $source_directory,
						target => $target_directory,
					}});
					$anvil->nice_exit({exit_code => 12});
				}
			}
		}
	}
	
	update_progress($anvil, 5, "log_0241");
	
	return(0);
}
