#!/usr/bin/perl -w

# Copyright (c) TUXEDO Computers GmbH <tux@tuxedocomputers.com>
# This file is part of TUXEDO Tomte.
#
# TUXEDO Tomte is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# TUXEDO Tomte is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with TUXEDO Tomte. If not, see <https://www.gnu.org/licenses/>.

package Tomte;

use strict qw(vars subs);
use warnings;
use 5.010;

# do not edit manually as it will be overwritten while packaging
our $VERSION = '2.56.0
';

# overrides warnings so that they get logged
local $SIG{__WARN__} = sub {
	my $warning = shift;
	chomp($warning);
	printLog($warning, "L0", '[WARN]');
};

use Tomte::Presets qw( devices initialModuleSettings supportedOS essentialRepos otherRepos kernels lockFiles postConfPrograms distributionType );
use Tomte::NvidiaList qw( nvidiaPackage nvidiaProductID );

use Storable qw(dclone);
use autovivification;
no autovivification;

# for file locking
use Fcntl qw(:DEFAULT :flock :seek :Fcompat);
use File::FcntlLock;

use File::Copy;
use File::Find;
use Cwd 'realpath';

use Time::HiRes qw(usleep);
use Dpkg::Version;
use JSON;
use Config::Tiny;

# for Translations
use Locale::TextDomain ('tomte', '/usr/share/locale');
use Locale::TextDomain qw(tomte);

use File::Slurp qw(append_file write_file read_file edit_file);

# for debugging
use Data::Dumper;
# logLevel:
# 0 = normal
# 1 = some debug
# 2 = lots of debug
my $logLevel = 0;
$logLevel = $ENV{LOGLEVEL} // $logLevel;

use Readonly;

# for testing
Readonly my $TEST_ALL_MODULES => 0;

# for console language and locale
my $consoleLanguage = 'LANG=C;LANGUAGE=C;';
local $ENV{LC_MESSAGES} = 'C';
local $ENV{DEBIAN_FRONTEND} = 'noninteractive';

my $logDir = '/var/log/tomte/';
my $logFile = $logDir.'tomte.log';
my $LOGFILE;
printLog("##############################################################################", 'L0', '[INFO]');
my $faiLogDir = '/var/log/';
my $faiLogfile = $faiLogDir.'fai-tomte.log';
my $FAILOGFILE;
my $faiLogfileExists = 0;	# 0 = no, 1 = exists

checkAndSetLocale();

###############################################################################
# Argument values given to script
#
my %argValue = (
	command => q{},
	module => q{},
);

printLog("arguments: @ARGV", 'L2', '[INFO]');
if (scalar(@ARGV) > 0) {
	$argValue{command} = $ARGV[0];
}
if (scalar(@ARGV) > 1) {
	$argValue{module} = $ARGV[1];
}

# make sure this is the only running instance
use Fcntl ':flock';

my $ME;
open ($ME, '<', $0);
if (!(flock $ME, LOCK_EX|LOCK_NB)) {
	dieSingleton();
}


my $grubFile = '/etc/default/grub';
my $noLogFile = 1;      # 0 = logFile ok, 1 = no logFile
my $noConfigFile = 1;   # 0 = configfile ok, 1 = no configfile
my $configDir = '/etc/tomte/';
my $oldConfigFile = $configDir.'tomte.cfg';
my $configFile = $configDir.'modules.cfg';
my $configIniFile = $configDir.'config.ini';
my $configIniValues = Config::Tiny->new;
readConfigIniValues();
if (defined($configIniValues->{initvalues}->{loglevel})) {
	$logLevel = $configIniValues->{initvalues}->{loglevel};
}

# overwrite loglevel if set as environment variable
$logLevel = $ENV{LOGLEVEL} // $logLevel;
my $tomteFirstInstallFile = '/tmp/tomteFirstInstall';

my %modeFile = (
	DONT_CONFIGURE => $configDir.'DONT_CONFIGURE',
	UPDATES_ONLY => $configDir.'UPDATES_ONLY',
	AUTOMATIC => $configDir.'AUTOMATIC',
);

my $runningAsRoot = 0;
my $mode = 'AUTOMATIC'; #default mode
my $shareDir = '/usr/share/tuxedo-tomte/';
my $chassisType = 0;
my $distribution;
my $distributionVersionID;
my $distributionVersion;
my $completeDistVersion;
my $versionCodename = q{};
my $distributionType = q{};
my $FAI = 0;
if ($argValue{command} eq 'FAI') {
	$FAI = 1;
}
my $LiveISO = 0;
my $noNetwork = 0;
my $packagekitState = q{};
my $packagekitOriginalState = getSystemdState('packagekit');
my $packagekitOriginalEnabled = getSystemdEnabled('packagekit');
$packagekitState = $packagekitOriginalState;
my $triesFile = '/tmp/tomteTries';
my $needsRestartFile = '/tmp/needs_restart';
my $longInstall = 0;
my $restartSystem = 0;
my $startLaterAgain = 0;
my @availableDebianPackages;
my %packageHash;
my $plasma6Installed = 0;

# whether an eGPU might be used on this system
# '0': no eGPU
# 'open': eGPU with open driver
# 'closed': eGPU with closed driver
my $eGPUPossible = '0';

Readonly my $SYSTEMD_DELAY => 30;
Readonly my $TRIES_LIMIT => 12;
Readonly my $NETWORK_CHECKS => 15;
Readonly my $NEG_ONE => -1;
Readonly my $FILE_MODE_SIXFOURFOUR => '0644';
Readonly my $FILE_MODE_SEVENZEROZERO => '0700';
Readonly my $CONFIGURATION_VALUE_3 => 3;
Readonly my $CONFIGURATION_VALUE_2 => 2;
Readonly my $RETVAL_CONVERTER => 256;
Readonly my $MAX_KERNEL_PACKAGES_REMOVE => 20;
Readonly my $MAX_HEADERS_TO_ANALYZE => 3;
Readonly my $MAX_PACKAGE_NAME => 50;
Readonly my $MAX_TO_DELETE_KERNEL => 3;
Readonly my $MAX_OLDEST_HEADER_NAME => 50;
Readonly my $MAX_FILESIZE_KERNEL_DELETE => 1_000_000;

# get current kernel version
# if in FAI it should check which is the installed
# kernel package version on target
my $kernelRelease;

if ($FAI) {
	printLog("starting Tomte version $VERSION in FAI", 'TL0', '[DEBUG]');
	my $searchCmd = 'dpkg-query -f \'${db:Status-Abbrev} ${Package} ${Version}\n\' -W \'linux-image-*\'';
	my $dpkgKernel = `$searchCmd`;
	my %kernelHash = $dpkgKernel =~ /ii (.*) (.*)/g;
	my $hashSize = keys %kernelHash;
	if ($hashSize != 0) {
		my $firstkey = (sort(keys %kernelHash))[0];
		$kernelRelease = $kernelHash{$firstkey};
	}
} else {
	$kernelRelease = `uname -r`;
}
$kernelRelease =~ s/[\n\r]//gs;
printLog("detected kernel version: $kernelRelease", 'L2', '[DEBUG]');


my $lspciOutput = `lspci -vn 2>/dev/null`;
my $lsusbOutput = `lsusb 2>/dev/null`;
my $boardnameFilename = "/sys/class/dmi/id/board_name";
my $boardname = q{};
$boardname = readFirstLineOfFile($boardnameFilename);

my $boardvendorFilename = "/sys/class/dmi/id/board_vendor";
my $boardvendor = q{};;
$boardvendor = readFirstLineOfFile($boardvendorFilename);

my $sysvendorFilename = "/sys/class/dmi/id/sys_vendor";
my $sysvendor = q{};
$sysvendor = readFirstLineOfFile($sysvendorFilename);

# for desktop messages
my $sessionID = readFirstLineOfFile('/proc/sys/kernel/random/boot_id');
if ($sessionID =~ /(.{7})/sm) {
	$sessionID = hex($1);
	printLog("sessionID: $sessionID", 'TL2', '[DEBUG]');
}


my %devicesList = devices();
my %presetModules = initialModuleSettings();
my %supportedOSList = supportedOS();
my %distributionTypeList = distributionType();
my %essentialReposList = essentialRepos();
my %otherReposList = otherRepos();
my %kernelsList = kernels();
my %lockFilesList = lockFiles();
my %postConfProgramsList = postConfPrograms();

# hash to load the configfile
# is a copy from presetModules to exclude undefined values
my %localConfModules = %{ dclone(\%presetModules) };


# empty hash, will be filled with successfully installed modules
# as hashes: {modulename}{status}
# where status should be "success" or "failed"
#
my %configuredModules;




###############################################################################
# simple print out of all supported devices and fixes
#
sub printTuxedoDevices {
	foreach my $ID (keys %devicesList) {
		if ((defined $devicesList{$ID}) && ($devicesList{$ID} ne q{}) && (exists $devicesList{$ID})) {
			printIfExistsNotUndefinedNotEmpty("$ID", "model");
			printIfExistsNotUndefinedNotEmpty("$ID", "board_name");
			printIfExistsNotUndefinedNotEmpty("$ID", "fix");
			printIfExistsNotUndefinedNotEmpty("$ID", "flavour");
			printIfExistsNotUndefinedNotEmpty("$ID", "pci_id");
			printIfExistsNotUndefinedNotEmpty("$ID", "cpu");
			printIfExistsNotUndefinedNotEmpty("$ID", "kernel");
			printIfExistsNotUndefinedNotEmpty("$ID", "display");
			printIfExistsNotUndefinedNotEmpty("$ID", "usb_device");
		}
	}
	return;
}



sub printIfExistsNotUndefinedNotEmpty {
	my $ID = shift;
	my $type = shift;
	if ((defined $devicesList{$ID}{$type}) && ($devicesList{$ID}{$type} ne q{}) && (exists $devicesList{$ID}{$type})) {
		if (ref($devicesList{$ID}{$type}) eq 'ARRAY') {
			if ($devicesList{$ID}{$type}->[0] ne q{}) {
				my $joinedArray = join q{ }, @{$devicesList{$ID}{$type}};
				print "$type: $joinedArray\n";
			}
		} else {
			print "$type: $devicesList{$ID}{$type}\n";
		}
	}
	return;
}



# for the localization
foreach my $module (keys %presetModules) {
	$presetModules{$module}{'description'} = __($module . "_description");
}



###############################################################################
# die if another instance is running
#
sub dieSingleton {
	print "Another instance is already running, please wait till it is finished\n";
	exit (1);
}


sub checkAndSetLocale {
    my $defaultLocale = "en";

    # Check if either LANG or LANGUAGE is set
    if (defined $ENV{'LANG'} || defined $ENV{'LANGUAGE'}) {
		# allow manual override of LANGUAGE environment variable
        my $lang = $ENV{'LANGUAGE'} || $ENV{'LANG'} || $defaultLocale;

        # Split the language code from the full locale using dot as the delimiter
        # a $lang could be de, de_DE or de_DE.UTF8
        my $languageCode = (split /\./sm, $lang)[0] // $lang;

		# $languageCode is now de or de_DE
        if ($languageCode) {
			my ($foundFolder, $localePath) = getMatchingLocaleForLang($languageCode);

            # Check if the directory exists
            if ($foundFolder ne q{}) {
				$ENV{'LANG'} = $ENV{'LANGUAGE'} = $foundFolder;
                printLog("Found Tomte locale file in: $localePath", "L1", '[INFO]');
            } else {
				if(length $languageCode == 2){
					# already searched possible endings and didn't found
					printLog("Tomte locale file doesn't exist for requested language $lang. Setting to default: $defaultLocale", "L1", '[INFO]');
					$ENV{'LANG'} = $ENV{'LANGUAGE'} = $defaultLocale;
				} else {
					# reduce de_DE to de to search broader
					$languageCode = (split /_/sm, $languageCode)[0];
					my ($foundFolder2, $localePath2) = getMatchingLocaleForLang($languageCode);

					# Check if the directory exists
					if ($foundFolder2 ne q{}) {
						if($languageCode ne $foundFolder2){
							$ENV{'LANG'} = $ENV{'LANGUAGE'} = $foundFolder2;
						}

						printLog("Found Tomte locale file in: $localePath2", "L1", '[INFO]');
					} else {
						printLog("Tomte locale file doesn't exist for requested language $lang. Setting to default: $defaultLocale", "L1", '[INFO]');
						$ENV{'LANG'} = $ENV{'LANGUAGE'} = $defaultLocale;
					}
				}
            }
        } else {
            printLog("Invalid LANG or LANGUAGE format: '$lang'", "L1", '[WARN]');
            printLog("Setting both to default: $defaultLocale", "L1", '[INFO]');
            $ENV{'LANG'} = $ENV{'LANGUAGE'} = $defaultLocale;
        }
    } else {
        # Neither LANG nor LANGUAGE is set. Setting both to default
        printLog("Neither LANG nor LANGUAGE is set. Setting both to default: $defaultLocale", "L1", '[WARN]');
        $ENV{'LANG'} = $ENV{'LANGUAGE'} = $defaultLocale;
    }
	return;
}

sub getMatchingLocaleForLang {
	my $lang = shift;
	printLog("Trying matching language for: $lang", "L1", '[INFO]');
	# Directory where the existing translations can be found
	my $localeBasePath = "/usr/share/locale";

	my $foundLanguage = q{};

	my @files = glob($localeBasePath . q{/} . $lang . "*/LC_MESSAGES/tomte.mo");

	foreach my $path (@files) {
		$path =~ s/\/LC_MESSAGES\/tomte.mo//ism;
		$foundLanguage = $path =~ s/$localeBasePath\///irsm;
		printLog("Found valid Tomte locale file in $localeBasePath/$foundLanguage", "L1", '[INFO]');
		last;
	}
	if ($foundLanguage eq q{}) {
		printLog("Found no valid Tomte locale file in $localeBasePath/$lang", "L1", '[ERROR]');
	}

	return ($foundLanguage, "$localeBasePath/$foundLanguage");
}


###############################################################################
# checks if a given file is locked
# returns 0 if it is not locked
# returns 1 if it is locked
# returns -1 if something went wrong
#
sub isLocked {
	my $key = shift;
	my $filename = $lockFilesList{$key}{filename};
	my $FH;
	my $fs;
	my $num;
	if (! -e $filename) {
		printLog("Locking: $filename does not exist", 'L2', '[ERROR]');
		return (0);
	}
	if (sysopen($FH, $filename, O_RDWR | O_CREAT)) {
		$FH->autoflush(1);
		printLog("Locking: checking lock: $filename", 'L2', '[DEBUG]');
		$fs = File::FcntlLock -> new();
		$fs->l_type( F_WRLCK );
		$fs->l_whence( SEEK_SET );
		$fs->l_start( 0 );
		if ($fs->lock( $FH, F_GETLK )) {
			if (!($fs->l_type() == F_UNLCK)) {
				printLog("Locking: => is locked", 'L2', '[DEBUG]');
				return (1);
			} else {
				printLog("Locking: => is not locked", 'L2', '[DEBUG]');
				return (0);
			}
		} else {
			printLog("Locking: failed to get lock information on: $filename error: $fs->error", 'L0', '[ERROR]');
			return ($NEG_ONE);
		}
	} else {
		printLog("Locking: failed to open: $filename", 'L0', '[ERROR]');
		return ($NEG_ONE);
	}
}



###############################################################################
# checks if all required files for package management are unlocked
# only use if it is not required to lock the files simultaneus or race
# conditions will rise!!
# returns 1 if any of the required files is locked
# returns 0 if all of the required files are unlocked
# returns -1 if something went wrong
#
sub isPMlocked {
	my $result;
	foreach my $key (keys %lockFilesList) {
		$result = isLocked($key);
		if ($result == 1) {
			printLog("Locking: one or more files are locked", 'L2', '[DEBUG]');
			return (1);
		}
		if ($result == $NEG_ONE) {
			printLog("Locking: something went wrong while checking for file locks", 'L0', '[WARN]');
			return ($NEG_ONE);
		}
	}
	printLog("Locking: all files are unlocked", 'L2', '[DEBUG]');
	return (0);
}



###############################################################################
# locks a file acquireLock(filename) if the file was not locked by another
# process already
# returns 1 if file could be locked
# returns 0 if file could not be locked
#
sub acquireLock {
	my $key = shift;
	my $filename = $lockFilesList{$key}{filename};
	my $FH;
	my $fs;
	my $num;
	if (sysopen($FH, $filename, O_RDWR | O_CREAT)) {
		$FH->autoflush(1);
		printLog("Locking: acquiring lock: $filename", 'L2', '[DEBUG]');
		$fs = File::FcntlLock -> new();
		$fs->l_type( F_WRLCK );
		$fs->l_whence( SEEK_SET );
		$fs->l_start( 0 );
		if ($fs->lock( $FH, F_SETLK )) {
			printLog("Locking: locking successfull", 'L2', '[DEBUG]');
			$num = <$FH> || 0;
			$lockFilesList{$key}{'FH'} = $FH;
			$lockFilesList{$key}{'number'} = $num;
			$lockFilesList{$key}{'locked'} = 1;
			return (1);
		} else {
			$lockFilesList{$key}{'locked'} = 0;
			printLog("Locking: failed to get write lock: $filename: $fs->error", 'L0', '[WARN]');
			return (0);
		}
	} else {
		printLog("Locking: failed to open: $filename: $!", 'L0', '[ERROR]');
		return (0);
	}
}



###############################################################################
# releases the lock on ONE file
# releaseLock(filename)
# returns 1 if lock could be released
# returns 0 if something went wrong
#
sub releaseLock {
	my $key = shift;
	my $filename = $lockFilesList{$key}{filename};
	my $FH = $lockFilesList{$key}{FH};
	my $num = $lockFilesList{$key}{number};
	my $fs;
	if (seek($FH, 0, SEEK_SET)) {
		if (print $FH "$num\n") {
			if (truncate($FH, tell($FH))) {
				$fs = File::FcntlLock -> new();
				$fs->l_type(F_UNLCK);
				if ($fs->lock( $FH, F_SETLK )) {
					if (close($FH)) {
						$lockFilesList{$key}{'locked'} = 0;
						printLog("Locking: lock released on $filename", 'L2', '[DEBUG]');
						return (1);
					} else {
						printLog("Locking: close failed!: $filename: $!", 'L2', '[ERROR]');
						return (0);
					}
				} else {
					printLog("Locking: unlock failed!: $filename: $fs->error", 'L2', '[ERROR]');
					return (0);
				}
			} else {
				printLog("Locking: truncate failed!: $filename: $!", 'L2', '[ERROR]');
				return (0);
			}
		} else {
			printLog("Locking: write failed!: $filename: $!", 'L2', '[ERROR]');
			return (0);
		}
	} else {
		printLog("Locking: seek failed!: $filename: $!", 'L2', '[ERROR]');
		return (0);
	}
}



###############################################################################
# locks all packagemanagement files
# it unlocks all files if some were locked by another process
# returns 1 if all files could be locked
# returns 0 if some could not be locked
#
sub lockPM {
	my $lockFilesListHashSize = keys %lockFilesList;
	my $processedLockFiles = 0;

	# in FAI it should never be necessary to lock any files
	if ($FAI) {
		return (1);
	}
	printLog("Locking: trying to lock all files for Tomte", 'L2', '[DEBUG]');
	foreach my $key (keys %lockFilesList) {
		# stop if can't get lock
		last if (acquireLock($key) != 1);
		$processedLockFiles++;
	}
	if ($processedLockFiles < $lockFilesListHashSize) {
		printLog("Locking: !!! some files are already locked! reverting all locks", 'L0', '[DEBUG]');
		unlockPM('Locking: some files locked -> reverting');
		return (0);
	}
	printLog("Locking: ### all files locked for Tomte", 'L3', '[DEBUG]');
	return (1);
}



###############################################################################
# unlocks all files from package management
# only unlocks locks owned by itself!!
# a reason for unlocking should be given as string, for logging purposes
# unlockPM('reason')
# returns 1 if unlocked
# returns 0 if some could not be unlocked
#
sub unlockPM {
	my $reason = shift;
	my $hashSize = keys %lockFilesList;
	my $countReleased = 0;
	my $countToRelease = 0;
	printLog("Locking: attempting to unlock all files for >$reason< ...", 'L2', '[DEBUG]');
	foreach my $key (keys %lockFilesList) {
		printLog("Locking: is $key locked?: $lockFilesList{$key}{locked}", 'L2', '[DEBUG]');
		if ($lockFilesList{$key}{locked}) {
			printLog("Locking: try to unlock", 'L2', '[DEBUG]');
			$countToRelease++;
			$countReleased += releaseLock($key);
			printLog("Locking: released $countReleased from $countToRelease", 'L2', '[DEBUG]');
		}
	}
	if ($countReleased == $countToRelease) {
		printLog("Locking: OOO successfully released all locks for >$reason<", 'L2', '[DEBUG]');
		return (1);
	} else {
		printLog("Locking: XXX unlock failed: $countReleased locks released out of $countToRelease for >$reason<", 'L0', '[ERROR]');
	}
	return (0);
}



###############################################################################
# reads a file and returns a line
# chomps the line
# returns undef if file can't be opened
#
sub readFirstLineOfFile {
	my @lines = read_file(shift, {chomp => 1, 'err_mode' => 'carp'});
	return $lines[0];
}





###############################################################################
# counts the lines in a given string
#
sub countLinesInString {
	my $myString = shift;
	return 0 if( (!defined $myString) or $myString eq q{});
	my $lastchar = substr $myString, $NEG_ONE,1;
	my $numlines = () = $myString =~ /\n/gsm;
	# was last line a whole line with a "\n"?;
	return $numlines + ($lastchar ne "\n");
}




###############################################################################
# find OS name and version and put them into $distribution and
# $distributionVersionID
# e.g.:
# $distribution = 'Ubuntu'
# $distributionVersionID = '24.04'
# $completeDistVersion = 'Ubuntu 24.04'
#
sub readOSData {
	my $osReleaseFile = '/etc/os-release';
	my @osReleaseLines;

	@osReleaseLines = read_file($osReleaseFile, 'err_mode' => 'carp');

	if ( !@osReleaseLines ) {
		print "Can't determine the operating system\n";
		exit (0);
	}
	foreach my $line ( @osReleaseLines ) {
		# removing leading/trailing whitespaces
		$line =~ s/^\s+|\s+$//g;
		if ( $line =~ /^NAME=/sm ) {
			# e.g. Ubuntu
			$line =~ /NAME=\"(.*)\"/sm;
			$distribution = $1;
		}
		if ( $line =~ /^VERSION_ID=/sm ) {
			# e.g. 24.04
			$line =~ /VERSION_ID=\"(.*)\"/sm;
			$distributionVersionID = $1;
		}
		if ( $line =~ /^VERSION_CODENAME=/ ) {
			# e.g. noble|trixie
			$line =~ /VERSION_CODENAME=(.*)/;
			$versionCodename = $1;
			$distributionType = $distributionTypeList{$versionCodename};
			printLog("found VERSION_CODENAME: $versionCodename", 'L2', '[DEBUG]');
			printLog("setting distributionType to: $distributionType", 'L2', '[DEBUG]');
		}
		if ( $line =~ /^VERSION=/sxm ) {
			# e.g. 24.04.4 LTS (Jammy Jellyfish)
			$line =~ /VERSION=\"(.*)\"/sm;
			$distributionVersion = $1;
		}
	}
	$completeDistVersion = $distribution.q{ }.$distributionVersionID;
	return;
}




###############################################################################
# returns the brand of the CPU (AMD or Intel)
# returns undef if no file was found
#
sub cpuBrand {
	my $infos;
	my $cpuinfo = '/proc/cpuinfo';
	my $FH;
	if ( (-e $cpuinfo) && open($FH, '<', '/proc/cpuinfo') ) {
		local $/;
		$infos = <$FH>;
		if (! close $FH) {
			printLog("error closing $cpuinfo", 'L0', '[ERROR]');
		}
		if ($infos =~ /vendor_id\s*:\s*AuthenticAMD/sm) {
			return ('AMD');
		}
	} else {
		printLog("file $cpuinfo was not found", 'L0');
	}
	return ('Intel');
}



###############################################################################
# checks the list of supported distributions and versions and returns
# 1 if supported
# 0 if not
#
sub isOSSupported {
	if (exists $supportedOSList{ $distribution }) {
		if ( grep(/^$distributionVersionID$/sm, @{ $supportedOSList{ $distribution } } ) ) {
			return (1);
		}
		printLog("The version $distributionVersionID of $distribution is not supported.", 'TL0', '[FATAL]');
		messageDesktop(__("This OS version is not supported"),
			__x("This version {version} of {distribution} is not supported.",
				("version" => $distributionVersionID, "distribution" => $distribution)),
			2, 'dialog-warning');
		return (0);
	}
	printLog("This OS is not supported", 'TL0', '[FATAL]');
	printLog("Please check on our website https://www.tuxedocomputers.com which Systems are supported by Tomte", 'TL0', '[FATAL]');
	printLog("distribution: >$distribution<", 'TL1', '[INFO]');
	printLog("version: >$distributionVersionID<", 'TL1', '[INFO]');
	return (0);
}


###############################################################################
# check basic requirements the system should fullfill and
# install missing parts
#
sub prerequisites {
	my @modulesList;
	printLog("starting prerequisites", 'L1', '[INFO]');

	# temporary workaround for no no no bug
	# remove no's from /etc/default/grub
	my $secCtr = 20;
	while (removeKernelParameters('', 1, 'no') && ($secCtr > 0)) {
		$secCtr -= 1;
		printLog("removed 'no' from grub", 'L2', '[DEBUG]');
	}

	foreach my $module (sort keys %presetModules) {
		# basically the required repos
		if (($presetModules{$module}{order} eq 'first') &&
			($presetModules{$module}{blocked} eq 'no')) {
			reconfigureSingleModule($presetModules{$module}{name});
			push (@modulesList, $module);
		}
	}

	# wait max. $NETWORK_CHECKS sec. as it takes some time to get network
	if (checkNetwork( $NETWORK_CHECKS, 1 )) {
		aptgetRefreshModule(1, @modulesList);
		printLog("ending prerequisites", 'L1', '[INFO]');
	}
	return;
}



###############################################################################
# section for clean-up of old modules
#
sub cleanUpModules {
	# remove systemdbacklightfix
	if ($localConfModules{systemdbacklightfix}{installed} eq 'yes') {
		$presetModules{systemdbacklightfix}{required} = 'no';
	}
	# remove backlightfix
	if ($localConfModules{backlightfix}{installed} eq 'yes') {
		$presetModules{backlightfix}{required} = 'no';
	}
	# remove disableguxfix
	if ($localConfModules{disablegucfix}{installed} eq 'yes') {
		$presetModules{disablegucfix}{required} = 'no';
	}
	# remove noecwakeupfix
	if ($localConfModules{noecwakeupfix}{installed} eq 'yes') {
		$presetModules{noecwakeupfix}{required} = 'no';
	}
	# remove amdgpubacklightauxoff
	if ($localConfModules{amdgpubacklightauxoff}{installed} eq 'yes') {
		$presetModules{amdgpubacklightauxoff}{required} = 'no';
	}
	return (0);
}


