ops-install.in 39.2 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@",
26
    "@TBTESTSUITEEMAIL@", "@TBERRORSEMAIL@", "@TBAUTOMAILEMAIL@");
Robert Ricci's avatar
Robert Ricci committed
27

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 $TBOPSEMAIL  = '@TBOPSEMAIL@';
38
my $ELABINELAB  = @ELABINELAB@;
39
my $WINSUPPORT  = @WINSUPPORT@;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
40
my $MAILMANSUPPORT = @MAILMANSUPPORT@;
41
my $CVSSUPPORT  = @CVSSUPPORT@;
42
my $BUGDBSUPPORT= @BUGDBSUPPORT@;
43
my $WIKISUPPORT = @WIKISUPPORT@;
44
my $QUOTA_FSLIST= '@FS_WITH_QUOTAS@';
45
46
my $LOGDIR      = "$PREFIX/log";
my $ETCDIR      = "$PREFIX/etc";
47
my $LIBDIR      = "$PREFIX/lib";
48
49
50
51
52
53
54
55
56
57
my $SCRATCHDIR =  '@FSDIR_SCRATCH@';

#
# Fixed paths for clients
#
my $PROJROOT    = "@PROJROOT_DIR@";
my $GROUPROOT   = "@GROUPSROOT_DIR@";
my $USERROOT    = "@USERSROOT_DIR@";
my $SCRATCHROOT	= "@SCRATCHROOT_DIR@";
my $SHAREROOT	= "@SHAREROOT_DIR@";
58

59
60
61
# True if we are also the FS node
my $ISFS	= ($USERNODE eq $FSNODE) ? 1 : 0;

62
63
64
65
# For /share export below.
my $CONTROL_NETWORK = "@CONTROL_NETWORK@";
my $CONTROL_NETMASK = "@CONTROL_NETMASK@";

66
67
68
# Should be configure variable
my $TBADMINGID  = 101;

69
#
Robert Ricci's avatar
Robert Ricci committed
70
# Allow this to work if the library is left in the source directory
71
#
Robert Ricci's avatar
Robert Ricci committed
72
73
74
75
use lib '@srcdir@';
   
use English;
use libinstall;
76
77
78
79
80
81
use Getopt::Std;

#
# Handle command-line options
#
sub usage {
82
    print "Usage: ops-install [-b] [-p packagedir] [-P portname]\n";
83
84
85
    exit(1);
}

86
87
88
89
90
91
92
93
94
# 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");
}

95
#
96
97
# The meta-ports (name and version) that drag in all the dependancies for
# an ops/fs node. These are OS dependent as we upgrade.
98
#
99
100
101
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";
102

103
my $packagedir = "";
104
my $batchmode  = 0;
105
my $password;
106
my %opts;
107
if (! getopts("P:p:bw:F:", \%opts)) {
108
109
110
111
    usage();
}
if (defined($opts{p})) {
    $packagedir = $opts{p};
112
}
113
if (defined($opts{b})) {
114
115
    $batchmode = 1;
}
116
117
118
if (defined($opts{P})) {
    $OPS_PORT = $opts{P};
}
119
120
121
122
123
124
if (defined($opts{F})) {
    $FS_PORT = $opts{F};
}
if (defined($opts{w})) {
    $password = $opts{w};
}
125
126
127
if (@ARGV) {
    usage();
}
128

129
130
131
132
133
134
135
136
#
# 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/..";

137
#
Robert Ricci's avatar
Robert Ricci committed
138
# Some programs we use
139
#
Robert Ricci's avatar
Robert Ricci committed
140
141
my $CHGRP      = "/usr/bin/chgrp";
my $CHMOD      = "/bin/chmod";
142
my $CHOWN      = "/usr/sbin/chown";
Robert Ricci's avatar
Robert Ricci committed
143
my $PW         = "/usr/sbin/pw";
144
my $PATCH      = "/usr/bin/patch";
Robert Ricci's avatar
Robert Ricci committed
145
my $NEWALIASES = "/usr/bin/newaliases";
146
147
my $SH         = "/bin/sh";
my $PKG_INFO   = "/usr/sbin/pkg_info";
148
my $PKG_ADD    = "/usr/sbin/pkg_add";
149
my $PWD        = "/bin/pwd";
150
my $CP         = "/bin/cp";
151
my $MV         = "/bin/mv";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
152
my $GMAKE      = "/usr/local/bin/gmake";
153
my $ENV        = "/usr/bin/env";
154
my $MOUNT      = "/sbin/mount";
155
156
my $TAR	       = "/usr/bin/tar";
my $MD5	       = "/sbin/md5";
157
158

