#!/usr/bin/perl
#
# This script is a component of Warewulf,
# http://www.runlevelzero.net/greg/warewulf
#
#########################################################################
#
# Copyright (c) 2003, The Regents of the University of California, through
# Lawrence Berkeley National Laboratory (subject to receipt of any
# required approvals from the U.S. Dept. of Energy).  All rights reserved.
#
# This program 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 2
# of the License, or (at your option) any later version.
#
# This program 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.
#
# The GNU GPL Document can be found at:
# http://www.gnu.org/copyleft/gpl.html
#
#########################################################################
#
# Written and maintained by:
#       Greg Kurtzer, <gmkurtzer@lbl.gov>

use lib "/usr/lib/warewulf/", "/usr/lib64/warewulf/";
use Warewulf::PXE;
use Warewulf::Util;
use Warewulf::Config;
use Getopt::Long;
use File::Path;
use Socket;

$boot_ip_default = "10.128.0.1";

my $usage = "USAGE: $0 [options]
  About:
   wwinit is used to build/configure non-warewulf services, as well as
   bootstrap anything needed for proper Warewulf configuration (ie. vnfs and
   wwinitrd).

  Options:
    --config   (Re)configure all services and configurations
    --pxe      Update the pxeconfig files for the nodes
    --dhcp     Configure the DHCP service only (when boot device != auto)
    --hosts    Rewrite the /etc/hosts file
    --exports  Reconfigure NFS exports
    --help     Show this banner

  This tool is part of the Warewulf cluster distribution
     http://warewulf-cluster.org/
";

GetOptions(
   'configure' => \$config,
   'init'      => \$config,
   'pxe'       => \$pxe,
   'hosts'     => \$hosts,
   'exports'   => \$exports,
   'dhcp'      => \$dhcp,
   'help'      => \$help,
);

if ( $help ) {
   print $usage;
   exit;
}

if ( $> != '0' ) {
      die "You need to run this as root!\n";
#   warn "You need to be root to make configuration changes!\n";
}


sub path_check {
   my ( $path, @foo ) = @_ ;
   if ( -d "$path" ) {
      print "      dir: $path present...\n";
   } else {
      mkpath("$path");
      print "      dir: $path created...\n";
   }
}

sub exit_check {
   my ( $exit, @foo ) = @_;
   if ( $exit != '0' ) {
      warn "Command errored out... Exiting!\n";
      exit ($exit);
   }
}

# check to see if we need to run init
if ( ! -f "/tftpboot/kernel" or
     ! -f "/tftpboot/wwinitrd.img" or
     ! -d "/tftpboot/pxelinux.cfg" ) {
  $config = "1";
  print "Doing the full configuration!\n";
}

$dhcp_tpl_auto = "
# Written by Warewulf

default-lease-time            21600;
max-lease-time                21600;
not authoritative;
ddns-update-style              none;

subnet #network# netmask #netmask# {
   authoritative;
   range #range_start# #range_end#;
   next-server          #boot_ipaddr#;
   filename            \"/pxelinux.0\";
}
";

$dhcp_tpl_managed = "
# Written by Warewulf

default-lease-time            21600;
max-lease-time                21600;
not authoritative;
ddns-update-style              none;
next-server                   #boot_ipaddr#;

#boot subnet#

group {
#node definitions#
}
";

