ops-install.in 25.1 KB
Newer Older
1 2 3 4
#!/usr/bin/perl -w

#
# EMULAB-COPYRIGHT
5
# Copyright (c) 2003, 2004, 2005, 2006 University of Utah and the Flux Group.
6 7 8 9
# All rights reserved.
#

#
10
# install-ops - Script to do the initial install of an ops node
11 12 13 14 15
#
# 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
16
#   User to edit them himself
17 18 19 20 21 22 23
#

#
# Configure variables
#
my $PREFIX = '@prefix@';

Robert Ricci's avatar
Robert Ricci committed
24
my @MAILING_LISTS = ("@TBOPSEMAIL@","@TBLOGSEMAIL@","@TBWWWEMAIL@",
25
    "@TBAPPROVALEMAIL@","@TBAUDITEMAIL@","@TBSTATEDEMAIL@",
Robert Ricci's avatar
Robert Ricci committed
26 27
    "@TBTESTSUITEEMAIL@");

28

29 30 31 32 33 34
my $OURDOMAIN   = '@OURDOMAIN@';
my $USERNODE    = '@USERNODE@';
my $FSNODE      = '@FSNODE@';
my $BOSSNODE    = '@BOSSNODE@';
my $BOSSNODE_IP = '@BOSSNODE_IP@';
my $USERNODE_IP = '@USERNODE_IP@';
35
my $FSNODE_IP   = '@FSNODE_IP@';
36
my $LOGFACIL    = '@TBLOGFACIL@';
37
my $ELABINELAB  = @ELABINELAB@;
38
my $WINSUPPORT  = @WINSUPPORT@;
39
my $QUOTA_FSLIST= '@FS_WITH_QUOTAS@';
40 41 42
my $LOGDIR      = "$PREFIX/log";
my $ETCDIR      = "$PREFIX/etc";

43

44 45 46
# True if we are also the FS node
my $ISFS	= ($USERNODE eq $FSNODE) ? 1 : 0;

47 48 49 50
# For /share export below.
my $CONTROL_NETWORK = "@CONTROL_NETWORK@";
my $CONTROL_NETMASK = "@CONTROL_NETMASK@";

51 52 53
# Should be configure variable
my $TBADMINGID  = 101;

54
#
Robert Ricci's avatar
Robert Ricci committed
55
# Allow this to work if the library is left in the source directory
56
#
Robert Ricci's avatar
Robert Ricci committed
57 58 59 60
use lib '@srcdir@';
   
use English;
use libinstall;
61 62 63 64 65 66
use Getopt::Std;

#
# Handle command-line options
#
sub usage {
67
    print "Usage: ops-install [-b] [-p packagedir] [-P portname]\n";
68 69 70
    exit(1);
}

71 72 73 74 75 76 77 78 79
# 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");
}

80
#
81 82
# The meta-ports (name and version) that drag in all the dependancies for
# an ops/fs node. These are OS dependent as we upgrade.
83
#
84 85 86
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";
87

88
my $packagedir = "";
89
my $batchmode  = 0;
90
my $password;
91
my %opts;
92
if (! getopts("P:p:bw:F:", \%opts)) {
93 94 95 96
    usage();
}
if (defined($opts{p})) {
    $packagedir = $opts{p};
97
}
98
if (defined($opts{b})) {
99 100
    $batchmode = 1;
}
101 102 103
if (defined($opts{P})) {
    $OPS_PORT = $opts{P};
}
104 105 106 107 108 109
if (defined($opts{F})) {
    $FS_PORT = $opts{F};
}
if (defined($opts{w})) {
    $password = $opts{w};
}
110 111 112
if (@ARGV) {
    usage();
}
113

114 115 116 117 118 119 120 121
#
# 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/..";