#
Robert Ricci's avatar
Robert Ricci committed
159
# Some files we edit/create
160
#
Robert Ricci's avatar
Robert Ricci committed
161
my $RCCONF          = "/etc/rc.conf";
162
my $HOSTS           = "/etc/hosts";
163
my $FSTAB           = "/etc/fstab";
Robert Ricci's avatar
Robert Ricci committed
164
165
my $RCLOCAL         = "/etc/rc.local";
my $RCCAPTURE       = "$PREFIX/etc/rc.capture";
Robert Ricci's avatar
Robert Ricci committed
166
167
my $LOCAL_HOSTNAMES = "/etc/mail/local-host-names";
my $ALIASES_FILE    = "/etc/mail/aliases";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
168
my $SENDMAIL_CF     = "/etc/mail/sendmail.cf";
Robert Ricci's avatar
Robert Ricci committed
169
170
171
172
173
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";
174
my $SSHD_CONFIG     = "/etc/ssh/sshd_config";
175
my $CRONTAB         = "/etc/crontab";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
176
my $AUTHKEYS	    = "/root/.ssh/authorized_keys";
Kirk Webb's avatar
   
Kirk Webb committed
177
178
my $SMBCONF_FILE    = "/usr/local/etc/smb.conf";
my $SMBCONF_HEAD    = "$SMBCONF_FILE.head";
179
180
181
182
183
184
185
186
187
188
189
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";
190
191

#
Robert Ricci's avatar
Robert Ricci committed
192
# Some directories we care about
193
#
194
195
196
197
198
199
200
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";
201
my $VARRUN        = "/var/run";
202
203

#
Robert Ricci's avatar
Robert Ricci committed
204
# And some lists that we use
205
#
206
207
my @LOCAL_HOSTS        = ($OURDOMAIN,$BOSSNODE,$USERNODE,$FSNODE);
my @LOGFILES           = ("/var/log/logins","/var/log/tiplogs/capture.log",
Robert Ricci's avatar
Robert Ricci committed
208
    "/var/log/mountd.log");
209
210
my @LOCAL_MAILING_LISTS = grep(/$OURDOMAIN$/,@MAILING_LISTS);
my @MAILING_LIST_NAMES  = map { /^([\w-]+)\@/ } @LOCAL_MAILING_LISTS;
Robert Ricci's avatar
Robert Ricci committed
211

212
213
214
my @TESTBED_DIRS       = ([$PREFIX, "0775"], [$USERROOT, "0755"],
			  [$PROJROOT, "0755"], [$GROUPROOT, "0755"],
			  [$SHAREROOT, "0775"]);
215

216
217
218
219
220
221
my @MOUNTPOINTS        = ($USERROOT, $PROJROOT, $GROUPROOT, $SHAREROOT);

if ($SCRATCHDIR) {
    push(@TESTBED_DIRS, [$SCRATCHROOT, "0755"]);
    push(@MOUNTPOINTS, $SCRATCHROOT);
}
222

223
224
225
226
227
#
# 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";
228
my $M2CRYPTO_PATCH  = "$TOP_SRCDIR/patches/m2crypto.patch";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
229
my $MAILMAN_PATCH   = "$TOP_SRCDIR/patches/mailman.patch";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
230
my $IDENTPUB        = "$TOP_SRCDIR/install/identity.pub";
231

232
233
234
235
#
# List of names that goes into $HOSTS and which must resolve.
# 
my @OPS_NAMES = ($USERNODE, "users", "ops");
236
237
238
if ($ISFS) {
    push(@OPS_NAMES, "fs");
}
239

240
#
Robert Ricci's avatar
Robert Ricci committed
241
# Make sure they know what they're getting into...
242
#
243
244
245
246
if (! $batchmode) {
    print STDERR
	"WARNING: This script is ONLY intended to be run on a machine\n";
    print STDERR
247
	"that is being set up as a dedicated ops or ops+fs node. Continue? [y/N] ";
248
249
250
    my $response = <>;
    die "Installation aborted!\n" unless ($response =~ /^y/i);
}
251