if ( $config ) {
   print "\n======================= Warewulf configuration tool ==========================\n\n";
   print "This program should be run on the master node of a Warewulf cluster after\n";
   print "the system has been completly configured (ie. drivers, network, etc..) and\n";
   print "the Warewulf programs and VNFS has been installed\n";
   print"\nHave you properly configured this system as a Warewulf master? [y/N]: ";
   chomp ($cont = <STDIN>);
   while (1) {
      if ( $cont =~ /^y[es]?$/i ) {
         last;
      } elsif ( $cont =~ /^n[o]?/i or ! $cont ) {
         print "Run this again when you are ready...\n";
         exit;
         } else {
         print "Answer (y)es or (n)o: ";
         chomp ($cont = <STDIN>);
      }
   }

	sub node_count {
	   my ($ipaddr, $netmask, @NULL) = @_;
	   my ( $range1, $range2, $tmp);
	   $start = &node_addr_start("0", "$ipaddr", "$netmask");
	   $range1 = &node_addr($node_offset, "$ipaddr", "$netmask");
	   for($i=1;;$i++) {
	      $tmp = &node_addr($i+$node_offset, "$ipaddr", "$netmask");
	      if ( $tmp eq $start) {
	         # Woops, gone too far... back up 2 iterations...
	         $i -= 2;
	         $range2 = &node_addr($i+$node_offset, "$ipaddr", "$netmask");
	         last;
	      }
	   }
	   return($i, $range1, $range2);
	}

   %master = &master_config();

   if ( ! $master{'network'}{'boot device'} or $master{'network'}{'boot device'} eq "auto" ) {
      print "\n======================= Auto boot device configuration =======================\n\n";
      print "Warewulf requires a specific non-conflicting network for booting the nodes.\n";
      print "when using the 'boot device = auto' paradigm. This will create a device alias\n";
      print "off of your admin network. Select an address range that is unique on the\n";
      print "network attached to $master{'network'}{'admin device'}.\n\n";
      while ($boot_ip !~ /^\d+\.\d+\.\d+\.\d+$/ ) {
         print "Specify the network address that should be used for the DHCP node\n";
         print "boot range [10.128.0.0]: ";
         chomp ($boot_ip = <STDIN> );
         if ( ! $boot_ip ) {
            $boot_ip = $boot_ip_default;
         }
      }
#      print "   $master{'network'}{'admin device'}:1";
#      print "   $boot_ip/$master{'network'}{'admin netmask'}\n";
#      print "\n";
#      while (1) {
#         print "Should I create this boot device alias and configure for you? [Y/n]: ";
#         chomp ($cont = <STDIN>);
#         if ( $cont =~ /^y[es]?$/i or ! $cont ) {
            open(IFCFG, "> $master{'paths'}{'ifcfg'}$master{'network'}{'admin device'}:1");
            print IFCFG "DEVICE=$master{'network'}{'admin device'}:1\n";
            print IFCFG "IPADDR=$boot_ip\n";
            print IFCFG "NETMASK=$master{'network'}{'admin netmask'}\n";
            print IFCFG "BOOTPROTO=static\n";
            print IFCFG "ONBOOT=yes\n";
            close IFCFG;
            #system("/sbin/ifdown $master{'network'}{'admin device'}:1 >/dev/null 2>&1");
            system("/sbin/ifup $master{'network'}{'admin device'}:1 >/dev/null 2>&1");
#            last
#         } elsif ( $cont =~ /^n[o]?$/i ) {
#            last;
#         }
#      }
      $boot_netmask = $master{'network'}{'admin netmask'};
      $boot_dev = "$master{'network'}{'admin device'}:1";
      ($boot_count, $boot_1, $boot_2) = 
         &node_count($boot_ip, $master{'network'}{'admin netmask'});
   } elsif ( $master{'network'}{'boot device'} ) {
      $boot_netmask = $master{'network'}{'boot netmask'};
      $boot_dev = $master{'network'}{'boot device'};
      $boot_ip = $master{'ipaddrs'}{'boot'};
      ($boot_count, $boot_1, $boot_2) = 
         &node_count($boot_ip, $master{'network'}{'boot netmask'});
   }


   %master = &master_config();
   %nodes = &node_config();

   print "\n============================== Node address ranges ===========================\n\n";
   print "Below are the addresses and ranges that will be used for your Warewulf cluster\n";
   print "This information is calculated directly from the current network configuration\n";
   print "of your master node.\n\n";

   ($admin_count, $admin_1, $admin_2) = 
      &node_count($master{'network'}{'admin ipaddr'}, $master{'network'}{'admin netmask'});
   ($cluster_count, $cluster_1, $cluster_2) = 
      &node_count($master{'network'}{'cluster ipaddr'}, $master{'network'}{'cluster netmask'});
   ($sharedfs_count, $sharedfs_1, $sharedfs_2) = 
      &node_count($master{'network'}{'sharedfs ipaddr'}, $master{'network'}{'sharedfs netmask'});

   printf ("%-10s %-10s %-6s %-15s %-15s\n",
      "Network",
      "Device",
      "Nodes",
      "Starting IP",
      "Ending IP" );

   printf ("%-10s %-10s %-6d %-15s %-15s\n",
      "BOOT",
      $boot_dev,
      $boot_count,
      $boot_1,
      $boot_2 );

   printf ("%-10s %-10s %-6d %-15s %-15s\n",
      "ADMIN",
      $master{'network'}{'admin device'},
      $admin_count,
      $admin_1,
      $admin_2 );

   printf ("%-10s %-10s %-6d %-15s %-15s\n",
      "SHAREDFS",
      $master{'network'}{'sharedfs device'},
      $sharedfs_count,
      $sharedfs_1,
      $sharedfs_2 );

   printf ("%-10s %-10s %-6d %-15s %-15s\n",
      "CLUSTER",
      $master{'network'}{'cluster device'},
      $cluster_count,
      $cluster_1,
      $cluster_2 );

   print "\nNote: If you want to tweak the the above configuration, you should edit\n";
   print "        the network configuration on the master node.\n";
   while (1) {
      print "\nDoes this configuration look correct? [Y/n]: ";
      chomp ( $cont = <STDIN> );
      if ( $cont =~ /^y[es]?$/i or ! $cont ) {
         last;
      } elsif ( $cont =~ /^n[o]?$/i ) {
         exit;
      }
   }
} else {
   %master = &master_config();
   %nodes = &node_config();
}