###############################################################################
# check whether the module is required for the system
# checks only for hardware
#
sub checkRequirements {
	my $FH;
	my %nvidiaPackage = nvidiaPackage();

	cleanUpModules();

	if ($boardname =~ /PF5PU1G/sm) {
		$presetModules{nobootonbattery}{required} = 'yes';
		printLog("detected $presetModules{nobootonbattery}{name} issue", 'L1', '[INFO]');
	}

	if (($boardname =~ /POLARIS1501A1650TI/sm) ||
		($boardname =~ /POLARIS1501A2060/sm) ||
		($boardname =~ /POLARIS1701A1650TI/sm) ||
		($boardname =~ /POLARIS1701A2060/sm) ||
		($boardname =~ /PULSE1401/sm) ||
		($boardname =~ /PULSE1501/sm) ||
		($boardname =~ /NL5xRU/sm) ||
		($boardname =~ /EDUBOOK1502/sm) ||
  		($boardname =~ /AURA1501/sm)) {
		$presetModules{amdxhcihcdusbquirksuspendfix}{required} = 'yes';
		printLog("detected $presetModules{amdxhcihcdusbquirksuspendfix}{name} issue", 'L1', '[INFO]');
	}

	if (($boardname =~ /APX958/sm) ||
		($boardname =~ /R14FA1/sm) ||
		($boardname =~ /GXxHRXx/sm) ||
		($boardname =~ /R14FA2/sm) ||
		($boardname =~ /GMxHGxx/sm)) {
		$presetModules{amdgpudisablepsr}{required} = 'yes';
		printLog("detected $presetModules{amdgpudisablepsr}{name} issue", 'L1', '[INFO]');
	}

	if ($boardname =~ /X35R/sm) {
		$presetModules{elantechtouchpadfix}{required} = 'yes';
		printLog("detected $presetModules{elantechtouchpadfix}{name} issue", 'L1', '[INFO]');
	}

	if (($boardname =~ /LAPQC71A/sm) ||
		($boardname =~ /LAPQC71B/sm) ||
		($boardname =~ /PF5PU1G/sm) ||
		($boardname =~ /POLARIS1501A1650TI/sm) ||
		($boardname =~ /POLARIS1501A2060/sm) ||
		($boardname =~ /POLARIS1501I1650TI/sm) ||
		($boardname =~ /POLARIS1501I2060/sm) ||
		($boardname =~ /POLARIS1701A1650TI/sm) ||
		($boardname =~ /POLARIS1701A2060/sm) ||
		($boardname =~ /POLARIS1701I1650TI/sm) ||
		($boardname =~ /POLARIS1701I2060/sm) ||
		($boardname =~ /PULSE1401/sm) ||
		($boardname =~ /PULSE1501/sm) ||
		($boardname =~ /TRINITY1501I/sm) ||
		($boardname =~ /TRINITY1701I/sm) ||
		($boardname =~ /PHxTxX1/sm) ||
		($boardname =~ /PH4TQx1/sm) ||
		($boardname =~ /PHxTQx1/sm) || #alias to PH4TQx1
		($boardname =~ /GMxNGxx/sm) ||
		($boardname =~ /GMxZGxx/sm) ||
		($boardname =~ /GMxTGxx/sm) ||
		($boardname =~ /GMxAGxx/sm) ||
		($boardname =~ /GMxMGxx/sm) ||
		($boardname =~ /GMxRGxx/sm) ||
		($boardname =~ /PF5LUXG/sm) ||
		($boardname =~ /PHxARX1_PHxAQF1/sm) ||
		($boardname =~ /PH6AG01_PH6AQ71_PH6AQI1/sm) ||
		($boardname =~ /GMxPXxx/sm) ||
		($boardname =~ /PH4PRX1_PH6PRX1/sm) ||
		($boardname =~ /PH4PG31/sm) ||
		($boardname =~ /PH6PG01_PH6PG71/sm) ||
		($boardname =~ /GM6XGxX/sm)) {
		$presetModules{tuxedotouchpadswitch}{required} = 'yes';
		printLog("detected $presetModules{tuxedotouchpadswitch}{name} issue", 'L1', '[INFO]');
	}

	if ($boardvendor =~ /NB02/sm) {
		$presetModules{tuxedotouchpadswitch}{required} = 'yes';
		printLog("detected $presetModules{tuxedotouchpadswitch}{name} issue", 'L1', '[INFO]');
	}

	if ($boardname =~ /NH5xAx/sm) {
		$presetModules{corefix}{required} = 'yes';
		printLog("detected $presetModules{corefix}{name} issue", 'L1', '[INFO]');
	}

	if ($distributionType =~ /noble|trixie/) {
		$presetModules{tuxedodrivers}{required} = 'yes';
		printLog("detected $presetModules{tuxedodrivers}{name} issue", 'L1', '[INFO]');
	}

	my $fingerprintLsUsb = `lsusb`;
	my @fingerprintRegExp = (
		qr/147e:100[0123]/sm,
		qr/147e:20(15|16|20)/sm,
		qr/147e:300[01]/sm,
		qr/147e:500[23]/sm,
		qr/0483:201[56]/sm,
		qr/1c7a:0603/sm);
	foreach my $fpkey (map { $fingerprintLsUsb =~ $_ } @fingerprintRegExp) {
		$presetModules{fingerprintreader}{required} = 'yes';
		printLog("detected $presetModules{fingerprintreader}{name} issue", 'L1', '[INFO]');
		last;
	}

	if ($boardname =~ m/N350TW/sm) {
		$presetModules{tuxedorestoreaudiofix}{required} = 'yes';
		printLog("detected $presetModules{tuxedorestoreaudiofix}{name} issue", 'L1', '[INFO]');
	}

	# if the right nvidia driver is installed, it should show up in 'tomte list'
	# this is for the case when an eGPU is being used and it is temporarily not connected to the device
	if ($distributionType =~ /noble/) {
		if (isNvidiaGPUInstalled() || iseGPUHandlingRequired()) {
			$presetModules{nvidiadriver}{required} = 'yes';
			printLog("detected $presetModules{nvidiadriver}{name} issue", 'L1', '[INFO]');
		}
	}

	if ($distributionType =~ /noble/) {
		if (-e "/etc/default/apport") {
			# apportfix will always be installed as a workaround
			$presetModules{apportfix}{required} = 'yes';
			printLog("detected $presetModules{apportfix}{name} issue", 'L1', '[INFO]');
		}
	}

	if (($boardname =~ m/PHxTxX1/sm) ||
		($boardname =~ m/PH4TQx1/sm) ||
		($boardname =~ m/PHxTQx1/sm)) {
		$presetModules{i915enablepsrfix}{required} = 'yes';
		printLog("detected $presetModules{i915enablepsrfix}{name} issue", 'L1', '[INFO]');
	}


	# kernel
	if ($distributionType =~ /noble/sm) {
		printLog("kernel $presetModules{kerneltuxedo2404}{name} will be default", 'L1', '[INFO]');
		$presetModules{kerneltuxedo2404}{required} = 'yes';
	}

	# only for removal as it is built into the kernel
	if ($distributionType =~ /noble/) {
		if ($boardname =~ m/X170KM-G/) {
			$presetModules{thunderboltbootdelayfix}{required} = 'no';
			printLog("detected $presetModules{thunderboltbootdelayfix}{name} issue", 'L1', '[INFO]');
		}
	}

	if ((($boardname =~ m/NV4XMB,ME,MZ/sm) ||
		($boardname =~ m/PHxTQx1/sm) ||
		($boardname =~ m/PH4TQx1/sm)) && (-d '/etc/lightdm/')) {
		$presetModules{lightdmlogindcheckgraphicalfix}{required} = 'yes';
		printLog("detected $presetModules{lightdmlogindcheckgraphicalfix}{name} issue", 'L1', '[INFO]');
	}

	if ($distributionType =~ /noble/) {
		if ($boardname =~ m/GMxTGxx/) {
			$presetModules{nvidiabacklightcontrolfix}{required} = 'yes';
			printLog("detected $presetModules{nvidiabacklightcontrolfix}{name} issue", 'L1', '[INFO]');
		}
	}

	if (($boardname =~ m/NV4XMB,ME,MZ/sm) ||
		($boardname =~ m/PHxTQx1/sm)) {
		$presetModules{earlyloadbacklightcontrolfix}{required} = 'yes';
		printLog("detected $presetModules{earlyloadbacklightcontrolfix}{name} issue", 'L1', '[INFO]');
	}

	# only for removal of nvmequirkswitchtodeepsleepfix
	if ($distributionType =~ /noble/) {
		if ($localConfModules{nvmequirkswitchtodeepsleepfix}{required} eq 'yes') {
			printLog("nvmefix in list, will mark to remove it", 'L2', '[DEBUG]');
			my @samsungNvme990Devices = ('144d:a80d');
			if ((($boardname =~ m/NS5X_NS7XAU/) || ($boardname =~ m/NS5X_7XAU/ism)) &&
				( findPciDevice($lspciOutput, @samsungNvme990Devices) )) {
				$presetModules{nvmequirkswitchtodeepsleepfix}{required} = 'yes';
				printLog("detected $presetModules{nvmequirkswitchtodeepsleepfix}{name} issue", 'L1', '[INFO]');
			}
		}
	}

	my $minPlasmaDesktopVersion = '5.25';
	my $currentPlasmaWorkspaceVersion = q{};
	my $versionCompareResult;

	# ommit Debian trixie as we don't install the Nvidia drivers
	if ($distributionType =~ /noble/) {
		my $minPlasmaDesktopVersion = '5.25';
		my $lspciGpus = q{};
		$lspciGpus = `lspci -d ::0300`;
		if (countLinesInString($lspciGpus) >= 2) {
			printLog("more than 1 GPU found" , 'L2', '[DEBUG]');
			$currentPlasmaWorkspaceVersion = getPackageVersion('plasma-workspace');
			if ($currentPlasmaWorkspaceVersion ne q{}) {
				$versionCompareResult = version_compare($currentPlasmaWorkspaceVersion, $minPlasmaDesktopVersion);
				if (! ($versionCompareResult == $NEG_ONE) ) {
					$presetModules{tuxedodgpurun}{required} = 'yes';
					printLog("detected $presetModules{tuxedodgpurun}{name} issue", 'L1', '[INFO]');
				}
			} else {
				printLog("plasma-workspace version could not be determined", 'L2', '[INFO]');
			}
		} else {
			printLog("plasma-workspace version could not be determined", 'L2', '[INFO]');
		}
	} else {
		printLog("less than 2 GPU found" , 'L2', '[DEBUG]');
	}

	if (($boardname =~ m/PD5x_7xPNP_PNR_PNN_PNT/sm) ||
		($boardname =~ m/GMxTGxx/sm)) {
		$presetModules{acpibacklightvideofix}{required} = 'yes';
		printLog("detected $presetModules{acpibacklightvideofix}{name} issue", 'L1', '[INFO]');
	}

	my @ath12kstandbyfixDevices = ('17cb:1107');
	if ( findPciDevice($lspciOutput, @ath12kstandbyfixDevices) ) {
		$presetModules{ath12kstandbyfix}{required} = 'yes';
		printLog("detected $presetModules{ath12kstandbyfix}{name} issue", 'L1', '[INFO]');
	}

	# tuxedoplasmaupgrade installation
	my $minPlasmaWorkspaceVersion = '6.0.0';
	printLog("distribution: $distribution distributionVersion: $distributionVersion", 'L2', '[DEBUG]');
	if (($distribution =~ /TUXEDO OS/sm) && ($distributionVersion =~ /24\.04.*/sm)) {
		$plasma6Installed = 1;
		printLog("we just assume plasma 6 is installed", 'L2', '[DEBUG]');
	}

	if (($distributionType =~ /noble/) ||
		($distributionType =~ /trixie/) ||
		($distribution =~ /TUXEDO OS/sm)) {
		my @tuxedoyt6801Devices = ('1f0a:6801');
		if ( findPciDevice($lspciOutput, @tuxedoyt6801Devices) ) {
			$presetModules{tuxedoyt6801}{required} = 'yes';
			printLog("detected $presetModules{tuxedoyt6801}{name} issue", 'L1', '[INFO]');
		}
	}

	if ($distribution =~ /TUXEDO OS/sm) {
		$presetModules{textboot}{required} = 'yes';
		printLog("detected $presetModules{textboot}{name} issue", 'L1', '[INFO]');
	}
	# get vendor text from vendor ID:device ID
	my @tuxedoWifiSetRegDomainDevices = ('14c3:0608','14c3:0616');
	if ( findPciDevice($lspciOutput, @tuxedoWifiSetRegDomainDevices) ) {
		$presetModules{tuxedowifisetregdomain}{required} = 'yes';
		printLog("detected $presetModules{tuxedowifisetregdomain}{name} issue", 'L1', '[INFO]');
	}

	if ((($distributionType =~ /noble/) &&
		($distribution !~ /TUXEDO OS/)) ||
		$distributionType =~ /trixie/) {
		$presetModules{gfxmode}{required} = 'yes';
		printLog("detected $presetModules{gfxmode}{name} issue", 'L1', '[INFO]');
	}

	my $lsusbMediaTekWirelessDeviceUsbId = '0e8d:0616';
	if (($distributionType =~ /noble|trixie/) &&
		(($boardname =~ /4X4-8000 Series/sm) ||
		($boardname =~ /4X4-7000 Series\/D5/sm) ||
		($boardname =~ /4X4-7040 Series\/D5/sm) ||
		($boardname =~ /4X4-5000 Series/sm) ||
		(findUsbDevice($lsusbOutput, $lsusbMediaTekWirelessDeviceUsbId)))) {
		$presetModules{tuxedobtoffsuspend}{required} = 'yes';
		printLog("detected $presetModules{tuxedobtoffsuspend}{name} issue", 'L1', '[INFO]');
	}

	# workaround for Linux Mint and Debian GNU mirror repositories
	if ($distribution =~ /Linux Mint|Debian GNU/) {
		$presetModules{'tuxedomirrors'}{'required'} = 'no';
		$presetModules{'tuxedomirrors'}{'reconfigure'} = 'no';
		$presetModules{'tuxedomirrors'}{'order'} = q{};
		$localConfModules{'tuxedomirrors'}{'required'} = 'no';
		$localConfModules{'tuxedomirrors'}{'reconfigure'} = 'no';
		$localConfModules{'tuxedomirrors'}{'order'} = q{};
		printLog("detected $distribution, repos will be left unchanged", 'L1', '[INFO]');
	}

	if (($distributionType =~ /noble|trixie/) &&
		($boardname =~ /4X4-KRK Series/sm)) {
		$presetModules{'krackanpointusb4suspendfix'}{'required'} = 'yes';
	}

	return (0);
}




###############################################################################
# check whether the module is required for the system
# checks only for hardware
# last check after configuring enything else for late hardware like eGPU's
#
sub checkRequirementsLast {
	printLog("in last check requirements", 'L1', '[INFO]');

	# if the right nvidia driver is installed, it should show up in 'tomte list'
	# this is for the case when an eGPU is being used and it is temporarily not connected to the device
	if ($distributionType =~ /noble/sm) {
		if (($presetModules{nvidiadriver}{required} ne 'yes') && isNvidiaGPUInstalled()) {
			$presetModules{nvidiadriver}{required} = 'yes';
			printLog("detected $presetModules{nvidiadriver}{name} issue", 'L1', '[INFO]');
			configureSingleModule('nvidia-driver');
		}
	}

	return (0);
}




###############################################################################
# returns true if the PCI device or subdevice was found
# parameter is an array of PCI devices (RegExp are allowed)
#
sub findPciDevice {
	my ($lspciList, @pciDevices) = @_;
	foreach my $pciDevice (@pciDevices) {
		if ($lspciList =~ /$pciDevice/sm) {
			return (1);
		}
	}
	return (0);
}



###############################################################################
# returns true if the USB device or subdevice was found
# parameter is an array of PCI devices (RegExp are allowed)
#       
sub findUsbDevice {
	my ($lsusbList, @usbDevices) = @_;
	foreach my $usbDevice (@usbDevices) {
		if ($lsusbList =~ /^Bus ... Device ...: ID $usbDevice .*$/m) {
			return (1);
		}
	}
	return (0);
}








###############################################################################
###############################################################################
###############################################################################
#
# each configure one module from %confModules
# they set 'installed' status and $postConfModule trigger
#
###############################################################################
###############################################################################
###############################################################################



###############################################################################
# deletes iommu=soft from grub
# was before for not being able to boot if system is on battery and used to set
# iommu=soft on grub
# affects BA15
# - PF5PU1G
#
sub nobootonbattery() {
	my $module = shift;
	my $action = shift;
	grubModuleEdit( $module, 'remove', 0, 'iommu=soft');
	return;
}


###############################################################################
# Fix for some AMD devices where the xhci_hcd driver causes a jump out of
# suspend immediately on entering suspend
# - POLARIS1501A1650TI
# - POLARIS1501A2060
# - POLARIS1701A1650TI
# - POLARIS1701A2060
# - PULSE1401
# - PULSE1501
# - AURA1501/NL5xRU
# - EDUBOOK1502
#
sub amdxhcihcdusbquirksuspendfix() {
	my $module = shift;
	my $action = shift;
	grubModuleEdit( $module, $action, 0, 'xhci_hcd.quirks=1073741824');
	return;
}


###############################################################################
# Disables PSR for APX958, Pulse 14 Gen3, and Pulse 14 Gen4
#
sub amdgpudisablepsr() {
	my $module = shift;
	my $action = shift;
	grubModuleEdit( $module, $action, 0, 'amdgpu.dcdebugmask=0x10');
	return;
}


###############################################################################
# Enables activation of discrete NVIDIA graphics on devices with built-in AMD
# graphics. Also enables switching between the two with prime-select
# affects:
# - POLARIS1701A1650TI
# - POLARIS1701A2060
# - POLARIS1501A1650TI
# - POLARIS1501A2060
#
sub amdgpuwithnvidiafix() {
	my $module = shift;
	my $action = shift;
	my $retval;
	my $fileName = '/usr/share/X11/xorg.conf.d/00-tuxedo-nvidia-amdgpu-fix.conf';
	my $niceTime = getBackupFileTime();
	my $fileText = "# added by tuxedo-tomte, any changes might be overwritten by Tomte as needed!! ".$niceTime."\n".
		"Section \"OutputClass\"\n".
		"\tIdentifier \"nvidia\"\n".
    	"\tMatchDriver \"nvidia-drm\"\n".
    	"\tDriver \"nvidia\"\n".
    	"\tOption \"AllowEmptyInitialConfiguration\"\n".
    	"\tModulePath \"/usr/lib/x86_64-linux-gnu/nvidia/xorg\"\n".
    	"\tOption \"PrimaryGPU\" \"Yes\"\n".
		"EndSection\n";
	if ($action eq 'install') {
		printLog("applying $presetModules{$module}{name}", 'L1', '[INFO]');
		$retval = write_file($fileName, {'err_mode' => 'carp'}, $fileText);
		if ($retval == 0) {
			$presetModules{$module}{installed} = "failed";
			addToConfiguredModules($module, "failed");
		} else {
			$presetModules{$module}{installed} = "yes";
			$presetModules{$module}{upgraded} = "yes";
			addToConfiguredModules($module, "success");
			setRestartValues($module);
		}
	}
	if ($action eq 'upgrade') {
		printLog("upgrading $presetModules{$module}{name}", 'L1', '[INFO]');
		$presetModules{$module}{installed} = "yes";
		$presetModules{$module}{upgraded} = "yes";
		addToConfiguredModules($module, "success");
	}
	if ($action eq 'remove') {
		if (removeWarningMessage("amd-gpu-with-nvidia-fix\n")) {
			printLog("removing $module", 'TL0', '[INFO]');
			unlink glob ("/usr/share/X11/xorg.conf.d/00-tuxedo-nvidia-amdgpu-fix.conf_*");
			if (backupFile($fileName)) {
				unlink($fileName);
			} else {
				printLog('can\'t remove fix, it seems to be gone already', 'TL0', '[WARN]');
			}
			$presetModules{$module}{installed} = 'no';
			$presetModules{$module}{upgraded} = 'yes';
			addToConfiguredModules($module, 'removed');
		}
	}
	return;
}


###############################################################################
# TUXEDO Control Center application
#
sub tuxedocontrolcenter() {
	my $module = shift;
	my $action = shift;
	debianPackageModule( $module, $action );
	return;
}



###############################################################################
# removes backlightfix
# this module is obsolete and only for removal
#
sub backlightfix() {
	my $module = shift;
	grubModuleEdit( $module, 'remove', 1, 'i915.enable_dpcd_backlight=1');
	return;
}




###############################################################################
# Adds the TUXEDO repos to the system
#
sub tuxedorepos() {
	my $module = shift;
	my $action = shift;
	my $retval;
	my $retval2;
	my $thirdPartyMirrorsFile = '/etc/update-manager/release-upgrades.d/tuxedo.cfg';
	my $thirdPartyMirrorsFileText = "[ThirdPartyMirrors]\ntuxedo = https://deb.tuxedocomputers.com/ubuntu\n";
	my $FH;

	# if this is a FAI-installation all repos should be already set correctly
	if ($FAI) {
		$presetModules{$module}{installed} = "yes";
		$presetModules{$module}{upgraded} = "yes";
		addToConfiguredModules($module, "success");
		return (1);
	}

	# create default repo list if it does not exist
	if (! -e '/etc/apt/sources.list') {
		if (open($FH, '>', '/etc/apt/sources.list')) {
			printLog('created /etc/apt/sources.list because it was not there', 'L2', '[WARN]');
			if (close($FH)) {
				printLog('could not close /etc/apt/sources.list thats very bad', 'L0', '[ERROR]');
			}
		} else {
			printLog('could not create /etc/apt/sources.list thats very bad', 'L0', '[ERROR]');
		}
	}

	if ($action eq 'install') {
		printLog("applying $presetModules{$module}{name}", 'L1', '[INFO]');
		$retval = createRepos(\%essentialReposList);
		addRepoKey();
		if ($retval == 0) {
			$presetModules{$module}{installed} = "failed";
			addToConfiguredModules($module, "failed");
		} else {
			# $thirdPartyMirrorsFile is needed for Ubuntu's which use update-manager,
			# Mint uses mintUpdate instead
			if (($distributionType =~ /noble/sm) &&
					(($distribution =~ /TUXEDO OS/sm) ||
					($distribution =~ /Ubuntu/sm) ||
					($distribution =~ /elementary OS/sm))) {
				if (! -e $thirdPartyMirrorsFile) {
					printLog("$thirdPartyMirrorsFile not present, will create one ...", 'L0', '[WARN]');
					if (! (write_file($thirdPartyMirrorsFile, {'err_mode' => 'carp'}, $thirdPartyMirrorsFileText)) ) {
						printLog("something is very fishy! can't create $thirdPartyMirrorsFile", 'L0', '[ERROR]');
					}
				}
			}
			if ($retval == 1) {
				$presetModules{$module}{installed} = "yes";
				$presetModules{$module}{upgraded} = "yes";
				addToConfiguredModules($module, "nothingToDo");
				printLog('did no modifications on repos', 'L1', '[INFO]');
			} else {
				$presetModules{$module}{installed} = "yes";
				$presetModules{$module}{upgraded} = "yes";
			}
		}
		# remove obsolete entries if they still exist
		if ($distributionType =~ /noble/) {
			if (-e '/etc/apt/sources.list.d/tuxedo-computers.list') {
				printLog("removing obsolete /etc/apt/sources.list.d/tuxedo-computers.list", 'L0', '[INFO]');
				unlink('/etc/apt/sources.list.d/tuxedo-computers.list');
			}
			if (-e '/etc/apt/sources.list.d/tuxedo-plasma.list') {
				printLog("removing obsolete /etc/apt/sources.list.d/tuxedo-plasma.list", 'L0', '[INFO]');
				unlink('/etc/apt/sources.list.d/tuxedo-plasma.list');
			}
			if (-e '/etc/apt/sources.list.d/tuxedo-os.list') {
				printLog("removing obsolete /etc/apt/sources.list.d/tuxedo-os.list", 'L0', '[INFO]');
				unlink('/etc/apt/sources.list.d/tuxedo-os.list');
			}
			if (-e '/etc/apt/sources.list.d/tuxedo-ubuntu-mirrors.list') {
				printLog("removing obsolete /etc/apt/sources.list.d/tuxedo-ubuntu-mirrors.list", 'L0', '[INFO]');
				unlink('/etc/apt/sources.list.d/tuxedo-ubuntu-mirrors.list');
			}
		}
	}
	if ($action eq 'upgrade') {
		printLog("upgrading $presetModules{$module}{name}", 'L1', '[INFO]');

		# remove obsolete entries if they still exist
		if ($distributionType =~ /noble/) {
			if (-e '/etc/apt/sources.list.d/tuxedo-computers.list') {
				printLog("removing obsolete /etc/apt/sources.list.d/tuxedo-computers.list", 'L0', '[INFO]');
				unlink('/etc/apt/sources.list.d/tuxedo-computers.list');
			}
			if (-e '/etc/apt/sources.list.d/tuxedo-plasma.list') {
				printLog("removing obsolete /etc/apt/sources.list.d/tuxedo-plasma.list", 'L0', '[INFO]');
				unlink('/etc/apt/sources.list.d/tuxedo-plasma.list');
			}
			if (-e '/etc/apt/sources.list.d/tuxedo-os.list') {
				printLog("removing obsolete /etc/apt/sources.list.d/tuxedo-os.list", 'L0', '[INFO]');
				unlink('/etc/apt/sources.list.d/tuxedo-os.list');
			}
			if (-e '/etc/apt/sources.list.d/tuxedo-ubuntu-mirrors.list') {
				printLog("removing obsolete /etc/apt/sources.list.d/tuxedo-ubuntu-mirrors.list", 'L0', '[INFO]');
				unlink('/etc/apt/sources.list.d/tuxedo-ubuntu-mirrors.list');
			}
		}

		backupFile('/etc/apt/sources.list');
		$retval = createRepos(\%essentialReposList);
		addRepoKey();
		if ($retval == 0) {
			$presetModules{$module}{installed} = "failed";
			addToConfiguredModules($module, "failed");
		} else {
			# $thirdPartyMirrorsFile is needed for Ubuntu's which use update-manager,
			# Mint uses mintUpdate instead
			if (($distributionType =~ /noble/sm) &&
				(($distribution =~ /TUXEDO OS/sm) ||
				($distribution =~ /Ubuntu/sm) ||
				($distribution =~ /elementary OS/sm))) {
				if (! -e $thirdPartyMirrorsFile) {
					printLog("$thirdPartyMirrorsFile not present, will create one ...", 'L0', '[WARN]');
					if (! (write_file($thirdPartyMirrorsFile, {'err_mode' => 'carp'}, $thirdPartyMirrorsFileText)) ) {
						printLog("something is very fishy! can't create $thirdPartyMirrorsFile", 'L0', '[ERROR]');
					}
				}
			}

			if ($retval == 1) {
				$presetModules{$module}{installed} = "yes";
				$presetModules{$module}{upgraded} = "yes";
				addToConfiguredModules($module, "nothingToDo");
				printLog('did no modifications on repos', 'L1', '[INFO]');
			} else {
				$presetModules{$module}{installed} = "yes";
				$presetModules{$module}{upgraded} = "yes";
			}
		}
	}
	if ($action eq 'remove') {
		printLog("This is a prerequisite, removal is not possible for this module", 'TL0', '[WARN]');
	}
	return;
}


###############################################################################
# Adds the TUXEDO mirrors to the system
#
sub tuxedomirrors() {
	my $module = shift;
	my $action = shift;
	my $retval;
	my $retval2;
	my $retval3;

	# if this is a FAI-installation all repos should be already set correctly
	if ($FAI) {
		$presetModules{$module}{installed} = "yes";
		$presetModules{$module}{upgraded} = "yes";
		addToConfiguredModules($module, "success");
		return (1);
	}

	if ($action eq 'install') {
		printLog("applying $presetModules{$module}{name}", 'L1', '[INFO]');
		$retval = createRepos(\%otherReposList);
		$retval3 = cleanSourcesList($otherReposList{$completeDistVersion}{mirrors}{filename});
		addRepoKey();
		if ($retval == 0) {
			$presetModules{$module}{installed} = "failed";
			addToConfiguredModules($module, "failed");
		} else {
			if (($retval == 1) && ($retval3 == 0)) {
				$presetModules{$module}{installed} = "yes";
				$presetModules{$module}{upgraded} = "yes";
				addToConfiguredModules($module, "nothingToDo");
				printLog('did no modifications on mirrors', 'L1', '[INFO]');
			} else {
				$presetModules{$module}{installed} = "yes";
				$presetModules{$module}{upgraded} = "yes";
			}
		}
	}
	if ($action eq 'upgrade') {
		printLog("uprading $presetModules{$module}{name}", 'L1', '[INFO]');
		backupFile('/etc/apt/sources.list');
		$retval = createRepos(\%otherReposList);
		$retval3 = cleanSourcesList($otherReposList{$completeDistVersion}{mirrors}{filename});
		addRepoKey();
		if ($retval == 0) {
			$presetModules{$module}{installed} = "failed";
			addToConfiguredModules($module, "failed");
		} else {
			if ($retval == 1) {
				$presetModules{$module}{installed} = "yes";
				$presetModules{$module}{upgraded} = "yes";
				addToConfiguredModules($module, "nothingToDo");
				printLog('did no modifications on mirrors', 'L1', '[INFO]');
			} else {
				$presetModules{$module}{installed} = "yes";
				$presetModules{$module}{upgraded} = "yes";
			}
		}
	}
	if ($action eq 'remove') {
		printLog("This only removes the module from the list, manual intervention is necessary to remove the respective repository entries", 'TL0', '[INFO]');
		addToConfiguredModules($module, "removed");
		$presetModules{$module}{installed} = "no";
		$presetModules{$module}{upgraded} = "yes";
		$presetModules{$module}{required} = "no";
	}
	return;
}


###############################################################################
# Adds the core-fix for certain BIOS versions on certain mainboards
#
sub corefix() {
	my $module = shift;
	my $action = shift;
	my $amlHookScript = q{tuxedo-corefix-clevo-nh5xax};
	my $amlHookScriptOrig = $shareDir.$amlHookScript;
	my $amlHookDirDest = '/etc/initramfs-tools/hooks/';
	my $amlHookScriptDest = $amlHookDirDest.$amlHookScript;
	my $amlFile = ('nh5xax-1.aml');
	my $amlFileOrig = $shareDir.$amlFile;
	my $amlFileDirDest = '/lib/firmware/tuxedo-corefix-clevo-nh5xax/';
	$amlFile =~ s/-\d*//sm;
	my $amlFileDest = $amlFileDirDest.$amlFile;

	if ($action eq 'install') {
		if (($argValue{command} eq 'configure') || ($argValue{command} eq 'reconfigure')) {
			messageLongInstall();
			printLog("applying $presetModules{$module}{name}", 'L1', '[INFO]');
			mkdir($amlFileDirDest, $FILE_MODE_SIXFOURFOUR) if ( !(-d $amlFileDirDest) );
			copy($amlFileOrig, $amlFileDest);
			mkdir($amlHookDirDest, $FILE_MODE_SIXFOURFOUR) if ( !(-d $amlHookDirDest) );
			copy($amlHookScriptOrig, $amlHookScriptDest);
			chmod $FILE_MODE_SEVENZEROZERO, $amlHookScriptDest;

			if ((-e $amlFileDest) and (-e $amlHookScriptDest)) {
				$presetModules{$module}{installed} = "yes";
				$presetModules{$module}{upgraded} = "yes";
				$postConfProgramsList{updateInitramfs}{trigger} = 1;
				addToConfiguredModules($module, "success");
			} else {
				$presetModules{$module}{installed} = "failed";
				addToConfiguredModules($module, "failed");
			}
		} else {
			printLog("$presetModules{$module}{name} was already installed", 'L1', '[INFO]');
			addToConfiguredModules($module, "nothingToDo");
		}
	}
	if ($action eq 'upgrade') {
		# not implemented yet
	}
	if ($action eq 'remove') {
		# not implemented yet
		printLog("remove is not implemented yet for this module", 'TL0', '[WARN]');
	}
	return;
}



###############################################################################
# Fixes problem with Elantech touchpad
# affects InfinityBook 14 v2
# - X35R
#
sub elantechtouchpadfix() {
	my $module = shift;
	my $action = shift;
	grubModuleEdit( $module, $action, 0, 'psmouse.elantech_smbus=0' );
	return;
}