Robert Ricci's avatar
Robert Ricci committed
252
253
if ($UID != 0) {
    die "This script must be run as root.\n";
254
255
256
}

#
Robert Ricci's avatar
Robert Ricci committed
257
# The phases are fairly self-explanatory
258
#
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
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
282
283
284
};

Phase "dirs", "Setting directory permissions", sub {
Robert Ricci's avatar
Robert Ricci committed
285
286
    foreach my $dirref (@TESTBED_DIRS) {
	my ($dir, $newmode) = @$dirref;
Robert Ricci's avatar
Robert Ricci committed
287
288
289
290
	Phase $dir, $dir, sub {
	    if (!-d $dir) {
		PhaseFail("Directory $dir does not exist");
	    }
Robert Ricci's avatar
Robert Ricci committed
291
292
293
294
	    # 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
295
296
	    # Fix up the mode (strip file type)
	    $mode = $mode & 0777;
Robert Ricci's avatar
Robert Ricci committed
297
	    if ($mode == eval $newmode && $group eq getgrnam("tbadmin")) {
Robert Ricci's avatar
Robert Ricci committed
298
299
		PhaseSkip("Already done");
	    }
Robert Ricci's avatar
Robert Ricci committed
300
301
	    ExecQuietFatal("$CHGRP tbadmin $realdir");
	    ExecQuietFatal("$CHMOD $newmode $realdir");
Robert Ricci's avatar
Robert Ricci committed
302
	};
303
    }
Robert Ricci's avatar
Robert Ricci committed
304
};
305

306
Phase "ports", "Installing ports", sub {
307
    Phase "packages", "Installing packages", sub {
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
	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");
	    };
328
329
	}
    };
330
331
332
333
334
335
336
337
338
339
340
341
    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");
    };
Leigh B. Stoller's avatar
Leigh B. Stoller committed
342
343
    if ($MAILMANSUPPORT) {
	Phase "mailman", "Installing Mailman package", sub {
344
	    if (!ExecQuiet("$PKG_INFO -x -E mailman")) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
345
346
347
348
349
		PhaseSkip("Mailman package already installed");
	    }
	    if (!$packagedir) {
		PhaseSkip("No package directory provided");
	    }
350
	    my $pname = GetPackage("mailman", $packagedir);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
351
352
353
	    ExecQuietFatal("$ENV PKG_PATH=$packagedir $PKG_ADD $pname");
	};
    }
354
355
356
357
358
    if ($CVSSUPPORT) {
	Phase "cvsd", "Installing cvsd package", sub {
	    PhaseSkip("cvsd not supported on ops")
		if ($FBSD_VERSION < 6);

359
	    if (!ExecQuiet("$PKG_INFO -x -E cvsd")) {
360
361
362
363
364
		PhaseSkip("cvsd package already installed");
	    }
	    if (!$packagedir) {
		PhaseSkip("No package directory provided");
	    }
365
	    my $pname = GetPackage("cvsd", $packagedir);
366
367
368
	    ExecQuietFatal("$ENV PKG_PATH=$packagedir $PKG_ADD $pname");
	};
    }
369
370
371
372
373
    if ($BUGDBSUPPORT) {
	Phase "flyspray", "Installing flyspray package support", sub {
	    PhaseSkip("flyspray not supported on ops")
		if ($FBSD_VERSION < 6);

374
	    if (!ExecQuiet("$PKG_INFO -x -E adodb")) {
375
376
377
378
379
		PhaseSkip("adodb package already installed");
	    }
	    if (!$packagedir) {
		PhaseSkip("No package directory provided");
	    }
380
	    my $pname = GetPackage("adodb", $packagedir);
381
382
383
	    ExecQuietFatal("$ENV PKG_PATH=$packagedir $PKG_ADD $pname");
	};
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
384
385
386
    PhaseSkip("Package directory provided; not installing from source")
	if ($packagedir);
    
387
    Phase "pcopy", "Copying ports into place", sub {
388
	DoneIfExists("$PORTSMISCDIR/emulab-ops");
389
390
	ExecQuietFatal("$SH $SRCDIR/ports/ports-install");
    };
391
392
    my $pwd = `$PWD`;
    chomp $pwd;
393
    Phase "pinstall", "Installing ports (may take a while)", sub {
394
	if (!ExecQuiet("$PKG_INFO -e $OPS_PORT")) {
395
396
397
398
399
400
	    PhaseSkip("Ports already installed");
	}

	#
	# This port is dead-simple, so it's safe to do it from this script
	#
401
402
	chdir "$PORTSMISCDIR/emulab-ops" or
		PhaseFail "Unable to change to $PORTSMISCDIR/emulab-ops: $!";
403
	ExecQuietFatal("make -DBATCH install");
404
    };
405
406
407
408
409
410
411
412
413
414
415
    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");
    };