print "\n============================ Services configuration ==========================\n\n";

sub dhcp_managed {

   $boot_subnet = "subnet $master{'network'}{'boot network'} netmask $master{'network'}{'boot netmask'} {\n";
   $boot_subnet .= "    authoritative;\n";
   $boot_subnet .= "}";

   nodeloop: foreach $node ( sort keys %nodes ) {
      next nodeloop if ( $nodes{$node}{'skip'} );
      next nodeloop if ( $nodes{$node}{'designation'} ne $node );

      $node_definations .= "   # NODE: $node\n";
      $node_definitions .= "   host $nodes{$node}{'boot ipaddr'} {\n";
      $node_definitions .= "      hardware ethernet   $nodes{$node}{'mac address'};\n";
      $node_definitions .= "      fixed-address       $nodes{$node}{'boot ipaddr'};\n";
      $node_definitions .= "      filename            \"/pxelinux.0\";\n";
      $node_definitions .= "   }\n";

      if ($nodes{$node}{'hardware addr2'}) {
         $node_definations .= "   # NODE (hwaddr 2): $node\n";
         $node_definitions .= "   host $nodes{$node}{'boot ipaddr'} {\n";
         $node_definitions .= "      hardware ethernet   $nodes{$node}{'hardware addr2'};\n";
         $node_definitions .= "      fixed-address       $nodes{$node}{'boot ipaddr'};\n";
         $node_definations .= "      filename            \"/pxelinux.0\";\n";
         $node_definations .= "   }\n";
      }
   }

   $dhcp_tpl_managed =~ s/#gateway#/$master{'ipaddrs'}{'boot'}/;
   $dhcp_tpl_managed =~ s/#boot subnet#/$boot_subnet/;
   $dhcp_tpl_managed =~ s/#other subnets#/$other_subnet/;
   $dhcp_tpl_managed =~ s/#node definitions#/$node_definitions/;
   $dhcp_tpl_managed =~ s/#boot_ipaddr#/$boot_ip/g;


   open(DHCPD, "> $master{'configs'}{'dhcp'}");
   print DHCPD $dhcp_tpl_managed;
   close DHCPD;
   system("/sbin/chkconfig dhcpd on");
   system("$master{'commands'}{'restart dhcpd'}");
   print "\n";
   if ( $? ne '0' ) {
      print "ERROR: Restart service by hand to figure out what is wrong!\n";
      exit 1;
   }
}