122
#
Robert Ricci's avatar
Robert Ricci committed
123
# Some programs we use
124
#
Robert Ricci's avatar
Robert Ricci committed
125 126
my $CHGRP      = "/usr/bin/chgrp";
my $CHMOD      = "/bin/chmod";
127
my $CHOWN      = "/usr/sbin/chown";
Robert Ricci's avatar
Robert Ricci committed
128
my $PW         = "/usr/sbin/pw";
129
my $PATCH      = "/usr/bin/patch";
Robert Ricci's avatar
Robert Ricci committed
130
my $NEWALIASES = "/usr/bin/newaliases";
131 132
my $SH         = "/bin/sh";
my $PKG_INFO   = "/usr/sbin/pkg_info";
133
my $PKG_ADD    = "/usr/sbin/pkg_add";
134
my $PWD        = "/bin/pwd";
135
my $CP         = "/bin/cp";
136
my $MV         = "/bin/mv";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
137
my $GMAKE      = "/usr/local/bin/gmake";
138
my $ENV        = "/usr/bin/env";
139
my $MOUNT      = "/sbin/mount";
140 141

#
Robert Ricci's avatar
Robert Ricci committed
142
# Some files we edit/create
143
#
Robert Ricci's avatar
Robert Ricci committed
144
my $RCCONF          = "/etc/rc.conf";
145
my $HOSTS           = "/etc/hosts";
146
my $FSTAB           = "/etc/fstab";
Robert Ricci's avatar
Robert Ricci committed
147 148
my $RCLOCAL         = "/etc/rc.local";
my $RCCAPTURE       = "$PREFIX/etc/rc.capture";
Robert Ricci's avatar
Robert Ricci committed
149 150 151 152 153 154 155
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";
156
my $SSHD_CONFIG     = "/etc/ssh/sshd_config";
157
my $CRONTAB         = "/etc/crontab";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
158
my $AUTHKEYS	    = "/root/.ssh/authorized_keys";
Kirk Webb's avatar
 
Kirk Webb committed
159 160
my $SMBCONF_FILE    = "/usr/local/etc/smb.conf";
my $SMBCONF_HEAD    = "$SMBCONF_FILE.head";
161 162 163 164 165 166 167 168 169 170 171
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";
172 173

#
Robert Ricci's avatar
Robert Ricci committed
174
# Some directories we care about
175
#
176 177 178 179 180 181 182
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";
183
my $VARRUN        = "/var/run";
184 185

#
Robert Ricci's avatar
Robert Ricci committed
186
# And some lists that we use
187
#
188 189
my @LOCAL_HOSTS        = ($OURDOMAIN,$BOSSNODE,$USERNODE,$FSNODE);
my @LOGFILES           = ("/var/log/logins","/var/log/tiplogs/capture.log",
Robert Ricci's avatar
Robert Ricci committed
190
    "/var/log/mountd.log");
191 192
my @LOCAL_MAILING_LISTS = grep(/$OURDOMAIN$/,@MAILING_LISTS);
my @MAILING_LIST_NAMES  = map { /^([\w-]+)\@/ } @LOCAL_MAILING_LISTS;
Robert Ricci's avatar
Robert Ricci committed
193 194 195

my @TESTBED_DIRS       = ([$PREFIX, "0775"], ["/users", "0755"],
    ["/proj", "0755"], ["/groups", "0755"], ["/share", "0775"]);
196

197 198
my @MOUNTPOINTS        = ("/users", "/proj", "/groups", "/share");

199 200 201 202 203
#
# 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";
204
my $M2CRYPTO_PATCH  = "$TOP_SRCDIR/patches/m2crypto.patch";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
205
my $IDENTPUB        = "$TOP_SRCDIR/install/identity.pub";
206

207 208 209 210
#
# List of names that goes into $HOSTS and which must resolve.
# 
my @OPS_NAMES = ($USERNODE, "users", "ops");
211 212 213
if ($ISFS) {
    push(@OPS_NAMES, "fs");
}
214