Leigh B. Stoller's avatar
Leigh B. Stoller committed
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
    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");
	};
    }
435
    chdir $pwd;
436
437
};

438
439
# XXX Temporary.
Phase "portfixup", "Fixing up packages", sub {
440
    Phase "rsync", "Looking for rsync and installing", sub {
441
442
443
	if (!ExecQuiet("$PKG_INFO -x rsync")) {
	    PhaseSkip("rsync already installed");
	}
444
445
	my $pname = GetPackage("rsync", $packagedir);
	ExecQuietFatal("$ENV PKG_PATH=$packagedir $PKG_ADD $pname");
446
    };
447
448
    if ($WINSUPPORT) {
	Phase "samba", "Looking for Samba and installing", sub {
449
450
451
	    if (!$ISFS) {
		PhaseSkip("Not FS Node");
	    }
452
453
454
	    if (!ExecQuiet("$PKG_INFO -x samba")) {
		PhaseSkip("samba already installed");
	    }
455
456
	    my $pname = GetPackage("samba", $packagedir);
	    ExecQuietFatal("$ENV PKG_PATH=$packagedir $PKG_ADD $pname");
457
458
	};
	Phase "gcc30", "Looking for GCC 3.0 and installing", sub {
459
460
	    if ($FBSD_VERSION > 4 ||
		!ExecQuiet("$PKG_INFO -x gcc30")) {
461
		PhaseSkip("GCC 3.0 already installed");
462
	    }
463
464
	    my $pname = GetPackage("gcc30", $packagedir);
	    ExecQuietFatal("$ENV PKG_PATH=$packagedir $PKG_ADD $pname");
465
466
	};
    }
467
468
};

469
470
Phase "patches", "Applying patches", sub {
    Phase "m2cryptopatch", "Patching m2crypto", sub {
471
472
473
474
475
476
477
478
479
	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");
480
    };
Leigh B. Stoller's avatar
Leigh B. Stoller committed
481
482
483
484
485
486
487
488
489
490
    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");
	};
    }
491
492
}; 

Robert Ricci's avatar
Robert Ricci committed
493
494
495
Phase "rc.conf", "Adding testbed content to rc.conf", sub {
    DoneIfEdited($RCCONF);
    AppendToFileFatal($RCCONF,
Robert Ricci's avatar
Robert Ricci committed
496
		      qq|sendmail_enable="YES"|,
497
498
		      qq|rpcbind_enable="YES"|,
		      qq|mountd_enable="YES"|,
Robert Ricci's avatar
Robert Ricci committed
499
500
		      qq|nfs_server_enable="YES"|,
		      qq|nfs_server_flags="-u -t -n 16"|,
501
		      qq|mountd_flags="-r -p 900"|,
502
		      (($ISFS && $WINSUPPORT) ? qq|smbd_enable="YES"| : ()),
503
		      qq|apache_enable="YES"|,
Robert Ricci's avatar
Robert Ricci committed
504
		      qq|syslogd_flags=""|);
Robert Ricci's avatar
Robert Ricci committed
505
};
506

507
Phase "hosts", "Adding boss/ops/fs IP addresses to $HOSTS", sub {
508
    DoneIfEdited($HOSTS);
509
510
511
512
513
514
    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);
515
516
};

517
518
519
520
521
522
523
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) {
524
525
	Phase $name, $name, sub {
	    if (gethostbyname($name)) {
526
		PhaseSucceed("$name resolves");
527
528
529
530
531
532
533
	    } else {
		PhaseFail("$name does not resolve");
	    }
	};
    }
};

Robert Ricci's avatar
Robert Ricci committed
534
535
536
537
538
539
540
541
542
543
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: $!");
544
545
	    ExecQuietFatal("$CHGRP mailnull $LIST_DIR");
	    ExecQuietFatal("$CHMOD 750 $LIST_DIR");