###############################################################################
# Uniwill touchpad LED fix
#
sub tuxedotouchpadswitch() {
	my $module = shift;
	my $action = shift;
	debianPackageModule( $module, $action );
	return;
}



###############################################################################
# fingerprint-reader
#
sub fingerprintreader() {
	my $module = shift;
	my $action = shift;
	my $wasInstalled = 1;
	my $packagefprintd;
	my $packagelibpam;
	if (($action eq 'install') && !$noNetwork) {
		# check if the package is already installed
		if( ! isPackageInstalled('fprintd') ) {
			$wasInstalled = 0;
			messageLongInstall();
			$packagefprintd = installPackage('fprintd', 'fingerprintreader');
		} else {
			$packagefprintd = 1;
		}
		if( ! isPackageInstalled('libpam-fprintd') ) {
			$wasInstalled = 0;
			messageLongInstall();
			$packagelibpam = installPackage('libpam-fprintd', 'fingerprintreader');
		} else {
			$packagelibpam = 1;
		}

		if( $packagefprintd && $packagelibpam ) {
			if ($wasInstalled) {
				printLog("$presetModules{$module}{name} was already installed", 'L0', '[INFO]');
				addToConfiguredModules($module, "nothingToDo");
			} else {
				addToConfiguredModules($module, 'success');
			}
			$presetModules{$module}{installed} = 'yes';
			$presetModules{$module}{upgraded} = 'yes';
		} else {
			$presetModules{$module}{installed} = 'failed';
			addToConfiguredModules($module, 'failed');
		}
	}
	if ($action eq 'upgrade') {
		printLog('upgrade not necessary for packages', 'L1', '[INFO]');
	}
	if ($action eq 'remove') {
		if (removeWarningMessage("fingerprint-reader\n")) {
			my $counter = 0;
			if( isPackageInstalled('fprintd') ) {
				if (!deinstallSinglePackage('fprintd')) {
					$counter = $counter+1;
				}
			} else {
				$counter = $counter+1;
			}
			if( isPackageInstalled('libpam-fprintd') ) {
				if (!deinstallSinglePackage('libpam-fprintd')) {
					$counter = $counter+1;
				}
			} else {
				$counter = $counter+1;
			}
			if ($counter == 2) {
				$presetModules{$module}{installed} = 'no';
				$presetModules{$module}{upgraded} = 'yes';
				addToConfiguredModules($module, 'removed');
			} else {
				$presetModules{$module}{installed} = 'yes';
				$presetModules{$module}{upgraded} = 'no';
				addToConfiguredModules($module, 'remove failed');
			}
		}
	}
	return;
}




###############################################################################
# Rescans the pci bus to reactivate missing audio device
# - N350TW
#
sub tuxedorestoreaudiofix() {
	my $module = shift;
	my $action = shift;
	my $retval1;
	my $retval2;
	my $fileName = '/lib/systemd/system/restore-audiocards.service';
	my $fileText = "# added by tuxedo-tomte, any changes might be overwritten by Tomte as needed!! \n".
		"[Unit]\n".
		"Description=Rescan the PCI-Bus for missed audio cards\n".
		"After=alsa-restore.service\n".
		"\n".
		"[Service]\n".
		"Type=oneshot\n".
		"RemainAfterExit=false\n".
		"ExecStart=/usr/bin/bash -c 'echo auto >> /sys/devices/pci0000:00/0000:00:1f.3/power/control'\n".
		"ExecStart=/usr/bin/sleep 1\n".
		"ExecStart=/usr/bin/bash -c 'echo 1 >> /sys/devices/pci0000:00/0000:00:1f.3/remove'\n".
		"ExecStart=/usr/bin/sleep 1\n".
		"ExecStart=/usr/bin/bash -c 'echo 1 >> /sys/bus/pci/rescan'\n".
		"\n".
		"[Install]\n".
		"WantedBy=multi-user.target\n";
	if ($action eq 'install') {
		printLog("applying $presetModules{$module}{name}", 'L1', '[INFO]');
		$retval1 = write_file($fileName, {'err_mode' => 'carp'}, $fileText);
		$retval2 = system('systemctl --now enable restore-audiocards.service');
		if ($retval1 == 0 || $retval2 != 0) {
			if ($retval2 != 0) {
				printLog('systemd could not initialize restore-audiocards.service', 'L0', '[WARN]');
			}
			$presetModules{$module}{installed} = "failed";
			addToConfiguredModules($module, "failed");
		} else {
			$presetModules{$module}{installed} = "yes";
			$presetModules{$module}{upgraded} = "yes";
			addToConfiguredModules($module, "success");
			setRestartValues($module);
		}
	}
	if ($action eq 'upgrade') {
		printLog("no upgrade available yet for tuxedo-restore-audio-fix", 'L1', '[INFO]');
	}
	if ($action eq 'remove') {
		# not implemented yet
		printLog("remove is not implemented yet for this module", 'TL0', '[INFO]');
	}
	return;
}




###############################################################################
# tuxedo-drivers
#
sub tuxedodrivers() {
	my $module = shift;
	my $action = shift;
	if (debianPackageModule( $module, $action ) && ($action eq 'install')) {
		if (isPackageInstalled('tuxedo-keyboard')) {
			debianPackageModule( 'tuxedokeyboard', 'remove' );
		}
		if (isPackageInstalled('tuxedo-keyboard-ite')) {
			debianPackageModule( 'tuxedokeyboardite', 'remove');
		}
	}
	return;
}



###############################################################################
# systemd-backlight-fix
# this module is obsolete and only for removal
#
sub systemdbacklightfix() {
	my $module = shift;
	printLog("$presetModules{$module}{name} will be removed", 'L0', '[INFO]');
	if (-e '/etc/udev/rules.d/99-tuxedo-systemd-backlight-fix.rules') {
		if (unlink('/etc/udev/rules.d/99-tuxedo-systemd-backlight-fix.rules')) {
			printLog("$presetModules{$module}{name} has been successfully removed", 'L0', '[INFO]');
		} else {
			printLog("$presetModules{$module}{name} could not be removed", '', '[ERROR]');
		}
	} else {
		printLog("$presetModules{$module}{name} is not installed", 'L2', '[INFO]');
	}
	return;
}



###############################################################################
# kernel-tuxedo-24.04
#
sub kerneltuxedo2404() {
	my $module = shift;
	my $action = shift;
	my $instFailed = 0;
	my $instDone = 0;
	if (($action eq 'install') || ($action eq 'upgrade')) {
		# clean up oldest tuxedo kernel in /boot (if not used)

		checkAndDeleteOldestTuxedoKernel();
		checkAndDeleteOldestRemnantLinuxHeader();
		installKernelFlavour('linux-tuxedo-24.04', \$instFailed, \$instDone);
		if ($instFailed > 0) {
			startTomteDelayed();
			addToConfiguredModules($module, "failed");
			$presetModules{$module}{installed} = "failed";
		} elsif ($instDone > 0) {
			addToConfiguredModules($module, "success");
			$presetModules{$module}{installed} = "yes";
			$presetModules{$module}{upgraded} = "yes";
			setRestartValues($module);
		} else {
			addToConfiguredModules($module, "nothingToDo");
			$presetModules{$module}{installed} = "yes";
			$presetModules{$module}{upgraded} = "yes";
		}
	}
	if ($action eq 'remove') {
		# not implemented yet
		printLog("remove is not implemented yet for this module", 'TL0', '[INFO]');
	}
	return;
}




###############################################################################
# nvidiadriver
# installs TUXEDO approved nvidia drivers and removes all the other ones
#
sub nvidiadriver() {
	my $module = shift;
	my $action = shift;
	my $wasInstalled = 1;
	my $packageNvidiaDriver = 0;
	my $packageNvidiaPrime = 0;
	my $packagePythonAppindicator = 0;
	my $packagePythonCairo = 0;
	my $packagePythonGtk2 = 0;
	my $oldDriversRemoved = 0;
	my $output;
	my %nvidiaPackage = nvidiaPackage();

	if ((($action eq 'install') || ($action eq 'upgrade')) && !$noNetwork) {
		if ($distributionType =~ /noble/) {
			my $nvidiaPackageType = nvidiaMetapackage();
			printLog("nvidiaPackageType: $nvidiaPackageType", 'L2', '[DEBUG]');
			my $tuxedoNvidiaDriver = $nvidiaPackage{$nvidiaPackageType};
			if (!defined $tuxedoNvidiaDriver
				|| $nvidiaPackageType eq q{}
				|| $nvidiaPackageType eq 'legacy') {
				printLog("no supported Nvidia driver found for this GPU", 'L2', '[INFO]');
				$presetModules{$module}{installed} = "failed";
				addToConfiguredModules($module, "failed");
				return;
			}
			printLog("the Nvidia package $tuxedoNvidiaDriver will be used", 'L2', '[DEBUG]');
			# check if the drivers to install are actually available in repos
			if (isPackageInRepo( $tuxedoNvidiaDriver )) {
				# deinstall other nvidia drivers
				$oldDriversRemoved = deinstExcept('nvidia-driver-', $tuxedoNvidiaDriver);

				my $oldDriversRemovedCounter = deinstExcept('tuxedo-nvidia-driver-', $tuxedoNvidiaDriver);
				$oldDriversRemoved += $oldDriversRemovedCounter;
				# check if the package is already installed
				if( ! isPackageInstalled($tuxedoNvidiaDriver) ) {
					messageLongInstall();
					$wasInstalled = 0;
					setDkmsVerbose( 'nvidiadriver', 1 );
					$packageNvidiaDriver = installPackage($tuxedoNvidiaDriver, 'nvidiadriver');
					setDkmsVerbose( 'nvidiadriver', 0 );
				} else {
					setAptMark($tuxedoNvidiaDriver,'manual');
					$packageNvidiaDriver = 1;
				}
			} else {
				printLog("old nvidia packages will not be uninstalled as the required packages for the new driver $tuxedoNvidiaDriver can't be found in the repositories", 'L0', '[WARN]');
			}

			if( ! isPackageInstalled('nvidia-prime') ) {
				$wasInstalled = 0;
				$packageNvidiaPrime = installPackage('nvidia-prime', 'nvidiadriver');
			} else {
				$packageNvidiaPrime = 1;
			}

			if( $packageNvidiaDriver && $packageNvidiaPrime ) {
				# temporarily activated fix
				if ($FAI || ($presetModules{$module}{installed} eq 'no')) {
					$output = `gpu-manager && prime-select nvidia`;
					printLog('selecting NVIDIA GPU: on', 'TL0', '[INFO]');
					printLog("$output", 'L2', '[DEBUG]');
				}
			}

			printLog("oldDriversRemoved: $oldDriversRemoved packageNvidiaDriver: $packageNvidiaDriver packageNvidiaPrime: $packageNvidiaPrime", 'L2', '[DEBUG]');
			if( $oldDriversRemoved && $packageNvidiaDriver && $packageNvidiaPrime ) {
				if ($wasInstalled) {
					printLog("$presetModules{$module}{name} was already installed", 'L1', '[INFO]');
					addToConfiguredModules($module, "nothingToDo");
				} else {
					printLog("installed $presetModules{$module}{name}", 'L1', '[INFO]');
					addToConfiguredModules($module, "success");
					setRestartValues($module);
				}
				$presetModules{$module}{installed} = "yes";
				$presetModules{$module}{upgraded} = "yes";
			} else {
				printLog("$presetModules{$module}{name} installation failed", 'L0', '[WARN]');
				$presetModules{$module}{installed} = "failed";
				addToConfiguredModules($module, "failed");
			}

		} else {
			printLog("unknown distribution/flavour, can't install nvidia drivers", 'TL0', '[WARN]');
		}
	}
	if ($action eq 'remove') {
		my $nvidiaDriver = q{};
		if ($distributionType =~ /noble/) {
			$nvidiaDriver = $nvidiaPackage{nvidiaMetapackage()};
		}
		if (removeWarningMessage("nvidia-driver\n")) {
			my $counter = 0;
			if( isPackageInstalled($nvidiaDriver) ) {
				if (!deinstallSinglePackage($nvidiaDriver)) {
					$counter = $counter+1;
				}
			} else {
				$counter = $counter+1;
			}
			if( isPackageInstalled('nvidia-prime') ) {
				if (!deinstallSinglePackage('nvidia-prime')) {
					$counter = $counter+1;
				}
			} else {
				$counter = $counter+1;
			}
			if ($counter == 2) {
				$presetModules{$module}{installed} = 'no';
				$presetModules{$module}{required} = 'no';
				$presetModules{$module}{upgraded} = 'yes';
				addToConfiguredModules($module, 'removed');
			} else {
				$presetModules{$module}{installed} = 'yes';
				$presetModules{$module}{required} = 'no';
				$presetModules{$module}{upgraded} = 'no';
				addToConfiguredModules($module, 'remove failed');
			}
		}
	}
	return;
}



###############################################################################
# deactivates apport reporting
#
sub apportfix() {
	my $module = shift;
	my $action = shift;
	my $oldApportLine = 'default=1';
	my $newApportLine = 'default=0';
	my $retval;
	if ($action eq 'install') {
		printLog("applying $presetModules{$module}{name}", 'L1', '[INFO]');

		if (isLineInFile('/etc/default/apport', 'enabled=1')) {
			$retval = edit_file { s/enabled=1/enabled=0/gsm } '/etc/default/apport', {'err_mode' => 'carp'};
			if ($retval == 1) {
				printLog('apport has been disabled in /etc/default/apport', 'L0', '[INFO]');
				#`service apport stop`;
				#printLog("service apport stop returned: $?", 'L2', '[DEBUG]');
				#`systemctl disable apport.service`;
				#printLog("systemctl disable apport.service returned: $?", 'L2', '[DEBUG]');
				$presetModules{$module}{installed} = "yes";
				$presetModules{$module}{upgraded} = "yes";
				addToConfiguredModules($module, "success");
			} else {
				$presetModules{$module}{installed} = "failed";
				addToConfiguredModules($module, "failed");
			}
		} elsif (isLineInFile('/etc/default/apport', 'enabled=0')) {
			printLog('apport was already disabled in /etc/default/apport', 'L1', '[DEBUG]');
			#`service apport stop`;
			#printLog("service apport stop returned: $?", 'L2', '[DEBUG]');
			#`systemctl disable apport.service`;
			#printLog("systemctl disable apport.service returned: $?", 'L2', '[DEBUG]');
			$presetModules{$module}{installed} = "yes";
			$presetModules{$module}{upgraded} = "yes";
			addToConfiguredModules($module, "success");
		} else {
			$presetModules{$module}{installed} = "failed";
			addToConfiguredModules($module, "failed");
		}
	}
	if ($action eq 'upgrade') {
		# not neccessary
	}
	if ($action eq 'remove') {
		# not implemented yet
		printLog("remove is not implemented yet for this module", 'TL0', '[INFO]');
	}
	return;
}



###############################################################################
# Fixes backlight issue with BOE Model 2423 Serial Number 0 displays on
# - GMxNGxx
# this module is obsolete and only for removal
#
sub amdgpubacklightauxoff() {
	my $module = shift;
	grubModuleEdit( $module, 'remove', 1, 'amdgpu.backlight=0' );
	return;
}