215
#
Robert Ricci's avatar
Robert Ricci committed
216
# Make sure they know what they're getting into...
217
#
218 219 220 221
if (! $batchmode) {
    print STDERR
	"WARNING: This script is ONLY intended to be run on a machine\n";
    print STDERR
222
	"that is being set up as a dedicated ops or ops+fs node. Continue? [y/N] ";
223 224 225
    my $response = <>;
    die "Installation aborted!\n" unless ($response =~ /^y/i);
}
226

Robert Ricci's avatar
Robert Ricci committed
227 228
if ($UID != 0) {
    die "This script must be run as root.\n";
229 230 231
}

#
Robert Ricci's avatar
Robert Ricci committed
232
# The phases are fairly self-explanatory
233
#
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
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);
Robert Ricci's avatar
Robert Ricci committed
257 258 259
};

Phase "dirs", "Setting directory permissions", sub {
Robert Ricci's avatar
Robert Ricci committed
260 261
    foreach my $dirref (@TESTBED_DIRS) {
	my ($dir, $newmode) = @$dirref;
Robert Ricci's avatar
Robert Ricci committed
262 263 264 265
	Phase $dir, $dir, sub {
	    if (!-d $dir) {
		PhaseFail("Directory $dir does not exist");
	    }
Robert Ricci's avatar
Robert Ricci committed
266 267 268 269
	    # Use the real path, to avoid symlink problems
	    my $realdir = `realpath $dir`;
	    chomp $realdir;
	    my ($mode,$group) = (stat($realdir))[2,5];
Robert Ricci's avatar
Robert Ricci committed
270 271
	    # Fix up the mode (strip file type)
	    $mode = $mode & 0777;
Robert Ricci's avatar
Robert Ricci committed
272
	    if ($mode == eval $newmode && $group eq getgrnam("tbadmin")) {
Robert Ricci's avatar
Robert Ricci committed
273 274
		PhaseSkip("Already done");
	    }
Robert Ricci's avatar
Robert Ricci committed
275 276
	    ExecQuietFatal("$CHGRP tbadmin $realdir");
	    ExecQuietFatal("$CHMOD $newmode $realdir");
Robert Ricci's avatar
Robert Ricci committed
277
	};
278
    }
Robert Ricci's avatar
Robert Ricci committed
279
};
280

281
Phase "ports", "Installing ports", sub {
282
    Phase "packages", "Installing packages", sub {
283 284
	if (!ExecQuiet("$PKG_INFO -e $OPS_PORT") &&
	    (($FBSD_VERSION == 4) || !ExecQuiet("$PKG_INFO -e $PHP4_PORT"))) {
285 286 287 288 289
	    PhaseSkip("Ports already installed");
	}
	if (!$packagedir) {
	    PhaseSkip("No package directory provided");
	}
290 291 292 293
	ExecQuietFatal("$ENV PKG_PATH=$packagedir $PKG_ADD $OPS_PORT")
	    if (!ExecQuiet("$PKG_INFO -e $OPS_PORT"));
	ExecQuietFatal("$ENV PKG_PATH=$packagedir $PKG_ADD $PHP4_PORT")
	    if ($FBSD_VERSION > 4 && !ExecQuiet("$PKG_INFO -e $PHP4_PORT"));
294
    };
295 296 297 298 299 300 301 302 303 304 305 306
    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");
    };
307
    Phase "pcopy", "Copying ports into place", sub {
308 309 310
	if ($packagedir) {
	    PhaseSkip("Package directory provided");
	}
311
	DoneIfExists("$PORTSMISCDIR/emulab-ops");
312 313
	ExecQuietFatal("$SH $SRCDIR/ports/ports-install");
    };
314 315
    my $pwd = `$PWD`;
    chomp $pwd;