Robert Ricci's avatar
Robert Ricci committed
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
	};
	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 {
561
	    PhaseSkip("No new aliases") unless @MAILING_LIST_NAMES;
Robert Ricci's avatar
Robert Ricci committed
562
563
564
565
566
567
568
569
570
571
572
573
	    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
574
575
	# 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
576
	#
577
	my @dirs = ('/var', $PREFIX);
578
	if ($ISFS) {
579
	    @dirs = (grep(!/^$SHAREROOT$/, @MOUNTPOINTS), @dirs);
580
	}
Robert Ricci's avatar
Robert Ricci committed
581
582
583
584
585
586
587
588
589
590
591
592
593
594
	@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,
595
596
		join(" ",@{$filesystems{$key}}) .
		    "\t$BOSSNODE -maproot=root";
Robert Ricci's avatar
Robert Ricci committed
597
598
	}

599
600
601
602
603
604
605
	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*)/);
606
	    my $realdir = `realpath $SHAREROOT`;
607
608
609
610
611
612
613
	    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");
	}
614

615
616
617
618
619
	#
	# Add localhost mount to proj/cvsrepos.
	#
	if ($CVSSUPPORT) {
	    my $pdir;
620
	    my $dfout = `df $PROJROOT | fgrep '/dev'`;
621
	    if ($?) {
622
		PhaseFail("'df $PROJROOT' failed!");
623
624
625
626
627
	    }
	    if ($dfout =~ /\s+([\/\w]*)$/) {
		$pdir = $1;
	    }
	    else {
628
		PhaseFail("Could not determine where $PROJROOT is mounted!");
629
630
631
632
	    }
	    push(@exports_lines, "$pdir\tlocalhost -alldirs");
	}

Robert Ricci's avatar
Robert Ricci committed
633
634
635
636
637
638
639
	#
	# Put them in exports.head, and copy that to /etc/exports
	#
	CreateFileFatal($EXPORTS_HEAD, @exports_lines);
	ExecQuietFatal("cp $EXPORTS_HEAD $EXPORTS_FILE");
    };

640
    # XXX Newhup
Robert Ricci's avatar
Robert Ricci committed
641
642
643
644
645
646
    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");
    };
};
647

648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
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);
664
665
666
667
668
669
670
671
672
673
	my @lines = ("$FSNODE:$USERROOT\t\t$USERROOT\tnfs\trw,nodev,nosuid\t0\t0",
		     "$FSNODE:$PROJROOT\t\t$PROJROOT\tnfs\trw,nodev,nosuid\t0\t0",
		     "$FSNODE:$GROUPROOT\t\t$GROUPROOT\tnfs\trw,nodev,nosuid\t0\t0",
		     "$FSNODE:$SHAREROOT\t\t$SHAREROOT\tnfs\trw,nodev,nosuid\t0\t0");
	if ($SCRATCHDIR) {
	    push(@lines,
		 "$FSNODE:$SCRATCHROOT\t\t$SCRATCHROOT\tnfs\trw,nodev,nosuid\t0\t0");
	}

	AppendToFileFatal($FSTAB, @lines);
674
675
676
677
678
679
680
681
682
683
684
    };
    Phase "mounts", "Mounting NFS filesystems", sub {
	foreach my $dir (@MOUNTPOINTS) {
	    Phase $dir, $dir, sub {
		DoneIfMounted($dir);
		ExecQuietFatal("$MOUNT -o '-R 1' $dir");
	    };
	}
    };
};

685
686
687
#
# Set up syslog
#
Robert Ricci's avatar
Robert Ricci committed
688
689
690
691
692
693
694
695
696
697
698
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>;
699
700
701
	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
702
703
	if (scalar(grep(/^cron/, @sc)) != 1) {
	    PhaseFail("Unable to find marker in /etc/syslog.conf!");
704
705
	}

Robert Ricci's avatar
Robert Ricci committed
706
707
708
709
710
	#
	# Clobber and re-write
	#
	seek(SC,0,0);
	truncate(SC,0);
711

Robert Ricci's avatar
Robert Ricci committed
712
	foreach my $line (@sc) {
713
714
715
716
717
718
719
	    #
	    # 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/;
	    }
720
721
722
723
724
725
726
727
728
729
730
731

	    #
	    # 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
732
	    print SC $line;
733
734
735
736

	    #
	    # Find the cron line, after which we place our auth.info line
	    #
