#! /usr/bin/perl use strict; # add the folder this script lives in to perl's search path for modules: use FindBin; use lib $FindBin::Bin; use Fcntl qw(:DEFAULT :flock); use Getopt::Long qw(:config pass_through); use OpenSLX::Basics; use OpenSLX::ConfigDB; my $pxelinux0Path = "/usr/share/syslinux/pxelinux.0"; my $pxeConfigDefaultTemplate = q[NOESCAPE 0 PROMPT 0 TIMEOUT 100 DEFAULT menu IMPLICIT 1 ALLOWOPTIONS 1 ONERROR menu MENU TITLE What would you like to do? (use cursor to select) MENU MASTER PASSWD secret ]; my ( $dryRun, # dryRun won't touch any file $defaultSystem, # configuration specified by default system $defaultClient, # configuration specified by default client %systemConf, # system configurations - straight from DB %clientConf, # configurations for each client, folded info from client, groups # and default client $systemConfCount, # number of system configurations written $clientSystemConfCount, # number of (system-specific) client configurations written ); GetOptions( 'dry-run' => \$dryRun # dry-run doesn't write anything, just prints statistic about what # would have been written ); openslxInit(); my $openslxDB = connectConfigDB(); my $configPath = "$openslxConfig{'private-basepath'}/config"; if (!-d $configPath) { die _tr("Unable to access config-path '%s'!", $configPath); } my $tempPath = "$openslxConfig{'temp-basepath'}/oslx-demuxer"; if (!$dryRun) { mkdir $tempPath; if (!-d $tempPath) { die _tr("Unable to create or access temp-path '%s'!", $tempPath); } } my $exportPath = "$openslxConfig{'public-basepath'}/tftpboot"; if (!$dryRun) { system("rm -rf $exportPath/client-conf/* $exportPath/pxe/pxelinux.cfg/*"); system("mkdir -p $exportPath/client-conf $exportPath/pxe/pxelinux.cfg"); if (!-d $exportPath) { die _tr("Unable to create or access export-path '%s'!", $exportPath); } if (!-e "$exportPath/pxe/pxelinux.0") { system("cp -a $pxelinux0Path $exportPath/pxe/pxelinux.0"); } } my $lockFile = "$exportPath/config-demuxer.lock"; lockScript($lockFile); fetchConfigurations(); writeConfigurations(); my $wr = ($dryRun ? "would have written" : "wrote"); print "$wr $systemConfCount systems and $clientSystemConfCount client-configurations to $exportPath/client-conf\n"; disconnectConfigDB($openslxDB); system("rm -rf $tempPath") unless $dryRun || length($tempPath) == 0; unlockScript($lockFile); exit; ################################################################################ ### ################################################################################ sub lockScript { my $lockFile = shift; return if $dryRun; # use a lock-file to singularize execution of this script: if (-e $lockFile) { my $ctime = (stat($lockFile))[10]; my $now = time(); if ($now - $ctime > 15*60) { # existing lock file is older than 15 minutes, wipe it: unlink $lockFile; } } sysopen(LOCKFILE, $lockFile, O_RDWR|O_CREAT|O_EXCL) or die _tr(qq[Lock-file <%s> exists, script is already running.\nPlease remove the logfile and try again if you are sure that no one else is executing this script.], $lockFile); } sub unlockScript { my $lockFile = shift; return if $dryRun; unlink $lockFile; } sub isAttribute { # returns whether or not the given key is an exportable attribute my $key = shift; return $key =~ m[^attr]; } sub mergeConfigAttributes { # copies all attributes of source that are unset in target over my $target = shift; my $source = shift; foreach my $key (grep { isAttribute($_) } keys %$source) { if (length($source->{$key}) > 0 && length($target->{$key}) == 0) { vlog 3, _tr("\tmerging %s (val=%s)", $key, $source->{$key}); $target->{$key} = $source->{$key}; } } } sub writeAttributesToFile { my $attrHash = shift; my $fileName = shift; return if $dryRun; open(ATTRS, "> $fileName") or die "unable to write to $fileName"; my @attrs = sort grep { isAttribute($_) } keys %$attrHash; foreach my $attr (@attrs) { if (length($attrHash->{$attr}) > 0) { my $shellVar = $attr; # convert 'attrExampleName' to 'example_name': $shellVar =~ s[([A-Z])]['_'.lc($1)]ge; $shellVar = substr($shellVar, 5); print ATTRS "$shellVar = $attrHash->{$attr}\n"; } } close(ATTRS); } sub copySystemConfig { # copies local configuration extensions of given system from private # config folder (var/lib/openslx/config/...) into a temporary folder my $systemName = shift; my $targetPath = shift; return if $dryRun; system("rm -rf $targetPath"); mkdir $targetPath; # first copy default files... my $defaultConfigPath = "$configPath/default"; if (-d $defaultConfigPath) { system("cp -r $defaultConfigPath/* $targetPath"); } # now pour system-specific configuration on top (if any): my $systemConfigPath = "$configPath/$systemName"; if (-d $systemConfigPath) { system("cp -r $systemConfigPath/* $targetPath"); } } sub createTarOfPath { my $buildPath = shift; my $tarName = shift; my $destinationPath = shift; my $tarFile = "$destinationPath/$tarName"; vlog 1, _tr('creating tar %s', $tarFile); return if $dryRun; mkdir $destinationPath; my $tarCmd = "cd $buildPath && tar czf $tarFile *"; if (system("$tarCmd") != 0) { die _tr("unable to execute shell-command:\n\t%s \n\t($!)", $tarCmd); } } sub externalClientIDFor { my $client = shift; my $mac = lc($client->{mac}); # PXE seems to expect MACs being all lowercase $mac =~ tr[:][-]; return "01-$mac"; } ################################################################################ ### ################################################################################ sub writePXEMenus { my $pxePath = "$exportPath/pxe/pxelinux.cfg"; foreach my $client (values %clientConf) { my $externalClientID = externalClientIDFor($client); my $pxeFile = "$pxePath/$externalClientID"; vlog 1, _tr("writing PXE-file $pxeFile"); open(PXE, "> $pxeFile") or die "unable to write to $pxeFile"; print PXE $pxeConfigDefaultTemplate; foreach my $system (values %{$client->{systems}}) { print PXE "LABEL openslx-$system->{name}\n"; print PXE "\tMENU DEFAULT\n"; print PXE "\tMENU LABEL ^$system->{label}\n"; print PXE "\tKERNEL $system->{kernel}\n"; print PXE "\tAPPEND $system->{kernel_params}\n"; print PXE "\tIPAPPEND 1\n"; } close(PXE); } } sub writeClientConfigurationsForSystem { my $system = shift; my $buildPath = shift; my $attrFile = shift; foreach my $client (values %{$system->{clients}}) { vlog 2, _tr("exporting client %d:%s", $client->{id}, $client->{name}); $clientSystemConfCount++; # copy this client's configuration in order to # merge system configuration into client config and write the # resulting attributes to a configuration file: my $clientSystemConf = { %$client }; mergeConfigAttributes($clientSystemConf, $system); writeAttributesToFile($clientSystemConf, $attrFile); my $externalClientID = externalClientIDFor($client); createTarOfPath($buildPath, "${externalClientID}.tgz", "$exportPath/client-conf/$system->{name}"); } } sub writeSystemConfigurations { foreach my $system (values %systemConf) { vlog 2, _tr('exporting system %d:%s', $system->{id}, $system->{name}); $systemConfCount++; my $buildPath = "$tempPath/build"; copySystemConfig($system->{name}, $buildPath); my $attrFile = "$buildPath/initramfs/machine-setup"; writeAttributesToFile($system, $attrFile); createTarOfPath($buildPath, "default.tgz", "$exportPath/client-conf/$system->{name}"); writeClientConfigurationsForSystem($system, $buildPath, $attrFile); system("rm -rf $buildPath") unless $dryRun; } } sub linkClientToSystems { my ($client, @systemIDs) = @_; my $clientID = $client->{id}; $client->{systems} = {} unless exists $client->{systems}; foreach my $sysID (@systemIDs) { my $sysConf = $systemConf{$sysID}; next if !defined $sysConf || $sysConf->{unbootable}; # refer from system to client: $sysConf->{clients} = {} unless exists $sysConf->{clients}; if (!exists $sysConf->{clients}->{$clientID}) { vlog 2, _tr('linking client %d:%s to system %d:%s', $clientID, $client->{name}, $sysID, $sysConf->{name}); $sysConf->{clients}->{$clientID} = $client; } # refer from client to system: if (!exists $client->{systems}->{$sysID}) { $client->{systems}->{$sysID} = $systemConf{$sysID}; } } } sub fetchSystemConfigurations { $defaultSystem = fetchSystemsByID($openslxDB, 0); foreach my $s (fetchSystemsByFilter($openslxDB)) { next unless $s->{id} > 0; vlog 2, _tr('read system %d:%s...', $s->{id}, $s->{name}); # replace any whitespace in name, as we will use it as a # directory name later: $s->{name} =~ s[\s+][_]g; # merge default system configuration into this system and store # that into hash: mergeConfigAttributes($s, $defaultSystem); $systemConf{$s->{id}} = $s; } } sub fetchClientConfigurations { my %groups; foreach my $g (fetchGroupsByFilter($openslxDB)) { vlog 2, _tr('read group %d:%s...', $g->{id}, $g->{name}); $groups{$g->{id}} = $g; } $defaultClient = fetchClientsByID($openslxDB, 0); foreach my $client (fetchClientsByFilter($openslxDB)) { next unless $client->{id} > 0; vlog 2, _tr('read client %d:%s...', $client->{id}, $client->{name}); # add all systems directly linked to client: linkClientToSystems($client, fetchSystemIDsOfClient($openslxDB, $client->{id})); # now fetch and step over all groups this client belongs to # (ordered by priority from highest to lowest): my @clientGroups = sort { $b->{priority} <=> $a->{priority} } map { $groups{$_} } grep { exists $groups{$_} } # just to be safe: filter out unknown group-IDs fetchGroupIDsOfClient($openslxDB, $client->{id}); foreach my $group (@clientGroups) { # fetch and add all systems that the client inherits from # the current group: linkClientToSystems($client, fetchSystemIDsOfGroup($openslxDB, $group->{id})); # merge configuration from this group into the current client: vlog 3, _tr('merging from group %d:%s...', $group->{id}, $group->{name}); mergeConfigAttributes($client, $group); } # merge configuration from default client and store client # configuration into hash: vlog 3, _tr('merging from default client...'); mergeConfigAttributes($client, $defaultClient); $clientConf{$client->{id}} = $client; } } sub fetchConfigurations { fetchSystemConfigurations(); fetchClientConfigurations(); } sub writeConfigurations { $systemConfCount = $clientSystemConfCount = 0; writeSystemConfigurations(); writePXEMenus(); }