316
    Phase "pinstall", "Installing ports (may take a while)", sub {
317
	if (!ExecQuiet("$PKG_INFO -e $OPS_PORT")) {
318 319
	    PhaseSkip("Ports already installed");
	}
320 321 322
	if ($packagedir) {
	    PhaseSkip("Package directory provided");
	}
323 324 325 326

	#
	# This port is dead-simple, so it's safe to do it from this script
	#
327 328
	chdir "$PORTSMISCDIR/emulab-ops" or
		PhaseFail "Unable to change to $PORTSMISCDIR/emulab-ops: $!";
329
	ExecQuietFatal("make -DBATCH install");
330
    };
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
    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");
	}
	if ($packagedir) {
	    PhaseSkip("Package directory provided");
	}
	    
	chdir "$PORTSMISCDIR/emulab-fs" or
	    PhaseFail "Unable to change to $PORTSMISCDIR/emulab-fs: $!";
	ExecQuietFatal("make -DBATCH install");
    };
    chdir $pwd;
347 348
};

349 350
# XXX Temporary.
Phase "portfixup", "Fixing up packages", sub {
351
    Phase "rsync", "Looking for rsync and installing", sub {
352 353 354 355
	if (!ExecQuiet("$PKG_INFO -x rsync")) {
	    PhaseSkip("rsync already installed");
	}

356
	PhaseFail("Must have PKG_PATH environment variable set")
357 358 359
	    if (!exists($ENV{"PKG_PATH"}));
	
	ExecQuietFatal("$PKG_ADD rsync-2.6.3_1");
360
    };
361 362
    if ($WINSUPPORT) {
	Phase "samba", "Looking for Samba and installing", sub {
363 364 365
	    if (!$ISFS) {
		PhaseSkip("Not FS Node");
	    }
366 367 368
	    if (!ExecQuiet("$PKG_INFO -x samba")) {
		PhaseSkip("samba already installed");
	    }
369
	    
370
	    PhaseFail("Must have PKG_PATH environment variable set")
371
		if (!exists($ENV{"PKG_PATH"}));
372
		
373 374 375
	    ExecQuietFatal("$PKG_ADD samba-3.0.7,1");
	};
	Phase "gcc30", "Looking for GCC 3.0 and installing", sub {
376 377
	    if ($FBSD_VERSION > 4 ||
		!ExecQuiet("$PKG_INFO -x gcc30")) {
378
		PhaseSkip("GCC 3.0 already installed");
379
	    }
380
	    
381
	    PhaseFail("Must have PKG_PATH environment variable set")
382 383 384
		if (!exists($ENV{"PKG_PATH"}));
	
	    ExecQuietFatal("$PKG_ADD gcc30-3.0.4_1");
385 386
	};
    }
387 388
};

389 390
Phase "patches", "Applying patches", sub {
    Phase "m2cryptopatch", "Patching m2crypto", sub {
391 392 393 394 395 396 397 398 399
	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");
400 401 402
    };
}; 

Robert Ricci's avatar
Robert Ricci committed
403 404 405
Phase "rc.conf", "Adding testbed content to rc.conf", sub {
    DoneIfEdited($RCCONF);
    AppendToFileFatal($RCCONF,
Robert Ricci's avatar
Robert Ricci committed
406
		      qq|sendmail_enable="YES"|,
407 408
		      qq|rpcbind_enable="YES"|,
		      qq|mountd_enable="YES"|,
Robert Ricci's avatar
Robert Ricci committed
409 410
		      qq|nfs_server_enable="YES"|,
		      qq|nfs_server_flags="-u -t -n 16"|,
411
		      qq|mountd_flags="-r -p 900"|,
412
		      (($ISFS && $WINSUPPORT) ? qq|smbd_enable="YES"| : ()),
413
		      qq|apache_enable="YES"|,
Robert Ricci's avatar
Robert Ricci committed
414
		      qq|syslogd_flags=""|);
Robert Ricci's avatar
Robert Ricci committed
415
};
416

417
Phase "hosts", "Adding boss/ops/fs IP addresses to $HOSTS", sub {
418
    DoneIfEdited($HOSTS);
419 420 421 422 423 424
    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);
425 426
};