Robert Ricci's avatar
Robert Ricci committed
737
738
739
740
741
742
	    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";
	    }
	}
743

Robert Ricci's avatar
Robert Ricci committed
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
	#
	# 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 : $!");
    };
    
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
    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
776
777
778
779
780
    Phase "logfiles", "Creating log files", sub {
	foreach my $logfile (@LOGFILES) {
	    Phase $logfile, $logfile, sub {
		DoneIfExists($logfile);
		CreateFileFatal($logfile);
781
		ExecQuietFatal("$CHGRP tbadmin $logfile");
Robert Ricci's avatar
Robert Ricci committed
782
783
784
785
786
787
788
789
790
791
792
793
794
795
		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");
    };
};

796
797
Phase "cron", "Adding cron jobs", sub {
    Phase "crontab", "Editing $CRONTAB", sub {
798
799
800
	if ($QUOTA_FSLIST eq "") {
	    PhaseSkip("No filesystem quotas");
	}
801
	DoneIfEdited($CRONTAB);
802
803
804
805
806
807
808
809

	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);
810
811
812
813
814
815
816
    };
    Phase "cronhup", "HUPing cron", sub {
	if (PhaseWasSkipped("crontab")) { PhaseSkip("No new crontab"); }
	HUPDaemon("cron");
    };
};

817
Phase "sudoers", "Editing $SUDOERS to allow wheel group", sub {
Robert Ricci's avatar
Robert Ricci committed
818
819
820
    DoneIfEdited($SUDOERS);
    AppendToFileFatal($SUDOERS,"%wheel    ALL=(ALL) NOPASSWD: ALL");
};
821

822
823
824
825
826
827
828
829
830
831
832
833
834
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");
835
    };
836
837
838
839
840
841
842
843
844
845
846
847
    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");
	};
    }
};
848

Robert Ricci's avatar
Robert Ricci committed
849
Phase "ssh", "Allowing root ssh", sub {
Robert Ricci's avatar
Robert Ricci committed
850
851
852
853
854
855
856
857
858
    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: $!");
    };
859
860
861
862
863
864
865
866
867
868
869
870
    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
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
};

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");
    };
896
897
};

898
899
Phase "event", "Setting up event system", sub {
    Phase "elvinconf", "Installing elvind config file", sub {
900
	DoneIfIdentical($ELVIND_CONF,$OPS_ELVIND_CONF);
901
902
903
904
	ExecQuietFatal("$CP $OPS_ELVIND_CONF $ELVIND_CONF");
    };
};

905
Phase "rc.d", "Setting up rc.d scripts", sub {
906
907
908
909
910
911
    Phase "rsyncd", "Removing rsyncd startup script",  sub {
	DoneIfDoesntExist("$RCDIR/rsyncd.sh");
	if (!unlink "$RCDIR/rsyncd.sh") {
	    PhaseFail("Unable to remove $RCDIR/rsyncd.sh: $!");
	}
    };
912
913
914
915
916
917
    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: $!");
	}
    };
918
919
920
921
922
923
    Phase "my-server2", "Removing $RCDIR/mysql-server", sub {
	DoneIfDoesntExist("$RCDIR/mysql-server");
	if (!unlink "$RCDIR/mysql-server") {
	    PhaseFail("Unable to remove $RCDIR/mysql-server: $!");
	}
    };
924
    Phase "rc.testbed", "Installing testbed RC scripts", sub {
925
926
927
928
        Phase "elvind.sh", "Removing port version of elvind.sh", sub {
	    DoneIfDoesntExist("$RCDIR/elvind.sh");
            ExecQuietFatal("/bin/rm -f $RCDIR/elvind.sh");
        };
929
930
931
932
933
	DoneIfExists("$RCDIR/2.elvind.sh");
	ExecQuietFatal("$GMAKE -C $TOP_OBJDIR/rc.d control-install");
    };
};

934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
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");
    };

986
987
988
989
990
991
992
993
    Phase "dbpatch", "Patching up mysql DB", sub {
	if (!ExecQuiet("$MYSQLDUMP -u root mysql emulab_dbs")) {
	    PhaseSkip("DB already patched");
	}

	ExecQuietFatal("$MYSQL -u root mysql < $TOP_SRCDIR/sql/opsdb.sql");
    };

994
995
996
997
998
999
1000
    # 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) {