###############################################################################
# Provides better battery life
# adds two parameters and deletes an old one in case it is present
#
sub i915enablepsrfix() {
	my $module = shift;
	my $action = shift;
	if (($action eq 'install') || ($action eq 'upgrade')) {
		edit_file { s/i915\.enable_psr=0//gsm } '/etc/default/grub', {'err_mode' => 'carp'};
	}
	grubModuleEdit( $module, $action, 0, 'i915.tuxedo_disable_psr2=1', 'i915.enable_psr=1');
	return;
}




###############################################################################
# for reducing the timeout on loading the thunderbolt kernel module when
# booting the system
# - X170KM-G
# adds some parameters and deletes one if found
# module is obsolete as it is built into the kernel
#
sub thunderboltbootdelayfix() {
	my $module = shift;
	my $action = shift;
	edit_file { s/thunderbolt.tuxedo_icm_driver_ready_timeout=2000//gsm } '/etc/default/grub', {'err_mode' => 'carp'};
	grubModuleEdit( $module, 'remove', 0, 'modprobe.blacklist=thunderbolt');
	return;
}



###############################################################################
# inserts a parameter to start lightdm after the windowmanager has started
#
sub lightdmlogindcheckgraphicalfix() {
	my $module = shift;
	my $action = shift;
	my $lightdmLine = 'logind-check-graphical=true';
	my $lightdmCheckGraphicalDir = '/etc/lightdm/lightdm.conf.d/';
	my $lightdmCheckGraphicalFile = $lightdmCheckGraphicalDir.'99_tuxedo.conf';

	if ($action eq 'install') {
		printLog("applying $presetModules{$module}{name}", 'L1', '[INFO]');
		if (! -d $lightdmCheckGraphicalDir) {
			if (! mkdir $lightdmCheckGraphicalDir) {
				printLog("could not create directory $lightdmCheckGraphicalDir", 'TL0', '[ERROR]');
				$presetModules{$module}{installed} = "failed";
				addToConfiguredModules($module, "failed");
				return (0);
			}
		}
		if (-e $lightdmCheckGraphicalFile) {
			if ( !(isTextInFile($lightdmCheckGraphicalFile, '[LightDM]') && isTextInFile($lightdmCheckGraphicalFile, 'logind-check-graphical=true')) ) {
				if (write_file($lightdmCheckGraphicalFile, {'err_mode' => 'carp'}, "[LightDM]\nlogind-check-graphical=true")) {
					$presetModules{$module}{installed} = "yes";
					$presetModules{$module}{upgraded} = "yes";
					addToConfiguredModules($module, "success");
				} else {
					printLog("lightdmlogindcheckgraphicalfix installation failed", 'TL0', '[ERROR]');
					$presetModules{$module}{installed} = "failed";
					addToConfiguredModules($module, "failed");
					return (0);
				}
			}
		} else {
			if (write_file($lightdmCheckGraphicalFile, {'err_mode' => 'carp'}, "[LightDM]\nlogind-check-graphical=true")) {
				$presetModules{$module}{installed} = "yes";
				$presetModules{$module}{upgraded} = "yes";
				addToConfiguredModules($module, "success");
			} else {
				printLog("lightdmlogindcheckgraphicalfix installation failed", 'TL0', '[ERROR]');
				$presetModules{$module}{installed} = "failed";
				addToConfiguredModules($module, "failed");
				return (0);
			}
		}
	}

	if ($action eq 'upgrade') {
		# not neccessary
	}
	if ($action eq 'remove') {
		# not implemented yet
		printLog("remove is not implemented yet for this module", 'TL0', '[INFO]');
	}
	return;
}



###############################################################################
# For functioning backlight control on Nvidia GPU's
#
# - GMxTGxx
#
# removes some old parameters if present
#
sub nvidiabacklightcontrolfix() {
	my $module = shift;
	my $action = shift;

	if (($action eq 'install') || ($action eq 'upgrade')) {
		edit_file { s/ NVreg_RegistryDwords=EnableBacklightHandler=0//gsm } '/etc/default/grub', {'err_mode' => 'carp'};
		edit_file { s/ NVreg_RegistryDwords=EnableBrightnessControl=1//gsm } '/etc/default/grub', {'err_mode' => 'carp'};
	}
	grubModuleEdit( $module, $action, 0, 'nvidia.NVreg_RegistryDwords=EnableBacklightHandler=0', 'nvidia.NVreg_RegistryDwords=EnableBrightnessControl=1', 'i915.enable_dpcd_backlight=0');
	return;
}



###############################################################################
# inserts a parameter to start lightdm after the windowmanager has started
#
sub earlyloadbacklightcontrolfix() {
	my $module = shift;
	my $action = shift;
	my $elbcLine = 'softdep nvidia pre: i915';
	my $elbcDir = '/etc/modprobe.d/';
	my $elbcFile = $elbcDir.'tuxedo_early_load_backlight_control_fix.conf';

	if ($action eq 'install') {
		printLog("applying $presetModules{$module}{name}", 'L1', '[INFO]');
		if (! -d $elbcDir) {
			if (! mkdir $elbcDir) {
				printLog("could not create directory $elbcDir", 'TL0', '[ERROR]');
				$presetModules{$module}{installed} = "failed";
				addToConfiguredModules($module, "failed");
				return (0);
			}
		}
		if (-e $elbcFile) {
			if (! isTextInFile($elbcFile, $elbcLine)) {
				if (write_file($elbcFile, {'err_mode' => 'carp'}, $elbcLine)) {
					$presetModules{$module}{installed} = "yes";
					$presetModules{$module}{upgraded} = "yes";
					addToConfiguredModules($module, "success");
				} else {
		printLog("$presetModules{$module}{name} installation failed", 'L1', '[ERROR]');
					$presetModules{$module}{installed} = "failed";
					addToConfiguredModules($module, "failed");
					return (0);
				}
			}
		} else {
			if (write_file($elbcFile, {'err_mode' => 'carp'}, $elbcLine)) {
				$presetModules{$module}{installed} = "yes";
				$presetModules{$module}{upgraded} = "yes";
				addToConfiguredModules($module, "success");
			} else {
				printLog("$presetModules{$module}{name} installation failed", 'L0', '[ERROR]');
				$presetModules{$module}{installed} = "failed";
				addToConfiguredModules($module, "failed");
				return (0);
			}
		}
	}

	if ($action eq 'upgrade') {
		# not neccessary
	}
	if ($action eq 'remove') {
		# not implemented yet
		printLog("remove is not implemented yet for this module", 'TL0', '[INFO]');
	}
	return;
}



###############################################################################
# InfinityBook 15/17 Gen8 in combination with some Samsung SSDs
# only for removal of kernel parameter set some time ago
#
sub nvmequirkswitchtodeepsleepfix() {
	my $module = shift;
	my $action = shift;
	grubModuleEdit( $module, 'remove', 0, 'mem_sleep_default=deep' );
	return;
}




###############################################################################
# Commandline tool and KDE context menu entries to run programms on dGPU while
# in on-demand mode
#
sub tuxedodgpurun() {
	my $module = shift;
	my $action = shift;
	debianPackageModule( $module, $action );
	return;
}



###############################################################################
# Boot delays/freezes in nvidia modes
#
sub acpibacklightvideofix() {
	my $module = shift;
	my $action = shift;
	grubModuleEdit( $module, $action, 0, 'acpi_backlight=video');
	return;
}



###############################################################################
# disabled GuC submission while keeping HuC on Intel iGPU
# this module is obsolete and only for removal
#       
sub disablegucfix() {
	my $module = shift;
	grubModuleEdit($module, 'remove', 1, 'i915.enable_guc=2');
	grubModuleEdit($module, 'remove', 1, 'i915.enable_guc=0');
	return;
}



###############################################################################
# Boot delays/freezes in nvidia modes
#
sub ath12kstandbyfix() {
	my $module = shift;
	my $action = shift;
	my $content = <<'END_TXT';
#!/bin/sh
# added by tuxedo-tomte, any changes might be overwritten by Tomte as needed!!

case $1 in
  pre)
    rmmod ath12k
    ;;
  post)
    modprobe ath12k
    ;;
esac
END_TXT
	createFileModule( $module, $action, '/usr/lib/systemd/system-sleep/tuxedo-ath12k-standby-fix', $content, "+x");
	return;
}




###############################################################################
# Installs Linux device driver for Ethernet controller Motorcomm YT6801
#
sub tuxedoyt6801() {
	my $module = shift;
	my $action = shift;
	debianPackageModule( $module, $action );
	return;
}




###############################################################################
# Installs module usefull for Wi-Fi
#
sub tuxedowifisetregdomain() {
	my $module = shift;
	my $action = shift;
	debianPackageModule( $module, $action );
	return;
}




###############################################################################
# solves black screen after plasma6 upgrade
#
sub textboot() {
	my $module = shift;
	my $action = shift;
	my @splashParameter = ('splash');
	my @nosplashParameter = ('nosplash');
	my $retPar;

	if (($action eq 'install') || ($action eq 'upgrade')) {
		removeKernelParameters($module, 1, @splashParameter);
		grubModuleEdit($module, $action, 0, @nosplashParameter);
	}
	if ($action eq 'remove') {
		grubModuleEdit( $module, $action, 1, @nosplashParameter);
		$retPar = insertGrub($module, \@splashParameter);
		if ($retPar >= 1) {
			printLog("@splashParameter successfully inserted into grub", 'L2', '[DEBUG]');
			block('text-boot');
		} else {
			printLog("something went wrong while inserting @splashParameter into grub", 'L0', '[ERROR]');
		}
	}
	return;
}



###############################################################################
# controls video mode
#
sub gfxmode() {
	my $module = shift;
	my $action = shift;
	my $retval;
	my $fileName = '/etc/grub.d/gfxmode.cfg';
	my $fileText = "GRUB_GFXMODE=1280x720,1280x800,1280x1024\n".
					"GRUB_GFXPAYLOAD_LINUX=keep\n";
	my $currentText;
	if (($action eq 'install') || ($action eq 'upgrade')) {
		if (-e $fileName) {
			$currentText = read_file($fileName, 'err_mode' => 'carp');
			if ( $currentText ne $fileText ) {
				printLog("applying $presetModules{$module}{name}", 'L1', '[INFO]');
				$retval = write_file($fileName, {'err_mode' => 'carp'}, $fileText);
				if ($retval == 0) {
					printLog("$presetModules{$module}{name} installation failed", 'L0', '[INFO]');
					$presetModules{$module}{installed} = "failed";
					addToConfiguredModules($module, "failed");
				} else {
					$presetModules{$module}{installed} = "yes";
					$presetModules{$module}{upgraded} = "yes";
					addToConfiguredModules($module, "success");
					setRestartValues($module);
				}
			}
		} else {
			printLog("applying $presetModules{$module}{name}", 'L1', '[INFO]');
			$retval = write_file($fileName, {'err_mode' => 'carp'}, $fileText);
			if ($retval == 0) {
				printLog("$presetModules{$module}{name} installation failed", 'L0', '[INFO]');
				$presetModules{$module}{installed} = "failed";
				addToConfiguredModules($module, "failed");
			} else {
				$presetModules{$module}{installed} = "yes";
				$presetModules{$module}{upgraded} = "yes";
				addToConfiguredModules($module, "success");
				setRestartValues($module);
			}
		}
	}
	if ($action eq 'remove') {
		if (-e $fileName) {
			if (! unlink($fileName)) {
				printLog("can't remove the file $fileName", 'TL0', '[WARN]');
				return (0);
			}
		} else {
			printLog("can't remove fix, the file $fileName seems to be gone already", 'TL0', '[WARN]');
		}
		$presetModules{$module}{installed} = 'no';
		$presetModules{$module}{upgraded} = 'yes';
		addToConfiguredModules($module, 'removed');
	}
	return (1);
}



###############################################################################
# Prevents spurious wake-up from suspend triggered by the EC
# this module is obsolete and only for removal
#
sub noecwakeupfix() {
	my $module = shift;
	grubModuleEdit( $module, 'remove', 1, 'acpi.ec_no_wakeup=1' );
	return;
}



###############################################################################
# Installs package tuxedo-bt-off-suspend
#
sub tuxedobtoffsuspend() {
	my $module = shift;
	my $action = shift;
	debianPackageModule( $module, $action );
	return;
}


###############################################################################
# Installs package tuxedo-fix-krackan-point-usb4-suspend
#
sub krackanpointusb4suspendfix() {
	my $module = shift;
	my $action = shift;
	debianPackageModule( $module, $action );
	return;
}





















###############################################################################
###############################################################################
###############################################################################
# subs for tuxedorepos & tuxedomirrors
#

###############################################################################
# Adds the TUXEDO repos to the system with public key
# returns 2 if successful and changes to repos were made
# returns 1 if successful and no changes to repos
# returns 0 if failed
#
sub createRepos {
	my %repos = %{$_[0]};
	my $DH;
	my $modified = 0;
	my $sourcesListDir = '/etc/apt/';
	my $sourcesListDirD = '/etc/apt/sources.list.d/';
	my $sourcesFileName = $sourcesListDir.'sources.list';
	my $distroKey = q{};

	if (exists($repos{$completeDistVersion})) {
		$distroKey = $completeDistVersion;
	} else {
		$distroKey = $distributionType;
	}
	printLog("detected distroKey: >$distroKey<", 'TL2', '[DEBUG]');

	# if either testdeb or testmirrors exist, leave everything as it is
	if (exists $repos{$distroKey}{deb}) {
		printLog("deb repos detected", 'TL2', '[DEBUG]');
		if (isLineInFile('/etc/apt/sources.list.d/tuxedo-computers.list', '^deb https?:\/\/testdeb.*')) {
			printLog('testing environment detected, leaving repos as they are', 'TL2', '[DEBUG]');
			return (1);
		}
	} elsif (exists $repos{$distroKey}{mirrors}) {
		printLog("mirror repos detected", 'TL2', '[DEBUG]');
		if (isLineInFile('/etc/apt/sources.list', '^deb https?:\/\/testmirrors.*')) {
			printLog('testing environment detected, leaving repos as they are', 'TL2', '[DEBUG]');
			return (1);
		}
	}

	if (!-d $sourcesListDir) {
		printLog("no $sourcesListDir found (something's really wrong)", 'TL0', '[ERROR]');
		# no apt directory, somethings fishy
		return (0);
	}
	if (!-d $sourcesListDirD) {
		printLog("no $sourcesListDirD found, creating one", 'TL0', '[WARN]');
		mkdir($sourcesListDirD);
	}

	# workaround for possible old entries in sources list
	# do NOT add more distributions/kernelsList here as this is meant only for
	# old installations where those entries might still exist
	if ($distributionType eq 'noble') {
		if(-e '/etc/apt/sources.list.d/tuxedo-computers.list'){
			edit_file { s/deb http:\/\/deb.tuxedocomputers.com\/ubuntu jammy main//gsm } '/etc/apt/sources.list.d/tuxedo-computers.list', {'err_mode' => 'carp'};
			edit_file { s/# Added by TUXEDO Tomte//gsm } '/etc/apt/sources.list.d/tuxedo-computers.list', {'err_mode' => 'carp'};
		}
		if (-e '/etc/apt/sources.list.d/graphics-tuxedo.list') {
			if (unlink('/etc/apt/sources.list.d/graphics-tuxedo.list')) {
				printLog('deleted /etc/apt/sources.list.d/graphics-tuxedo.list', 'TL1', '[INFO]');
			}
		}
		if (-e '/etc/apt/sources.list.d/oibaf-tuxedo.list') {
			if (unlink('/etc/apt/sources.list.d/oibaf-tuxedo.list')) {
				printLog('deleted /etc/apt/sources.list.d/oibaf-tuxedo.list', 'TL1', '[INFO]');
			}
		}
	}

	my $repoFileText = q{};
	foreach my $key (keys %{ $repos{$distroKey} }) {
		# each line
		printLog("key: $key distroKey: $distroKey", 'L2', '[DEBUG]');
		if ($key ne 'name') {
			# hack to set the TUXEDO plasma repos to the correct values
			if ($plasma6Installed && ($distroKey ne 'TUXEDO OS 24.04')) {
				printLog("adding plasma6-repo", 'L2', '[DEBUG]');
				if (($key eq 'plasma6plasma') || ($key eq 'deb') || ($key eq 'mirrors')) {
					$repoFileText = join "\n", @{$repos{$distroKey}{$key}{content}};
				}
			} elsif (($distribution =~ /TUXEDO OS/sm) && ($distroKey ne 'TUXEDO OS 24.04')) {
				printLog("adding non-plasma6-repo", 'L2', '[DEBUG]');
				if (($key eq 'nonplasma6plasma') || ($key eq 'deb') || ($key eq 'mirrors')) {
					$repoFileText = join "\n", @{$repos{$distroKey}{$key}{content}};
				}
			} else {
				printLog("key: $key", 'L2', '[DEBUG]');
				printLog("content: @{$repos{$distroKey}{$key}{content}}", 'L2', '[DEBUG]');
				$repoFileText = join "\n", @{$repos{$distroKey}{$key}{content}};
			}
			printLog("repofiletext: $repoFileText", 'L2', '[DEBUG]');

			if ($repoFileText ne q{}) {
				my $fileText = "# tuxedo-tomte will overwrite this file if you modify anything,\n".
					"# we recommend you to put your own modifications into some other file\n".
					"# e.g. /etc/apt/sources.list.d/own-modifications.list\n".
					$repoFileText . "\n";

				my $noChangesNeeded = 0;
				if (-e $repos{$distroKey}{$key}{filename}) {
					my $fileContentText = read_file($repos{$distroKey}{$key}{filename}, 'err_mode' => 'carp');
					$noChangesNeeded = $fileContentText eq $fileText;
				}

				if($noChangesNeeded){
					printLog("repo file $repos{$distroKey}{$key}{filename} found and with correct content", 'L2', '[DEBUG]');
				} else {
					printLog("$repos{$distroKey}{$key}{filename} incorrect", 'L0', '[WARN]');
					printLog("recreating $repos{$distroKey}{$key}{filename}", 'L0', '[INFO]');

					if (write_file($repos{$distroKey}{$key}{filename}, {'err_mode' => 'carp'}, $fileText) ) {
						$modified += 1;
						printLog("wrote file: $repos{$distroKey}{$key}{filename}", 'L0', '[INFO]');
					} else {
						printLog("could not create file: $repos{$distroKey}{$key}{filename}", 'L0', '[ERROR]');
					}
				}
			}
			$repoFileText = q{};
		}
	}
	return ($modified+1);
}



###############################################################################
# checks if the repo key is installed by comparing its content
# adds the repo key for TUXEDO repos if not present
# returns 1 if package is installed or was installed successfully
# returns 0 if not
#
sub addRepoKey {
	my $output;
	my $retval;
	my $errorCode;
	my $outputKey;
	my $counter = 0;
	my $sleepTime = 1; # seconds
	# install TUXEDO keyring for repositories
	if (! isPackageInstalled('tuxedo-archive-keyring')) {
		printLog("TUXEDO keyring is not installed", 'L0', '[WARN]');
		if ( checkNetwork( $NETWORK_CHECKS, 1 ) ) {
			$outputKey = `wget -r -nd --no-parent -P /tmp/ -A 'tuxedo-archive-keyring*.deb' https://deb.tuxedocomputers.com/ubuntu/pool/main/t/tuxedo-archive-keyring/`;
			deactivatePackagekit();
			# wait a bit for package management to settle down
			while ($counter < $NETWORK_CHECKS) {
				$counter++;
				my $command = $consoleLanguage.'apt-get -y --allow-unauthenticated -o Dpkg::lock::timeout=0 install /tmp/tuxedo-archive-keyring* 2>/dev/null';
				if (unlockPM("$command") && (isPMlocked() == 0)) {
					printLog("executing: $command", 'L2', '[DEBUG]');
					$outputKey = `$command`;
					$errorCode = $?/$RETVAL_CONVERTER;
					lockPM();
					if ($errorCode != 0) {
						printLog("error code apt-get: $errorCode", 'L0', '[ERROR]');
						printLog("output apt-get: $outputKey", 'L0', '[ERROR]');
					} else {
						printLog("no errors installing keyrings", 'L2', '[INFO]');
						last;
					}
				} else {
					printLog("something is wrong while looking for locks for keyring installation", 'L0', '[ERROR]');
				}
				printLog("problem installing keyring, trying again in $sleepTime seconds", 'L0', '[WARN]');
				sleep($sleepTime);
			}
			if (isPackageInstalled("tuxedo-archive-keyring")) {
				printLog("TUXEDO keyring installed successfully", 'L2', '[INFO]');
				return (1);
			} else {
				printLog("The TUXEDO repository key could not be installed!", 'TL0', '[ERROR]');
				if (!$LiveISO) {
					printLog("Tomte will try again in few minutes or after reboot or if startet manually with:\nsudo tomte configure all", 'TL0', '[INFO]');
				}
				startTomteDelayed(__("Tomte will try again in a few minutes or after reboot or when activated manually with: sudo tomte configure all"));
				return (0);
			}
		} else {
			printLog("no network connection found! the TUXEDO repository keys can't be installed", 'TL0', '[WARN]');
			if (!$LiveISO) {
				printLog("Tomte will try again in a few minutes or after reboot or if startet manually with:\nsudo tomte configure all", 'TL0', '[INFO]');
			}
			startTomteDelayed(__("Tomte will try again in a few minutes or after reboot or when activated manually with: sudo tomte configure all"));
			return (0);
		}
	}
	return (1);
}



###############################################################################
# comment out anything else on sources.list which has
# deb mirrorurl and not tuxedocomputers
# returns 0 if nothing was changed
# returns 1 if modifications were made
#
sub cleanSourcesList {
	my $FHsource;
	my @sourceLines;
	my $sourcesListFile = '/etc/apt/sources.list';
	if ($distributionType eq 'noble') {
		$sourcesListFile = '/etc/apt/sources.list.d/ubuntu.sources';
	}
	my $modified = 0;
	my $tomteMessage = '# tuxedo-tomte commented out all entries at installation, any TUXEDO repositories will be automatically deleted';

	# read all the lines
	if ( (-e $sourcesListFile) && open($FHsource, "<", $sourcesListFile)) {
		chomp(@sourceLines = <$FHsource>);
		if (! close $FHsource) {
			printLog("could not close $sourcesListFile", 'L0', '[ERROR]');
		}
		# open for writing
		if (! -e $sourcesListFile) {
			return (0);
		}
		if (open($FHsource, ">", $sourcesListFile)) {
			if (! grep( /$tomteMessage/sm, @sourceLines ) ) {
				# no backup for the file as it only comments out lines
				print $FHsource "$tomteMessage\n";
			}
			printLog("tuxedomirrors version: $localConfModules{'tuxedomirrors'}{version}", 'L2', '[DEBUG]');
			# in case this is an upgrade from a version without this feature or a first time installation
			if (-e $tomteFirstInstallFile) {
				printLog("first time installation file exists !!!", 'L2', '[DEBUG]');
			}
			if ((($localConfModules{'tuxedomirrors'}{version} + 0) < 5) || (-e $tomteFirstInstallFile)) {
				printLog("old tuxedomirrors module installed", 'L2', '[INFO]');
				foreach my $sourceLine (@sourceLines) {
					if (commentOut(\$sourceLine)) {
						$modified = 1;
					}
					if (! ($sourceLine =~ /mirrors\.tuxedocomputers\./sm) ) {
						print $FHsource "$sourceLine\n";
					}
				}
			} else {
				printLog("current tuxedomirrors module is already installed", 'L2', '[INFO]');
				# remove all tuxedo mirrors entries
				foreach my $sourceLine (@sourceLines) {
					if (! ($sourceLine =~ /mirrors\.tuxedocomputers\./sm) ) {
						print $FHsource "$sourceLine\n";
					}
				}
			}
			if (! close $FHsource ) {
				printLog("could not close $sourcesListFile", 'L0', '[ERROR]');
			}
		} else {
			printLog("file $sourcesListFile not found", 'L0', '[ERROR]');
		}
	} else {
		printLog("file $sourcesListFile not found", 'L0', '[ERROR]');
	}
	return ($modified);
}



###############################################################################
# puts a '#' in front of the passed string and returns it
#
sub commentOut {
	my $line = shift;
	if (${$line} =~ /^#/sm) {
		return (0);
	} else {
		${$line} = "# ${$line}";
		return (1);
	}
}






###############################################################################
# package handling
#


###############################################################################
# get packagekit status from systemd getSystemStatus(systemdmodule)
# returns 'active', 'inactive' or '0' if no status could be retrieved
#
sub getSystemdState {
	my $systemdModule = shift;
	if ($FAI) {
		return (0);
	}
	my $systemdCmd = "systemctl is-active $systemdModule";
	my $systemdReturn = `$systemdCmd`;
	$systemdReturn =~ s/\s+//gsm;
	printLog("systemd return for $systemdModule: $systemdReturn", 'L2', '[INFO]');
	if ($systemdReturn =~ /^active/sm) {
		return 'active';
	} elsif ($systemdReturn =~ /^inactive/sm) {
		return 'inactive';
	} else {
		return (0);
	}
}



###############################################################################
# get packagekit is-enabled from systemd getSystemdEnabled(systemdmodule)
# returns the value from 'systemctl is-enabled'
#
sub getSystemdEnabled {
	my $systemdModule = shift;
	if ($FAI) {
		return (0);
	}
	my $systemdCmd = "systemctl is-enabled $systemdModule";
	my $systemdReturn = `$systemdCmd`;
	$systemdReturn =~ s/\s+//gsm;
	printLog("systemd returns: $systemdReturn", 'L2', '[INFO]');
	return ($systemdReturn);
}



###############################################################################
# sends a command to the module by systemd
# commandSystemdModule(command, module)
# eg: mask --now, unmask, start, stop, enable, disable
#
sub commandSystemdModule {
	my ($command, $systemdModule) = @_;
	my $systemdCmd = "systemctl --quiet $command $systemdModule";
	my $systemdReturn = `$systemdCmd`;
	printLog("systemd returns: $systemdReturn", 'L2', '[INFO]');
	return;
}



###############################################################################
# sets the systemd module to inactive and masks it
# returns 1 if successful, 0 if not
# does nothing if packagekitState is set to "inactive"
#
sub deactivatePackagekit {
	if ($packagekitState eq 'inactive') {
		return (1);
	}
	if ($packagekitState eq 'active') {
		printLog("packagekit is active => deactivating ...", 'L2', '[INFO]');
		# temporary disabling masking
		commandSystemdModule('disable', 'packagekit');
		#commandSystemdModule('mask --now', 'packagekit');
		if (getSystemdState('packagekit') eq 'active') {
			$packagekitState = 'active';
			return (0);
		} else {
			$packagekitState = 'inactive';
			return (1);
		}
	} else {
		$packagekitState = 'inactive';
		printLog("packagekit is already inactive", 'L2', '[INFO]');
		return (1);
	}
}



###############################################################################
# creates a file if the content isn't already the same
# it removes the file if the module gets removed
# createFileModule( $module, $action, $filePath, $content, $executable );
# createFileModule( $module, $action, $filePath, $content );
#
sub createFileModule {
	my ($module, $action, $filePath, $content, $executable) = @_;
	$executable //= 0;

	if ($action eq 'install' || $action eq 'upgrade') {

		printLog("applying $presetModules{$module}{name}", 'L1', '[INFO]');
		my $success = createFile($filePath, $content, $executable);

		if (1 == $success){
			printLog("$filePath for $presetModules{$module}{name} was created", 'L1', '[INFO]');

			$presetModules{$module}{installed} = "yes";
			$presetModules{$module}{upgraded} = "yes";

			addToConfiguredModules($module, "success");

			if ("yes" eq $presetModules{$module}{restart}) {
				setRestartValues($module);
			}
		} elsif (0 == $success){
			printLog("$presetModules{$module}{name} was already created with correct content", 'L1', '[INFO]');

			$presetModules{$module}{installed} = "yes";
			$presetModules{$module}{upgraded} = "yes";

			addToConfiguredModules($module, "nothingToDo");
			addToConfiguredModules($module, "success");
		} else {
			printLog("$filePath for $presetModules{$module}{name} failed to be created", 'L0', '[ERROR]');

			$presetModules{$module}{installed} = "failed";

			addToConfiguredModules($module, "failed");
		}
	}

	if ($action eq 'remove') {
		unlink($filePath);
		if (-e $filePath) {
			printLog("$filePath for $presetModules{$module}{name} failed to be removed", 'L0', '[ERROR]');

			$presetModules{$module}{installed} = "remove failed";
			$presetModules{$module}{version} = $localConfModules{$module}{version};
			$presetModules{$module}{hwid} = $localConfModules{$module}{hwid};

			addToConfiguredModules($module, "remove failed");
		} else {
			printLog("$filePath for $presetModules{$module}{name} was removed", 'L1', '[INFO]');

			$presetModules{$module}{installed} = 'no';
			$presetModules{$module}{upgraded} = 'yes';

			addToConfiguredModules($module, 'removed');

			if ("yes" eq $presetModules{$module}{restart}) {
				setRestartValues($module);
			}
		}
	}
	return;
}



###############################################################################
# creates a file if the content isn't already the same
# createFile( $filePath, $content, $executable );
# createFile( $filePath, $content );
#
# $executable has to be "yes"|"+x" to be executable
#
# returns
#        1 if the content was changed successfully
#        0 if the content was already correct
#       -1 if there was an error writing to the file
sub createFile {
    my ($filePath, $content, $executable) = @_;
    $executable //= 'no';  # Set default value if $executable is not provided

	if (-e $filePath) {
		# Read the current content of the file
		my $currentContent = read_file($filePath, {'err_mode' => 'carp'});

		my $fileIsExecutableCheck = 1;
		if($executable eq 1 || $executable eq 'yes' || $executable eq '+x') {
			$fileIsExecutableCheck = -x $filePath;
		}

		# Check if the current content is the same as the provided content
		if ($currentContent eq $content && $fileIsExecutableCheck) {
			return 0;  # Return 0 if the content is already correct
		}
	}

    # Write the new content to the file
    if(! write_file($filePath, {'err_mode' => 'carp'}, $content)) {
        return $NEG_ONE;  # Return -1 if there was an error writing to the file
    }

    # If the file is supposed to be executable, set the execution permission
    if($executable eq 'yes' || $executable eq '+x') {
        if(system("chmod +x $filePath")) {
            return $NEG_ONE;  # Return -1 if there was an error setting the execution permission
        }
    }

    # Read the current content of the file again
    my $currentContent = read_file($filePath, {'err_mode' => 'carp'});

	my $fileIsExecutableCheck = 1;
	if($executable eq 'yes'|| $executable eq '+x') {
		$fileIsExecutableCheck = -x $filePath;
	}

    # Check if the content of the file is not the same as the provided content
    if ($currentContent ne $content || ! $fileIsExecutableCheck) {
        return $NEG_ONE;  # Return -1 if the content is not correctly written to the file
    }

    return 1;  # Return 1 if the content was changed successfully
}



###############################################################################
# add's, upgrades or deletes entries into the grub file /etc/default/grub
# grubModuleEdit( module, silent, install|upgrade|remove, kernelParameter|... );
# where $silent = 0 means normal operation and
# $silent = 1 means that it will not activate addToConfiguredModules
#
# returns 0 if operation failed
# returns 1 if operation succeeded or entry already existed
#
sub grubModuleEdit {
	my ($module, $action, $silent, @kernelParameters) = @_;
	my $retVal;
	
	if ($LiveISO) {
		printLog("not changing grub for $module as this is a LiveISO", 'L0', '[INFO]');
		return (0);
	}

	# do nothing if module is blocked
	if ($localConfModules{$module}{blocked} eq 'yes') {
		printLog("module: $module is blocked and will not be modified", 'L2' ,'[DEBUG]');
		return (0);
	}

	if ($action eq 'install') {
		printLog("applying $presetModules{$module}{name}", 'L1', '[INFO]');
		printLog("kernelparameters to insert: @kernelParameters", 'L2', '[DEBUG]');
		$retVal = insertGrub($module, \@kernelParameters);
		printLog("retVal: $retVal", 'L2', '[DEBUG]');
		if ($retVal < 0) {
			printLog("$presetModules{$module}{name} installation failed", 'L0', '[INFO]');
			$presetModules{$module}{installed} = "failed";
			if (!$silent) {
				addToConfiguredModules($module, "failed");
			}
			return (0);
		} else {
			$presetModules{$module}{installed} = "yes";
			$presetModules{$module}{upgraded} = "yes";
			if ($retVal > 0) {
				$postConfProgramsList{updateGrub}{trigger} = 1;
				if (!$silent) {
					addToConfiguredModules($module, "success");
				}
				return (1);
			} else {
				printLog("$presetModules{$module}{name} was already installed", 'L1', '[INFO]');
				if (!$silent) {
					addToConfiguredModules($module, "nothingToDo");
				}
				return (0);
			}
		}
	}
	
	if ($action eq 'upgrade') {
		printLog("upgrading $presetModules{$module}{name}", 'L1', '[INFO]');
		$retVal = insertGrub($module, \@kernelParameters);
		if ($retVal < 0) {
			printLog("$presetModules{$module}{name} upgrade failed", 'L0', '[INFO]');
			$presetModules{$module}{installed} = "failed";
			if (!$silent) {
				addToConfiguredModules($module, "failed");
			}
			return (0);
		} else {
			$presetModules{$module}{installed} = "yes";
			$presetModules{$module}{upgraded} = "yes";
			if ($retVal > 0) {
				$postConfProgramsList{updateGrub}{trigger} = 1;
				if (!$silent) {
					addToConfiguredModules($module, "success");
				}
				return (1);
			} else {
				printLog("$presetModules{$module}{name} was already installed", 'L1', '[INFO]');
				if (!$silent) {
					addToConfiguredModules($module, "nothingToDo");
				}
				return (1);
			}
		}
	}
	
	if ($action eq 'remove') {
		# don't ask the user if in silent mode
		if ($silent || removeWarningMessage("$presetModules{$module}{name}\n")) {
			if (removeKernelParameters($module, $silent, @kernelParameters) > 0) {
				return (1);
			} else {
				return (0);
			}
		} else {
			return (0);
		}
	}
	return (0);
}





###############################################################################
# add's, upgrades or deletes debian packages
# debianPackageModule( module, install|upgrade|remove );
# returns 1 if successfull
# returns 0 if not succesfull
#
sub debianPackageModule {
	my ($module, $action) = @_;
	my $success = 0;

	if (($action eq 'install') && !$noNetwork) {
		# check before if the package is already installed
		if (! isPackageInstalled($presetModules{$module}{name})) {
			messageLongInstall();

			# if it is a DKMS package, check if it should be installed in FAI
			# or LiveISO and set the verbose line
			setDkmsVerbose( $module, 1 );

			if ( installPackage($presetModules{$module}{name}, $module) ) {
				$presetModules{$module}{installed} = 'yes';
				$presetModules{$module}{upgraded} = 'yes';
				addToConfiguredModules($module, 'success');
				if ($presetModules{$module}{restart} eq 'yes') {
					setRestartValues($module);
				}
				$success = 1;
			} else {
				printLog("$presetModules{$module}{name} installation failed", 'L0', '[INFO]');
				$presetModules{$module}{installed} = 'failed';
				addToConfiguredModules($module, 'failed');
				$success = 0;
			}

			# remove verbose line if it was set above
			setDkmsVerbose( $module, 0 );

		} else {
			printLog("$presetModules{$module}{name} was already installed", 'L1', '[INFO]');
			addToConfiguredModules($module, 'nothingToDo');
			$presetModules{$module}{installed} = 'yes';
			$presetModules{$module}{upgraded} = 'yes';
			$success = 1;
		}
	}
	if (($action eq 'upgrade') && !$noNetwork) {
		printLog("upgrade not necessary for packages", '[INFO]');
		$success = 0;
	}
	if ($action eq 'remove') {
		if (removeWarningMessage("$presetModules{$module}{name}\n")) {
			$success = removeDebianModule($module, $presetModules{$module}{restart}, q{});
		}
	}
	return $success;
}





###############################################################################
# adds/removes 'verbose="1"' in /etc/dkms/framework.conf
# accepts setDkmsVerbose( 0|1 )
#
sub setDkmsVerbose {
	my ( $module, $value ) = @_;
	my $dkmsFile = '/etc/dkms/framework.conf';
	my $result;
	my $output;
	if ($value == 1) {
		if (($presetModules{$module}{dkms} eq 'yes') && (($FAI && ($presetModules{$module}{FAI} eq 'yes')) || ($LiveISO && ($presetModules{$module}{LiveISO} eq 'yes')))) {
			if ( !(isLineInFile( $dkmsFile, 'verbose="1"' )) ) {
				$result = append_file( $dkmsFile, {'err_mode' => 'carp'}, "verbose=\"1\"\n");
				printLog("append verbose into dkms file for $module", 'L2', '[INFO]');
				return ($result);
			} else {
				printLog("line verbose is already in $dkmsFile", 'L2', '[INFO]');
				return (1);
			}
		}
	} elsif ($value == 0) {
		if (($presetModules{$module}{dkms} eq 'yes') && (($FAI && ($presetModules{$module}{FAI} eq 'yes')) || ($LiveISO && ($presetModules{$module}{LiveISO} eq 'yes')))) {
			printLog("substitute 'verbose' in dkms file $dkmsFile for $module", 'L2', '[INFO]');
			$result = edit_file { s/verbose="1"//gsm } $dkmsFile, {'err_mode' => 'carp'};
			if ($result >= 0) {
				printLog("verbose DKMS substitution successful", 'L2', '[INFO]');
				return (1);
			}
		}
	}
	return (0);
}





###############################################################################
# Deinstalls all packages beginning with a given string except the one given as
# second parameter
# returns 0 if the search for the packages failed or if the deinstallation was
# unsuccessful
# returns 1 if everything was ok, even if no packages were found
#
# !!!!! WARNING !!!! this is mostly adapted to the nvidia installation module
#
sub deinstExcept {
	my $deinst = shift;
	my $except = shift;
	my $retValue = q{};
	my $searchCmd = $consoleLanguage."dpkg-query -W -f=\'\${Package} \${db:Status-Abbrev}\n\' $deinst*"." 2>&1";
	printLog("search command: $searchCmd", 'L2', '[DEBUG]');
	my $retString = `$searchCmd`;
	$retValue = $?/$RETVAL_CONVERTER;
	printLog("first result for $deinst:\n$retString", 'L2', '[DEBUG]');
	my $deinstCmd;
	my $deinstList =q{};
	if ($retValue != 0) {
		if ($retValue == 1) {
			printLog("no packages found matching search string to deinstall packages: $deinst with except pattern: $except", 'L1', '[DEBUG]');
			return (1);
		}
		printLog('something went wrong while searching for packages', 'L0', '[WARN]');
		return (0);
	}
	my %retHash = $retString =~ /(\S+)\s*(\S+)/gsm;
	my @exceptDependencies = getPackageDependencies($except);

	$deinstCmd = $consoleLanguage.'apt-get -yq remove -o Dpkg::lock::timeout=0';
	my $deinstCounter = 0;

	foreach my $deinstKey (keys %retHash) {
		printLog("deinstKey: $deinstKey except: $except retHash: $retHash{$deinstKey}", 'L2', '[DEBUG]');
		# usefull for debug
		if (grep( /^$deinstKey$/sm, @exceptDependencies)) {
			printLog("grep: $deinstKey found in dependencies, do NOT delete!", 'L2', '[DEBUG]');
		} else {
			printLog("grep: $deinstKey not found in dependencies, delete!", 'L2', '[DEBUG]');
		}
		# check if it is installed/not exempt/not a dependency of the exemp
		if (($retHash{$deinstKey} =~ /i./sm) &&
			!($deinstKey =~ $except) &&
			!(grep( /^$deinstKey$/sm, @exceptDependencies ))) {
			$deinstCounter++;
			$deinstCmd .= q{ }.$deinstKey;
			$deinstList .= q{ }.$deinstKey;
		}
	}
	if ($deinstCounter != 0) {
		printLog("deinstCounter not 0, deinstalling: $deinstList", 'L0', '[DEBUG]');
		my $retText = q{};
		if ($FAI) {
			printLog("command to deinstall: $deinstCmd", 'L2', '[DEBUG]');
			$retText = `$deinstCmd`;
		} else {
			$deinstCmd .= ' >/dev/null 2>&1';
			printLog("command to deinstall: $deinstCmd", 'L2', '[DEBUG]');
			if (unlockPM("$deinstCmd") && (isPMlocked() == 0)) {
				printLog("deinstalling: $deinstList", 'L2', '[DEBUG]');$retText = `$deinstCmd`;
				lockPM();
			}
		}
		$retValue = q{};
		$retValue = $?/$RETVAL_CONVERTER;
		if ($retValue != 0) {
			printLog("failed to deinstall packages, retvalue: >$retValue<", 'L0', '[WARN]');
			printLog("retText: $retText", 'L0', '[WARN]');
			return (0);
		} else {
			printLog('deinstallation of packages successful', 'L1', '[INFO]');
			return (1);
		}
	} else {
		printLog('nothing to deinstall', 'L1', '[INFO]');
		return (1);
	}
}



###############################################################################
# finds out which nvidia metapackage should be installed
# returns 'open' if the open driver should be used
# returns 'closed' if the universal driver should be used
# returns 'legacy' if no driver should be used
# returns '' if no GPU was found
#
sub nvidiaMetapackage {
	my $deviceID = q{};
	my $subvendorIDGroup = q{};
	my $type;
	my @lines = split "\n", $lspciOutput;
	my $nvidiaGPU = 0;
	my %nvidiaProductIDList = nvidiaProductID();

	if ((eGPUPossibleValue() ne '0') && !isNvidiaGPUInstalled()) {
		printLog("eGPU possibility detected", 'L0', '[DEBUG]');
		return (eGPUPossibleValue());
	}
	if (isNvidiaGPUInstalled()) {
		while (my ($index, $element) = each @lines) {
			if ($element =~ /.*030[02]:.*(10de|104a|12d2):([0-9a-f]{4}).*/) {
				$nvidiaGPU = 1;
				$deviceID = uc($2);
				$lines[$index+1] =~ /.*Subsystem: ([0-9a-f]{4}):([0-9a-f]{4})/;
				$subvendorIDGroup = uc($1) . uc($2);
				printLog("IDs found: $deviceID $subvendorIDGroup", 'L2', '[INFO]');
				last;
			}
		}
		if (exists $nvidiaProductIDList{$deviceID}{$subvendorIDGroup}) {
			printLog("found matching DeviceID with matching SubvendorIDs", 'L2', '[DEBUG]');
			printLog("found: $nvidiaProductIDList{$deviceID}{$subvendorIDGroup}{'name'}", 'L0', '[INFO]');
			printLog("type: $nvidiaProductIDList{$deviceID}{$subvendorIDGroup}{'type'}", 'L2', '[DEBUG]');
			return $nvidiaProductIDList{$deviceID}{$subvendorIDGroup}{'type'};
		} elsif (exists $nvidiaProductIDList{$deviceID}{'none'}) {
			printLog("found matching DeviceID with empty SubvendorIDs", 'L2', '[DEBUG]');
			printLog("found: $nvidiaProductIDList{$deviceID}{'none'}{'name'}", 'L0', '[INFO]');
			printLog("type: $nvidiaProductIDList{$deviceID}{'none'}{'type'}", 'L2', '[DEBUG]');
			return $nvidiaProductIDList{$deviceID}{'none'}{'type'};
		} elsif (exists $nvidiaProductIDList{$deviceID}) {
			printLog("found matching DeviceID with non matching SubvendorIDs", 'L2', '[DEBUG]');
			printLog("found a variant of: $nvidiaProductIDList{$deviceID}{'none'}{'name'}", 'L0', '[INFO]');
			printLog("type: $nvidiaProductIDList{$deviceID}{'none'}{'type'}", 'L2', '[DEBUG]');
			return $nvidiaProductIDList{$deviceID}{'none'}{'type'};
		} elsif (exists $nvidiaProductIDList{$deviceID}) {
			printLog("found matching DeviceID while ignoring SubvendorIDs", 'L2', '[DEBUG]');
			printLog("the GPUs name can't be determined exactly", 'L2', '[DEBUG]');
			foreach my $anyKey (keys %{ $nvidiaProductIDList{$deviceID} }) {
				$type = $nvidiaProductIDList{$deviceID}{$anyKey}{'type'};
				last;
			}
			printLog("type: $type", 'L2', '[DEBUG]');
			return $type;
		} elsif ($nvidiaGPU) {
			printLog("found NO matching DeviceID with fully matching or empty SubvendorIDs", 'L2', '[DEBUG]');
			printLog("Nvidia GPU not in the list => the open driver will be installed", 'L2', '[DEBUG]');
			return ('open');
		}
		printLog("this Nvidia GPU is not in our list, using open by default", 'L2', '[DEBUG]');
		return ('open');
	}
	return q{};
}



###############################################################################
# uses lspci output to find the Nvidia GPU
# returns 0 if none found
# returns 1 if the GPU was found
#
sub isNvidiaGPUInstalled {
	my @lines = split "\n", $lspciOutput;
	my $nvidiaGPU = 0;
	while (my ($index, $element) = each @lines) {
		if ($element =~ /.*030[02]:.*(10de|104a|12d2):.*/) {
			printLog("Nvidia GPU detected", 'L2', '[DEBUG]');
			return (1);
		}
	}
	return (0);
}




###############################################################################
# tries to guess whether eGPU handling is necessary
# returns 1 if yes
# returns 0 if no handling necessary
#
sub iseGPUHandlingRequired {
	if ($distributionType =~ /noble/) {
		my %nvidiaPackage = nvidiaPackage();
		if (isPackageInstalled($nvidiaPackage{'open'})) {
			eGPUPossibleValue('open');
			return (1);
		} elsif (isPackageInstalled($nvidiaPackage{'closed'})) {
			eGPUPossibleValue('closed');
			return (1);
		}
	}
	return (0);
}




###############################################################################
# sets/gets value for variable $eGPUPossible
#
sub eGPUPossibleValue {
	my $value = shift;
	if (defined $value) {
		$eGPUPossible = $value;
	} else {
		return $eGPUPossible;
	}
	return;
}




###############################################################################
# Finds all dependencies for a given package and returns them in an array
#
sub getPackageDependencies {
	my $package = shift;
	my $result;
	my @lines;
	my $pkgString;
	my @packages;
	my $searchCmd = $consoleLanguage.'apt-cache depends --important '.$package;
	$result = `$searchCmd`;
	@lines = split /\n/sm, $result;
	foreach my $line (@lines) {
		if ($line =~ /\s[\s|]Depends:\s[<]?([a-z0-9\-\_\.]+)[>]?/sm) {
			$pkgString = $1;
			printLog("found dependency: $pkgString", 'L2', '[DEBUG]');
			push @packages, $pkgString;
		}
	}
	return @packages;
}




###############################################################################
# Checks if the package is in any of the repositories
#
sub isPackageInRepo {
	my $package = shift;
	my $returnFromSearch = `apt-cache search --names-only ^$package\$`;
	chop($returnFromSearch);
	$returnFromSearch =~ s/ .*//sm;
	printLog("package: >$package< returnFromSearch: >$returnFromSearch<", '2', '[DEBUG]');
	if ($package eq $returnFromSearch) {
		printLog("package $package found in repos", 'L2', '[DEBUG]');
		return (1);
	} else {
		printLog("package $package can't be found in the repositories", 'L0', '[ERROR]');
		return (0);
	}
}





###############################################################################
# Deinstalls a package
# returns 1 if the search for the package failed or if the deinstallation was
# unsuccessful
# returns 0 if everything was ok, even if no package was found
#
sub deinstallSinglePackage {
	my $deinst = shift;
	my $aptgetDeinst = 'apt-get -yq remove -o Dpkg::lock::timeout=0';
	my $retValue;

	$aptgetDeinst .= " $deinst";
	printLog("deinstalling: $deinst", 'L0', '[INFO]');
	$aptgetDeinst .= " >/dev/null 2>&1";
	if (isPackageInstalled($deinst)) {
		deactivatePackagekit();
		if (unlockPM("$aptgetDeinst") && (isPMlocked() == 0)) {
			printLog("executing deinstallation", 'L2', '[DEBUG]');
			if (system($aptgetDeinst)) {
				$retValue = $?/$RETVAL_CONVERTER;
				lockPM();
				printLog("failed to deinstall package, retvalue: $retValue", 'L0', '[WARN]');
				return (1);
			} else {
				printLog('deinstallation of package successful', 'L0', '[INFO]');
				return (0);
			}
		} else {
			printLog("package deinstallation failed!! Could not get package manager lock", 'L0', '[WARN]');
			return (1);
		}
	} else {
		printLog("package $deinst is not installed", 'L0', '[WARN]');
		return (0);
	}
	return;
}





###############################################################################
# Checks if the package is installed on the system
# return 1 if yes
# return 0 if no
#
sub isPackageInstalled {
	my $package = shift;
	if ($package eq q{}) {
		return (0);
	}
	my $cmd = $consoleLanguage.'dpkg-query -W -f=\'${db:Status-Abbrev}\' '.$package.' 2>&1';
	my $retString = `$cmd`;
	my $retValue = $?/$RETVAL_CONVERTER;
	printLog("checking if $package is installed\nanswer: $retString", 'L2', '[DEBUG]');
	if ($retString =~ m/^[ih][i]\s$/sm) {
		printLog("package $package is installed: $retString", 'L1', '[DEBUG]');
		return (1);
	} else {
		printLog("package $package is not installed: $retString", 'L1', '[DEBUG]');
		return (0);
	}
}



###############################################################################
# returns the package version string or '0' if the return value from
# dpkg-query was an empty string
#
sub getPackageVersion {
	my $package = shift;
	my $cmd = $consoleLanguage.'dpkg-query --showformat=\'${Version}\' --show '.$package.' 2>&1';
	my $retString = q{};
	$retString = `$cmd`;
	my $retValue = $?/$RETVAL_CONVERTER;
	printLog("version string for $package: $retString retValue: $retValue", 'L2', '[DEBUG]');
	if ($retValue == 0) {
		$retString =~ s/.*://sm;
		if ($retString ne q{}) {
			printLog("package $package has version: $retString", 'L1', '[DEBUG]');
			return $retString;
		} else {
			printLog("package $package is probably not installed, can't get its version number", 'L1', '[WARN]');
			return q{};
		}
	} else {
		printLog("package $package is probably not installed, can't get its version number", 'L1', '[WARN]');
		return q{};
	}
}




###############################################################################
# installs a package by apt-get
# returns 1 if successful
# returns 0 if package was not installed
#
sub installPackage {
	my ( $packageName, $module ) = @_;
	my $retval;
	if (isPackageInstalled($packageName)) {
		return (1);
	}
	if ($FAI) {
		printLog("install package $packageName", 'TL0', '[INFO]');
		# usefull debug option: Debug::pkgDPkgProgressReporting=true
		if ($logLevel > 1) {
			$retval = `apt-get -o Debug::pkgDPkgProgressReporting=true -o Dpkg::lock::timeout=0 -y install $packageName`;
		} else {
			$retval = `apt-get -y install -o Dpkg::lock::timeout=0 $packageName`;
		}
		printLog("return from apt-get:\n$retval", 'TL2', '[DEBUG]');
		$retval = isPackageInstalled($packageName);
	} elsif (!($LiveISO && $noNetwork)) {
		if (checkNetwork( $NETWORK_CHECKS, 1)) {
			messageLongInstall();
			printLog("install package $packageName", 'L0', '[INFO]');
			deactivatePackagekit();
			if (unlockPM("apt-get -yq install $packageName") && (isPMlocked() == 0)) {
				printLog("installing $packageName", 'L2', '[DEBUG]');
				if ($logLevel > 1) {
					$retval = `apt-get -yq -o Dpkg::lock::timeout=0 install $packageName >>$logFile 2>&1`;
				} else {
					$retval = `apt-get -yq -o Dpkg::lock::timeout=0 install $packageName >/dev/null 2>&1`;
				}
				lockPM();
				printLog("return from apt-get:\n$retval", 'L2', '[DEBUG]');
				$retval = isPackageInstalled($packageName);
				if ($retval == 0) {
					printLog("failed to install $packageName", 'L0', '[WARN]');
					printLog("will try again later", 'L0', '[WARN]');
					# repair a possibly wrecked package manager
					if (unlockPM('dpkg --configure -a') && (isPMlocked() == 0)) {
						printLog("executing --configure -a", 'L2', '[DEBUG]');
						`dpkg --configure -a`;
						lockPM();
						startTomteDelayed();
					} else {
						printLog('Could not repair package management, will try again later', 'L0', '[WARN]');
						startTomteDelayed();
					}
				} elsif ($retval == 1) {
					if ($presetModules{$module}{restart} eq 'yes') {
						setRestartValues($module);
					}
				}
			} else {
				printLog("failed to get package management lock to install $packageName!!", 'L0', '[WARN]');
				startTomteDelayed();
				return (0);
			}
		} else {
			printLog("no network connection found, can't install packages!!", 'TL0', '[WARN]');
			startTomteDelayed();
		}
	} else {
		printLog("not installing package $packageName because no network detected", 'L0', '[WARN]');
		startTomteDelayed();
	}
	return $retval;
}




###############################################################################
# fills the hash value with all the available packages
#
sub initAvailablePackagesList {
	@availableDebianPackages = `apt-cache pkgnames`;
	chomp(@availableDebianPackages);
	return (0);
}




###############################################################################
# isPackageAvailable( $package ) where package might be a regular expression !!
#
sub isPackageAvailable {
	my $package = shift;
	if ( grep( /^$package$/sm, @availableDebianPackages ) ) {
		return (1);
	}
	return (0);
}



###############################################################################
# setPackageInstallStatus( package, status )
# sets the installation status of a package and writes it into the hash
#
sub setPackageInstallStatus {
	my ($package, $status) = @_;
	$packageHash{$package} = $status;
	return;
}




###############################################################################
# removes a package by apt-get
# parameters are parameters for apt-get
# removePackage( packagename, parameters )
# returns 1 if successful or not installed
# returns 0 if failed
#
sub removePackage {
	my ($packageName, $parameter) = @_;
	my $retval;
	printLog("remove package $packageName", 'TL0', '[INFO]');
	deactivatePackagekit();
	if (!defined($parameter)) {
		printLog("parameter was undefined", 'L2', '[WARN]');
		$parameter = q{};
	}
	if (unlockPM("apt-get -yq remove $parameter $packageName") && (isPMlocked() == 0)) {
		printLog("removing $packageName", 'L2', '[DEBUG]');
		$retval = `$consoleLanguage apt-get -yq -o Dpkg::lock::timeout=0 remove $parameter $packageName >/dev/null 2>&1`;
		lockPM();
		printLog("return from apt-get:\n$retval", 'L2', '[DEBUG]');
		$retval = isPackageInstalled($packageName);
		if ($retval == 1) {
			printLog("failed to remove $packageName", 'L0', '[WARN]');
			printLog("will try again later", 'L0', '[INFO]');
			startTomteDelayed();
			return (0);
		}
	} else {
		printLog("locks for package management could not be unlocked!!", 'L0', '[WARN]');
		startTomteDelayed();
		return (0);
	}
	return (1);
}




###############################################################################
# sets mark to auto or manual
# usage setAptMark('package_name','auto|manual')
# returns 1 if set
# returns 0 if not
#
sub setAptMark {
	my ($package, $mark) = @_;
	my $exitStatus;
	my $retString;
	my $aptMark = getAptMark($package);
	if ( $aptMark && ($mark eq 'manual') ) {
		printLog("package $package is set to \"auto\", changing to \"manual\" now", 'L1', '[INFO]');
		$retString = `$consoleLanguage apt-mark manual $package`;
		$exitStatus = $?;
		printLog("result: $retString exitStatus: $exitStatus", 'L2', '[INFO]');
		if (($retString =~ /set to manually installed/sm) && ($exitStatus == 0)) {
			printLog("return value confirmed set to manually installed", 'L2', '[DEBUG]');
			return (1);
		} else {
			printLog("desired mark: not set !!!", 'L0', '[ERROR]');
			return (0);
		}
	} elsif ( !$aptMark && ($mark eq 'auto') ) {
		printLog("package $package is set to \"manual\", changing to \"auto\" now", 'L1', '[INFO]');
		$retString = `$consoleLanguage apt-mark auto $package`;
		$exitStatus = $?;
		printLog("result: $retString exitStatus: $exitStatus", 'L2', '[INFO]');
		if (($retString =~ /set to automatically installed/sm) && ($exitStatus == 0)) {
			printLog("return value confirmed set to automatically installed", 'L2', '[DEBUG]');
			return (1);
		} else {
			printLog("desired mark: not set !!!", 'L0', '[ERROR]');
			return (0);
		}
	}
}




###############################################################################
# gets apt-mark auto or manual
# returns 1 if auto
# returns 0 if manual or package does not exist
#
sub getAptMark {
	my $package = shift;
	my $retVal;
	$retVal = `$consoleLanguage apt-mark showauto $package`;
	if ( $retVal =~ /$package/mgs ) {
		printLog("package: $package mark is auto", 'L2', '[DEBUG]');
		return (1);
	} else {
		printLog("package: $package mark is manual", 'L2', '[DEBUG]');
		return (0);
	}
}



###############################################################################
# installs a linux kernel from the name of the main metapackage
# installKernelFlavour('metapackageName', \$failed, \$somethingDone)
# reqires two parameters failed and nothingDone
# returns 0 if something went wrong
# returns 1 if subroutine run through
#
sub installKernelFlavour {
	my ($flavour, $failed, $somethingDone) = @_;
	${$failed} = 0;
	${$somethingDone} = 0;
	my $installCmd = 'apt-get install -yq -o Dpkg::lock::timeout=0';
	my $retString;
	my $exitStatus;

	if (!(exists $kernelsList{$distributionType}{$flavour})) {
		printLog("flavour $flavour does not exist!!", 'TL0', '[FATAL]');
		${$failed} += 1;
		return (0);
	}

	$installCmd .= " $kernelsList{$distributionType}{$flavour}";
	$installCmd .= ' 2>&1';
	printLog("install kernel metapackage", 'L2', '[INFO]');
	printLog("install command: $installCmd", 'L2', '[DEBUG]');
	if (unlockPM("$installCmd") && (isPMlocked() == 0)) {
		printLog("executing installation", 'L2', '[DEBUG]');
		$retString = `$installCmd`;
		$exitStatus = $?/$RETVAL_CONVERTER;
		lockPM();
		printLog("return from install:\n$retString", 'L2', '[DEBUG]');
		if (($retString =~ /is already the newest version/sm) && ($exitStatus == 0)) {
			printLog("$kernelsList{$distributionType}{$flavour} is already the newest version", 'L2', '[INFO]');
		} elsif ($exitStatus == 0) {
			${$somethingDone} = 1;
			printLog("$kernelsList{$distributionType}{$flavour} has been installed", 'L2', '[INFO]');
		} elsif ($exitStatus != 0) {
			printLog("installation of kernel meta package failed !!!", 'L0', '[ERROR]');
			printLog("$retString", 'L0', '[ERROR]');
			if ($retString =~ /dpkg --configure -a/) {
				printLog("package management demands repair", 'L0', '[ERROR]');
				if (unlockPM('dpkg --configure -a') && (isPMlocked() == 0)) {
					printLog("executing --configure -a", 'L0', '[DEBUG]');
					`dpkg --configure -a`;
					lockPM();
				} else {
					printLog('Could not repair package management, will try again later', 'L0', '[WARN]');
				}
				startTomteDelayed();
			}
			${$failed} += 1;
		}
	}

	printLog("failed: ${$failed}, somethingdone: ${$somethingDone}", 'L2', '[ERROR]');
	return (1);
}




###############################################################################
# accepts a kernel version and returns an array with all dependant packages
# found for linux-image-$version-tuxedo, linux-headres-$version-tuxedo and
# linux-modules-extra-$version-tuxedo
# returns an empty string if nothing was found or the version entry is wrong
sub populateKernelPackages {
	my $version = shift;
	if ($version =~ /\s/sm) {
		printLog("something is wrong with the kernel version number", 'L0', '[ERROR]');
		return q{};
	}
	my @rootPackages = ("linux-headers-$version-tuxedo", "linux-image-$version-tuxedo");
	my @packages;
	my @packagesUnique;
	my $packageLine;
	my $packageString;
	my $searchCommand;
	my $result;
	my @lines;
	$searchCommand = $consoleLanguage."apt-cache depends --important @rootPackages";
	printLog("first searchcommand: $searchCommand", 'L2', '[DEBUG]');
	$result = q{};
	$result = `$searchCommand`;
	printLog("first result:\n$result", 'L2', '[DEBUG]');
	@lines = split /\n/sm, $result;
	foreach my $lineFirstResult (@lines) {
		if ($lineFirstResult =~ /\s[\s|]Depends:\s[<]?([a-z0-9\-\_\.]+)[>]?/sm) {
			$packageString = $1;
			if ($packageString =~ /^linux-.*(?:image|headers|modules|unsigned)-.*/sm) {
				printLog("Accepted: $packageString", 'L2', '[DEBUG]');
				$packageLine .= " $packageString";
				push @packages, $packageString;
			}
		}
	}
	if (@packages) {
		$searchCommand = $consoleLanguage.'apt-cache depends --important --installed '.$packageLine;
		printLog("second searchcommand: $searchCommand", 'L2', '[DEBUG]');
		$result = q{};
		$result = `$searchCommand`;
		printLog("second result:\n$result", 'L2', '[DEBUG]');
		@lines = ();
		@lines = split /\n/sm, $result;
		foreach my $lineSecondResult (@lines) {
			if ($lineSecondResult =~ /\s[\s|]Depends:\s[<]?([a-z0-9\-\_\.]+)[>]?/sm) {
				$packageString = $1;
				if ($packageString =~ /^linux-.*(?:image|headers|modules|unsigned)-.*/sm) {
					printLog("Accepted: $packageString", 'L2', '[DEBUG]');
					push @packages, $packageString;
				}
			}
		}
	}
	$searchCommand = $consoleLanguage.'dpkg-query -W -f=\'${db:Status-Abbrev}\' '."linux-modules-extra-$version-tuxedo".' 2>&1';
	$result = `$searchCommand`;
	if ($result =~ m/^[uihrp][HUFWti]\s$/sm) {
		push @packages, "linux-modules-extra-$version-tuxedo";
	}
	push @packages, ("linux-headers-$version-tuxedo", "linux-image-$version-tuxedo");
	@packagesUnique = uniqInArray(@packages);
	printLog("kernel packages found: @packagesUnique", 'L2', '[DEBUG]');

	# safety check: something went wrong if we got more than 20 packages for removal
	if (scalar(@packagesUnique) < $MAX_KERNEL_PACKAGES_REMOVE) {
		return (@packagesUnique);
	} else {
		return q{};
	}
	return;
}





###############################################################################
# receives an array
# returns an array where all values are unique
#
sub uniqInArray {
	my %seen;
	return grep { !$seen{$_}++ } @_;
}






###############################################################################
# deletes the oldest remnant linux header
# returns 0 if nothing was done
# returns 1 if some linux header was deleted
sub checkAndDeleteOldestRemnantLinuxHeader {
	# Check if the tries file exists
	if (-e $triesFile) {
		printLog("won't delete oldest header because Tomte is running in retry-mode", "L2", '[INFO]');
		return (0);
	}

	my %headers;
	# Get the list of installed linux-tuxedo headers using dpkg-query
	# example: linux-tuxedo-5.15-headers-5.15.0-10033
	my @headersList = split /\n/sm, qx(dpkg-query -W -f='\${db:Status-Status}=\${Version}=\${Package}\nq{ }linux-tuxedo-*-headers-*');

	# Check each header line and store installed headers with their versions
	foreach my $line (@headersList) {
		chomp $line;
		my ($status, $version, $package) = split /=/sm, $line, $MAX_HEADERS_TO_ANALYZE;
		if (
			# Check if $package is installed
			"installed" eq $status &&

			# Check if $package matches the expected pattern
			$package =~ /^linux-tuxedo-.*-headers-.*$/sm &&

			# Check if $package does not contain any whitespace characters
			$package !~ /\s/sm &&

			# Check if the length of $package exceeds 50 characters
			length($package) < $MAX_PACKAGE_NAME

		) {
			$headers{$version} = $package;
		}
	}

	my %images;
	# Get the list of installed linux-image packages using dpkg-query
	# example: linux-image-5.15.0-10025-tuxedo
	my @imagesList = split /\n/sm, qx(dpkg-query -W -f='\${db:Status-Status}=\${Version}=\${Package}\nq{ }linux-image-*-tuxedo');

	# Check each images line and store installed headers with their versions
	foreach my $line (@imagesList) {
		chomp $line;
		my ($status, $version, $package) = split /=/sm, $line, $MAX_TO_DELETE_KERNEL;
		if ("installed" eq $status){
			$images{$version} = $package;
		}
	}

	# Get the current kernel version by removing the "-tuxedo" suffix
	my $currentKernelVersion = `uname -r`;
	$currentKernelVersion =~ s/-tuxedo$//sm;

	my @headersOnly;
	# Get the headers where no corresponding image exists
	foreach my $key (keys %headers) {
		# Skip if there is a corresponding image for the header version
		if (exists $images{$key}) {
			next;
		}

		# Skip if the header version matches the current kernel version
		# header version can be 6.5.0-10005.5 but currentKernelVersion is then just 6.5.0-10005
		if ($key =~ /$currentKernelVersion/sm) {
			next;
		}

		# Add the header version to the @headersOnly array
		push @headersOnly, $key;
	}

	# If there are headers without corresponding images
	if (@headersOnly) {
		# Sort the headers in ascending order
		@headersOnly = sort { version_compare($a, $b) } @headersOnly;

		# Get the oldest header package name
		my $oldestHeader = $headers{$headersOnly[0]};

		# Safety check: Verify the format and length of $oldestHeader
		if (
			# Check if $oldestHeader does not match the expected pattern
			$oldestHeader !~ /^linux-tuxedo-.*-headers-.*$/sm ||

			# Check if $oldestHeader contains any whitespace characters
			$oldestHeader =~ /\s/sm ||

			# Check if the length of $oldestHeader exceeds 50 characters
			length($oldestHeader) > $MAX_OLDEST_HEADER_NAME
		) {
				printLog("Skipping removal of header $oldestHeader due to invalid format or length", "L0", '[WARN]');
				return 0;
		}

		printLog("Removing header $oldestHeader", "L2", '[INFO]');

		# Construct the apt-get purge command to remove the oldest header
		my $purgeCommand = $consoleLanguage."apt-get purge -o Dpkg::lock::timeout=0 --assume-yes $oldestHeader";
		printLog("purge command: $purgeCommand", 'L2', '[DEBUG]');

		# Attempt to unlock the package management and check if it's unlocked
		if (unlockPM("purge command: $purgeCommand") && (isPMlocked() == 0)) {
			# Execute the purge command and capture the result and error
			printLog("executing purge", 'L2', '[DEBUG]');
			my $result = `$purgeCommand`;
			my $error = $?;
			lockPM();
			printLog("result: $result\nerror: $error", 'L2', '[DEBUG]');

			# Log the success message
			printLog("successfully removed old header $oldestHeader", 'L0', '[INFO]');
			return 1;
		} else {
			# Log the failure message if package management is busy
			printLog("could not remove the header $oldestHeader as package management is busy", 'L0', '[WARN]');
		}
	}
	return 0;
}




###############################################################################
# deletes the oldest TUXEDO kernel by version
# returns 0 if nothing was done
# returns 1 if some kernel was deleted
sub checkAndDeleteOldestTuxedoKernel {
	my $DH;
	my %fileListInitrd;
	my %fileListVmlinuz;
	my $fileSize;
	my $version;
	my $file;
	my $purgeCommand;
	my $bootDir = '/boot';

	if (-e $triesFile) {
		printLog("won't delete oldest kernel because Tomte is running in retry-mode", "L2", '[INFO]');
		return (0);
	}

	if (opendir($DH,$bootDir)) {
		while ($file = readdir($DH)) {
			if ($file =~ /initrd\.img-(.*)-tuxedo$/sm) {
				printLog("found file: $file", 'L2', '[DEBUG]');
				$fileSize = -s "$bootDir/$file";
				printLog("size: $fileSize", 'L2', '[DEBUG]');
				if (($fileSize) > $MAX_FILESIZE_KERNEL_DELETE) {
					printLog("filesize accepted into hash-list", 'L2', '[DEBUG]');
					$fileListInitrd{$1} = $fileSize;
				}
			}
			if ($file =~ /vmlinuz-(.*)-tuxedo/sm) {
				printLog("found file: $file", 'L2', '[DEBUG]');
				$fileSize = -s "$bootDir/$file";
				printLog("size: $fileSize", 'L2', '[DEBUG]');
				if (($fileSize) > $MAX_FILESIZE_KERNEL_DELETE) {
					printLog("filesize accepted into hash-list", 'L2', '[DEBUG]');
					$fileListVmlinuz{$1} = $fileSize;
				}
			}
		}
	} else {
		printLog("could not open $bootDir", 'L2', '[ERROR]');
		return (0);
	}

	my @fileListResult;
	foreach my $versionKey (keys %fileListInitrd) {
		if (exists $fileListVmlinuz{$versionKey}) {
			printLog("matching vmlinuz found with version: $versionKey", 'L2', '[DEBUG]');
			push (@fileListResult, $versionKey);
		}
	}

	printLog("all found versions: @fileListResult", 'L2', '[DEBUG]');

	my $keepKernels = 3;
	if (scalar(@fileListResult) > $keepKernels) {
		printLog("more than $keepKernels kernels found, will delete the oldest one", 'L2', '[INFO]');
		@fileListResult = sort { version_compare($b, $a) } @fileListResult;
		printLog("sorted filelist: @fileListResult", 'L2', '[DEBUG]');
		$version = q{};
		$version = $fileListResult[-1];
		my $currentKernelVersion = `uname -r`;
		$currentKernelVersion =~ s/-tuxedo$//sm;
		if (! ($currentKernelVersion =~ /$version/sm)) {
			printLog("not current running kernel => trying to delete version: $version", 'L2', '[INFO]');
			# get all related kernel packages
			my @packageList = populateKernelPackages($fileListResult[-1]);
			# purge package
			$purgeCommand = $consoleLanguage."apt-get purge -o Dpkg::lock::timeout=0 --assume-yes @packageList";
			printLog("purge command: $purgeCommand", 'L2', '[DEBUG]');
			my $result;
			my $error;
			if (unlockPM("purge command: $purgeCommand") && (isPMlocked() == 0)) {
				printLog("executing purge", 'L2', '[DEBUG]');
				$result = `$purgeCommand`;
				$error = $?;
				lockPM();
				printLog("result: $result\nerror: $error", 'L2', '[DEBUG]');

				# log success
				printLog("successfully removed old kernel $version", 'L0', '[INFO]');

			} else {
				printLog("could not remove the kernel $version as package management is busy", 'L0', '[WARN]');
			}
		} else {
			printLog("kernel version ($version) to delete is the same as the one currently running => abort", 'L0', '[WARN]');
			return (0);
		}
	} else {
		printLog("less than 4 kernels present => nothing to do", 'L2', '[DEBUG]');
		return (0);
	}
	return;
}



###############################################################################
# removes a module containing a debian pakage
# removeDebianModule(module, restart, parameters)
# module is the short modulename
# if restart = 0 => don't restart
# if restart = 1 => restart
# checks if the package is installed
# returns 1 if successfull
# returns 0 if not successfull
#
sub removeDebianModule {
	my ($module, $restart, $parameter) = (@_);
	my $success = 0;
	if (isPackageInstalled($presetModules{$module}{name})) {
		if (removePackage($presetModules{$module}{name}, "$parameter")) {
			addToConfiguredModules($module, "removed");
			$presetModules{$module}{installed} = "no";
			$presetModules{$module}{upgraded} = "yes";
			$presetModules{$module}{required} = "no";
			if ($restart =~ /yes/) {
				setRestartValues($module);
			}
			$success = 1;
		} else {
			printLog("$presetModules{$module}{name} remove failed", 'L0', '[INFO]');
			addToConfiguredModules($module, "remove failed");
			$presetModules{$module}{installed} = "remove failed";
			$presetModules{$module}{version} = $localConfModules{$module}{version};
			$presetModules{$module}{hwid} = $localConfModules{$module}{hwid};
			$success = 0;
		}
	} else {
		printLog("$presetModules{$module}{name} is not installed, which is the desired result", 'TL0', '[INFO]');
		addToConfiguredModules($module, "nothingToDo");
		$presetModules{$module}{installed} = "no";
		$presetModules{$module}{upgraded} = "yes";
		$presetModules{$module}{required} = "no";
		$success = 1;
	}
	return $success;
}




###############################################################################
# removes kernel parameter from grub
# removeKernelParameters($module, $silent, @kernelParameters)
# where $silent = 0 means normal operation and
# $silent = 1 means that it will not activate addToConfiguredModules
# returns amount of parameters found
# returns 0 if nothing found or changed
#
sub removeKernelParameters {
	my ($module, $silent, @parameters) = @_;
	my $GF;
	my @grubLines;
	my $counter = 0;
	if ((-r $grubFile) && (open($GF, '<', $grubFile))) {
		chomp(@grubLines = <$GF>);
		if (! close($GF)) {
			printLog("could not close $GF", 'L0', '[WARN]');
		}
		if ((-w $grubFile) && (open($GF, '>', $grubFile))) {
			foreach my $line (@grubLines) {
				if ($line =~ /^GRUB_CMDLINE_LINUX_DEFAULT=["|']/s) {
					foreach my $parameter (@parameters) {
						printLog("removing kernel parameter: $parameter", 'L2', '[INFO]');
						if ($line =~ /^GRUB_CMDLINE_LINUX_DEFAULT=("|')(.*\ )$parameter(\ .*)("|')/s) {
							$line =~ s/\ $parameter\ /\ /s;
							++$counter;
						} elsif ($line =~ /^GRUB_CMDLINE_LINUX_DEFAULT=".*\ $parameter"/s) {
							$line =~ s/\ $parameter"/"/s;
							++$counter;
						} elsif ($line =~ /^GRUB_CMDLINE_LINUX_DEFAULT='.*\ $parameter'/s) {
							$line =~ s/\ $parameter'/'/s;
							++$counter;
						} elsif ($line =~ /^GRUB_CMDLINE_LINUX_DEFAULT="$parameter(\ .*)"/s) {
							$line =~ s/"$parameter\ /"/s;
							++$counter;
						} elsif ($line =~ /^GRUB_CMDLINE_LINUX_DEFAULT='$parameter(\ .*)'/s) {
							$line =~ s/'$parameter\ /'/s;
							++$counter;
						}
						$line =~ s/ +/ /g;
						$line =~ s/=" /="/s;
						$line =~ s/ "$/"/s;
					}
				}
				print $GF "$line\n";
			}
			if (! close($GF) ) {
				printLog("could not close $grubFile", 'L0', '[ERROR]');
			}

			# return if no module was given, as no modules may be configured
			if ((!defined($module)) || ($module eq q{})) {
				return ($counter);
			}

			if (! $silent) {
				addToConfiguredModules($module, "removed");
			}
			$presetModules{$module}{installed} = "no";
			$presetModules{$module}{upgraded} = "yes";
			if ($counter > 0) {
				$postConfProgramsList{updateGrub}{trigger} = 1;
			}
			return ($counter);
		} else {
			printLog("could not open and write $grubFile", 'TL0', '[ERROR]');
			if (!$silent) {
				addToConfiguredModules($module, "remove failed");
			}
			$presetModules{$module}{installed} = "remove failed";
			$presetModules{$module}{version} = $localConfModules{$module}{version};
			$presetModules{$module}{hwid} = $localConfModules{$module}{hwid};
			return (0);
		}
	} else {
		printLog("could not open and read $grubFile", 'TL0', '[ERROR]');
		if (! $silent) {
			addToConfiguredModules($module, "remove failed");
		}
		$presetModules{$module}{installed} = "remove failed";
		$presetModules{$module}{version} = $localConfModules{$module}{version};
		$presetModules{$module}{hwid} = $localConfModules{$module}{hwid};
		return (0);
	}
	return (0);
}




###############################################################################
# setNoNetwork( 0 or 1 )
# sets the network availability variable
# 0 = network available
# 1 = no network
#
sub setNoNetwork {
	my $value = shift;
	if ($LiveISO) {
		if ($value == 1) {
			printLog("setting permanently for this session no network available", 'L0', '[INFO]');
			$noNetwork = 1;
		}
	} else {
		$noNetwork = $value;
	}
	return (1);
}



###############################################################################
# refreshes the package manager database
# needs a module name to set the result and the amount of retries it should do
# in case it fails the first time for some reason
# aptgetRefreshModule($module, $retries)
#
sub aptgetRefreshModule {
	my ($refreshTries, @aptgetModules) = @_;
	my $output;
	my $exitStatus = 1;
	my $refreshTriesDone = 0;
	my $refreshWait = 1;
	if ( !($LiveISO && $noNetwork)) {
		if (checkNetwork( $NETWORK_CHECKS, 1 )) {
			deactivatePackagekit();
			while (($exitStatus != 0) && ($refreshTriesDone <= $refreshTries)) {
				my $command = $consoleLanguage.'apt-get update 2>/dev/null';
				if(unlockPM($command) && (isPMlocked() == 0)) {
					printLog("executing apt-get update", 'L2', '[DEBUG]');
					$output = `$command`;
					$exitStatus = $?/$RETVAL_CONVERTER;
					initAvailablePackagesList();
					lockPM();
					if ($exitStatus != 0) {
						printLog('failed: update packages', 'L0', '[ERROR]');
						printLog("output: $output error code: $exitStatus", 'L0', '[ERROR]');
						if ($exitStatus == 7) {
							printLog('possible network problems', 'L0', '[WARN]');
						}
						foreach my $module (@aptgetModules) {
							addToConfiguredModules($module, "failed");
							if ($refreshTriesDone <= $refreshTries) {
								printLog("will try to refresh the repos again", 'L2', '[INFO]');
							}
						}
						$refreshTriesDone++;
						sleep($refreshWait);
					} else {
						printLog("success: update packages", 'L1', '[INFO]');
						foreach my $module (@aptgetModules) {
							addToConfiguredModules($module, "success");
						}
					}
				} else {
					printLog("failed to get package management lock to update package management for >@aptgetModules<", 'L0', '[WARN]');
					startTomteDelayed();
					return(0);
				}
			}
		} else {
			printLog("no network available, repos could not be updated", 'TL0', '[WARN]');
			foreach my $module (@aptgetModules) {
				addToConfiguredModules($module, "failed");
			}
			setNoNetwork(1);
			return (0);
		}
	} else {
		printLog("not refreshing package management DB as no network available", 'L0', '[WARN]');
		setNoNetwork(1);
		return (0);
	}
	return;
}



###############################################################################
# backups file with timestamp
#
sub backupFile {
	my $fileName = shift;
	my $backupFile = $fileName.'_'.getBackupFileTime().'.bak';
	if (copy($fileName, $backupFile)) {
		printLog("created backup for $fileName", 'TL', '[INFO]');
		return (1);
	} else {
		printLog("Err: $!", 'L0', '[ERROR]');
		printLog("can't create backup file for $fileName", 'TL0', '[ERROR]');
		return (0);
	}
}



###############################################################################
# isLineInFile('filename', 'string')
# matches a line with the 'string' as RegEx(!!!) and returns
# 1 if found
# 0 if not found or failed to open file
#
sub isLineInFile {
	my $filename = shift;
	my $match = shift;
	my $FH;
	if ( (-e $filename) && open($FH, '<', $filename) ) {
		while( <$FH> ) {
			if ( $_ =~ /^$match$/sm) {
				return (1);
			}
		}
		if (! close($FH)) {
			printLog("can't close file $filename", 'L0', '[WARN]');
		}
	} else {
		printLog("can't open file $filename", 'L1', '[WARN]');
	}
	return (0);
}



###############################################################################
# isTextInFile('filename', 'string')
# matches a line with the 'string' and returns
# 1 if found
# 0 if not found or failed to open file
#
sub isTextInFile {
	my $filename = shift;
	my $match = shift;
	my $FH;
	if ( (-e $filename) && open($FH, '<', $filename) ) {
		if(! defined $FH) {
			printLog("Filehandle NOT defined for $filename", 'TL0', '[WARN]');
			return (0);
		}
		while( <$FH> ) {
			if ( $_ =~ /$match/sm) {
				return (1);
			}
		}
		if (! close($FH)) {
			printLog("can't close file $filename", 'L0', '[WARN]');
		}
	} else {
		printLog("can't open file $filename", 'L1', '[WARN]');
	}
	return (0);
}




###############################################################################
# returns 1 if active interface found
# returns 0 if none found
#
sub networkStatus {
	my $dirHandle;
	my $interfacesDir = '/sys/class/net';
	if ( !opendir ($dirHandle, $interfacesDir)) {
		printLog("unable to open $interfacesDir: $!", 'L0', '[WARN]');
		return (0);
	}
	my @FILES = grep { $_ ne q{.} && $_ ne q{..} && $_ ne q{lo} } readdir($dirHandle);
	if (scalar(@FILES) < 1) { return (0); }
	foreach my $file (@FILES) {
		if (readFirstLineOfFile("$interfacesDir/$file/operstate") eq 'up' ) {
			return (1);
		}
	}
	return (0);
}



###############################################################################
# waitForNetwork('number of tries', 'wait seconds')
# polls for a network interface for status up
# tries 'number of tries' times and waits in between for 'wait seconds' seconds
# returns 1 if network is active
# returns 0 if network not active
#
sub waitForNetwork {
	my $triesNbr = shift;
	my $wait = shift;
	# only check network status repeatedly $triesNbr times the first time
	my $triesAlreadyDone = triesAlreadyDone();
	if ($triesAlreadyDone < 1) {
		for ( my $i=1; $i<=$triesNbr; $i++ ) {
			if (networkStatus()) {
				return (1);
			}
			printLog("waiting for network: $i of $triesNbr", 'L2', '[INFO]');
			sleep ($wait);
		}
	} else {
		if (networkStatus()) { return (1); }
		printLog("done waiting for network", 'L2', '[INFO]');
	}
	return (0);
}




###############################################################################
# checkNetwork('number of tries', 'delay between checks')
# returns 1 if found
# returns 0 if not found
#
sub checkNetwork {
	my $triesNbr = shift;
	my $wait = shift;
	if ((! $FAI) && ($noNetwork == 0)) {
		if (waitForNetwork($triesNbr, $wait)) {
			setNoNetwork(0);
			printLog('found network !', 'L1', '[INFO]');
			return (1);
		} else {
			setNoNetwork(1);
			printLog('no network found!! some fixes might not be applied correctly', 'L0', '[WARN]');
			startTomteDelayed();
			return (0);
		}
	}
	return (0);
}




###############################################################################
# inserts array values as kernel parameters if not present
# example: my $retval = insertGrub(\@kernelParameters);
# returns number of inserted parameters
# return value of -1 means it failed
#
sub insertGrub {
    my $grubFH;
    my @grubLines;
	my $module = shift;
    my $parameters = shift;
	my $inserted = 0;

	if ( (-e $grubFile) && open($grubFH, "<", $grubFile) ) {
		while (<$grubFH>) {
	    	push (@grubLines, $_);
		}
		if (! close($grubFH)) {
			printLog("Can't close $grubFile: $!", 'L0', '[ERROR]');
		}
    } else {
		printLog("Err: $!", 'L0', '[ERROR]');
		printLog("no $grubFile present or unable to open the file for reading", 'L0', '[ERROR]');
		return ($NEG_ONE);
    }

    foreach my $grubLine ( @grubLines ) {
		# find the correct line
		if ( $grubLine =~ /^GRUB_CMDLINE_LINUX_DEFAULT=["']/sm ) {
			foreach my $parameter ( @{$parameters} ) {
				if (insertGrubParameter(\$grubLine, \$parameter)) {
					++$inserted;
				}
			}
		}
    }

	# write the changes into grub config
    if ( open($grubFH, ">", $grubFile) ) {
		print $grubFH @grubLines;
		if (! close($grubFH)) {
			printLog("Can't close $grubFile", 'L0', '[ERROR]');
		}
    } else {
		printLog("Err: $!", 'L0', '[ERROR]');
		printLog("no $grubFile present or unable to open the file for writing", 'L0', '[ERROR]');
		return ($NEG_ONE);
    }
    return $inserted;
}


###############################################################################
# inserts a parameter into a grub line
# the line must begin with: 'GRUB_CMDLINE_LINUX_DEFAULT='
# if the parameter is not present yet
# returns 1 if parameter inserted
# returns 0 if parameter already present
# returns -1 if line is wrong
#
sub insertGrubParameter {
	my ($line, $parameter) = @_;
	my $cleanLine;

	if ( !($$line =~ /^GRUB_CMDLINE_LINUX_DEFAULT=/sm)) {
		printLog("grub kernel parameter line is malformed, can't insert $$parameter", 'L0', '[ERROR]');
		return ($NEG_ONE);
	}

	$cleanLine = $$line;
	$cleanLine =~ s/^\s+//sm;
	$cleanLine =~ s/\s+$//ism;
	$cleanLine =~ s/["']//gsm;
	$cleanLine =~ s/^GRUB_CMDLINE_LINUX_DEFAULT=//sm;

	if (($cleanLine =~ /^$$parameter /sm) || ($cleanLine =~ /^$$parameter$/sm) || ($cleanLine =~ / $$parameter$/sm) || ($cleanLine =~ / $$parameter /sm)) {
		printLog("$$parameter is already in grub", 'L2', '[DEBUG]');
		return (0);
	} else {
		if ($$line =~ /GRUB_CMDLINE_LINUX_DEFAULT="/sm) {
			$$line =~ s/^(GRUB_CMDLINE_LINUX_DEFAULT=".*)"/$1 $$parameter"/sm;
		}
		if ($$line =~ /GRUB_CMDLINE_LINUX_DEFAULT='/sm) {
			$$line =~ s/^(GRUB_CMDLINE_LINUX_DEFAULT='.*)'/$1 $$parameter'/sm;
		}
		printLog("inserted '$$parameter' into grub line: $$line", 'L1', '[INFO]');
		return (1);
	}
}






###############################################################################
# send message to desktop
# returns 1 if message was sent
# returns 0 if message could not be delivered for any reason
# icon:
# 	dialog-warning
# 	dialog-error
# 	dialog-information
# 	freedesktop.org
# urgency:
# 	0 : Low
# 	1 : Normal
# 	2 : Critical
#
sub messageDesktop {
	my $summary = shift;
	my $body = shift;
	my $urgency = shift;
	my $icon = shift;
	my $retValue = 0;

	my $mUsername = q{};
	my $mPid = q{};
	my @whoLines = `who -u`;
	my $environ = q{};
	my $dbusAddress;

	# don't message the desktop if LiveISO or FAI
	if ($LiveISO || $FAI) {
		return (0);
	}

	foreach my $mLine (@whoLines) {
		if ($mLine =~ /\(:\d+\)/sm) {
			$mLine =~ /^(\w*).*\s(\d*)\s.*/sm;
			$mUsername = $1;
			$mPid = $2;
		}
	}
	if (($mUsername eq q{}) || ($mPid eq q{})) {
		printLog('No display for message output found, no username or PID', 'L1', '[INFO]');
		return (0);
	}

	$environ = "/proc/$mPid/environ";
	my $FH;
	if ( (-e $environ) && open($FH, "<", $environ) ) {
		if (defined $FH) {
			$dbusAddress = do { local $/; <$FH> };
			if (! close($FH)) {
				printLog("Can't close $environ", 'L0', '[WARN]');
			}
			$dbusAddress =~ /.*?(DBUS_SESSION_BUS_ADDRESS=unix:path=\/run\/user\/\d*\/bus).*/sm;
			$dbusAddress = $1;
			if ($dbusAddress eq q{}) {
				printLog('No display for message output found, no DBUS address', 'L0', '[INFO]');
				return (0);
			}
		} else {
			printLog('No display for message output found', 'L1', '[INFO]');
			return (0);
		}
	} else {
		printLog("could not open $environ $!", 'L2', '[WARN]');
		return (0);
	}

	printLog("messaging desktop", 'L2', '[INFO]');
	my $mCmd = $consoleLanguage."sudo -u $mUsername $dbusAddress gdbus call --session --dest=org.freedesktop.Notifications --object-path=/org/freedesktop/Notifications --method=org.freedesktop.Notifications.Notify \"TUXEDO Tomte\" $sessionID \"$icon\" '$summary' '$body' '[]' '{\"urgency\": <$urgency>, \"desktop-entry\": <\"tuxedo-control-center\">}' 5000";

	`$mCmd`;
	$retValue = $? >> 8;

	if ($retValue != 0) {
		printLog("No display for message output found with command:\n$mCmd", 'L0', '[DEBUG]');
		return (0);
	}
	return (1);
}



###############################################################################
# prints a message on display informing about an expected long install time
# does nothing if the message was already sent
#
sub messageLongInstall {
	if (($longInstall == 0) && (triesAlreadyDone() < 1)) {
		if (messageDesktop(__('TUXEDO Tomte is installing fixes'),__('Please do not restart or shutdown the system.'), 2, 'dialog-warning')) {
			$longInstall = 1;
		}
	}
	return;
}


###############################################################################
# initialize logFile
# create one if not existing already
# exits program if logfile can not be created
#
sub initLogging {
	my %options;
	@options{"configure", "reconfigure", "remove", "block", "unblock", "update", "loglevel", "FAI", "DONT_CONFIGURE", "AUTOMATIC", "UPDATES_ONLY"} = ();

	# do not log if no arguments
	if ($argValue{command} eq q{}) {
		return;
	}
	# do not log if false arguments
	if (! (exists $options{$argValue{command}})) {
		return;
	}

	# check if root permissions available
	if (! isRoot()) {
		return;
	}

	my $timestamp;
	# create logDir if it does not exist
	if (! -d $logDir ) {
		mkdir($logDir, 0755);
		if (! -d $logDir ) {
			$noLogFile = 1;
			print "Could not create $logDir\n";
			return;
		}
	}
	# check for logfile
	if ( -e $logFile ) {
		# append if logFile exists
		if ( open($LOGFILE, '>>', $logFile) )
		{
			$noLogFile = 0;
			$timestamp = getLoggingTime();
			if (! close $LOGFILE) {
				printLog("could not close $logFile", 'L0', '[ERROR]');
			}
			printLog("starting Tomte $VERSION", 'L0', '[INFO]');
			if ($FAI) {
				printLog('in FAI mode', 'L0', '[INFO]');
			}
			return;
		} else {
			printLog("Err: $!", 'L1', '[ERROR]');
			$noLogFile =1;
			print "[ERROR] Could not open logFile $logFile\n$!\n";
			exit (0);
		}
	} else {
		# create a new logFile
		if ( open($LOGFILE, '>', $logFile) ) {
			$noLogFile = 0;
			$timestamp = getLoggingTime();
			if (! close($LOGFILE)) {
				print "[ERROR] couldn't close $logFile";
			}
			printLog("starting Tomte $VERSION", 'L0', '[INFO]');
			if ($FAI) {
				printLog('in FAI mode', 'L0', '[INFO]');
			}
			return;
		} else {
			printLog("Err: $!", 'L0', 'L1', '[ERROR]');
			$noLogFile =1;
			print "Could not create logFile $logFile\n";
			exit (0);
		}
	}
}



###############################################################################
#
sub getLoggingTime {
	my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time);
	my $nice_timestamp = sprintf ( "%04d%02d%02d %02d:%02d:%02d",
									$year+1900,$mon+1,$mday,$hour,$min,$sec);
	return $nice_timestamp;
}



###############################################################################
#
sub getBackupFileTime {
	my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time);
	my $nice_timestamp = sprintf ( "%04d%02d%02d%02d%02d%02d",
									$year+1900,$mon+1,$mday,$hour,$min,$sec);
	return $nice_timestamp;
}



###############################################################################
# prints into logFile or to terminal
# printLog('message', 'tlX', severity)
# it assumes 'l' or 'L' if 't', 'T', 'L' or 'l' are ommited
# loglevels for X:
# 0 highest/errors/fatal
# 1 normal
# 2 debug
# severity is a string which will be put in front of the line
# possible severities are:
# [INFO] [DEBUG] [WARN] [ERROR] [FATAL]
#
sub printLog {
	my ($message, $type, $severity) = @_;
	if ((!defined($type)) || ($type eq q{})) {
		$type = 'L1';
	}
	my $level = $type;
	if ($level =~ /[0-2]/sm) {
		$level =~ s/\D//gsm;
		$level += 0;
	} else {
		$level = 1;
		$level += 0;
	}
	if ($type =~ /[LlTt]/sm) {
		$type =~ s/[\W\d]//gsm;
	} else {
		$type = 'L';
	}

	if ($FAI) {
		my $timestamp = getLoggingTime();
		print "$severity $timestamp $message\n";
		if ($faiLogfileExists == 0) {
			if (!(-e $faiLogfile)) {
				print "logfile does not exist, creating one\n";
				if (open($FAILOGFILE, ">", $faiLogfile)) {
					if (!close($FAILOGFILE)) {
						print "[ERROR] couldn't close $faiLogfile\n";
					} else {
						$faiLogfileExists = 1;
					}
				} else {
					print "[ERROR] couldn't create $faiLogfile\n";
				}
			}
		}
		if ( ($faiLogfileExists == 1) && open($FAILOGFILE, '>>', $faiLogfile) ) {
			if (defined($severity)) {
				print $FAILOGFILE "$severity $timestamp $message\n";
			} else {
				print $FAILOGFILE "$timestamp $message\n";
			}
			if (! close($FAILOGFILE)) {
				print "[ERROR] couldn't close $faiLogfile\n";
			}
		} else {
			print "could not open $faiLogfile logfile\n";
		}

	} elsif ($level <= $logLevel) {
		if ((-e $logFile) && ($type =~ /L/ism)) {
			if (open($LOGFILE, '>>', $logFile) ) {
				my $timestamp = getLoggingTime();
				if (defined($severity)) {
					print $LOGFILE "$severity $timestamp $message\n";
				} else {
					print $LOGFILE "$timestamp $message\n";
				}
				close $LOGFILE;
			}
		}
		if ($type =~ /T/ism) {
			print "$message\n";
		}
	}
	return (0);
}




###############################################################################
# reads config values from configIniFile
# returns 1 if file exists
# retuns 0 if file does not exist
#
sub readConfigIniValues {
	if (-e $configIniFile) {
		$configIniValues = Config::Tiny->new;
		$configIniValues = Config::Tiny->read($configIniFile, 'utf8');
		if (defined $configIniValues->{debugMode}) {
			printLog("debugLevel before: $logLevel ValueFromIniFile: $configIniValues->{debugMode}", 'L2', '[DEBUG]');
			$logLevel = $configIniValues->{loglevel};
		}
		return (1);
	}
	return (0);
}



###############################################################################
# renames the old config file name to a new name
#
sub renameOldConfigFile {
	if (-e $oldConfigFile) {
		rename($oldConfigFile, $configFile);
	}
	return (0);
}





###############################################################################
# fills global variable %localConfModules with data from $configFile
# if there is no configFile then it is left empty
# returns '0' if configfile can not be read, else returns '1'
#
sub readConfigFile {
	my %comp;
	my $FH;
	my $shortname;
	renameOldConfigFile();
	readConfigIniValues();
	if ( (-e $configFile) && open($FH, '<', $configFile) ) {
		while ( my $line=<$FH> ) {
			chomp $line;
			# ignore comments
			if ($line =~ /^#/sm) {
				next;
			}
			# ignore empty lines
			if ($line =~ /^(\s)*$/sm) {
				next;
			}
			# check data format
			if (validConfigLine($line)) {
				my ($name, @cols) = split /\s+/sm, $line;
				# check if module name exists in orig and return shortname
				$shortname = validModuleName($name);
				if ($shortname ne q{}) {
					# @comp values have to be same as in writeConfigFile
					@comp{qw(name version installed blocked required hwid)} = ($name, @cols);

					$localConfModules{$shortname} = { %comp };
					# convert to numbers
					$localConfModules{$shortname}{version} = $localConfModules{$shortname}{version} * 1;
					$localConfModules{$shortname}{hwid} = $localConfModules{$shortname}{hwid} * 1;
				} else {
					printLog("module: $name does not exist", 'L1', '[WARN]');
				}
			} else {
				printLog("invalid line in $configFile", 'L0', '[ERROR]');
			}
		}
		if (! close($FH)) {
			printLog("could not close configFile: $configFile $!", 'TL0', '[ERROR]');
		}
	} else {
		printLog("Err: $!", 'L0', '[ERROR]');
		printLog("could not open configFile: $configFile $!", 'TL0', '[ERROR]');
		return (0);
	}
	return (1);
}


###############################################################################
# checks if the line has valid values
# return 0 if invalid, 1 if valid
#
sub validConfigLine {
	my $line = shift;
	# name version installed blocked required hwid
	if ($line =~ /^[a-zA-Z0-9\-\.]+\s[a-zA-Z0-9\-]\s(yes|no|failed|LiveISO)\s(yes|no)\s(yes|no|prerequisite)\s[a-zA-Z0-9\-]\s*$/sm) {
		return (1);
	} else {
		printLog("found invalid configuration line in $configFile:\n$line", 'L0', '[ERROR]');
		return (0);
	}
}



###############################################################################
# creates $configIniFile if it does not exist
# over writes file with known config data if it exists
#
sub writeConfigIniFile {
	if ($configIniValues->write($configIniFile, 'utf8')) {
		printLog("$configIniFile successfully written", 'L2', '[INFO]');
		return (1);
	} else {
		printLog("problems writing $configIniFile", 'L0', '[WARN]');
		return (0);
	}
}



###############################################################################
# overwrites $configFile with data from $presetModules if it does not exist
#
sub writeConfigFile {
	my $FH;
	my $installed;
	my $version;
	my $hwid;

	if ($FAI) {
		$configIniValues->{installation}->{FAI} = 'first reboot after FAI';
	}
	writeConfigIniFile();

	renameOldConfigFile();
	if ( open($FH, '>', $configFile) ) {
		printLog("writing configfile", 'L1', '[INFO]');
		print $FH "# name version installed blocked required hwid\n";

		foreach my $name (sort keys %presetModules) {
			# values have to be same as in readConfigFile
			# blocked values should always be present
			if (($presetModules{$name}{required} ne 'no') ||
				($presetModules{$name}{blocked} eq 'yes')) {
				if ($presetModules{$name}{upgrade} eq 'yes') {
					# upgrade available
					if ($presetModules{$name}{upgraded} eq 'yes') {
						# upgrade was executed
						printLog("upgrade was executed", 'L2', '[DEBUG]');
						$version = $presetModules{$name}{version};
						$hwid = $presetModules{$name}{hwid};
						$installed = $presetModules{$name}{installed};
					} else {
						# upgrade was not executed
						printLog("upgrade was not executed", 'L0', '[DEBUG]');
						if ( $presetModules{$name}{installed} eq 'yes' ) {
							$version = $localConfModules{$name}{version};
							$hwid = $localConfModules{$name}{hwid};
							$installed = $localConfModules{$name}{installed};
						} else {
							$version = $presetModules{$name}{version};
							$hwid = $presetModules{$name}{hwid};
							$installed = $localConfModules{$name}{installed};
						}
					}
				} else {
					# no upgrade available
					printLog("no upgrade available for $presetModules{$name}{name}", 'L2', '[DEBUG]');
					$version = $presetModules{$name}{version};
					$hwid = $presetModules{$name}{hwid};
					$installed = $presetModules{$name}{installed};
				}
				# don't add to list if it is not installed and not required
				# but add to list if it is blocked
				if (!(($presetModules{$name}{installed} eq 'no') &&
					($presetModules{$name}{required} eq 'no')) ||
					($presetModules{$name}{blocked} eq 'yes')) {
					print $FH "$presetModules{$name}{name} ".
						"$version ".
						"$installed ".
						"$presetModules{$name}{blocked} ".
						"$presetModules{$name}{required} ".
						"$hwid\n";
				}
			}
		}
		if (! close($FH)) {
			printLog("Couldn't close config file $configFile", 'L0', '[ERROR]');
		}
		printLog("$configFile successfully written", 'L2', '[DEBUG]');
	} else {
		printLog("Err: $!", 'L0', 'L1', '[ERROR]');
		printLog("can't write to configfile $configFile $!", 'TL0', '[FATAL]');
		exit (0);
	}
	return (0);
}



###############################################################################
# check if config directory exists
# if not creates a new directory
# if that is not possible, exits (!!!)
#
sub initConfigDir {
	# create configDir if it does not exist
	if (! ( -d $configDir )) {
		mkdir($configDir, 0755);
		if (! ( -d $configDir )) {
			$noConfigFile = 1;
			printLog("could not create $configDir, that's really bad!!", 'TL0', '[FATAL]');
			exit (0);
		}
	}
	return (1);
}


# loads the config or if non existent
# creates a new directory and configfile
#
sub initConfigFile {
	# create configDir if it does not exist
	initConfigDir();

	# read config file
	renameOldConfigFile();
	if ( -f $configFile ) {
		$noConfigFile = 0;
		readConfigFile();
		return;
	} else {
		# set flag for first installation
		write_file( $tomteFirstInstallFile, {'err_mode' => 'carp'}, 'tomteFirstInstall');
		# create a new configFile
		printLog("no configfile present ... creating one", 'TL', '[INFO]');
		writeConfigFile();
		return;
	}
	return (0);
}



###############################################################################
# print status from all modules in a nice formated output

###############################################################################
# print status from all modules in a nice formated output
# uses the data from the local configuration file if available
# else it checks the hardware
#
# used pattern:
# installed and new module version equal (no update)
# installed	req. old	req. new	notes
# no		no			no			ign
# yes		no			no			installed but not required!
# no		yes			no			ign
# yes		yes			no			ign
# no		no			yes			ign
# yes		no			yes			ign
# no		yes			yes			not inst. but required
# yes		yes			yes			inst. and req.
#
# installed and new module version not equal (update?)
# to be done in next version
#
sub listStatus {
	my @arr = ( ['Name', 'Version', 'Installed', 'Blocked', 'Required'] );
	my $noneRequired = 1;
	my $printIt = 0;
	my $upgradeAvailable = q{};

	foreach my $module (sort keys %localConfModules) {
		if($presetModules{$module}{upgrade} eq 'yes') {
			$upgradeAvailable = ' - upgrade available';
		} else {
			$upgradeAvailable = q{};
		}


		if (($localConfModules{$module}{installed} eq 'yes') &&
			($localConfModules{$module}{required} eq 'no') &&
			($presetModules{$module}{required} eq 'no')) {
			# module is installed but should not be
			$printIt = 1;
			push(@arr,
				["$localConfModules{$module}{name}",
				"$localConfModules{$module}{version}",
				"$localConfModules{$module}{installed}",
				"$localConfModules{$module}{blocked}",
				"$localConfModules{$module}{required}"]);
		}
		# required, installed and not removed
		if (($localConfModules{$module}{required} eq 'yes') || ($localConfModules{$module}{required} eq 'prerequisite')) {
			$noneRequired = 0;
			$printIt = 1;
			push(@arr,
				["$localConfModules{$module}{name}",
				"$localConfModules{$module}{version}",
				"$localConfModules{$module}{installed}",
				"$localConfModules{$module}{blocked}",
				"$localConfModules{$module}{required}$upgradeAvailable"]);
		}
	}
	if ($printIt) {
		print "Selected installation mode: $mode\n";
		print "Only showing fixes that are available for this hardware\n";
		$~ = "LISTSTATI";
		for my $row (@arr) {
			format LISTSTATI =
@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<< @<<<<<<<<< @<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<
			@$row
.
			write;
		}
	}
	if ($printIt && $FAI) {
		if (! (-e $faiLogfile)) {
			print "logfile does not exist, creating one\n";
			if (! open($FAILOGFILE, ">", $faiLogfile)) {
				print "[ERROR] could not create the logfile $faiLogfile\n";
			}
			if (! close($FAILOGFILE)) {
				print "[ERROR] could not close the logfile $faiLogfile\n";
			}
		}
		if (open($FAILOGFILE, '>>', $faiLogfile) ) {
			for my $row (@arr) {
				print $FAILOGFILE "@{$row}\n";
			}
			if (! close($FAILOGFILE)) {
				print "[ERROR] could not close the logfile $faiLogfile\n";
			}
		} else {
			print "could not open logfile\n";
		}
	}
	if (-e $needsRestartFile) {
		print "Some fixes need a system restart to be effective\n";
		if ($LiveISO) {
			print "As this is a LiveISO, restarting will not make sense\n";
		}
	}
	if ($noneRequired) {
		print "No fixes available for this system\n";
	}
	return (0);
}




###############################################################################
# print status from all modules in a nice formated output
# uses the data from the local configuration file if available
# else it checks the hardware
#
# used pattern:
# installed and new module version equal (no update)
# installed req. old    req. new    notes
# no        no          no          ign
# yes       no          no          installed but not required!
# no        yes         no          ign
# yes       yes         no          ign
# no        no          yes         ign
# yes       no          yes         ign
# no        yes         yes         not inst. but required
# yes       yes         yes         inst. and req.
#
# installed and new module version not equal (update?)
# to be done in next version
#
sub listVersions {
	my @arr = ( ['Name', 'Packageversion', 'Version', 'Installed', 'Blocked', 'Required'] );
	my $noneRequired = 1;
	my $printIt = 0;
	my $upgradeAvailable = q{};

	print "tuxedo-tomte $VERSION\n";

	foreach my $module (sort keys %localConfModules) {
		if($presetModules{$module}{upgrade} eq 'yes') {
			$upgradeAvailable = ' - upgrade available';
		} else {
			$upgradeAvailable = q{};
		}
		$localConfModules{$module}{packageVersion} = q{};
		if ($module eq 'nvidiadriver') {
			$localConfModules{$module}{packageVersion} = getPackageVersion('nvidia-driver-*');
		}
		if ($module =~ /kernel.*/sm ) {
			$localConfModules{$module}{packageVersion} = getPackageVersion($presetModules{$module}{name});
		}
		if (($localConfModules{$module}{installed} eq 'yes') &&
			($presetModules{$module}{package} eq 'yes')) {
			$localConfModules{$module}{packageVersion} = getPackageVersion($presetModules{$module}{name});
			if ($localConfModules{$module}{packageVersion} eq q{}) {
				$localConfModules{$module}{packageVersion} = 'unkwn';
			}
		}

		if (($localConfModules{$module}{installed} eq 'yes') &&
			($localConfModules{$module}{required} eq 'no') &&
			($presetModules{$module}{required} eq 'no')) {
			# module is installed but should not be
			$printIt = 1;
			push(@arr,
				["$localConfModules{$module}{name}",
				"$localConfModules{$module}{packageVersion}",
				"$localConfModules{$module}{version}",
				"$localConfModules{$module}{installed}",
				"$localConfModules{$module}{blocked}",
				"$localConfModules{$module}{required}"]);
		}
		# required, installed and not removed
		if (($localConfModules{$module}{required} eq 'yes') ||  ($localConfModules{$module}{required} eq 'prerequisite')) {
			$noneRequired = 0;
			$printIt = 1;
			push(@arr,
				["$localConfModules{$module}{name}",
				"$localConfModules{$module}{packageVersion}",
				"$localConfModules{$module}{version}",
				"$localConfModules{$module}{installed}",
				"$localConfModules{$module}{blocked}",
				"$localConfModules{$module}{required}$upgradeAvailable"]);
		}
	}
	if ($printIt) {
		print "Selected installation mode: $mode\n";
		print "Only showing fixes that are available for this hardware\n";
		$~ = "LISTVERSIONS";
		for my $row (@arr) {
			format LISTVERSIONS =
@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<< @<<<<<<< @<<<<<<<<< @<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<
			@$row
.
			write;
		}
	}
	if (-e $needsRestartFile) {
		print "Some fixes need a system restart to be effective\n";
		if ($LiveISO) {
			print "As this is a LiveISO, restarting will not make sense\n";
		}
	}
	if ($noneRequired) {
		print "No fixes available for this system\n";
	}
	return (0);
}



###############################################################################
# checks whether all modules have been installed successfully
# returns 1 if everything was installed
# returns 0 if something went wrong
#
sub checkNoFailedModules {
	my $successfull = 1;
	foreach my $module (sort keys %localConfModules) {
		if (($localConfModules{$module}{installed} eq 'no') &&
			($presetModules{$module}{required} eq 'yes')) {
			$successfull = 0;
		}
		if (($localConfModules{$module}{installed} eq 'no') &&
			($presetModules{$module}{required} eq 'prerequisite')) {
			$successfull = 0;
		}
	}
	return $successfull;
}



sub listStatusJson {
	my $upgradeAvailable = q{};
	my %list;

	$list{version} = $VERSION;
	$list{mode} = $mode;
	if (-e $needsRestartFile) {
		$list{restart} = 'yes';
	} else {
		$list{restart} = 'no';
	}


	foreach my $module (sort keys %localConfModules) {
		if($presetModules{$module}{upgrade} eq 'yes') {
			$upgradeAvailable = ' - upgrade available';
		} else {
			$upgradeAvailable = q{};
		}
		if (($localConfModules{$module}{installed} eq 'yes') &&
			($localConfModules{$module}{required} eq 'no') &&
			($presetModules{$module}{required} eq 'no')) {
			# module is installed but should not be
			push @{ $list{modules} }, {
				name => $localConfModules{$module}{name},
				version => $localConfModules{$module}{version},
				installed => $localConfModules{$module}{installed},
				blocked => $localConfModules{$module}{blocked},
				required => "$localConfModules{$module}{required}$upgradeAvailable",
			};
		}
		# required, installed and not removed
		if (($localConfModules{$module}{required} eq 'yes') || ($localConfModules{$module}{required} eq 'prerequisite')) {
			push @{ $list{modules} }, {
				name => $localConfModules{$module}{name},
				version => $localConfModules{$module}{version},
				installed => $localConfModules{$module}{installed},
				blocked => $localConfModules{$module}{blocked},
				required => "$localConfModules{$module}{required}$upgradeAvailable",
			};
		}
	}
	my $jsonText = encode_json \%list;
	print "$jsonText\n";
	return (0);
}





###############################################################################
# copies values from local configuration module into new configuration module
# copies always 'blocked'
#
sub transferConfigValues {
	foreach my $name (sort keys %localConfModules) {
		# always copy blocked status
		$presetModules{$name}{blocked} = $localConfModules{$name}{blocked};
		if (newVersion($name) || newHwid($name)) {
			# new version or new hwid => upgrade necessary
			$presetModules{$name}{upgrade} = 'yes';
			$presetModules{$name}{upgraded} = 'no';
		} else {
			$presetModules{$name}{installed} = $localConfModules{$name}{installed};
			$presetModules{$name}{upgrade} = 'no';
			$presetModules{$name}{upgraded} = 'no';
		}
	}
	return (0);
}



###############################################################################
# returns 0 if no new version
# returns 1 if new version
#
sub newVersion {
	my $module = shift;
	if ($localConfModules{$module}{version} < $presetModules{$module}{version}) {
		return (1);
	} else {
		return (0);
	}
}


###############################################################################
# returns 0 if no new hwid
# returns 1 if new hwid
#
sub newHwid {
	my $module = shift;
	if ($localConfModules{$module}{hwid} < $presetModules{$module}{hwid}) {
		return (1);
	} else {
		return (0);
	}
}



###############################################################################
# adds module hash to list of configured modules with status
#
sub addToConfiguredModules {
	my $module = shift;
	my $status = shift;
	if (defined($module) && ($module ne q{})) {
		$configuredModules{$module} = $status;
		if ($status =~ /failed/sm) {
			printLog("failed to install $presetModules{$module}{name}", 'L0', '[ERROR]');
		} elsif ($status =~ /success/sm) {
			printLog("succesfully installed $presetModules{$module}{name}", 'L0', '[INFO]');
		} elsif ($status =~ /removed/sm) {
			if (($mode ne 'DONT_CONFIGURE') || ($mode ne 'UPDATES_ONLY')) {
				printLog("succesfully removed $presetModules{$module}{name}", 'L0', '[INFO]');
				print "The module $presetModules{$module}{name} has been removed. Please block it with:\n";
				print "\$ sudo tomte block $presetModules{$module}{name}\n";
				print "unless you set the mode DONT_CONFIGURE or UPDATE_ONLY\n";
				print "or tomte will reinstall it again at the next start\n";
			}
		} elsif ($status =~ /remove failed/sm) {
			printLog("failed to remove $presetModules{$module}{name}", 'L0', '[ERROR]');
		} elsif ($status =~ /nothingToDo/sm) {
			printLog("nothing to do for $presetModules{$module}{name}", 'L0', '[INFO]');
		}
	} else {
		printLog("modules not defined at addToConfiguredModules", 'L2', '[ERROR]');
		return (0);
	}
	return (0);
}



###############################################################################
# parses sub name from %presetModules list from given module parameter name
# returns the name of the module vector found
# returns an empty string if not found
#
sub getSubName {
	my $moduleName = shift;
	foreach my $module ( keys %presetModules ) {
		if (defined $presetModules{$module}{name}) {
			if ( $moduleName eq $presetModules{$module}{name} ) {
				return $module;
			}
		}
	}
	# return empty string if module is not found
	return q{};
}


###############################################################################
# returns the shortname of module if the name is a valid module name
# returns '' if not
#
sub validModuleName {
	my $moduleName = shift;
	foreach my $module ( keys %presetModules ) {
		if (defined $presetModules{$module}{name}) {
			if ($presetModules{$module}{name} eq $moduleName) {
				return $module;
			}
		}
	}
	return q{};
}



###############################################################################
# configures all modules
# if not blocked, required and not installed
#
# u=upgrade
# c=configure
# R=remove
# .=nothing
#
# installed blocked		required	newversion	newhwid
# tomte.cfg	tomte.cfg	check		origConfig	origConfig
# yes		yes			yes			yes			yes		.
# yes		yes			yes			yes			no		.
# yes		yes			yes			no			yes		.
# yes		yes			yes			no			no		.
# yes		yes			no			yes			yes		.
# yes		yes			no			yes			no		.
# yes		yes			no			no			yes		.
# yes		yes			no			no			no		.
# yes		no			yes			yes			yes		u
# yes		no			yes			yes			no		u
# yes		no			yes			no			yes		u
# yes		no			yes			no			no		.
# yes		no			no			yes			yes		R
# yes		no			no			yes			no		R
# yes		no			no			no			yes		R
# yes		no			no			no			no		R
# no		yes			yes			yes			yes		.
# no		yes			yes			yes			no		.
# mo		yes			yes			no			yes		.
# no		yes			yes			no			no		.
# no		yes			no			yes			yes		.
# no		yes			no			yes			no		.
# no		yes			no			no			yes		.
# no		yes			no			no			no		.
# no		no			yes			yes			yes		C
# no		no			yes			yes			no		C
# no		no			yes			no			yes		C
# no		no			yes			no			no		C
# no		no			no			yes			yes		.
# no		no			no			yes			no		.
# no		no			no			no			yes		.
# no		no			no			no			no		.

sub configureAllModules {
	my $exit_status;
	foreach my $module (keys %presetModules) {
		# for testing
		if ($TEST_ALL_MODULES) {
			printLog("testing all modules: >$module<", 'TL0', '[DEBUG]');
			$presetModules{$module}{required} = 'yes';
		}

		# prerequisites are automatically ommited (required=prerequisite)
		# they are done in prerequisite and postrequisite subroutines
		if (($presetModules{$module}{required} eq 'yes') &&
			($presetModules{$module}{order} ne 'first') &&
			($presetModules{$module}{order} ne 'last')) {

			if ($presetModules{$module}{reconfigure} eq 'always') {
				
				# reconfigure modules with 'always' every single time
				reconfigureSingleModule($presetModules{$module}{name});
			} else {
				configureSingleModule($presetModules{$module}{name});
			}
		} elsif (($presetModules{$module}{required} eq 'no') &&
				(($localConfModules{$module}{installed} eq 'yes') ||
				($localConfModules{$module}{installed} eq 'failed'))) {
			removeSingleModule($presetModules{$module}{name});
		}
	}
	return (0);
}



###############################################################################
# configures a single module
# if not blocked, required and not installed
#
sub configureSingleModule {
	my $moduleName = shift;
	my $module;
	$module = getSubName($moduleName);

	# for testing
	if ($TEST_ALL_MODULES) {
		printLog("testing all modules: >$module<", 'TL0', '[DEBUG]');
		$presetModules{$module}{required} = 'yes';
	}

	if ($module eq q{}) {
		printLog("wrong module name", 'TL', '[WARN]');
		return;
	}

	# check if in LiveISO and exit from subroutine if module should not be
	# installed
	if ($LiveISO && ($presetModules{$module}{LiveISO} eq 'no')) {
		printLog("Module: $module will not be installed as this is a LiveISO", 'L0', '[WARN]');
		addToConfiguredModules($module, 'nothingToDo');
		$localConfModules{$module}{installed} = 'LiveISO';
		return;
	}

	# grub file should always be checked for correct entries if there
	# is a fix available for this system
	if ( (($localConfModules{$module}{installed} eq 'no') ||
		($localConfModules{$module}{installed} eq 'failed') ||
	   	($presetModules{$module}{postconf} eq 'updateGrub')) &&
		$localConfModules{$module}{blocked} eq 'no' &&
		$presetModules{$module}{required} eq 'yes') {
		printLog("configure module: $module", 'L1', '[INFO]');
		configureModule($module);
	} elsif ( $localConfModules{$module}{installed} eq 'yes' &&
		$localConfModules{$module}{blocked} eq 'no' &&
		$presetModules{$module}{required} eq 'yes' &&
		$presetModules{$module}{upgrade} eq 'yes' ) {
		# upgrade if new version or hwid
		printLog("upgrade module: $module", 'L1', '[INFO]');
		updateModule($module);
	} elsif ( $localConfModules{$module}{installed} eq 'yes' ) {
		$configuredModules{$module} = 'nothingToDo';
	}
	if (($localConfModules{$module}{blocked} eq 'yes') &&
		($presetModules{$module}{required} ne 'prerequisite') &&
   		($mode ne 'DONT_CONFIGURE') &&
		($mode ne 'UPDATES_ONLY')) {
		print "Module $moduleName cannot be installed/removed/updated because:\n";
		print "- it is blocked (maybe use 'unblock')\n";
	}
	return (0);
}



###############################################################################
# checks if the conditions to remove a module are met
#
sub removeSingleModule {
	my $moduleName = shift;
	my $module;
	$module = getSubName($moduleName);

	if ($module eq q{}) {
		printLog("wrong module name", 'TL', '[WARN]');
		return;
	}

	if ($localConfModules{$module}{blocked} eq 'no') {
		printLog("removing module $moduleName", 'TL', '[INFO]');
		removeModule($module);
	}

	if ( $localConfModules{$module}{blocked} eq 'yes' ) {
		print "Module $moduleName cannot be removed because:\n";
		print "- it is blocked (maybe use 'unblock')\n";
	}
	return (0);
}



###############################################################################
# reconfigures all modules if not blocked and if required even if it is
# already installed
# ommits modules which are "prerequisite" or as order "first" or "last"
#
sub reconfigureAllModules {
	my $exit_status;
	

	foreach my $module (keys %presetModules) {
		# to debug on all modules
		if ($TEST_ALL_MODULES) {
			$presetModules{$module}{required} = 'yes';
		
		}
		if ($presetModules{$module}{blocked} eq 'yes') {
			print "Module $presetModules{$module}{name} can't be modified because:\n";
			print "- it is blocked (maybe use 'unblock')\n";
		} elsif (($presetModules{$module}{required} eq 'yes') &&
			($presetModules{$module}{order} ne 'first') &&
			($presetModules{$module}{order} ne 'last')) {
			reconfigureSingleModule($presetModules{$module}{name});
		} elsif (($presetModules{$module}{required} eq 'no') &&
				(($localConfModules{$module}{installed} eq 'yes') ||
				($localConfModules{$module}{installed} eq 'failed'))) {
			removeModule($module);
		}
	}
	return (0);
}



###############################################################################
# reconfigures a single module if not blocked and if required or prerequisite
# even if it is already installed
#
sub reconfigureSingleModule {
	my $moduleName = shift;
	my $module;
	$module = getSubName($moduleName);
	printLog("reconfigureSingleModule: $moduleName", 'L2', '[INFO]');

	# for testing
	if ($TEST_ALL_MODULES) {
		printLog("testing all modules: >$module<", 'TL0', '[DEBUG]');
		$presetModules{$module}{required} = 'yes';
	}

	if ($module eq q{}) {
		printLog("wrong module name", 'TL', '[WARN]');
		return;
	}

	# check if in LiveISO and exit from subroutine if module should not be
	# installed
	if ($LiveISO && ($presetModules{$module}{LiveISO} eq 'no')) {
		printLog("Module: $module will not be reconfigured as this is a LiveISO", 'L0', '[WARN]');
		addToConfiguredModules($module, 'nothingToDo');
		$localConfModules{$module}{installed} = 'LiveISO';
		return;
	}

	if (($presetModules{$module}{required} eq 'prerequisite') ||
		(($localConfModules{$module}{blocked} eq 'no') && ($presetModules{$module}{required} eq 'yes'))) {

		if ($presetModules{$module}{upgrade} eq 'yes') {
			printLog("update module: $module", 'L1', '[INFO]');
			updateModule($module);
		} else {
			printLog("configure module: $module", 'L1', '[INFO]');
			configureModule($module);
		}
	} elsif ( $localConfModules{$module}{installed} eq 'yes' ) {
		$configuredModules{$module} = 'nothingToDo';
	}

	if (($localConfModules{$module}{blocked} eq 'yes') &&
		($presetModules{$module}{required} ne 'prerequisite') &&
   		($mode ne 'DONT_CONFIGURE') &&
		($mode ne 'UPDATES_ONLY')) {
		printLog("module $module is blocked", 'L2', '[INFO]');
		print "Module: $moduleName cannot be installed because:\n";
		print "- it is blocked (maybe use 'unblock')\n";
		if ($LiveISO) {
			print "this block has probably been set by the distribution maintainer\n";
			print "as it does not make much sense to install this module at this stage\n";
		}
	}

	if (($presetModules{$module}{required} eq 'no') &&
		($configuredModules{$module} ne 'remove failed') &&
		($configuredModules{$module} ne 'removed')) {
		printLog("module $module is not required by this system", 'L2', '[INFO]');
		print "Module: $moduleName cannot be installed because:\n";
		print "- it is not required by this system (this is the main reason)\n";
	}
	return (0);
}




###############################################################################
# blocks a module
# block( $moduleName )
#
sub block {
	my $moduleName = shift;
	my $module = getSubName($moduleName);
	printLog("block module: >$module<", 'L2', '[INFO]');
	if ($module eq q{}) {
		print "Module $moduleName does not exist\n";
		return;
	}
	if ($presetModules{$module}{required} eq 'prerequisite') {
		print "Module $moduleName is a prerequisite and can't be blocked\n";
		return;
	}
	if ($localConfModules{$module}{blocked} eq 'yes') {
		print "Module $moduleName is already blocked\n";
		return;
	}
	if ($localConfModules{$module}{blocked} eq 'no') {
		print "Blocking module $moduleName\n";
		print "$moduleName will not be upgraded or reinstalled if removed\n";
		$presetModules{$module}{blocked} = 'yes';
		printLog("blocking module $moduleName", 'L1', '[INFO]');
		return;
	}
}

###############################################################################
# unblocks a module
#
sub unblock {
	my $moduleName = shift;
	my $module = getSubName($moduleName);
	printLog("unblock module: >$module<", 'L2', '[INFO]');
	if ($module eq q{}) {
		print "Module $moduleName does not exist\n";
		return;
	}
	if ($localConfModules{$module}{blocked} eq 'no') {
		print "Module $moduleName is not blocked\n";
		return;
	}
	if ($localConfModules{$module}{blocked} eq 'yes') {
		print "Unblocking module $moduleName\n";
		$presetModules{$module}{blocked} = 'no';
		printLog("unblocking module $moduleName", 'L1', '[INFO]');
		return;
	}
}


###############################################################################
# blocks all modules
#
sub blockAllModules {
	foreach my $module (keys %presetModules) {
		if (($presetModules{$module}{required} eq 'yes') &&
			($presetModules{$module}{blocked} ne 'yes') &&
			($presetModules{$module}{required} ne 'prerequisite')) {
			$presetModules{$module}{blocked} = 'yes';
			printLog("blocking module $presetModules{$module}{name}", 'TL0', '[INFO]');
		}
	}
	return (0);
}



###############################################################################
# unblocks all modules
#
sub unblockAllModules {
	foreach my $module (keys %presetModules) {
		if (($presetModules{$module}{required} eq 'yes') &&
			($presetModules{$module}{blocked} eq 'yes')) {
			$presetModules{$module}{blocked} = 'no';
			printLog("unblocking module $presetModules{$module}{name}", 'TL0', '[INFO]');
		}
	}
	return (0);
}




###############################################################################
# set configuration mode setMode('mode')
# AUTOMATIC: install/remove/update everything automatically
# UPDATES_ONLY: only update installed modules
# DONT_CONFIGURE: do not configure anything
#
sub setMode {
	my $requestedMode = shift;

	$mode = checkMode();
	if ($mode eq $requestedMode) {
		printLog("Requested mode '$requestedMode' is already set", 'TL0', '[INFO]');
	} else {
		foreach my $key (keys %modeFile) {
			if ($key eq $requestedMode) {
				write_file($modeFile{$requestedMode}, {'err_mode' => 'carp'}, q{});
				printLog("creating modefile: $modeFile{$requestedMode} for $requestedMode", 'TL0', '[INFO]');
			} elsif (-e $modeFile{$key}) {
				unlink($modeFile{$key});
				printLog("removing superfluous modefile: $modeFile{$key}", 'TL2', '[INFO]');
			}
		}
	}
	return (0);
}



###############################################################################
# returns the mode configured as file in the configuration directory
# 'AUTOMATIC', 'UPDATES_ONLY' or 'DONT_CONFIGURE'
#
sub checkMode {
	my $countModes = 0;
	if (-e $modeFile{AUTOMATIC}) {
		++$countModes;
		$mode = 'AUTOMATIC';
	}
	if (-e $modeFile{UPDATES_ONLY}) {
		++$countModes;
		$mode = 'UPDATES_ONLY';
	}
	if (-e $modeFile{DONT_CONFIGURE}) {
		++$countModes;
		$mode = 'DONT_CONFIGURE';
	}
	if ($countModes == 0) {
		$mode = 'AUTOMATIC';
		printLog('No mode setting found, assuming AUTOMATIC', 'L2', '[INFO]');
		$mode = 'AUTOMATIC';
	}
	if ($countModes == 1) {
		printLog("Found mode setting $mode", 'L2', '[INFO]');
	}
	if ($countModes > 1) {
		printLog("More then one mode setting found, assuming $mode", 'L2', '[INFO]');
	}
	return $mode;
}



###############################################################################
# checks if the operation is allowed in this mode/command/module
# usage: checkModePermision(install|remove|update, MODULE)
# returns 0 if not and 1 if allowed
#
sub checkModePermission {
	my ($operation, $module) = (@_);
	# this is a double check, just in case ...

	if ($presetModules{$module}{required} eq 'prerequisite') {
		printLog("module is prerequisite", 'L2', '[INFO]');
		# always permit if it is a prerequisite
		return (1);
	}
	# when FAI assume yes on all
	if ($argValue{command} eq 'FAI') {
		return (1);
	}
	# when Live ISO or FAI assume yes on all
	if ($LiveISO || $FAI) {
		return (1);
	}
	if ($argValue{command} eq 'configure') {
		if ($argValue{module} eq 'all') {
			if ($mode eq 'AUTOMATIC') {
				if ($operation eq 'install') {
					return (1);
				}
				if ($operation eq 'update') {
					return (1);
				}
				if ($operation eq 'remove') {
					return (1);
				}
			}
			if ($mode eq 'UPDATES_ONLY') {
				if ($operation eq 'install') {
					return (0);
				}
				if ($operation eq 'update') {
					return (1);
				}
				if ($operation eq 'remove') {
					return (0);
				}
			}
			if ($mode eq 'DONT_CONFIGURE') {
				if ($operation eq 'install') {
					return (0);
				}
				if ($operation eq 'update') {
					return (0);
				}
				if ($operation eq 'remove') {
					return (0);
				}
			}
		} else {
			if ($mode eq 'AUTOMATIC') {
				if ($operation eq 'install') {
					return (1);
				}
				if ($operation eq 'update') {
					return (1);
				}
				if ($operation eq 'remove') {
					return (1);
				}
			}
			if ($mode eq 'UPDATES_ONLY') {
				if ($operation eq 'install') {
					return (1);
				}
				if ($operation eq 'update') {
					return (1);
				}
				if ($operation eq 'remove') {
					return (1);
				}
			}
			if ($mode eq 'DONT_CONFIGURE') {
				if ($operation eq 'install') {
					return (1);
				}
				if ($operation eq 'update') {
					return (1);
				}
				if ($operation eq 'remove') {
					return (1);
				}
			}
		}
	}
	if ($argValue{command} eq "reconfigure") {
		if ($argValue{module} eq 'all') {
			if ($mode eq 'AUTOMATIC') {
				if ($operation eq 'install') {
					return (1);
				}
				if ($operation eq 'update') {
					return (1);
				}
				if ($operation eq 'remove') {
					return (1);
				}
			}
			if ($mode eq 'UPDATES_ONLY') {
				if ($operation eq 'install') {
					return (0);
				}
				if ($operation eq 'update') {
					return (1);
				}
				if ($operation eq 'remove') {
					return (0);
				}
			}
			if ($mode eq 'DONT_CONFIGURE') {
				if ($operation eq 'install') {
					return (0);
				}
				if ($operation eq 'update') {
					return (0);
				}
				if ($operation eq 'remove') {
					return (0);
				}
			}
		} else {
			if ($mode eq 'AUTOMATIC') {
				if ($operation eq 'install') {
					return (1);
				}
				if ($operation eq 'update') {
					return (1);
				}
				if ($operation eq 'remove') {
					return (1);
				}
			}
			if ($mode eq 'UPDATES_ONLY') {
				if ($operation eq 'install') {
					return (1);
				}
				if ($operation eq 'update') {
					return (1);
				}
				if ($operation eq 'remove') {
					return (1);
				}
			}
			if ($mode eq 'DONT_CONFIGURE') {
				if ($operation eq 'install') {
					return (1);
				}
				if ($operation eq 'update') {
					return (1);
				}
				if ($operation eq 'remove') {
					return (1);
				}
			}
		}
	}
	if ($argValue{command} eq "remove") {
		if ($argValue{module} eq 'all') {
			if ($mode eq 'AUTOMATIC') {
				if ($operation eq 'install') {
					return (1);
				}
				if ($operation eq 'update') {
					return (1);
				}
				if ($operation eq 'remove') {
					return (1);
				}
			}
			if ($mode eq 'UPDATES_ONLY') {
				if ($operation eq 'install') {
					return (0);
				}
				if ($operation eq 'update') {
					return (0);
				}
				if ($operation eq 'remove') {
					return (0);
				}
			}
			if ($mode eq 'DONT_CONFIGURE') {
				if ($operation eq 'install') {
					return (0);
				}
				if ($operation eq 'update') {
					return (0);
				}
				if ($operation eq 'remove') {
					return (0);
				}
			}
		} else {
			if ($mode eq 'AUTOMATIC') {
				if ($operation eq 'install') {
					return (1);
				}
				if ($operation eq 'update') {
					return (1);
				}
				if ($operation eq 'remove') {
					return (1);
				}
			}
			if ($mode eq 'UPDATES_ONLY') {
				if ($operation eq 'install') {
					return (1);
				}
				if ($operation eq 'update') {
					return (1);
				}
				if ($operation eq 'remove') {
					return (1);
				}
			}
			if ($mode eq 'DONT_CONFIGURE') {
				if ($operation eq 'install') {
					return (1);
				}
				if ($operation eq 'update') {
					return (1);
				}
				if ($operation eq 'remove') {
					return (1);
				}
			}
		}
	}
	# just to be safe return 0
	return (0);
}



###############################################################################
# configures a module, does no checks
#
sub configureModule {
	my $module = shift;
	my $permission = checkModePermission('install', $module);
	printLog("configure module: $module permission: $permission", 'L2', '[INFO]');
	if ($permission) {
		$module->($module, 'install');
	}
	return (0);
}


###############################################################################
# remove a module, does no checks
#
sub removeModule {
	my $module = shift;
	my $permission = checkModePermission('remove', $module);
	printLog("remove module: $module permission: $permission", 'L2', '[INFO]');
	if ($permission) {
		$module->($module, 'remove');
	}
	return (0);
}



###############################################################################
# configures a module, does no checks
#
sub updateModule {
	my $module = shift;
	my $permission = checkModePermission('update', $module);
	printLog("upgrade module: $module permission: $permission", 'L2', '[INFO]');
	if ($permission) {
		$module->($module, 'upgrade');
	}
	return (0);
}



###############################################################################
# remove warning message and confirmation
# removeWarningMessage('message')
# returns 1 if answered with y or Y
# returns 0 otherwise
# checks if in a valid tty => returns 1
#
sub removeWarningMessage {
	my $message = shift;
	my $answer;
	if (!isatty()) {
		return (1);
	}
	if ($FAI || $LiveISO) {
		return (1);
	}
	if ($message ne q{}) {
		print "$message";
	}
	print "Are you sure you want to remove this module? (y/n) ";
	chomp($answer = <STDIN>);
	if (($answer eq 'y') || ($answer eq 'Y')) {
		return (1);
	} else {
		return (0);
	}
}




###############################################################################
# checks if we are in a terminal
# returns 1 if yes
# returns 0 if not
#
sub isatty {
	no autodie;
	my $ttyDevice = '/dev/tty';
	if (!-e $ttyDevice) {
		return (0);
	}
	state $isatty = open(my $tty, '+<', $ttyDevice);
	return $isatty;
}




###############################################################################
# starts system programs (if necessary) to activate the module changes
#
sub postConfigure {
	my $exitStatus;
	my $postConfTrigger = 0;
	my $output;
	my $command;
	printLog("\ninitializing post-configuration", 'TL0', '[INFO]');
	foreach my $sysProgram (keys %postConfProgramsList) {
		if ($postConfProgramsList{$sysProgram}{trigger} == 1 ) {
			printLog("starting $sysProgram", 'L1', '[INFO]');
			$command = $postConfProgramsList{$sysProgram}{command}.' 2>&1';
			$output = `$command`;
			$exitStatus = $?;
			if ($exitStatus == 0) {
				printLog("success: $postConfProgramsList{$sysProgram}{command}", 'L1', '[INFO]');
				postSetModulesState($sysProgram, "success");
				setRestartValues($sysProgram);
			} else {
				$postConfTrigger = 1;
				printLog("failed: $postConfProgramsList{$sysProgram}{command}", 'L0', '[WARN]');
				printLog("returned error code: $exitStatus", 'L0', '[WARN]');
				postSetModulesState($sysProgram, "failed");
			}
		}
	}
	if ($postConfTrigger == 1) {
		startTomteDelayed();
	}
	return (0);
}




###############################################################################
# Starts the tuxedo-tomte service for delayed configuration
# procedure accepts an optional message startTomteDelayed("message")
#
sub startTomteDelayed {
	my $message = shift;
	my $retVal;
	$restartSystem = 0;
	my $triesValue;

	if ($LiveISO) {
		printLog("not starting Tomte delayed as this is a live ISO", 'L0', '[INFO]');
		return (0);
	}
	if ($FAI) {
		printLog("not starting Tomte delayed as this is a FAI installation", 'L0', '[INFO]');
		return (0);
	}
	if (defined($configIniValues->{installation}->{FAI}) &&
		(($configIniValues->{installation}->{FAI} eq 'first reboot after FAI') ||
		($configIniValues->{installation}->{FAI} eq 'second reboot after FAI'))) {
		printLog("not starting Tomte delayed as this is in OEM configuration", 'L0', '[INFO]');
		return (0);
	}

	printLog("start later again: $startLaterAgain", 'L2', '[INFO]');
	if ($startLaterAgain != 1) {
		$triesValue = tries($TRIES_LIMIT);
		if (! $triesValue) {
			printLog("repeat limit reached, program will not start again, tries: $triesValue", 'L2', '[INFO]');
			$startLaterAgain = 1;
			if ($restartSystem) {
				messageDesktop(__('Restart required'), __('Please restart the system for the changes to take effect. Tomte could not install all the fixes and will try again the next time it is activated'), 2, 'dialog-warning');
				print "\nPlease restart the system for the changes to take effect, not all the fixes could be installed\n";
			} else {
				if (defined($message)) {
					messageDesktop(__('Tomte finished'), $message, 2, 'dialog-warning');
				} elsif (!$noNetwork && somethingFailed()) {
					# only message if we have a working network connection and something could not be fixed
					messageDesktop(__('Tomte finished'), __('Tomte could not install all the fixes and will try again the next time it is activated'), 2, 'dialog-warning');
					print "\nNot all the fixes could be installed\n";
				}
			}
			return (1);
		} else {
			printLog("program will start again, tries: $triesValue, trieslimit: $TRIES_LIMIT", 'L2', '[INFO]');
		}
	}
	if ($startLaterAgain == 0) {
		$startLaterAgain = 1;
		# This might not be completely reliable, specially when starting a fresh installed system
		my $systemctlCmd = 'systemd-run --on-active="'.$SYSTEMD_DELAY.'sec" tuxedo-tomte configure all >/dev/null 2>&1';

		printLog("systemctlCmd: $systemctlCmd", 'L0', '[DEBUG]');
		printLog("starting \"systemd-run tuxedo-tomte configure all\" for delayed configuration", 'L0', '[DEBUG]');
		$retVal = system($systemctlCmd);
		printLog("ret value: $retVal", 'L0', '[DEBUG]');
	}
	return (0);
}



###############################################################################
# sets all modules dependant of some system program to work to status failed
#
sub postSetModulesState {
	my $sysProgram = shift;
	my $status = shift;
	$postConfProgramsList{$sysProgram}{status} = $status;
	foreach my $module (keys %configuredModules) {
		$configuredModules{$module}{status} = $status;
	}
	return (0);
}



###############################################################################
# returns the amount of failed modules or programs that configure some modules
sub somethingFailed {
	my $counter = 0;
	foreach my $confProgram (keys %postConfProgramsList) {
		if ($postConfProgramsList{$confProgram}{status} eq "failed") {
			$counter++;
		}
	}
	foreach my $confModule (keys %configuredModules) {
		if ($configuredModules{$confModule} eq "failed") {
			$counter++;
		}
	}
	return ($counter);
}



###############################################################################
# lists which modules and system programs where installed, configured
# or executed correctly and which not
#
sub listSuccess {
	my @failedPrograms;
	my @failedModules;
	my @successModules;
	my @nothingToDoModules;
	my @removedModules;
	my $successOrFailed = 0;

	foreach my $confProgram (keys %postConfProgramsList) {
		if ($postConfProgramsList{$confProgram}{status} eq "failed") {
			$successOrFailed = 1;
			push(@failedPrograms, $postConfProgramsList{$confProgram}{command});
		}
	}
	foreach my $confModule (keys %configuredModules) {
		if ($configuredModules{$confModule} eq "failed") {
			$successOrFailed = 1;
			push(@failedModules, $confModule);
		} elsif ($configuredModules{$confModule} eq "success") {
			$successOrFailed = 1;
			push(@successModules, $confModule);
		} elsif ($configuredModules{$confModule} eq "nothingToDo") {
			push(@nothingToDoModules, $confModule);
		} elsif ($configuredModules{$confModule} eq "removed") {
			push(@removedModules, $confModule);
		} else {
			# put the modules without defined value into "nothingToDo"
			push(@nothingToDoModules, $confModule);
		}
	}
	if (@successModules) {
		print "\nThe following modules have been installed successfully:\n";
		printNameDescriptionModules(@successModules);
	}
	if (@failedModules) {
		print "\nThe following modules have failed to be installed:\n";
		if (triesAlreadyDone() <= $TRIES_LIMIT) {
			if (! $LiveISO) {
				print "Tomte will try to install them again in a few minutes\n";
			}
		}
		printNameDescriptionModules(@failedModules);
	}
	if (@nothingToDoModules) {
		print "\nNothing was done for the following modules:\n";
		printNameDescriptionModules(@nothingToDoModules);
	}
	if (@failedPrograms) {
		print "\nThe following post installation programs have failed:\n";
		printNameDescriptionPrograms(@failedPrograms);
	}
	if (@removedModules) {
		print "\nThe following modules have been removed:\n";
		printNameDescriptionModules(@removedModules);
	}
	if (! $successOrFailed) {
		print "\nNothing to do in post configuration\n";
	}
	if ($restartSystem == 1) {
		messageDesktop(__('Restart required'), __('Please restart the system for the changes to take effect.'), 2, 'dialog-warning');
		print "\nPlease restart the system for the changes to take effect\n";
	} elsif ( configuredModulesExceptDefault(@successModules) ) {
		messageDesktop(__('TUXEDO Tomte finished'), __('TUXEDO Tomte finished applying all the required fixes available for this system.'), 1, 'dialog-information');
	}
	return (0);
}



###############################################################################
# returns 1 if any modules except defined ones were configured
# returns 0 if otherwise
#
sub configuredModulesExceptDefault {
	my @modulesList = @_;
	my @defaultModules = ( 'tuxedorepos', 'tuxedomirrors' );
	foreach ( @defaultModules ) {
		for my $index (reverse 0..$#modulesList) {
			if ($modulesList[$index] =~ /$_/sm) {
				splice(@modulesList, $index, 1, ());
			}
		}
	}
	if ((scalar(@modulesList) > 0) || ($longInstall == 1)) {
		return (1);
	}
	return (0);
}



###############################################################################
# print name and description from modules given as array
#
sub printNameDescriptionModules {
	my @elements = @_;
	foreach my $element (@elements) {
		print "- $presetModules{$element}{name}: $presetModules{$element}{description}\n";
	}
	return (0);
}


###############################################################################
# print name and description for programs given as array
#
sub printNameDescriptionPrograms {
	my @elements = @_;
	foreach my $element (@elements) {
		print "- $element: $postConfProgramsList{$element}{description}\n";
	}
	return (0);
}


###############################################################################
# show description of module
#
sub moduleDescription {
	my $moduleName = $argValue{module};
	my $module;
	if ($moduleName) {
		$module = getSubName($moduleName);
		if ( $module ne q{} ) {
	    print "$presetModules{$module}{description}\n";
		} else {
			print "module: $moduleName is not in the modules list\n";
		}
    } else {
		print "Missing module name\n";
	}
	return (0);
}



###############################################################################
# show all modules
#
sub modulesList() {
	foreach my $module (keys %presetModules) {
		print "$presetModules{$module}{name}\n";
	}
	return (0);
}



###############################################################################
# sets values to restart the system
#
sub setRestartValues {
	my $element = shift;
	$restartSystem = 1;
	printLog("setting restart for: $element", 'L2', '[DEBUG]');
	write_file($needsRestartFile, {'err_mode' => 'carp'}, 'Tomte: system has to be restarted');
	return (0);
}




###############################################################################
# limits number of tries Tomte will do to accomplish some task
# returns 0 if limit has been reached
# returns 1 if limit is not reached
#
sub tries() {
	my $limit = shift;
	printLog("tries: given limit: $limit", 'L2', '[DEBUG]');
	if (-e $triesFile) {
		my $triesDone = triesAlreadyDone();
		printLog("repeat number $triesDone of $limit", 'L0', '[DEBUG]');
		if ( $triesDone < $limit ) {
			printLog("rising tries, triesDone below limit", 'L2', '[DEBUG]');
			write_file($triesFile, {'err_mode' => 'carp'}, $triesDone+1);
			return (1);
		} else {
			printLog("limit tries done reached", 'L2', '[DEBUG]');
			printLog('deleting triesFile', 'L2', '[DEBUG]');
			unlink($triesFile);
			return (0);
		}
	} else {
		printLog('creating triesFile', 'L2', '[DEBUG]');
		write_file($triesFile, {'err_mode' => 'carp'}, '1');
	}
	return (1);
}




###############################################################################
# returns how many times Tomte got repeated
#
sub triesAlreadyDone() {
	if (-e $triesFile) {
		return (readFirstLineOfFile($triesFile)+0);
	} else {
		return (0);
	}
}



###############################################################################
# prints help
#
sub help {
	print "tuxedo-tomte $VERSION\n";
	print __('HELP_tuxedo-tomte'), "\n", __('HELP_tomte'). "\n";
	print __('HELP_list'), "\n";
	print __('HELP_status'), "\n";
	print __('HELP_versions'), "\n";
	print __('HELP_modules'), "\n";
	print __('HELP_description_MODULE'), "\n";
	print __('HELP_configure_MODULE'), "\n";
	print __('HELP_reconfigure_MODULE'), "\n";
	print __('HELP_remove_MODULE'), "\n";
	print __('HELP_block_MODULE'), "\n";
	print __('HELP_unblock_MODULE'), "\n";
	print __('HELP_AUTOMATIC'), "\n";
	print __('HELP_UPDATES_ONLY'), "\n";
	print __('HELP_DONT_CONFIGURE'), "\n";
	print __('HELP_loglevel'), "\n";
	print __('HELP_help'), "\n";
	print "\n\n";
	print __('HELP_footer'), "\n";
	exit 0;
}



###############################################################################
# checks if root and returns 1 if yes, otherwise 0
# also sets a global variable
#
sub isRoot {
	# check if root
	if( $> != 0 ) {
		$runningAsRoot = 0;
		return (0);
	}
	$runningAsRoot = 1;
	return (1);
}



###############################################################################
# checks system requirements, loads configfile
#
sub prepareValues {
	checkRequirements();
	initAvailablePackagesList();
	initConfigFile();
	transferConfigValues();
	reportPlannedChanges();
	return (0);
}



###############################################################################
# report planned changes according to list of installed modules and modules yet
# to be installed
#
sub reportPlannedChanges {
	foreach my $module (sort keys %presetModules) {
		if ($presetModules{$module}{required} eq 'yes') {
			if (($localConfModules{$module}{installed} eq 'no') &&
				($localConfModules{$module}{blocked} eq 'no') &&
				(checkModePermission('install', $module))) {
				printLog("module $module will be installed", 'L0', '[INFO]');
			}
		}
	}
	return (0);
}




###############################################################################
# checks if this is a TUXEDO device
#
sub tuxedoDevice {
	if ((defined $boardname) && (defined $boardvendor) && (defined $sysvendor)) {
		if ($boardname =~ /P65_P67RGRERA/sm) {
			return (1);
		}
		if ($boardname =~ /P64_HJ,HK1/sm) {
			return (1);
		}
		if ($boardvendor =~ /TUXEDO/sm) {
			return (1);
		}
		if ($sysvendor =~ /TUXEDO/sm) {
			return (1);
		}
		if ($boardname =~ /LAPQC71[AB]/sm) {
			return (1);
		}
	}
	return (0);
}



###############################################################################
# checks if this is a notebook
# returns 'notebook' if a notebook was detected
# returns 'desktoppc' if a desktop pc was detected
# returns 0 if there was an error or nothing was detected
#
sub getChassisType {
	my $fileName = '/sys/class/dmi/id/chassis_type';
	my $chassisTypeNbr;
	my $FH;
	if ((-e $fileName) && open($FH, "<", $fileName)) {
		$chassisTypeNbr = <$FH>;
		$chassisTypeNbr =~ s/\n//gsm;
		if (! close($FH)) {
			printLog("Could not close $fileName", 'TL0', '[ERROR]');
		}
	}
	if ($chassisTypeNbr eq '3') { return ('desktoppc'); }
	if ($chassisTypeNbr eq '4') { return ('desktoppc'); }
	if ($chassisTypeNbr eq '6') { return ('desktoppc'); }
	if ($chassisTypeNbr eq '7') { return ('desktoppc'); }
	if ($chassisTypeNbr eq '8') { return ('notebook'); }
	if ($chassisTypeNbr eq '9') { return ('notebook'); }
	if ($chassisTypeNbr eq '10') { return ('notebook'); }
	if ($chassisTypeNbr eq '17') { return ('desktoppc'); }
	if ($chassisTypeNbr eq '34') { return ('desktoppc'); }
	if ($chassisTypeNbr eq '35') { return ('desktoppc'); }
	if ($chassisTypeNbr eq '36') { return ('desktoppc'); }

	# special handling in case of virtual machines
	if ($chassisTypeNbr eq '1') {
		printLog("chassis defined as virtual machine ...", 'TL2', '[DEBUG]');
		if (isLineInFile('/sys/class/dmi/id/board_vendor', '(?i)nb.*')) {
			printLog("this virtual machine defined as notebook", 'TL2', '[DEBUG]');
			return ('notebook');
		} else {
			printLog("this virtual machine defined as desktoppc", 'TL2', '[DEBUG]');
			return ('desktoppc');
		}
	}

	my $hostnamectlCmd = $consoleLanguage.'hostnamectl';
	my $returnHostnamectl = `$hostnamectlCmd`;
	if ($returnHostnamectl =~ /^\s+Chassis: vm/gsm) {
		printLog("chassis is a virtual machine", 'TL2', '[DEBUG]');
		printLog("machine will be treated as desktoppc", 'TL2', '[DEBUG]');
		return ('desktoppc');
	}
	return ('0');
}





###############################################################################
###############################################################################
###############################################################################
###############################################################################
# MAIN PROGRAM

if ($argValue{command} eq "FAI") {
	$FAI = 1;
}
initLogging();

$chassisType = getChassisType();
printLog("chassistype will be: $chassisType", 'TL2', '[DEBUG]');
if ($chassisType eq '0') {
	print "Something went wrong and I could not determine what type of system this might be ...\n";
	exit (0);
}

if ((!tuxedoDevice()) && ($chassisType eq 'notebook')) {
	print "It seems that this is not a TUXEDO device. Please contact TUXEDO Computers if this is a mistake.\n";
	exit (0);
}

readOSData();

if (!isOSSupported()) {
	exit (0);
}

# define for each argvalues procedures
# modify $USAGE if any modifications are made here

if (scalar(@ARGV) != 0) {
	my %options;
	@options{"configure", "reconfigure", "remove", "block", "unblock", "update", "loglevel"} = ();

	if (exists $options{$argValue{command}} && (! isRoot() )) {
		print "You must have root access to use this option\n";
		exit (0);
	}

	# check whether it is a LiveISO installation
	# checks if directory /fll exists
	if ((-d '/fll') && isTextInFile('/proc/cmdline', 'fll')) {
		$LiveISO = 1;
	}

	# list
	if (($argValue{command} eq "list") || ($argValue{command} eq "status")) {
		checkMode();
		renameOldConfigFile();
		if (!-e $configFile) {
			print "no config file present yet, please use 'sudo tomte list' or 'sudo tomte status' to initialize one\n";
		} else {
			prepareValues();
		}
		listStatus();

	# versions
	} elsif ($argValue{command} eq "versions") {
		checkMode();
		renameOldConfigFile();
		if (!-e $configFile) {
			print "no config file present yet, please use 'sudo tomte list' or 'sudo tomte status' to initialize one\n";
		} else {
			prepareValues();
		}
		listVersions();

	# modules
	} elsif ($argValue{command} eq "modules") {
		modulesList();

	# printcompletefixeslist
	} elsif ($argValue{command} eq "printcompletefixeslist") {
		printTuxedoDevices();

	# listJSON
	} elsif ($argValue{command} eq "listjson") {
		checkMode();
		renameOldConfigFile();
		if (!-e $configFile) {
			print "no config file present yet, please use 'sudo tomte list' or 'sudo tomte status' to initialize one\n";
		} else {
			prepareValues();
		}
		listStatusJson();

	# return whether everything could be installed correctly
	} elsif ($argValue{command} eq "ok") {
		checkMode();
		renameOldConfigFile();
		if (!-e $configFile) {
			print "no config file present yet, please use 'sudo tomte list' or 'sudo tomte status' to initialize one\n";
		} else {
			prepareValues();
		}
		print checkNoFailedModules();

	# description
	} elsif ($argValue{command} eq "description") {
		moduleDescription();

	# help
	} elsif ($argValue{command} eq "help") {
		help();

	# FAI
	} elsif ($argValue{command} eq "FAI") {
		printLog("starting Tomte $VERSION in FAI mode", 'L0', '[INFO]');
		print( "Starting tuxedo-tomte $VERSION in FAI mode\n".
				"This should not be used in a normal situation\n".
				"only when installing a new system with FAI\n" );
		setMode('AUTOMATIC');
		prepareValues();
		configureAllModules();
		postConfigure();
		listSuccess();
		writeConfigFile();
		listStatus();
		printLog('Tomte in FAI finished', 'L0', '[INFO]');

	# configure
	} elsif ($argValue{command} eq "configure") {
		printLog("starting Tomte $VERSION in configure mode", 'L2', '[INFO]');
		# check if there is a second argument
		if ($argValue{module} ne q{}) {
			checkMode();
			prepareValues();
			prerequisites();
			if ($argValue{module} eq "all") {
				if ($LiveISO) {
					printLog("starting Tomte in LiveISO mode", 'L0', '[INFO]');
					print "Starting tuxedo-tomte $VERSION in LiveISO mode\n".
					"This should not be used in a normal situation\n".
					"only when installing a new system with LiveISO\n";
				}
				if ($mode eq 'DONT_CONFIGURE') {
					printLog("Mode DONT_CONFIGURE is set", 'TL0', '[INFO]');
					printLog("only prerequisite modules will be installed", 'TL0', '[INFO]');
				}
				# Update Tuxedo Tomte to ensure current version
				if (! ($LiveISO or $FAI)){
					if (unlockPM("apt-get -yq install tuxedo-tomte") && (isPMlocked() == 0)) {
						my $retval;
						printLog("executing tuxedo-tomte installation", 'L2', '[DEBUG]');
						if ($logLevel > 1) {
							$retval = `apt-get -yq -o Dpkg::lock::timeout=0 install tuxedo-tomte >>$logFile 2>&1`;
						} else {
							$retval = `apt-get -yq -o Dpkg::lock::timeout=0 install tuxedo-tomte >/dev/null 2>&1`;
						}
						lockPM();
						printLog("return from apt-get:\n$retval", 'L2', '[DEBUG]');
					} else {
						printLog("Couldn't get PM-Locks for 'apt-get -yq install tuxedo-tomte'", 'L1', '[DEBUG]');
					}
				}
				configureAllModules();
				checkRequirementsLast();
				postConfigure();
				listSuccess();
				writeConfigFile();
			} elsif (validModuleName($argValue{module}) ne q{}) {
				if ($mode eq 'DONT_CONFIGURE') {
					printLog("Mode DONT_CONFIGURE is set", 'TL0', '[INFO]');
					printLog("only prerequisite modules will be installed", 'TL0', '[INFO]');
				}
				configureSingleModule($argValue{module});
				postConfigure();

				listSuccess();
				writeConfigFile();
			} else {
				print "module $argValue{module} does not exist\n";
			}
		} else {
			print "Module name or \"all\" missing\n";
		}
		printLog('Tomte finished', 'L0', '[INFO]');

	# remove
	} elsif ($argValue{command} eq "remove") {
		printLog("starting Tomte $VERSION in remove mode", 'L2', '[INFO]');
		if ($argValue{module} ne q{}) {
			if (validModuleName($argValue{module}) ne q{}) {
				checkMode();
				prepareValues();
				removeSingleModule($argValue{module});
				postConfigure();
				listSuccess();
				writeConfigFile();
			} else {
				print "module $argValue{module} does not exist\n";
			}
		} else {
			print "Module name or \"all\" missing\n";
		}
		printLog('Tomte finished', 'L0', '[INFO]');

	# reconfigure
	} elsif ($argValue{command} eq "reconfigure") {
		printLog("starting Tomte $VERSION in reconfigure mode", 'L2', '[INFO]');
		# reconfigure interrupts repeated execution
		if (-e $triesFile) {
			printLog('delete triesFile', 'L2', '[DEBUG]');
			unlink($triesFile);
		}
		if ($argValue{module} ne q{}) {
			checkMode();
			prepareValues();
			prerequisites();
			if ($argValue{module} eq "all") {
				if ($mode eq 'DONT_CONFIGURE') {
					printLog("DONT_CONFIGURE is set", 'TL0', '[INFO]');
					printLog("only prerequisite modules will be installed", 'TL0', '[INFO]');
				}
				reconfigureAllModules();
				postConfigure();
				listSuccess();
				writeConfigFile();
			} elsif (validModuleName($argValue{module}) ne q{}) {
				if ($mode eq 'DONT_CONFIGURE') {
					printLog("DONT_CONFIGURE is set", 'TL0', '[INFO]');
					printLog("only prerequisite modules will be installed", 'TL0', '[INFO]');
				}
				reconfigureSingleModule($argValue{module});
				postConfigure();
				listSuccess();
				writeConfigFile();
			} else {
				print "module $argValue{module} does not exist\n";
			}
		} else {
			print "Module name or \"all\" missing\n";
		}
		printLog('Tomte finished', 'L0', '[INFO]');

	# blocking/unblocking should work in any mode
	# block
	} elsif ($argValue{command} eq "block") {
		if ($argValue{module} ne q{}) {
			prepareValues();
			if ($argValue{module} eq 'all') {
				blockAllModules();
				writeConfigFile();
				printLog('Tomte finished', 'L1', '[INFO]');
			} elsif (validModuleName($argValue{module}) ne q{}) {
				block($argValue{module});
				writeConfigFile();
				printLog('Tomte finished', 'L0', '[INFO]');
			} else {
				print "module $argValue{module} does not exist\n";
			}
		} else {
			print "Module name missing\n";
		}

	# unblock
	} elsif ($argValue{command} eq "unblock") {
		if ($argValue{module} ne q{}) {
			prepareValues();
			if ($argValue{module} eq 'all') {
				unblockAllModules();
				writeConfigFile();
				printLog('Tomte finished', 'L1', '[INFO]');
			} elsif (validModuleName($argValue{module}) ne q{}) {
				unblock($argValue{module});
				writeConfigFile();
				printLog('Tomte finished', 'L0', '[INFO]');
			} else {
				print "module $argValue{module} does not exist\n";
			}
		} else {
			print "Module name missing\n";
		}

	# modes
	} elsif (($argValue{command} eq "DONT_CONFIGURE") ||
			($argValue{command} eq "AUTOMATIC") ||
			($argValue{command} eq "UPDATES_ONLY")) {
		setMode($argValue{command});
		prepareValues();
		printLog('Tomte finished', 'L0', '[INFO]');
	} elsif ($argValue{command} eq "loglevel") {
		printLog("before loglevel: $logLevel requested loglevel: $argValue{module}", 'L2', '[DEBUG]');
		if (defined($argValue{module}) && $argValue{module} ne q{}) {
			$argValue{module} += 0;
			if (($argValue{module} >= 0) && ($argValue{module} < 3)) {
				$logLevel = $argValue{module};
				$logLevel += 0;
				printLog("resulting loglevel: $logLevel", 'L2', '[DEBUG]');
				$configIniValues->{initvalues}->{loglevel} = $logLevel;
				writeConfigIniFile();
			} else {
				printLog("invalid entry for loglevel. Valid numbers are 0, 1 and 2", 'TL0', '[DEBUG]');
			}
		} else {
			printLog("$logLevel", 'T0', '[DEBUG]');
		}
	} else {
		print "Unknown command: $argValue{command}\n";
		help();
	}
} else {
	help();
}

# interrupt repeated delayed execution if not necessary anymore
if (($startLaterAgain == 0) &&
	(($argValue{command} eq 'configure') || ($argValue{command} eq 'reconfigure'))) {
	if (-e $triesFile) {
		if (-w $triesFile) {
			printLog('deleting triesFile', 'L2', '[DEBUG]');
			unlink($triesFile);
		}
	}
}

END {
	my %options;
	@options{"configure", "reconfigure", "remove", "block", "unblock", "update", "loglevel"} = ();
	if ( exists $options{$argValue{command}} && $runningAsRoot ) {
		if (!$FAI) {
			my $packagekitEnabled = getSystemdEnabled('packagekit');
			$packagekitState = getSystemdState('packagekit');
			printLog("\npackagekitEnabled: $packagekitEnabled\npackagekitOriginalEnabled: $packagekitOriginalEnabled\npackagekitState: $packagekitState\npackagekitOriginalState: $packagekitOriginalState", 'L2', '[DEBUG]');
			if ($packagekitEnabled ne $packagekitOriginalEnabled) {
				printLog("packagekit enabled was originally: $packagekitOriginalEnabled now: $packagekitEnabled", 'L2', '[DEBUG]');
				if ($packagekitOriginalEnabled eq 'enabled') {
					commandSystemdModule('unmask', 'packagekit');
				}
			}
			if ($packagekitOriginalState ne $packagekitState) {
				printLog("packagekit was originally: $packagekitOriginalState it is now: $packagekitState", 'L2', '[DEBUG]');
				if ($packagekitOriginalState eq 'active') {
					commandSystemdModule('enable', 'packagekit');
				}
			}
		}
	}
	if ($runningAsRoot) {
		if ($distribution eq 'TUXEDO OS') {
			if ($FAI || $LiveISO) {
				# do nothing special for now
			} elsif (!$FAI && defined($configIniValues->{installation}->{FAI}) && ($configIniValues->{installation}->{FAI} eq 'first reboot after FAI')) {
				# this happens when in OEM stage after a FAI installation,
				# if the OEM stage gets interrupted by a reboot or shutdown
				# if you reboot the system after a FAI installation in OEM, please,
				# just make a new FAI installation! to make sure everything is proper
				printLog("after FAI installation detected, this is probably the OEM stage", 'L2', '[DEBUG]');
				my $calamaresProcesses = `pgrep -c calamares`;
				printLog("calamares processes found: $calamaresProcesses", 'L2', '[DEBUG]');
				printLog("first boot out of OEM, setting second reboot flag", 'L2', '[DEBUG]');
				$configIniValues->{installation}->{FAI} = 'second reboot after FAI';
				writeConfigIniFile();
			} elsif (!$FAI && defined($configIniValues->{installation}->{FAI}) && ($configIniValues->{installation}->{FAI} eq 'second reboot after FAI')) {
				printLog("after FAI installation second stage detected, this is probably the OEM stage", 'L2', '[DEBUG]');
				my $calamaresProcesses = `pgrep -c calamares`;
				if ($calamaresProcesses =~ /0/gsm) {
					printLog("third boot out of OEM, no calamares detected, deleting boot flag", 'L2', '[DEBUG]');
					delete $configIniValues->{installation};
					writeConfigIniFile();
				}
			}
		} elsif (!$FAI && defined($configIniValues->{installation}->{FAI}) && ($configIniValues->{installation}->{FAI} eq 'first reboot after FAI')) {
			delete $configIniValues->{installation};

			writeConfigIniFile();
		}
		# just to be sure, always write the tomte_finished file
	}
}
