#!/usr/bin/perl -w # # EMULAB-COPYRIGHT # Copyright (c) 2003, 2004 University of Utah and the Flux Group. # All rights reserved. # # # install-boss.sh - Script to do the initial install of a boss node # # The main things it does not do yet: # * Figure out where to put directories such as /usr/testbed - they must # already exist # * Set up named - we probably have to do that later, when the interfaces table # is filled in # * Set up a sup tree. Not sure what the right thing to do here is! # * Doesn't do anything about SSL certificates for the web # # Configure variables # my $PREFIX = '@prefix@'; my $SRCDIR = '@srcdir@'; my $TOP_SRCDIR = '@top_srcdir@'; my $DBNAME = "@TBDBNAME@"; my $OURDOMAIN = '@OURDOMAIN@'; my $LOGFACIL = '@TBLOGFACIL@'; my $USERNODE = '@USERNODE@'; my $FSNODE = '@FSNODE@'; my $BOSSNODE = '@BOSSNODE@'; # # Some programs we use # my $SH = "/bin/sh"; my $CHMOD = "/bin/chmod"; my $CHGRP = "/usr/bin/chgrp"; my $CHOWN = "/usr/sbin/chown"; my $PWD = "/bin/pwd"; my $PW = "/usr/sbin/pw"; my $PATCH = "/usr/bin/patch"; my $SSH_KEYGEN = "/usr/bin/ssh-keygen"; my $PKG_INFO = "/usr/sbin/pkg_info"; my $TOUCH = "/usr/bin/touch"; my $SUIDPERL = "/usr/bin/suidperl"; my $MYSQL = "/usr/local/bin/mysql"; my $MYSQLADMIN = "/usr/local/bin/mysqladmin"; my $MYSQLSHOW = "/usr/local/bin/mysqlshow"; my $MYSQLDUMP = "/usr/local/bin/mysqldump"; my $GMAKE = "/usr/local/bin/gmake"; my $RNDC_CONFGEN = "/usr/local/sbin/rndc-confgen"; # # Some files we edit/create # my $CRONTAB = "/etc/crontab"; my $FSTAB = "/etc/fstab"; my $RCCONF = "/etc/rc.conf"; my $SYSLOG_CONF = "/etc/syslog.conf"; my $NEWSYSLOG_CONF = "/etc/newsyslog.conf"; my $ROOT_PRIVKEY = "/root/.ssh/identity"; my $ROOT_PUBKEY = "$ROOT_PRIVKEY.pub"; my $ROOT_AUTHKEY = "/root/.ssh/authorized_keys"; my $SUDOERS = "/usr/local/etc/sudoers"; my $HTTPD_CONF = "/usr/local/etc/apache/httpd.conf"; my $PHP_INI = "/usr/local/etc/php.ini"; my $CRACKLIB_DICT = "/usr/local/lib/pw_dict.pwd"; my $STL_PATCH = "$TOP_SRCDIR/patches/g++.patch"; my $SSH_CONFIG = "/etc/ssh/ssh_config"; my $RNDC_KEY = "/usr/local/etc/rndc.key"; my $LOADER_CONF = "/boot/loader.conf"; my $SYSCTL_CONF = "/etc/sysctl.conf"; my $EMULAB_PEM = "emulab.pem"; # # Some directories we care about # my $LOGDIR = "$PREFIX/log"; my $MYSQL_LOGDIR = "$LOGDIR/mysql"; my $RCDIR = "/usr/local/etc/rc.d"; my $USERSVAR_DIR = "$PREFIX/usersvar"; my $OPSDIR_DIR = "$PREFIX/opsdir"; my $PORTSDIR = "/usr/ports/misc"; my $MIBPATH = "/usr/local/share/snmp/mibs"; my $TFTP_DIR = "$PREFIX/tftpboot"; my $TFTP_PROJ_DIR = "$TFTP_DIR/proj"; # # URLs # my $CISCO_MIB_FTP = "ftp://ftp.cisco.com/pub/mibs/v2"; # # And some lists that we use # my @TESTBED_DIRS = ($PREFIX); my @MOUNTPOINTS = ("$PREFIX/usersvar", "$PREFIX/opsdir", "/users", "/proj", "/groups", "/share"); my @LOGFILES = ("$LOGDIR/bootinfo.log", "$LOGDIR/tmcd.log", "$LOGDIR/capture.log", "$LOGDIR/dhcpd.log", "$LOGDIR/capserver.log", "$LOGDIR/frisbeed.log", "$LOGDIR/tevd.log", "$LOGDIR/proxydhcpd.log", "$LOGDIR/elvind.log", "$LOGDIR/stated.log", "$LOGDIR/osselect.log", "$LOGDIR/tftpd.log", "$LOGDIR/sdcollectd.log", "$LOGDIR/genlastlog.log", "$LOGDIR/sshxmlrpc.log", "$LOGDIR/plabgetfree.log", "$LOGDIR/plabrenew.log"); my @CISCO_MIBS = ("CISCO-SMI", "CISCO-TC", "CISCO-VTP-MIB", "CISCO-PAGP-MIB", "CISCO-PRIVATE-VLAN-MIB", "CISCO-STACK-MIB", "CISCO-VLAN-MEMBERSHIP-MIB"); my @OPS_NAMES = ($FSNODE, $USERNODE); # # Figure out which directory we live in, so that some stages can do thing # relative to it. # my $OBJDIR = `/usr/bin/dirname $0`; chomp $OBJDIR; my $TOP_OBJDIR = "$OBJDIR/.."; # # Allow this to work if the library is left in the source directory # use lib '@srcdir@'; use English; use libinstall; # # Make sure they know what they're getting into... # warn "***** Please run install-ops on ops, and reboot it, before running\n"; warn "this script!\n\n"; print STDERR "WARNING: This script is ONLY intended to be run on a machine\n"; print STDERR "that is being set up as a dedicated boss node. Continue? [y/N] "; my $response = <>; die "Installation aborted!\n" unless ($response =~ /^y/i); if ($UID != 0) { die "This script must be run as root.\n"; } Phase "groups", "Creating groups", sub { Phase "tbadmin", "Creating tbadmin group", sub { if (getgrnam("tbadmin")) { PhaseSkip("tbadmin group already exists"); } ExecQuietFatal("$PW groupadd tbadmin -g 101"); }; Phase "root", "Creating root group", sub { if (getgrnam("root")) { PhaseSkip("root group already exists"); } ExecQuietFatal("$PW groupadd root -g 103"); }; }; Phase "dirs", "Setting directory permissions", sub { foreach my $dir (@TESTBED_DIRS) { Phase $dir, $dir, sub { if (!-d $dir) { PhaseFail("Directory $dir does not exist"); } my ($mode,$group) = (stat($dir))[2,5]; # Fix up the mode (strip file type) $mode = $mode & 0777; if ($mode == 0775 && $group eq getgrnam("tbadmin")) { PhaseSkip("Already done"); } ExecQuietFatal("$CHGRP tbadmin $dir"); ExecQuietFatal("$CHMOD 0775 $dir"); }; } }; Phase "tftp", "Setting up directories for tftp", sub { Phase "tftpboot", "Creating $TFTP_DIR", sub { DoneIfExists($TFTP_DIR); mkdir $TFTP_DIR,0775 or PhaseFail("Unable to create $TFTP_DIR : $!"); ExecQuietFatal("$CHGRP tbadmin $TFTP_DIR"); }; Phase "tftpproj", "Creating $TFTP_PROJ_DIR", sub { DoneIfExists($TFTP_PROJ_DIR); mkdir $TFTP_PROJ_DIR,0775 or PhaseFail("Unable to create $TFTP_PROJ_DIR : $!"); ExecQuietFatal("$CHGRP tbadmin $TFTP_PROJ_DIR"); }; Phase "tftplink", "Linking /tftpboot", sub { DoneIfExists("/tftpboot"); ExecQuietFatal("ln -s $TFTP_DIR /tftpboot"); }; }; Phase "ports", "Installing ports", sub { Phase "pcopy", "Copying ports into place", sub { DoneIfExists("$PORTSDIR/emulab-boss"); ExecQuietFatal("$SH $SRCDIR/ports/ports-install"); }; Phase "pinstall", "Checking for port installation", sub { if (!ExecQuiet("$PKG_INFO -e emulab-boss-1.4")) { PhaseSkip("Ports already installed"); } PhaseFail("Please install ports manually, since some\n of them are " . "interactive. Run: \n" . "cd $PORTSDIR/emulab-boss && make install\n" . "then re-run this script."); }; }; Phase "patches", "Applying patches", sub { Phase "g++patch", "Patching g++'s STL", sub { if (!ExecQuiet("$PATCH -C -f -R -p0 -i $STL_PATCH")) { PhaseSkip("$STL_PATCH already applied"); } ExecQuietFatal("$PATCH -f -p0 -i $STL_PATCH"); }; }; Phase "cracklib", "Installing cracklib", sub { DoneIfExists("$CRACKLIB_DICT"); my $pwd = `$PWD`; chomp $pwd; chdir "$TOP_SRCDIR/tbsetup/checkpass/cracklib,2.7" or PhaseFail "Unable to change to " . "$TOP_SRCDIR/tbsetup/checkpass/cracklib,2.7: $!"; ExecQuietFatal("make install clean"); chdir $pwd; }; Phase "apache", "Installing apache config file", sub { DoneIfEdited("$HTTPD_CONF"); # ICK!!! If we installed apache AFTER we unpacked the source tarball, # make will not properly install the new apache config file! So, we use # this shameful hack to force it to do so! ExecQuietFatal("$TOUCH -t 01010000 $HTTPD_CONF"); ExecQuietFatal("$GMAKE -C $TOP_OBJDIR/apache install"); }; Phase "rc.d", "Setting up rc.d scripts", sub { Phase "my-client", "Moving $RCDIR/mysql-client.sh", sub { DoneIfDoesntExist("$RCDIR/mysql-client.sh"); ExecQuietFatal("mv $RCDIR/mysql-client.sh $RCDIR/1.mysql-client.sh"); }; Phase "my-server", "Removing $RCDIR/mysql-server.sh", sub { DoneIfDoesntExist("$RCDIR/mysql-server.sh"); if (!unlink "$RCDIR/mysql-server.sh") { PhaseFail("Unable to remove $RCDIR/mysql-server.sh: $!"); } }; Phase "snmpd", "Removing snmpd starup script", sub { DoneIfDoesntExist("$RCDIR/snmpd.sh"); if (!unlink "$RCDIR/snmpd.sh") { PhaseFail("Unable to remove $RCDIR/snmpd.sh: $!"); } }; Phase "rc.testbed", "Installing testbed RC scripts", sub { DoneIfExists("$RCDIR/3.testbed.sh"); ExecQuietFatal("$GMAKE -C $TOP_OBJDIR/rc.d install"); }; }; Phase "syslog", "Setting up syslog", sub { Phase "sysconf", "Editing $SYSLOG_CONF", sub { DoneIfEdited($SYSLOG_CONF); # # Modify the /var/log/messages line to exclude testbed stuff # open(SC,"+<$SYSLOG_CONF") or PhaseFail("Unable to open $SYSLOG_CONF : $!"); my @sc = ; if (scalar(grep(/$LOGFACIL/, @sc)) != 0) { PhaseFail("Testbed chosen facility $LOGFACIL already in use in /etc/syslog.conf!"); } seek(SC,0,0); truncate(SC,0); foreach my $line (@sc) { my $pat = q(\s+/var/log/messages); if ($line =~ /^[^#].*$pat/) { $line =~ s/($pat)/\;$LOGFACIL.none$1/; } print SC $line; } close(SC); AppendToFileFatal($SYSLOG_CONF, "!bootinfo", "*.*\t\t\t\t\t\t$LOGDIR/bootinfo.log", "!tmcd", "*.*\t\t\t\t\t\t$LOGDIR/tmcd.log", "!capture", "*.*\t\t\t\t\t\t$LOGDIR/capture.log", "!dhcpd", "*.*\t\t\t\t\t\t$LOGDIR/dhcpd.log", "!proxydhcpd","*.*\t\t\t\t\t\t$LOGDIR/proxydhcpd.log", "!tftpd", "*.*\t\t\t\t\t\t$LOGDIR/tftpd.log", "!capserver", "*.*\t\t\t\t\t\t$LOGDIR/capserver.log", "!frisbeed", "*.*\t\t\t\t\t\t$LOGDIR/frisbeed.log", "!tevd", "*.*\t\t\t\t\t\t$LOGDIR/tevd.log", "!elvind", "*.*\t\t\t\t\t\t$LOGDIR/elvind.log", "!stated", "*.*\t\t\t\t\t\t$LOGDIR/stated.log", "!osselect", "*.*\t\t\t\t\t\t$LOGDIR/osselect.log", "!genlastlog","*.*\t\t\t\t\t\t$LOGDIR/genlastlog.log", "!sdcollectd","*.*\t\t\t\t\t\t$LOGDIR/sdcollectd.log", "!plabgetfree","*.*\t\t\t\t\t\t$LOGDIR/plabgetfree.log", "!plabrenew", "*.*\t\t\t\t\t\t$LOGDIR/plabrenew.log", "!sshxmlrpc", "*.*\t\t\t\t\t\t$LOGDIR/sshxmlrpc.log"); }; Phase "logdir", "Creating log directory", sub { DoneIfExists($LOGDIR); mkdir $LOGDIR, 0775 or PhaseFail("Unable to create $LOGDIR : $!"); ExecQuietFatal("$CHGRP tbadmin $LOGDIR"); ExecQuietFatal("$CHMOD 775 $LOGDIR"); }; Phase "logdir", "Creating mysql log directory", sub { DoneIfExists($MYSQL_LOGDIR); mkdir $MYSQL_LOGDIR, 0775 or PhaseFail("Unable to create $MYSQL_LOGDIR : $!"); ExecQuietFatal("$CHOWN mysql:mysql $MYSQL_LOGDIR"); ExecQuietFatal("$CHMOD 775 $MYSQL_LOGDIR"); }; Phase "logfiles", "Creating log files", sub { foreach my $logfile (@LOGFILES) { Phase $logfile, $logfile, sub { DoneIfExists($logfile); CreateFileFatal($logfile); ExecQuietFatal("$CHGRP tbadmin $logfile"); ExecQuietFatal("$CHMOD 640 $logfile"); }; } }; Phase "newsyslog", "Setting up $NEWSYSLOG_CONF", sub { DoneIfEdited($NEWSYSLOG_CONF); AppendToFileFatal($NEWSYSLOG_CONF, "$LOGDIR/tmcd.log 640 9 1000 * Z", "$LOGDIR/stated.log 640 9 300 * Z", "$LOGDIR/osselect.log 640 9 300 * Z", "$LOGDIR/power.log 640 7 300 * Z", "$LOGDIR/frisbeed.log 640 7 300 * Z", "$LOGDIR/tftpd.log 640 7 200 * Z", "$LOGDIR/dhcpd.log 640 7 200 * Z", "$LOGDIR/bootinfo.log 640 7 200 * Z", "$LOGDIR/capserver.log 640 5 200 * Z", "$LOGDIR/elvind.log 640 5 1000 * Z", "$LOGDIR/tevd.log 640 3 200 * Z", "$LOGDIR/suexec.log 640 3 200 * Z", "$LOGDIR/genlastlog.log 640 3 200 * Z", "$LOGDIR/genlastlog 640 3 200 * Z " . "/var/run/lastlog_daemon.pid", "$LOGDIR/plabmetrics.log 640 7 1000 * Z", "$LOGDIR/plablinkdata.log 640 7 1000 * Z", "$LOGDIR/sshxmlrpc.log 640 7 300 * Z"); }; }; Phase "database", "Setting up database", sub { Phase "mysql", "Starting mysql", sub { if (!ExecQuiet("$MYSQLADMIN ping")) { PhaseSkip("mysqld already running"); } ExecQuietFatal("$RCDIR/2.mysql-server.sh start"); # Give mysqld some time to start, then make sure it did sleep 5; ExecQuietFatal("$MYSQLADMIN ping"); }; Phase "$DBNAME", "Creating $DBNAME", sub { if (!ExecQuiet("$MYSQLSHOW $DBNAME")) { PhaseSkip("tbdb already exists"); } ExecQuietFatal("$MYSQLADMIN create $DBNAME"); }; Phase "tables", "Creating tables in $DBNAME", sub { if (!ExecQuiet("$MYSQLDUMP -d $DBNAME users")) { PhaseSkip("Tables have already been created"); } ExecQuietFatal("$MYSQL $DBNAME < $TOP_SRCDIR/sql/database-create.sql"); }; Phase "dbdata", "Filling tables with initial data", sub { my ($exitval, @rows) = ExecQuiet("echo 'select * from " . "exported_tables' | $MYSQL -s $DBNAME"); if ($exitval) { PhaseFail("Error running query"); } if (scalar @rows) { PhaseSkip("Already done"); } ExecQuietFatal("$MYSQL $DBNAME < $TOP_SRCDIR/sql/database-fill.sql"); }; Phase "sdbdata", "Filling tables with supplemental data", sub { my ($exitval, @rows) = ExecQuiet("echo 'select * from " . "os_info' | $MYSQL -s $DBNAME"); if ($exitval) { PhaseFail("Error running query"); } if (scalar @rows) { PhaseSkip("Already done"); } ExecQuietFatal("$MYSQL $DBNAME < " . "$TOP_SRCDIR/sql/database-fill-supplemental.sql"); }; Phase "sitevars", "Setting sitevars to default values", sub { my ($exitval, @rows) = ExecQuiet("echo 'select * from " . "sitevariables' | $MYSQL -s $DBNAME"); if ($exitval) { PhaseFail("Error running query"); } if (scalar @rows) { PhaseSkip("Already done"); } ExecQuietFatal("$MYSQL $DBNAME < $TOP_SRCDIR/sql/sitevars-create.sql"); }; }; Phase "rc.conf", "Adding testbed content to $RCCONF", sub { DoneIfEdited($RCCONF); AppendToFileFatal($RCCONF, qq|nfs_server_enable="YES"|, qq|nfs_server_flags="-u -t -n 16"|, qq|nfs_client_enable="YES"|, qq|inetd_enable="YES"|, qq|inetd_flags="-wW -R 0"|, qq|named_enable="NO"|, qq|xntpd_enable="YES"|, qq|syslogd_flags=""|, qq|tftpd_flags="-lvvvv -C 40 -s /tftpboot"|, qq|apache_enable="YES"|); }; Phase "suidperl", "Setting the suid bit on $SUIDPERL", sub { PhaseSkip("Already done") if (-u $SUIDPERL); ExecQuietFatal("$CHMOD u+s $SUIDPERL"); }; Phase "mountpoints", "Creating mountpoints", sub { foreach my $dir (@MOUNTPOINTS) { Phase $dir, $dir, sub { DoneIfExists($dir); mkdir $dir, 0777 or PhaseFail("Unable to create $dir : $!"); }; } }; Phase "resolve", "Checking to make sure names for ops resolve", sub { foreach my $name (@OPS_NAMES) { Phase $name, $name, sub { if (gethostbyname($name)) { PhaseSkip("$name resolves"); } else { PhaseFail("$name does not resolve - please see setup.txt\n" . "for further instructions!"); } }; } }; Phase "mounts", "Setting up mounts of fs and ops", sub { DoneIfEdited($FSTAB); AppendToFileFatal($FSTAB, "$FSNODE:/users\t\t/users\tnfs\trw,nodev,nosuid\t0\t0", "$FSNODE:/proj\t\t/proj\tnfs\trw,nodev,nosuid\t0\t0", "$FSNODE:/groups\t\t/groups\tnfs\trw,nodev,nosuid\t0\t0", "$FSNODE:/share\t\t/share\tnfs\trw,nodev,nosuid\t0\t0", "$USERNODE:/usr/testbed\t\t$OPSDIR_DIR\tnfs\trw,soft,-b,nodev,nosuid\t0\t0", "$USERNODE:/var\t\t$USERSVAR_DIR\tnfs\tro,soft,-b,nodev,nosuid\t0\t0"); # XXX - Do we want to do a 'mount -a' at this point? }; Phase "mibs", "Fetching Cisco MIBs", sub { foreach my $mib (@CISCO_MIBS) { my $localfile = "$MIBPATH/$mib.txt"; my $mibURL = "$CISCO_MIB_FTP/$mib.my"; Phase "$mib", "Fetching $mib", sub { DoneIfExists($localfile); FetchFileFatal($mibURL,$localfile); }; } }; Phase "cron", "Adding cron jobs", sub { Phase "crontab", "Editing $CRONTAB", sub { DoneIfEdited($CRONTAB); AppendToFileFatal($CRONTAB, "45 \t1\t*\t*\t*\troot\t$PREFIX/sbin/backup", "*/5\t*\t*\t*\t*\troot\t$PREFIX/sbin/node_status", "*/5\t*\t*\t*\t*\troot\t$PREFIX/sbin/idlemail"); }; }; Phase "sudoers", "Editing $SUDOERS", sub { DoneIfEdited($SUDOERS); AppendToFileFatal($SUDOERS,"%wheel ALL=(ALL) NOPASSWD: ALL"); }; Phase "php.ini", "Creating php.ini file", sub { DoneIfExists($PHP_INI); CreateFileFatal($PHP_INI, "[PHP]","", ";", "; So that quotes are not escaped. Needed for netbuild application.", ";", "magic_quotes_gpc = Off","", ";", "; Our scripts depend on this!", ";", "register_globals = On"); }; Phase "ssh", "Setting up root ssh from boss to ops", sub { Phase "keygen", "Creating root private key", sub { DoneIfExists($ROOT_PRIVKEY); ExecQuietFatal("$SSH_KEYGEN -t rsa1 -P '' -f $ROOT_PRIVKEY"); }; Phase "ssh", "Editing ssh config file", sub { DoneIfEdited($SSH_CONFIG); AppendToFileFatal($SSH_CONFIG, "Host *", " StrictHostKeyChecking no", " Protocol 1,2"); }; }; Phase "rndc", "Setting up rndc for control of nameserver", sub { DoneIfExists($RNDC_KEY); ExecQuietFatal("$RNDC_CONFGEN -a -r /dev/urandom"); }; Phase "loader.conf", "Setting up $LOADER_CONF", sub { DoneIfEdited($LOADER_CONF); AppendToFileFatal($LOADER_CONF, "kern.hz=1000" ); }; Phase "sysctl.conf", "Setting up $SYSCTL_CONF", sub { DoneIfEdited($SYSCTL_CONF); AppendToFileFatal($SYSCTL_CONF, "net.local.dgram.maxdgram=65536", "net.local.dgram.recvspace=65536" ); }; Phase "sslcerts", "Setting up SSL certificates", sub { Phase "sslgen", "Generating SSL certificates", sub { DoneIfExists("$TOP_OBJDIR/ssl/$EMULAB_PEM"); ExecQuietFatal("$GMAKE -C $TOP_OBJDIR/ssl remote-site"); }; Phase "sslinstall", "Installing SSL certificates", sub { DoneIfExists("$PREFIX/etc/$EMULAB_PEM"); ExecQuietFatal("$GMAKE -C $TOP_OBJDIR/ssl remote-site-boss-install"); }; }; print "----------------------------------------------------------------------\n"; print "Installation completed succesfully!\n"; print "Please reboot this machine before proceeding with boss setup\n"; if (!PhaseWasSkipped("ssh")) { print "You'll need to manually copy boss's public SSH key over to ops,\n"; print "so boss can get into ops without a password. Run the following\n"; print "as root:\n"; print "scp $ROOT_PUBKEY ops:$ROOT_AUTHKEY\n"; } exit 0;