427 428 429 430 431 432 433
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) {
434 435
	Phase $name, $name, sub {
	    if (gethostbyname($name)) {
436
		PhaseSucceed("$name resolves");
437 438 439 440 441 442 443
	    } else {
		PhaseFail("$name does not resolve");
	    }
	};
    }
};

Robert Ricci's avatar
Robert Ricci committed
444 445 446 447 448 449 450 451 452 453
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: $!");
454 455
	    ExecQuietFatal("$CHGRP mailnull $LIST_DIR");
	    ExecQuietFatal("$CHMOD 750 $LIST_DIR");
Robert Ricci's avatar
Robert Ricci committed
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470
	};
	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 {
471
	    PhaseSkip("No new aliases") unless @MAILING_LIST_NAMES;
Robert Ricci's avatar
Robert Ricci committed
472 473 474 475 476 477 478 479 480 481 482 483
	    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
484 485
	# filesystems.  Note: we cannot do /share on the same exports line
	# as the other filesystems because of the RO mount below (trust me).
Robert Ricci's avatar
Robert Ricci committed
486
	#
487 488 489 490
	my @dirs = ('/var','/usr/testbed');
	if ($ISFS) {
	    @dirs = ('/users','/groups','/proj',@dirs);
	}
Robert Ricci's avatar
Robert Ricci committed
491 492 493 494 495 496 497 498 499 500 501 502 503 504
	@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,
505 506
		join(" ",@{$filesystems{$key}}) .
		    "\t$BOSSNODE -maproot=root";
Robert Ricci's avatar
Robert Ricci committed
507 508
	}

509 510 511 512 513 514 515 516 517 518 519 520 521 522 523
	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");
	}
524

Robert Ricci's avatar
Robert Ricci committed
525 526 527 528 529 530 531
	#
	# Put them in exports.head, and copy that to /etc/exports
	#
	CreateFileFatal($EXPORTS_HEAD, @exports_lines);
	ExecQuietFatal("cp $EXPORTS_HEAD $EXPORTS_FILE");
    };

532
    # XXX Newhup
Robert Ricci's avatar
Robert Ricci committed
533 534 535 536 537 538
    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");
    };
};
539

540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571
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");
	    };
	}
    };
};

572 573 574
#
# Set up syslog
#
Robert Ricci's avatar
Robert Ricci committed
575 576 577 578 579 580 581 582 583 584 585
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 = <SC>;
586 587 588
	if (scalar(grep(/$LOGFACIL/, @sc)) != 0) {
	    PhaseFail("Testbed chosen facility $LOGFACIL already in use in /etc/syslog.conf!");
	}
Robert Ricci's avatar
Robert Ricci committed
589 590
	if (scalar(grep(/^cron/, @sc)) != 1) {
	    PhaseFail("Unable to find marker in /etc/syslog.conf!");
591 592
	}

Robert Ricci's avatar
Robert Ricci committed
593 594 595 596 597
	#
	# Clobber and re-write
	#
	seek(SC,0,0);
	truncate(SC,0);
598

Robert Ricci's avatar
Robert Ricci committed
599
	foreach my $line (@sc) {
600 601 602 603 604 605 606
	    #
	    # 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/;
	    }
607 608 609 610 611 612 613 614 615 616 617 618

	    #
	    # 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/^/#/;
	    }

Robert Ricci's avatar
Robert Ricci committed
619
	    print SC $line;
620 621 622 623

	    #
	    # Find the cron line, after which we place our auth.info line
	    #
Robert Ricci's avatar
Robert Ricci committed
624 625 626 627 628 629
	    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";
	    }
	}
630

Robert Ricci's avatar
Robert Ricci committed
631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647
	#
	# 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 : $!");
    };
    
648 649 650 651 652 653 654 655 656 657 658 659 660 661 662
    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");
    };