sub dhcp_auto {
   my ($boot_count, $boot_1, $boot_2) =
         &node_count($boot_ip, $master{'network'}{'admin netmask'});

   if ( ! $master{'network'}{'boot device'} or $master{'network'}{'boot device'} eq "auto" ) {
      $bootdev = "$master{'network'}{'admin device'}:1";
   } else {
      $bootdev = "$master{'network'}{'boot device'}";
   }

   $network = &ipcalc_network($boot_ip, $master{'network'}{'admin netmask'});
   $dhcp_tpl_auto =~ s/#network#/$network/g;
   $dhcp_tpl_auto =~ s/#netmask#/$master{'network'}{'admin netmask'}/g;
   $dhcp_tpl_auto =~ s/#range_start#/$boot_1/g;
   $dhcp_tpl_auto =~ s/#range_end#/$boot_2/g;
   $dhcp_tpl_auto =~ s/#boot_ipaddr#/$boot_ip/g;
   open(DHCPD, "> $master{'configs'}{'dhcp'}");
   print DHCPD $dhcp_tpl_auto;
   close DHCPD;
   system("/sbin/chkconfig dhcpd on");
   system("$master{'commands'}{'restart dhcpd'}");
   print "\n";
   if ( $? ne '0' ) {
      print "ERROR: Restart service by hand to figure out what is wrong!\n";
      exit 1;
   }
}

if ( $hosts ) {

   while (1) {
      $outfile = ();
      if ( $hosts ) {
         $cont = "y";
         print "Rewriting /etc/hosts\n";
      } else {
         print "Should I rewrite your /etc/hosts file now? [Y/n]: ";
         chomp ( $cont = <STDIN> );
      }
      if ( $cont =~ /^y[es]?$/i or ! $cont ) {
         &write_hosts();
         last;
      } elsif ( $cont =~ /^n[o]?$/i ) {
         last;
      }
   }
}

if ( $config or $dhcp ) {
   while (1) {
      if ( $dhcp ) {
         $cont = "y";
         print "Reconfiguring dhcp...\n";
      } else {
         print "Should I configure DHCP for you now? [Y/n]: ";
         chomp ( $cont = <STDIN> );
      }
      if ( $cont =~ /^y[es]?$/i or ! $cont ) {
         if ( $master{"network"}{"boot device"} and $master{"network"}{"boot device"} ne "auto" ) {
            &dhcp_managed();
            &pxe_update();
         } else {
            &dhcp_auto();
            &pxe_update();
         }
         last;
      } elsif ( $cont =~ /^n[o]?$/i ) {
         last;
      }
   }
}

if ( $pxe ) {
   print "Building the default PXE configuration...\n";
   &pxe_update("1");
}


if ( $config or $exports ) {
   while (1) {
      $outfile = ();
      if ( $exports ) {
         $cont = "y";
         print "Reconfiguring your NFS exports...\n";
      } else {
         print "Should I configure your NFS exports now? [Y/n]: ";
         chomp ( $cont = <STDIN> );
      }
      if ( $cont =~ /^y[es]?$/i or ! $cont ) {
         open(OUTFILE, "< $master{'configs'}{'exports'}");
         while (<OUTFILE>) {
            chomp;
            if ( $_ eq '# Warewulf exports #' ) {
               last;
            } else {
               $outfile .= "$_\n";
            }
         }
         close OUTFILE;
         $outfile .= "# Warewulf exports #\n";
         $outfile .= "# Do not edit below this line or it may be overwritten by wwmaster.exports! #\n";
         $outfile .= "$master{'paths'}{'sharedfs dir'}\t$master{'network'}{'sharedfs network'}/$master{'network'}{'sharedfs netmask'}(rw,root_squash,async)\n";
         $outfile .= "$master{'paths'}{'vnfs dir'}\t$master{'network'}{'sharedfs network'}/$master{'network'}{'sharedfs netmask'}(ro,no_root_squash,async)\n";
         open(OUTFILE, "> $master{'configs'}{'exports'}")
            or die "Could not open $master{'configs'}{'exports'} for writing!\n";
         print OUTFILE "$outfile\n";
         close OUTFILE;
         system("/sbin/chkconfig portmap on");
         system("/sbin/chkconfig nfs on");
         system("$master{'commands'}{'restart portmap'}");
         system("$master{'commands'}{'restart nfs'}");
         print "\n";
         last;
      } elsif ( $cont =~ /^n[o]?$/i ) {
         last;
      }
   }
}

if ( $config ) {
   while (1) {
      print "Should I configure TFTP for you now? [Y/n]: ";
      chomp ( $cont = <STDIN> );
      if ( $cont =~ /^y[es]?$/i or ! $cont ) {
         system("/sbin/chkconfig tftp on");
         system("/sbin/chkconfig xinetd on");
         system("$master{'commands'}{'restart inetd'}");
         print "\n";
         last;
      } elsif ( $cont =~ /^n[o]?$/i ) {
         last;
      }
   }

}

