#!/usr/bin/perl -w # # EMULAB-COPYRIGHT # Copyright (c) 2003, 2004, 2005, 2006 University of Utah and the Flux Group. # All rights reserved. # # # install-ops - Script to do the initial install of an ops node # # The main things it does not do yet: # * Figure out where to put directories such as /users /proj - they must # already exist # * Fill out mailing list files - presumably, it's easier to just get the # User to edit them himself # # # Configure variables # my $PREFIX = '@prefix@'; my @MAILING_LISTS = ("@TBOPSEMAIL@","@TBLOGSEMAIL@","@TBWWWEMAIL@", "@TBAPPROVALEMAIL@","@TBAUDITEMAIL@","@TBSTATEDEMAIL@", "@TBTESTSUITEEMAIL@", "@TBERRORSEMAIL@"); my $OURDOMAIN = '@OURDOMAIN@'; my $USERNODE = '@USERNODE@'; my $FSNODE = '@FSNODE@'; my $BOSSNODE = '@BOSSNODE@'; my $BOSSNODE_IP = '@BOSSNODE_IP@'; my $USERNODE_IP = '@USERNODE_IP@'; my $FSNODE_IP = '@FSNODE_IP@'; my $LOGFACIL = '@TBLOGFACIL@'; my $TBOPSEMAIL = '@TBOPSEMAIL@'; my $ELABINELAB = @ELABINELAB@; my $WINSUPPORT = @WINSUPPORT@; my $MAILMANSUPPORT = @MAILMANSUPPORT@; my $CVSSUPPORT = @CVSSUPPORT@; my $BUGDBSUPPORT= @BUGDBSUPPORT@; my $WIKISUPPORT = @WIKISUPPORT@; my $QUOTA_FSLIST= '@FS_WITH_QUOTAS@'; my $LOGDIR = "$PREFIX/log"; my $ETCDIR = "$PREFIX/etc"; my $LIBDIR = "$PREFIX/lib"; # True if we are also the FS node my $ISFS = ($USERNODE eq $FSNODE) ? 1 : 0; # For /share export below. my $CONTROL_NETWORK = "@CONTROL_NETWORK@"; my $CONTROL_NETMASK = "@CONTROL_NETMASK@"; # Should be configure variable my $TBADMINGID = 101; # # Allow this to work if the library is left in the source directory # use lib '@srcdir@'; use English; use libinstall; use Getopt::Std; # # Handle command-line options # sub usage { print "Usage: ops-install [-b] [-p packagedir] [-P portname]\n"; exit(1); } # Version of FreeBSD. my $FBSD_VERSION = 4; if (`uname -r` =~ /^(\d)/) { $FBSD_VERSION = $1; } else { die("Could not determine what version of FreeBSD you are running!\n"); } # # The meta-ports (name and version) that drag in all the dependancies for # an ops/fs node. These are OS dependent as we upgrade. # my $OPS_PORT = (($FBSD_VERSION == 4) ? "emulab-ops-1.4" : "emulab-ops-2.0"); my $FS_PORT = (($FBSD_VERSION == 4) ? "emulab-fs-1.4" : "emulab-fs-2.0"); my $PHP4_PORT = "php4-extensions-1.0"; my $packagedir = ""; my $batchmode = 0; my $password; my %opts; if (! getopts("P:p:bw:F:", \%opts)) { usage(); } if (defined($opts{p})) { $packagedir = $opts{p}; } if (defined($opts{b})) { $batchmode = 1; } if (defined($opts{P})) { $OPS_PORT = $opts{P}; } if (defined($opts{F})) { $FS_PORT = $opts{F}; } if (defined($opts{w})) { $password = $opts{w}; } if (@ARGV) { usage(); } # # 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/.."; # # Some programs we use # my $CHGRP = "/usr/bin/chgrp"; my $CHMOD = "/bin/chmod"; my $CHOWN = "/usr/sbin/chown"; my $PW = "/usr/sbin/pw"; my $PATCH = "/usr/bin/patch"; my $NEWALIASES = "/usr/bin/newaliases"; my $SH = "/bin/sh"; my $PKG_INFO = "/usr/sbin/pkg_info"; my $PKG_ADD = "/usr/sbin/pkg_add"; my $PWD = "/bin/pwd"; my $CP = "/bin/cp"; my $MV = "/bin/mv"; my $GMAKE = "/usr/local/bin/gmake"; my $ENV = "/usr/bin/env"; my $MOUNT = "/sbin/mount"; my $TAR = "/usr/bin/tar"; my $MD5 = "/sbin/md5"; # # Some files we edit/create # my $RCCONF = "/etc/rc.conf"; my $HOSTS = "/etc/hosts"; my $FSTAB = "/etc/fstab"; my $RCLOCAL = "/etc/rc.local"; my $RCCAPTURE = "$PREFIX/etc/rc.capture"; my $LOCAL_HOSTNAMES = "/etc/mail/local-host-names"; my $ALIASES_FILE = "/etc/mail/aliases"; my $SENDMAIL_CF = "/etc/mail/sendmail.cf"; my $EXPORTS_FILE = "/etc/exports"; my $EXPORTS_HEAD = "$EXPORTS_FILE.head"; my $SYSLOG_CONF = "/etc/syslog.conf"; my $NEWSYSLOG_CONF = "/etc/newsyslog.conf"; my $SUDOERS = "/usr/local/etc/sudoers"; my $SSHD_CONFIG = "/etc/ssh/sshd_config"; my $CRONTAB = "/etc/crontab"; my $AUTHKEYS = "/root/.ssh/authorized_keys"; my $SMBCONF_FILE = "/usr/local/etc/smb.conf"; my $SMBCONF_HEAD = "$SMBCONF_FILE.head"; my $APACHE_ETCDIR = "/usr/local/etc/apache"; my $HTTPD_CONF = "$APACHE_ETCDIR/httpd.conf"; # For installing mysqld 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 $MYSQLINSTALL = "/usr/local/bin/mysql_install_db"; my $MYSQLDBDIR = "/var/db/mysql"; my $MYSQL_LOGDIR = "$LOGDIR/mysql"; # # Some directories we care about # my $LIST_DIR = "/etc/mail/lists"; my $TIPLOG_DIR = "/var/log/tiplogs"; my $PORTSDIR = "/usr/ports"; my $PORTSMISCDIR = "$PORTSDIR/misc"; my $SRCDIR = '@srcdir@'; my $TOP_SRCDIR = "@top_srcdir@"; my $RCDIR = "/usr/local/etc/rc.d"; my $VARRUN = "/var/run"; # # And some lists that we use # my @LOCAL_HOSTS = ($OURDOMAIN,$BOSSNODE,$USERNODE,$FSNODE); my @LOGFILES = ("/var/log/logins","/var/log/tiplogs/capture.log", "/var/log/mountd.log"); my @LOCAL_MAILING_LISTS = grep(/$OURDOMAIN$/,@MAILING_LISTS); my @MAILING_LIST_NAMES = map { /^([\w-]+)\@/ } @LOCAL_MAILING_LISTS; my @TESTBED_DIRS = ([$PREFIX, "0775"], ["/users", "0755"], ["/proj", "0755"], ["/groups", "0755"], ["/share", "0775"]); my @MOUNTPOINTS = ("/users", "/proj", "/groups", "/share"); # # A few files we have to deal with # my $ELVIND_CONF = "/usr/local/etc/elvind.conf"; my $OPS_ELVIND_CONF = "$TOP_OBJDIR/event/etc/elvind-ops.conf"; my $M2CRYPTO_PATCH = "$TOP_SRCDIR/patches/m2crypto.patch"; my $MAILMAN_PATCH = "$TOP_SRCDIR/patches/mailman.patch"; my $IDENTPUB = "$TOP_SRCDIR/install/identity.pub"; # # List of names that goes into $HOSTS and which must resolve. # my @OPS_NAMES = ($USERNODE, "users", "ops"); if ($ISFS) { push(@OPS_NAMES, "fs"); } # # Make sure they know what they're getting into... # if (! $batchmode) { 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 ops or ops+fs 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"; } # # The phases are fairly self-explanatory # Phase "usersgroups", "Creating users and groups", sub { Phase "tbadmin", "Creating tbadmin group", sub { if (getgrnam("tbadmin")) { PhaseSkip("tbadmin group already exists"); } ExecQuietFatal("$PW groupadd tbadmin -g $TBADMINGID"); }; # Added next two cause the mysql package does not do this (port does). Phase "mysqlgroup", "Creating mysql group", sub { if (getgrnam("mysql")) { PhaseSkip("mysql group already exists"); } ExecQuietFatal("$PW groupadd mysql -g 88"); }; Phase "mysqluser", "Creating mysql user", sub { if (getpwnam("mysql")) { PhaseSkip("mysql user already exists"); } ExecQuietFatal("$PW useradd mysql -g 88 -g 88 -h - ". "-d $MYSQLDBDIR -s /sbin/nologin -c 'MySQL Daemon'"); }; ExecQuietFatal("$CHOWN mysql:mysql $MYSQLDBDIR") if (-e $MYSQLDBDIR); }; Phase "dirs", "Setting directory permissions", sub { foreach my $dirref (@TESTBED_DIRS) { my ($dir, $newmode) = @$dirref; Phase $dir, $dir, sub { if (!-d $dir) { PhaseFail("Directory $dir does not exist"); } # Use the real path, to avoid symlink problems my $realdir = `realpath $dir`; chomp $realdir; my ($mode,$group) = (stat($realdir))[2,5]; # Fix up the mode (strip file type) $mode = $mode & 0777; if ($mode == eval $newmode && $group eq getgrnam("tbadmin")) { PhaseSkip("Already done"); } ExecQuietFatal("$CHGRP tbadmin $realdir"); ExecQuietFatal("$CHMOD $newmode $realdir"); }; } }; Phase "ports", "Installing ports", sub { Phase "packages", "Installing packages", sub { Phase "main", "Installing main package", sub { if (!ExecQuiet("$PKG_INFO -e $OPS_PORT")) { PhaseSkip("Package already installed"); } if (!$packagedir) { PhaseSkip("No package directory provided"); } ExecQuietFatal("$ENV PKG_PATH=$packagedir $PKG_ADD $OPS_PORT"); }; if ($FBSD_VERSION > 4) { Phase "php4", "Installing php4 package", sub { if (!ExecQuiet("$PKG_INFO -e $PHP4_PORT")) { PhaseSkip("Package already installed"); } if (!$packagedir) { PhaseSkip("No package directory provided"); } ExecQuietFatal("$ENV PKG_PATH=$packagedir ". " $PKG_ADD $PHP4_PORT"); }; } }; Phase "fs-packages", "Installing FS packages", sub { if (!$ISFS) { PhaseSkip("Not FS Node"); } if (!ExecQuiet("$PKG_INFO -e $FS_PORT")) { PhaseSkip("FS ports already installed"); } if (!$packagedir) { PhaseSkip("No package directory provided"); } ExecQuietFatal("$ENV PKG_PATH=$packagedir $PKG_ADD $FS_PORT"); }; if ($MAILMANSUPPORT) { Phase "mailman", "Installing Mailman package", sub { if (!ExecQuiet("$PKG_INFO -x -E mailman")) { PhaseSkip("Mailman package already installed"); } if (!$packagedir) { PhaseSkip("No package directory provided"); } # Lets not hardwire the package name; whatever it is will do. my $pname = `ls $packagedir/mailman-*.tbz`; PhaseFail("Cannot find mailman package in $packagedir!") if ($?); chomp($pname); ExecQuietFatal("$ENV PKG_PATH=$packagedir $PKG_ADD $pname"); }; } if ($CVSSUPPORT) { Phase "cvsd", "Installing cvsd package", sub { PhaseSkip("cvsd not supported on ops") if ($FBSD_VERSION < 6); if (!ExecQuiet("$PKG_INFO -x -E cvsd")) { PhaseSkip("cvsd package already installed"); } if (!$packagedir) { PhaseSkip("No package directory provided"); } # Lets not hardwire the package name; whatever it is will do. my $pname = `ls $packagedir/cvsd-*.tbz`; PhaseFail("Cannot find cvsd package in $packagedir!") if ($?); chomp($pname); ExecQuietFatal("$ENV PKG_PATH=$packagedir $PKG_ADD $pname"); }; } if ($BUGDBSUPPORT) { Phase "flyspray", "Installing flyspray package support", sub { PhaseSkip("flyspray not supported on ops") if ($FBSD_VERSION < 6); if (!ExecQuiet("$PKG_INFO -x -E adodb")) { PhaseSkip("adodb package already installed"); } if (!$packagedir) { PhaseSkip("No package directory provided"); } # Lets not hardwire the package name; whatever it is will do. my $pname = `ls $packagedir/adodb-*.tbz`; PhaseFail("Cannot find adodb package in $packagedir!") if ($?); chomp($pname); ExecQuietFatal("$ENV PKG_PATH=$packagedir $PKG_ADD $pname"); }; } PhaseSkip("Package directory provided; not installing from source") if ($packagedir); Phase "pcopy", "Copying ports into place", sub { DoneIfExists("$PORTSMISCDIR/emulab-ops"); ExecQuietFatal("$SH $SRCDIR/ports/ports-install"); }; my $pwd = `$PWD`; chomp $pwd; Phase "pinstall", "Installing ports (may take a while)", sub { if (!ExecQuiet("$PKG_INFO -e $OPS_PORT")) { PhaseSkip("Ports already installed"); } # # This port is dead-simple, so it's safe to do it from this script # chdir "$PORTSMISCDIR/emulab-ops" or PhaseFail "Unable to change to $PORTSMISCDIR/emulab-ops: $!"; ExecQuietFatal("make -DBATCH install"); }; Phase "fs-pinstall", "Installing FS ports (may take a while)", sub { if (!$ISFS) { PhaseSkip("Not FS Node"); } if (!ExecQuiet("$PKG_INFO -e $FS_PORT")) { PhaseSkip("Ports already installed"); } chdir "$PORTSMISCDIR/emulab-fs" or PhaseFail "Unable to change to $PORTSMISCDIR/emulab-fs: $!"; ExecQuietFatal("make -DBATCH install"); }; Phase "php4-pinstall", "Installing PHP4 ports (may take a while)", sub { if (!ExecQuiet("$PKG_INFO -e $PHP4_PORT")) { PhaseSkip("Ports already installed"); } chdir "$PORTSMISCDIR/emulab-php4" or PhaseFail "Unable to change to $PORTSMISCDIR/emulab-php4: $!"; ExecQuietFatal("make -DBATCH install"); }; if ($MAILMANSUPPORT) { Phase "mailman-pinstall", "Installing mailman ports", sub { if (!ExecQuiet("$PKG_INFO -x -e mailman")) { PhaseSkip("Ports already installed"); } chdir "$PORTSMISCDIR/emulab-mailman" or PhaseFail("Unable to change to ". "$PORTSMISCDIR/emulab-mailman: $!"); ExecQuietFatal("make -DBATCH install"); }; } chdir $pwd; }; # XXX Temporary. Phase "portfixup", "Fixing up packages", sub { Phase "rsync", "Looking for rsync and installing", sub { if (!ExecQuiet("$PKG_INFO -x rsync")) { PhaseSkip("rsync already installed"); } PhaseFail("Must provide -p (packagedir) argument!") if (!$packagedir); ExecQuietFatal("$ENV PKG_PATH=$packagedir $PKG_ADD rsync-2.6.3_1"); }; if ($WINSUPPORT) { Phase "samba", "Looking for Samba and installing", sub { if (!$ISFS) { PhaseSkip("Not FS Node"); } if (!ExecQuiet("$PKG_INFO -x samba")) { PhaseSkip("samba already installed"); } PhaseFail("Must provide -p (packagedir) argument!") if (!$packagedir); ExecQuietFatal("$ENV PKG_PATH=$packagedir $PKG_ADD samba-3.0.7,1"); }; Phase "gcc30", "Looking for GCC 3.0 and installing", sub { if ($FBSD_VERSION > 4 || !ExecQuiet("$PKG_INFO -x gcc30")) { PhaseSkip("GCC 3.0 already installed"); } PhaseFail("Must provide -p (packagedir) argument!") if (!$packagedir); ExecQuietFatal("$ENV PKG_PATH=$packagedir $PKG_ADD gcc30-3.0.4_1"); }; } }; Phase "patches", "Applying patches", sub { Phase "m2cryptopatch", "Patching m2crypto", sub { my $patchfile = $M2CRYPTO_PATCH; if (ExecQuiet("$PKG_INFO -I -x m2crypto | fgrep -q -s '0.13'") == 0) { $patchfile = "${patchfile}-0.13"; } if (!ExecQuiet("$PATCH -C -f -l -R -p0 -i $patchfile")) { PhaseSkip("$patchfile already applied"); } ExecQuietFatal("$PATCH -f -l -p0 -i $patchfile"); }; if ($MAILMANSUPPORT) { Phase "mailmanpatch", "Patching mailman", sub { my $patchfile = $MAILMAN_PATCH; if (!ExecQuiet("$PATCH -C -f -l -R -p0 -i $patchfile")) { PhaseSkip("$patchfile already applied"); } ExecQuietFatal("$PATCH -f -l -p0 -i $patchfile"); }; } }; Phase "rc.conf", "Adding testbed content to rc.conf", sub { DoneIfEdited($RCCONF); AppendToFileFatal($RCCONF, qq|sendmail_enable="YES"|, qq|rpcbind_enable="YES"|, qq|mountd_enable="YES"|, qq|nfs_server_enable="YES"|, qq|nfs_server_flags="-u -t -n 16"|, qq|mountd_flags="-r -p 900"|, (($ISFS && $WINSUPPORT) ? qq|smbd_enable="YES"| : ()), qq|apache_enable="YES"|, qq|syslogd_flags=""|); }; Phase "hosts", "Adding boss/ops/fs IP addresses to $HOSTS", sub { DoneIfEdited($HOSTS); my $hstr = "${BOSSNODE_IP}\t${BOSSNODE} boss" . "\n${USERNODE_IP}\t@OPS_NAMES"; if (!$ISFS) { $hstr .= "\n${FSNODE_IP}\t${FSNODE} fs"; } AppendToFileFatal($HOSTS, $hstr); }; Phase "resolve", "Checking to make sure names for boss/ops/fs resolve", sub { my @hnames = (@OPS_NAMES, $BOSSNODE, "boss"); if (!$ISFS) { push @hnames, $FSNODE, "fs"; } foreach my $name (@hnames) { Phase $name, $name, sub { if (gethostbyname($name)) { PhaseSucceed("$name resolves"); } else { PhaseFail("$name does not resolve"); } }; } }; Phase "sendmail","Configuring sendmail", sub { Phase "localhosts", "Setting up $LOCAL_HOSTNAMES", sub { DoneIfExists($LOCAL_HOSTNAMES); CreateFileFatal($LOCAL_HOSTNAMES,@LOCAL_HOSTS); }; Phase "maillists", "Setting up mailing lists", sub { Phase "listdir", "Creating $LIST_DIR", sub { DoneIfExists($LIST_DIR); mkdir($LIST_DIR,0755) or PhaseFail("Unable to create $LIST_DIR: $!"); ExecQuietFatal("$CHGRP mailnull $LIST_DIR"); ExecQuietFatal("$CHMOD 750 $LIST_DIR"); }; Phase "listfiles", "Creating mailing list files", sub { foreach my $list (@MAILING_LIST_NAMES) { Phase $list, $list, sub { DoneIfExists("$LIST_DIR/$list"); CreateFileFatal("$LIST_DIR/$list"); }; } }; Phase "aliases", "Adding lists to $ALIASES_FILE", sub { DoneIfEdited($ALIASES_FILE); AppendToFileFatal($ALIASES_FILE, map("$_:\t:include:$LIST_DIR/$_",@MAILING_LIST_NAMES)); }; Phase "newaliases", "Running newaliases", sub { PhaseSkip("No new aliases") unless @MAILING_LIST_NAMES; PhaseSkip("No new aliases") if PhaseWasSkipped("aliases"); ExecQuietFatal($NEWALIASES); }; }; }; Phase "exports", "Setting up exports", sub { Phase "ex.head", "Creating $EXPORTS_HEAD", sub { DoneIfExists($EXPORTS_HEAD); # # Figure out which of these directories are on the same # filesystems. Note: we cannot do /share on the same exports line # as the other filesystems because of the RO mount below (trust me). # my @dirs = ('/var','/usr/testbed'); if ($ISFS) { @dirs = ('/users','/groups','/proj',@dirs); } @dirs = map {`realpath $_`} @dirs; chomp @dirs; my %filesystems; foreach my $dir (@dirs) { my ($dev,@junk) = stat $dir; push @{$filesystems{$dev}}, $dir; } # # Use that knowledge to create lines for /etc/exports.head # my @exports_lines; foreach my $key (keys %filesystems) { push @exports_lines, join(" ",@{$filesystems{$key}}) . "\t$BOSSNODE -maproot=root"; } if ($ISFS) { # # /share is special. We want to export to boss read-write, # but to the control network read-only. # my ($a,$b,$c,$d) = ($CONTROL_NETWORK =~ /^(\d*)\.(\d*)\.(\d*)\.(\d*)/); my $realdir = `realpath /share`; chomp($realdir); push(@exports_lines, "$realdir\t$BOSSNODE -maproot=root"); push(@exports_lines, "$realdir\t-network ${a}.${b}.${c} -mask $CONTROL_NETMASK ". "-maproot=root -ro"); } # # Add localhost mount to proj/cvsrepos. # if ($CVSSUPPORT) { my $pdir; my $dfout = `df /proj | fgrep '/dev'`; if ($?) { PhaseFail("'df /proj' failed!"); } if ($dfout =~ /\s+([\/\w]*)$/) { $pdir = $1; } else { PhaseFail("Could not determine where /proj is mounted!"); } push(@exports_lines, "$pdir\tlocalhost -alldirs"); } # # Put them in exports.head, and copy that to /etc/exports # CreateFileFatal($EXPORTS_HEAD, @exports_lines); ExecQuietFatal("cp $EXPORTS_HEAD $EXPORTS_FILE"); }; # XXX Newhup Phase "mountd", "HUPing mountd", sub { PhaseSkip("No new exports file") if PhaseWasSkipped("ex.head"); PhaseSkip("mountd not running") unless `ps -auxw | grep mountd | grep -v grep`; ExecQuietFatal("killall -HUP mountd"); }; }; Phase "NFSmounts", "Setting up NFS mounts", sub { if ($ISFS) { PhaseSkip("FSes are local"); } 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 "fstab", "Adding NFS mounts to $FSTAB", 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"); }; Phase "mounts", "Mounting NFS filesystems", sub { foreach my $dir (@MOUNTPOINTS) { Phase $dir, $dir, sub { DoneIfMounted($dir); ExecQuietFatal("$MOUNT -o '-R 1' $dir"); }; } }; }; # # Set up syslog # Phase "syslog", "Setting up syslog", sub { Phase "sysconf", "Editing $SYSLOG_CONF", sub { DoneIfEdited($SYSLOG_CONF); # # Can't just append to this file, unfortunately. Have to put some of # the lines in the middle of the file # 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!"); } if (scalar(grep(/^cron/, @sc)) != 1) { PhaseFail("Unable to find marker in /etc/syslog.conf!"); } # # Clobber and re-write # seek(SC,0,0); truncate(SC,0); foreach my $line (@sc) { # # Modify the /var/log/messages line to exclude testbed stuff # my $pat = q(\s+/var/log/messages); if ($line =~ /^[^#].*$pat/) { $line =~ s/($pat)/\;$LOGFACIL.none$1/; } # # XXX don't send anything to logged in root users. # Per-user linktest proxies run on ops as root in a "full" # ssh ("-t -t") which appears as a login shell. Thus the # linktest output given to the user might include syslog # messages. # if ($line =~ /root$/) { $line =~ s/^/#/; } print SC $line; # # Find the cron line, after which we place our auth.info line # if ($line =~ /^cron/) { print SC "# " . MAGIC_TESTBED_START . "\n"; print SC "auth.info\t\t\t\t\t/var/log/logins\n"; print SC "# " . MAGIC_TESTBED_END . "\n"; } } # # Put a few more lines at the end # print SC "# " . MAGIC_TESTBED_START . "\n"; print SC "!capture\n"; print SC "*.*\t\t\t\t\t\t/var/log/tiplogs/capture.log\n"; print SC "!mountd\n"; print SC "*.*\t\t\t\t\t\t/var/log/mountd.log\n"; print SC "# " . MAGIC_TESTBED_END . "\n"; close SC; }; Phase "tiplog", "Creating $TIPLOG_DIR", sub { DoneIfExists($TIPLOG_DIR); mkdir($TIPLOG_DIR,0755) or PhaseFail("Unable to make $TIPLOG_DIR : $!"); }; 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 "mysql-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, "/var/log/logins\t\t\t\t640 7 200 * Z", "/var/log/mountd.log\t\t\t640 5 200 * Z", "/var/log/tiplogs/capture.log\t\t644 7 * 168 Z"); }; }; Phase "cron", "Adding cron jobs", sub { Phase "crontab", "Editing $CRONTAB", sub { if ($QUOTA_FSLIST eq "") { PhaseSkip("No filesystem quotas"); } DoneIfEdited($CRONTAB); my @cronlist = ("0 \t6\t*\t*\t*\troot\t$PREFIX/sbin/quotamail"); if ($WIKISUPPORT) { push(@cronlist, "*/15 \t*\t*\t*\t*\troot\t(cd /usr/local/www/data/twiki/bin;". " ./mailnotify -q >> /var/tmp/mailnotify.log 2>&1)"); } AppendToFileFatal($CRONTAB, @cronlist); }; Phase "cronhup", "HUPing cron", sub { if (PhaseWasSkipped("crontab")) { PhaseSkip("No new crontab"); } HUPDaemon("cron"); }; }; Phase "sudoers", "Editing $SUDOERS to allow wheel group", sub { DoneIfEdited($SUDOERS); AppendToFileFatal($SUDOERS,"%wheel ALL=(ALL) NOPASSWD: ALL"); }; Phase "samba", "Setting up Samba", sub { if (!$ISFS) { PhaseSkip("Not FS node"); } if (!$WINSUPPORT) { PhaseSkip("Windows support not enabled"); } Phase "smb.conf", "Installing smb.conf[.head]", sub { DoneIfEdited($SMBCONF_HEAD); ExecQuietFatal("$CP -pf $TOP_OBJDIR/install/smb.conf.head $SMBCONF_HEAD"); AppendToFileFatal($SMBCONF_HEAD, "# This file created by Emulab Control"); ExecQuietFatal("$CP -pf $SMBCONF_HEAD $SMBCONF_FILE"); }; Phase "samba.sh", "Installing samba.sh", sub { DoneIfExists("$RCDIR/samba.sh"); DoneIfDoesntExist("$RCDIR/samba.sh.sample"); ExecQuietFatal("$MV -f $RCDIR/samba.sh.sample $RCDIR/samba.sh"); }; if ($ELABINELAB) { Phase "starting", "Starting Samba", sub { DoneIfExists("$VARRUN/smbd.pid"); ExecQuietFatal("$RCDIR/samba.sh start"); }; } }; Phase "ssh", "Allowing root ssh", sub { Phase "sshdconfig", "Permitting root login through ssh", sub { DoneIfEdited($SSHD_CONFIG); AppendToFileFatal($SSHD_CONFIG,"PermitRootLogin yes"); }; Phase "dotssh", "Making root's .ssh directory", sub { DoneIfExists("/root/.ssh"); mkdir("/root/.ssh",0700) or PhaseFail("Unable to create /root/.ssh: $!"); }; Phase "authkeys", "Adding stub identity to root authorized_keys", sub { DoneIfEdited($AUTHKEYS); my $ident = `cat $IDENTPUB`; PhaseFail("Could not read $IDENTPUB") if ($?); chomp($ident); if (! -e $AUTHKEYS) { CreateFileFatal($AUTHKEYS); } AppendToFileFatal($AUTHKEYS, "from=\"${BOSSNODE}\" $ident"); }; }; Phase "capture", "Setting up capture", sub { Phase "rc.local", "Creating $RCLOCAL", sub { DoneIfExists($RCLOCAL); CreateFileFatal($RCLOCAL, "if [ -f /etc/defaults/rc.conf ]; then", "\t. /etc/defaults/rc.conf", "fi", "", "if [ -x $RCCAPTURE ]; then", "\techo -n \" capture\"", "\t$RCCAPTURE", "fi"); }; Phase "etc", "Creating $PREFIX/etc", sub { DoneIfExists("$PREFIX/etc"); mkdir("$PREFIX/etc",0755) or PhaseFail("Unable to create $PREFIX/etc: $!"); }; Phase "rc.capture", "Creating empty $RCCAPTURE", sub { DoneIfExists($RCCAPTURE); CreateFileFatal($RCCAPTURE,"#!/bin/sh"); ExecQuietFatal("$CHMOD a+rx $RCCAPTURE"); }; }; Phase "event", "Setting up event system", sub { Phase "elvinconf", "Installing elvind config file", sub { DoneIfIdentical($ELVIND_CONF,$OPS_ELVIND_CONF); ExecQuietFatal("$CP $OPS_ELVIND_CONF $ELVIND_CONF"); }; }; Phase "rc.d", "Setting up rc.d scripts", sub { Phase "rsyncd", "Removing rsyncd startup script", sub { DoneIfDoesntExist("$RCDIR/rsyncd.sh"); if (!unlink "$RCDIR/rsyncd.sh") { PhaseFail("Unable to remove $RCDIR/rsyncd.sh: $!"); } }; Phase "mysql-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 "rc.testbed", "Installing testbed RC scripts", sub { Phase "elvind.sh", "Removing port version of elvind.sh", sub { DoneIfDoesntExist("$RCDIR/elvind.sh"); ExecQuietFatal("/bin/rm -f $RCDIR/elvind.sh"); }; DoneIfExists("$RCDIR/2.elvind.sh"); ExecQuietFatal("$GMAKE -C $TOP_OBJDIR/rc.d control-install"); }; }; Phase "apache", "Installing apache config file", sub { PhaseSkip("apache not supported on ops") if ($FBSD_VERSION == 4); DoneIfEdited("$HTTPD_CONF"); ExecQuietFatal("$GMAKE -C $TOP_OBJDIR/apache control-install"); }; Phase "database", "Setting up database", sub { PhaseSkip("mysqld not supported on ops") if ($FBSD_VERSION == 4); # Get a password for the the DB. Phase "password", "Asking for mysqld root password", sub { DoneIfExists("$ETCDIR/mysqld.pwd"); if (!defined($password)) { print "\n\nPick a password for mysqld (warning, will be echoed): "; $password = <>; chomp($password) if (defined($password)); } PhaseFail("Invalid password supplied") if (!defined($password) || $password eq ""); CreateFileFatal("$ETCDIR/mysqld.pwd", "$password"); ExecQuietFatal("$CHMOD 750 $ETCDIR/mysqld.pwd"); ExecQuietFatal("$CHGRP tbadmin $ETCDIR/mysqld.pwd"); }; $password = `cat $ETCDIR/mysqld.pwd`; chomp($password); Phase "initialize", "Initializing mysql", sub { PhaseSkip("mysqld already initialzed") if (-d "$MYSQLDBDIR/mysql"); ExecQuietFatal("$MYSQLINSTALL --ldata=${MYSQLDBDIR}"); ExecQuietFatal("$CHOWN -R mysql:mysql $MYSQLDBDIR"); }; Phase "start", "Starting mysqld", sub { my ($exitval) = ExecQuiet("$MYSQLADMIN -u mysql ping"); PhaseSkip("mysqld already running") if ($exitval == 0); ExecQuietFatal("$RCDIR/1.mysql-server.sh start"); # Give mysqld some time to start, then make sure it did sleep 5; ExecQuietFatal("$MYSQLADMIN -u mysql ping"); }; # Once the password is inserted and privs flushed, will need a password # from this point forward! Phase "privs", "Initializing mysqld priv system", sub { my ($exitval, @rows) = ExecQuiet("echo 'select * from user limit 1' | ". "$MYSQL -s -u root mysql"); if ($exitval) { PhaseSkip("Privs already initialized"); } ($exitval, @rows) = ExecQuietFatal("echo 'update user set ". " Password=PASSWORD(\"$password\") ". "where user=\"root\"; flush privileges' | ". "$MYSQL -s -u root mysql"); }; }; if ($MAILMANSUPPORT) { my $MAILMANDIR = "/usr/local/mailman"; my $MAILMANCFG = "$MAILMANDIR/Mailman/mm_cfg.py"; my $MAILMANLISTS = "$MAILMANDIR/lists"; my $MAILMANPWD = "$ETCDIR/mailman.pwd"; my $MAILMANALIAS = "/etc/mail/aliases.mailman"; Phase "Mailman", "Setting up Mailman", sub { PhaseSkip("mailman not supported on ops") if ($FBSD_VERSION == 4); # Patch for broken install ExecQuietFatal("$CHMOD 2770 $MAILMANDIR/archives/private"); # Get a password for the mailman installation. Phase "password", "Generating mailman password", sub { DoneIfExists($MAILMANPWD); my $string = substr(GenSecretKey(), 0, 10); PhaseFail("Invalid password supplied") if (!defined($string) || $string eq ""); CreateFileFatal("$MAILMANPWD", "$string"); ExecQuietFatal("$CHMOD 750 $MAILMANPWD"); ExecQuietFatal("$CHGRP tbadmin $MAILMANPWD"); }; my $secret = `cat $MAILMANPWD`; chomp($secret); Phase "config", "Editing local config file", sub { DoneIfEdited("$MAILMANCFG"); AppendToFileFatal($MAILMANCFG, "DEFAULT_EMAIL_HOST = '${OURDOMAIN}'", "DEFAULT_URL_HOST = '${USERNODE}'", "add_virtualhost(DEFAULT_URL_HOST, ". "DEFAULT_EMAIL_HOST)"); }; Phase "mailman", "Creating mailman list", sub { PhaseSkip("List already created") if (-d "$MAILMANLISTS/mailman"); ExecQuietFatal("cd $MAILMANDIR; ". "bin/newlist -q mailman $TBOPSEMAIL $secret"); }; Phase "subscribe", "Subscribing testbed-ops", sub { PhaseSkip("Already subscribed") if (ExecQuiet("$MAILMANDIR/bin/list_members mailman | ". "fgrep -q -s $TBOPSEMAIL") == 0); ExecQuietFatal("cd $MAILMANDIR; ". "echo $TBOPSEMAIL | ". " bin/add_members -r - -w n -a n mailman"); }; # Can do this as many times as we like. No way to know. ExecQuietFatal("cd $MAILMANDIR; ". "bin/config_list -i data/sitelist.cfg mailman"); # Ditto ExecQuietFatal("cd $MAILMANDIR; ". "bin/mmsitepass $secret; ". "bin/mmsitepass -c $secret"); Phase "config", "Editing $SENDMAIL_CF", sub { PhaseSkip("Already edited") if (ExecQuiet("fgrep AliasFile $SENDMAIL_CF | ". "fgrep -q -s mailman") == 0); ExecQuietFatal("sed -i .orig -e ". " 's,\\(AliasFile.*\\),\\1\\,$MAILMANALIAS,' ". "$SENDMAIL_CF"); }; Phase "aliases", "Creating $MAILMANALIAS", sub { DoneIfExists($MAILMANALIAS); CreateFileFatal($MAILMANALIAS); }; Phase "newaliases", "Running newaliases", sub { PhaseSkip("No new aliases") if PhaseWasSkipped("aliases"); ExecQuietFatal($NEWALIASES); }; }; } if ($CVSSUPPORT) { my $CVSDDIR = "/usr/local/etc/cvsd"; my $CVSDCONF = "$CVSDDIR/cvsd.conf"; my $CVSDHEAD = "$LIBDIR/cvsd.conf.head"; my $CVSDJAIL = "/var/cvsjail"; my $BUILDROOT = "/usr/local/sbin/cvsd-buildroot"; my $REPOSDIR = "/proj/cvsrepos"; Phase "cvsd", "Installing cvsd", sub { PhaseSkip("cvsd not supported on ops") if ($FBSD_VERSION < 6); Phase "files", "Installing cvsd files", sub { DoneIfExists($CVSDHEAD); ExecQuietFatal("$GMAKE -C $TOP_OBJDIR/collab/cvs control-install"); }; Phase "cvsd.conf", "Installing cvsd.conf", sub { DoneIfExists($CVSDCONF); ExecQuietFatal("$CP -f $CVSDHEAD $CVSDCONF"); }; Phase "jail", "Creating cvsd jail hierarchy", sub { DoneIfExists($CVSDJAIL); ExecQuietFatal("$BUILDROOT $CVSDJAIL"); }; Phase "dirs", "Creating extra directories", sub { if (! -e "$CVSDJAIL/dummy") { mkdir("$CVSDJAIL/dummy", 0755) or PhaseFail("Unable to make $CVSDJAIL/dummy : $!"); } if (! -e "$CVSDJAIL/cvsrepos") { mkdir("$CVSDJAIL/cvsrepos", 0755) or PhaseFail("Unable to make $CVSDJAIL/cvsrepos : $!"); } ExecQuietFatal("$CHGRP nobody $CVSDJAIL/dummy $CVSDJAIL/cvsrepos"); if (! -e "$REPOSDIR") { mkdir("$REPOSDIR", 0775) or PhaseFail("Unable to make $REPOSDIR : $!"); } }; Phase "cvsd.sh", "Installing cvsd.sh", sub { DoneIfExists("$RCDIR/cvsd.sh"); ExecQuietFatal("mv $RCDIR/cvsd.sh.sample $RCDIR/cvsd.sh"); ExecQuietFatal("$CHMOD a+x $RCDIR/cvsd.sh"); }; }; } if ($BUGDBSUPPORT) { my $FLYSPRAYPWD = "$ETCDIR/flyspray.pwd"; my $FLYSPRAYDIR = "/usr/local/www/data/flyspray"; my $FLYSPRAYURL = "http://www.emulab.net/downloads/flyspray-0.9.9.tgz"; my $FLYSPRAYCONF = "$ETCDIR/flyspray.conf.php"; my $PHPCONFIG = "/usr/local/etc/php.ini"; my $localtarfile = "/tmp/flyspray.tgz"; my $ADODBPATH = "/usr/local/share/adodb/adodb.inc.php"; Phase "flyspray", "Installing flyspray", sub { PhaseSkip("flyspray not supported on ops") if ($FBSD_VERSION < 6); # Get a password for the installation. Phase "password", "Generating flyspray password", sub { DoneIfExists($FLYSPRAYPWD); my $string = substr(GenSecretKey(), 0, 10); PhaseFail("Invalid password supplied") if (!defined($string) || $string eq ""); CreateFileFatal("$FLYSPRAYPWD", "$string"); ExecQuietFatal("$CHMOD 750 $FLYSPRAYPWD"); ExecQuietFatal("$CHGRP tbadmin $FLYSPRAYPWD"); }; my $secret = `cat $FLYSPRAYPWD`; chomp($secret); Phase "php", "Patching up PHP config file", sub { DoneIfExists($PHPCONFIG); CreateFileFatal($PHPCONFIG, "[PHP]", ";", "; For ADODB, which is needed by flyspray.", ";", "include_path=\"/usr/local/share/adodb\""); }; Phase "dbcreate", "Creating flyspray DB", sub { if (! -d "$MYSQLDBDIR/flyspray") { ExecQuietFatal("$MYSQLADMIN -u root -p${password} ". " create flyspray"); } ExecQuietFatal("echo 'grant all on flyspray.* to ". " flyspray\@localhost ". " identified by \"$secret\"' | ". "$MYSQL -s -u root -p${password} mysql"); }; Phase "fetching", "Fetching flyspray distribution", sub { DoneIfExists($localtarfile); FetchFileFatal($FLYSPRAYURL, $localtarfile); }; Phase "unpacking", "Unpacking flyspray distribution", sub { if (! -e "$FLYSPRAYDIR") { mkdir("$FLYSPRAYDIR", 0770) or PhaseFail("Unable to make $FLYSPRAYDIR : $!"); } ExecQuietFatal("$CHOWN nobody $FLYSPRAYDIR"); PhaseSkip("Flyspray already unpacked") if (-e "$FLYSPRAYDIR/.htaccess"); ExecQuietFatal("$TAR zxf $localtarfile -C $FLYSPRAYDIR"); }; Phase "dbinit", "Initializing flyspray DB", sub { if (!ExecQuiet("$MYSQLDUMP -d -u flyspray -p${secret} ". "flyspray flyspray_groups")) { PhaseSkip("DB already initialized"); } ExecQuietFatal("$MYSQL -u flyspray -p${secret} flyspray ". " < $FLYSPRAYDIR/sql/flyspray-0.9.8.mysql"); ExecQuietFatal("$MYSQL -u flyspray -p${secret} flyspray ". " < $FLYSPRAYDIR/sql/flyspray-0.9.9-devel.mysql"); }; Phase "dbpatch", "Patching up flyspray DB", sub { if (!ExecQuiet("$MYSQLDUMP -d -u flyspray -p${secret} ". "flyspray emulab_project_mapping")) { PhaseSkip("DB already patched"); } ExecQuietFatal("$MYSQL -u flyspray -p${secret} flyspray ". " < $TOP_SRCDIR/bugdb/sqlextras"); }; # # Fix up the password for the flyspray "superuser" # my $md5_secret = `$MD5 -q -s $secret`; PhaseFail("Could not take MD5 of flyspray password!") if ($?); chomp($md5_secret); ExecQuietFatal("echo 'update flyspray_users set ". " user_pass=\"${md5_secret}\", ". " jabber_id=\"\", ". " email_address=\"${TBOPSEMAIL}\"' | ". "$MYSQL -u flyspray -p${secret} flyspray"); Phase "configfile", "Creating flyspray config file", sub { DoneIfExists($FLYSPRAYCONF); CreateFileFatal($FLYSPRAYCONF, ";", "; Flyspray configuration", ";", "[general]", "basedir = \"${FLYSPRAYDIR}\"", "baseurl = \"https://${USERNODE}/flyspray/\"", "adodbpath = \"$ADODBPATH\"", "cookiesalt= 130918332", "output_buffering = \"on\"", "passwdcrypt = \"md5\"", "address_rewriting = \"0\"", "reminder_daemon = \"0\"", "[database]", "dbtype = \"mysql\"", "dbhost = \"localhost\"", "dbname = \"flyspray\"", "dbprefix = \"flyspray_\"", "dbuser = \"flyspray\"", "dbpass = \"$secret\"" ); ExecQuietFatal("$CHOWN nobody:tbadmin $FLYSPRAYCONF"); ExecQuietFatal("$CHMOD 750 $FLYSPRAYCONF"); }; }; } if ($WIKISUPPORT) { my $WIKIDIR = "/usr/local/www/data/twiki"; my $WIKICONF = "LocalSite.cfg"; my $WIKIURL = "http://www.emulab.net/downloads/twiki-4.1.tgz"; my $localtarfile = "/tmp/twiki.tgz"; my $CGISESSDIR = "/var/db/cgisess"; my $WIKI_LOGDIR = "$LOGDIR/twiki"; Phase "TWiki", "Installing TWiki", sub { PhaseSkip("TWiki not supported on ops") if ($FBSD_VERSION < 6); Phase "fetching", "Fetching TWiki distribution", sub { DoneIfExists($localtarfile); FetchFileFatal($WIKIURL, $localtarfile); }; if (! -e "$CGISESSDIR") { mkdir("$CGISESSDIR", 0770) or PhaseFail("Unable to make $CGISESSDIR : $!"); } ExecQuietFatal("$CHOWN nobody:wheel $CGISESSDIR"); Phase "unpacking", "Unpacking TWiki distribution", sub { if (! -e "$WIKIDIR") { mkdir("$WIKIDIR", 0770) or PhaseFail("Unable to make $WIKIDIR : $!"); } ExecQuietFatal("$CHMOD 770 $WIKIDIR"); PhaseSkip("Flyspray already unpacked") if (-e "$WIKIDIR/data"); ExecQuietFatal("$TAR zxf $localtarfile -C $WIKIDIR"); }; ExecQuietFatal("$CHOWN -R nobody:tbadmin $WIKIDIR"); Phase "logdir", "Creating TWiki log directory", sub { DoneIfExists($WIKI_LOGDIR); mkdir $WIKI_LOGDIR, 0775 or PhaseFail("Unable to create $WIKI_LOGDIR : $!"); ExecQuietFatal("$CHOWN nobody:tbadmin $WIKI_LOGDIR"); ExecQuietFatal("$CHMOD 770 $WIKI_LOGDIR"); }; Phase "config", "Configuring TWiki", sub { # Must overwrite the distribution version. ExecQuietFatal("$CP -f $TOP_OBJDIR/wiki/$WIKICONF ". " $WIKIDIR/lib/$WIKICONF"); }; }; } exit(0) if ($ELABINELAB); print "----------------------------------------------------------------------\n"; print "Installation completed succesfully!\n"; print "Please reboot this machine before proceeding with boss setup\n"; if (!PhaseWasSkipped("maillists")) { print "Local mailing lists have been created, with no members, in\n"; print "$LIST_DIR . Please add members to the following lists:\n"; print map "$_\n", @LOCAL_MAILING_LISTS; }