Robert Ricci's avatar
Robert Ricci committed
663 664 665 666 667
    Phase "logfiles", "Creating log files", sub {
	foreach my $logfile (@LOGFILES) {
	    Phase $logfile, $logfile, sub {
		DoneIfExists($logfile);
		CreateFileFatal($logfile);
668
		ExecQuietFatal("$CHGRP tbadmin $logfile");
Robert Ricci's avatar
Robert Ricci committed
669 670 671 672 673 674 675 676 677 678 679 680 681 682
		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");
    };
};

683 684
Phase "cron", "Adding cron jobs", sub {
    Phase "crontab", "Editing $CRONTAB", sub {
685 686 687
	if ($QUOTA_FSLIST eq "") {
	    PhaseSkip("No filesystem quotas");
	}
688 689
	DoneIfEdited($CRONTAB);
	AppendToFileFatal($CRONTAB,
690
	    "0 \t6\t*\t*\t*\troot\t$PREFIX/sbin/quotamail");
691 692 693 694 695 696 697
    };
    Phase "cronhup", "HUPing cron", sub {
	if (PhaseWasSkipped("crontab")) { PhaseSkip("No new crontab"); }
	HUPDaemon("cron");
    };
};

698
Phase "sudoers", "Editing $SUDOERS to allow wheel group", sub {
Robert Ricci's avatar
Robert Ricci committed
699 700 701
    DoneIfEdited($SUDOERS);
    AppendToFileFatal($SUDOERS,"%wheel    ALL=(ALL) NOPASSWD: ALL");
};
702

703 704 705 706 707 708 709 710 711 712 713 714 715
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");
716
    };
717 718 719 720 721 722 723 724 725 726 727 728
    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");
	};
    }
};
729

Robert Ricci's avatar
Robert Ricci committed
730
Phase "ssh", "Allowing root ssh", sub {
Robert Ricci's avatar
Robert Ricci committed
731 732 733 734 735 736 737 738 739
    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: $!");
    };
740 741 742 743 744 745 746 747 748 749 750 751
    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");
    };
Robert Ricci's avatar
Robert Ricci committed
752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776
};

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");
    };
777 778
};

779 780
Phase "event", "Setting up event system", sub {
    Phase "elvinconf", "Installing elvind config file", sub {
781
	DoneIfIdentical($ELVIND_CONF,$OPS_ELVIND_CONF);
782 783 784 785
	ExecQuietFatal("$CP $OPS_ELVIND_CONF $ELVIND_CONF");
    };
};

786
Phase "rc.d", "Setting up rc.d scripts", sub {
787 788 789 790 791 792
    Phase "rsyncd", "Removing rsyncd startup script",  sub {
	DoneIfDoesntExist("$RCDIR/rsyncd.sh");
	if (!unlink "$RCDIR/rsyncd.sh") {
	    PhaseFail("Unable to remove $RCDIR/rsyncd.sh: $!");
	}
    };
793 794 795 796 797 798
    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: $!");
	}
    };
799
    Phase "rc.testbed", "Installing testbed RC scripts", sub {
800 801 802 803
        Phase "elvind.sh", "Removing port version of elvind.sh", sub {
	    DoneIfDoesntExist("$RCDIR/elvind.sh");
            ExecQuietFatal("/bin/rm -f $RCDIR/elvind.sh");
        };
804 805 806 807 808
	DoneIfExists("$RCDIR/2.elvind.sh");
	ExecQuietFatal("$GMAKE -C $TOP_OBJDIR/rc.d control-install");
    };
};

809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877
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");
    };
};

878 879 880
print "----------------------------------------------------------------------\n";
print "Installation completed succesfully!\n";
print "Please reboot this machine before proceeding with boss setup\n";
Robert Ricci's avatar
Robert Ricci committed
881
if (!PhaseWasSkipped("maillists")) {
882
    print "Local mailing lists have been created, with no members, in\n";
Robert Ricci's avatar
Robert Ricci committed
883
    print "$LIST_DIR . Please add members to the following lists:\n";
884
    print map "$_\n", @LOCAL_MAILING_LISTS;
885
}