#!/usr/bin/perl -w # # EMULAB-COPYRIGHT # Copyright (c) 2003 University of Utah and the Flux Group. # All rights reserved. # # # install-ops.sh - 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@","@TBLOGSEMAIL@","@TBAUDITEMAIL@","@TBSTATEDEMAIL@", "@TBTESTSUITEEMAIL@"); my $OURDOMAIN = '@OURDOMAIN@'; my $USERNODE = '@USERNODE@'; my $FSNODE = '@FSNODE@'; my $BOSSNODE = '@BOSSNODE@'; # # Allow this to work if the library is left in the source directory # use lib '@srcdir@'; use English; use libinstall; # # Some programs we use # my $CHGRP = "/usr/bin/chgrp"; my $CHMOD = "/bin/chmod"; my $PW = "/usr/sbin/pw"; my $NEWALIASES = "/usr/bin/newaliases"; my $SH = "/bin/sh"; my $PKG_INFO = "/usr/sbin/pkg_info"; my $PWD = "/bin/pwd"; # # Some files we edit/create # my $RCCONF = "/etc/rc.conf"; my $LOCAL_HOSTNAMES = "/etc/mail/local-host-names"; my $ALIASES_FILE = "/etc/mail/aliases"; 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"; # # Some directories we care about # my $LIST_DIR = "/etc/mail/lists"; my $TIPLOG_DIR = "/var/log/tiplogs"; my $PORTSDIR = "/usr/ports/misc"; my $SRCDIR = '@srcdir@'; # # 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 @MAILING_LIST_NAMES = map { /^([\w-]+)\@/ } @MAILING_LISTS; my @TESTBED_DIRS = ($PREFIX,"/users","/proj","/groups","/share"); # # Make sure they know what they're getting into... # 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 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 "groups", "Creating admin group", sub { if (getgrnam("tbadmin")) { PhaseSkip("tbadmin group already exists"); } ExecQuietFatal("$PW groupadd tbadmin -g 101"); }; 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 "ports", "Installing ports", sub { Phase "pcopy", "Copying ports into place", sub { DoneIfExists("$PORTSDIR/emulab-ops"); ExecQuietFatal("$SH $SRCDIR/ports/ports-install"); }; Phase "pinstall", "Installing ports (may take a while)", sub { if (!ExecQuiet("$PKG_INFO -e emulab-ops-1.0")) { PhaseSkip("Ports already installed"); } # # This port is dead-simple, so it's safe to do it from this script # my $pwd = `$PWD`; chomp $pwd; chdir "$PORTSDIR/emulab-ops" or PhaseFail "Unable to change to $PORTSDIR/emulab-ops: $!"; ExecQuietFatal("make install"); chdir $pwd; }; }; Phase "rc.conf", "Adding testbed content to rc.conf", sub { DoneIfEdited($RCCONF); AppendToFileFatal($RCCONF, qq|sendmail_enable = "YES"|, qq|nfs_server_enable = "YES"|, qq|nfs_server_flags = "-u -t -n 16"|, qq|syslogd_flags = ""|); }; 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: $!"); }; 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_LISTS; 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 # my @dirs = ('/users','/groups','/proj','/var'); @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 -alldirs -maproot=root"; } # # 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"); }; }; # # 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(/^cron/, @sc)) != 1) { PhaseFail("Unable to find marker in /etc/syslog.conf!"); } # # Clobber and re-write # seek(SC,0,0); truncate(SC,0); # # Find the cron line, after which we place our auth.info line # foreach my $line (@sc) { print SC $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 "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 "sudoers", "Editing $SUDOERS", sub { DoneIfEdited($SUDOERS); AppendToFileFatal($SUDOERS,"%wheel ALL=(ALL) NOPASSWD: ALL"); }; Phase "ssh", "Allowing root ssh from boss", sub { DoneIfEdited($SSHD_CONFIG); AppendToFileFatal($SSHD_CONFIG,"PermitRootLogin Yes"); }; 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", @MAILING_LISTS; }