# We will now determine if the syslog configuration seems okay or not
# We check to see if the syslog server defined in the master.conf is
# resolvable or not.  If the syslog server is the local system, then 
# we make sure it can receive remote syslog messages, unless the 
# sysadmin elects not to.

if ( $config ) {
   if ( "$master{network}{nodename}-admin" ne $master{"servers"}{syslog} ) {
      $SERV = $master{'servers'}{syslog};
      # is it resolvable?
      ($name,$aliases,$addrtype,$length,@addrs) = gethostbyname("$SERV"); 
      if ( $name ne "" ) {
         while (1) {
            printf "Your syslog server is to $name which resolves to %d.%d.%d.%d.\n", unpack('C4',$addrs[0]); 
            print "Is this what you want? [Y/n]:";
            chomp ($cont = <STDIN> );
            if ($cont =~ /^y[es]$/i or ! $cont ) {
               last;
            } elsif ( $cont =~ /^n[o]?/i ) {
               print "Please edit /etc/warewulf/master.conf and rerun wwinit when you are done.\n";
               exit;
            }
         }
      } else { 
         while (1) {
            print "You have $SERV defined as your syslog server.\n";
            print "However, I cannot resolve that name to an address.\n";
            print "Do you wish to continue? [y/N]: ";
            chomp ( $cont = <STDIN>);
            if ( $cont =~ /^y[es]?/i ) {
               print "Ok.\n";
               last;
            } elsif ( $cont =~ /^n[o]?/i or ! $cont ) {
               print "Please run wwinit after fixing the syslog entry.\n";
               exit;
            }
         }
      }
   } else {
      if ( -e "/etc/sysconfig/syslog" ) {
         # check to see if we are accepting remote connctions
         open (SYSLOG_CONF, "/etc/sysconfig/syslog");
         while ($currline = <SYSLOG_CONF>) { 
            if ( $currline =~ /^SYSLOGD_OPTIONS/) { 
               if ( $currline =~ /-r/) {
                  print "Syslog is accepting remote connections.\n";
                  $syslog_ok = 1;
                  last;
               }
            }
         }
         close SYSLOG_CONF;
         if ( ! $syslog_ok ) {
            while (1) {
               print "Should I configure syslog to recieve the node's logs? [Y/n]: ";
               chomp ( $cont = <STDIN> );
               if ( $cont =~ /^y[es]?/i or ! $cont ) {
                  open (SYSLOG_CONF, "/etc/sysconfig/syslog");
                  while ($currline = <SYSLOG_CONF>) {
                     if ($currline =~ /^SYSLOGD_OPTIONS="/) {
                        $currline =~ s/SYSLOGD_OPTIONS="/SYSLOGD_OPTIONS="-r / ;
                     }
                     $out .= $currline;
                  }
                  open (SYSLOG_CONF, "> /etc/sysconfig/syslog");
                  print SYSLOG_CONF $out;
                  close SYSLOG_CONF;
                  system("$master{'commands'}{'restart syslog'}");
                  print "\n";
                  last;
               } elsif ( $cont =~ /^n[o]?/i ) {
                  last;
               }
            }
         }
      }
   }

   print "\n======================= Building boot enviornment for nodes ==================\n\n";

   if ( ! -f "/tftpboot/wwinitrd.img" ) {
      print "Creating the Warewulf initial ram disk...\n";
      system("/usr/sbin/wwmkinitrd");
      print "\n";
   }

   if ( ! -f "/srv/vnfs/default/vnfs.tar.gz" and -d "/vnfs/default" ) {
      print "Creating the Virtual Node disk image...\n";
      system("/usr/sbin/wwvnfs --build --quiet");
      print "\n";
   }

   system("$master{'commands'}{'restart vnfsd'}");
   print "\n";

   system("$master{'commands'}{'restart wwnewd'}");
   print "\n";

   system("$master{'commands'}{'restart warewulf'}");
   print "\n";

   system("/usr/sbin/wwnodes --sync");
   print "\n";

}

