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

#
# EMULAB-COPYRIGHT
5
# Copyright (c) 2003, 2004 University of Utah and the Flux Group.
6
7
8
9
10
11
12
13
14
15
# 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
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

my $OURDOMAIN = '@OURDOMAIN@';

my $USERNODE = '@USERNODE@';
Robert Ricci's avatar
Robert Ricci committed
32
my $FSNODE =   '@FSNODE@';
33
34
my $BOSSNODE = '@BOSSNODE@';

35
36
my $LOGFACIL = '@TBLOGFACIL@';

37
#
Robert Ricci's avatar
Robert Ricci committed
38
# Allow this to work if the library is left in the source directory
39
#
Robert Ricci's avatar
Robert Ricci committed
40
41
42
43
use lib '@srcdir@';
   
use English;
use libinstall;
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
use Getopt::Std;

#
# Handle command-line options
#
sub usage {
    print "Usage: ops-install [-p packagedir]\n";
    exit(1);
}

my $packagedir = "";
my %opts;
getopt("p:",%opts);

if ($opt{p}) {
    $packagedir = $opt{p};
}

if (@ARGV) {
    usage();
}
65

66
67
68
69
70
71
72
73
#
# 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/..";

74
#
Robert Ricci's avatar
Robert Ricci committed
75
# Some programs we use
76
#
Robert Ricci's avatar
Robert Ricci committed
77
78
79
80
my $CHGRP      = "/usr/bin/chgrp";
my $CHMOD      = "/bin/chmod";
my $PW         = "/usr/sbin/pw";
my $NEWALIASES = "/usr/bin/newaliases";
81
82
my $SH         = "/bin/sh";
my $PKG_INFO   = "/usr/sbin/pkg_info";
83
my $PKG_ADD    = "/usr/sbin/pkg_add";
84
my $PWD        = "/bin/pwd";
85
my $CP         = "/bin/cp";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
86
my $GMAKE      = "/usr/local/bin/gmake";
87
my $ENV        = "/usr/bin/env";
88
89

#
Robert Ricci's avatar
Robert Ricci committed
90
# Some files we edit/create
91
#
Robert Ricci's avatar
Robert Ricci committed
92
my $RCCONF          = "/etc/rc.conf";
Robert Ricci's avatar
Robert Ricci committed
93
94
my $RCLOCAL         = "/etc/rc.local";
my $RCCAPTURE       = "$PREFIX/etc/rc.capture";
Robert Ricci's avatar
Robert Ricci committed
95
96
97
98
99
100
101
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";
102
my $SSHD_CONFIG     = "/etc/ssh/sshd_config";
103
my $CRONTAB         = "/etc/crontab";
104
105

#
Robert Ricci's avatar
Robert Ricci committed
106
# Some directories we care about
107
#
Robert Ricci's avatar
Robert Ricci committed
108
109
my $LIST_DIR   = "/etc/mail/lists";
my $TIPLOG_DIR = "/var/log/tiplogs";
110
my $PORTSDIR   = "/usr/ports/misc";
111
my $SRCDIR     = '@srcdir@';
112
my $RCDIR      = "/usr/local/etc/rc.d";
113
114

#
Robert Ricci's avatar
Robert Ricci committed
115
# And some lists that we use
116
#
117
118
my @LOCAL_HOSTS        = ($OURDOMAIN,$BOSSNODE,$USERNODE,$FSNODE);
my @LOGFILES           = ("/var/log/logins","/var/log/tiplogs/capture.log",
Robert Ricci's avatar
Robert Ricci committed
119
    "/var/log/mountd.log");
120
121
my @LOCAL_MAILING_LISTS = grep(/$OURDOMAIN$/,@MAILING_LISTS);
my @MAILING_LIST_NAMES  = map { /^([\w-]+)\@/ } @LOCAL_MAILING_LISTS;
Robert Ricci's avatar
Robert Ricci committed
122
123
124

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

126
127
128
129
130
131
#
# 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";

132
133
134
135
136
137
#
# The meta-port (name and version) that drags in all the dependancies for
# an ops node
#
my $OPS_PORT = "emulab-ops-1.4";

138
#
Robert Ricci's avatar
Robert Ricci committed
139
# Make sure they know what they're getting into...
140
#
Robert Ricci's avatar
Robert Ricci committed
141
142
143
144
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);
145

Robert Ricci's avatar
Robert Ricci committed
146
147
if ($UID != 0) {
    die "This script must be run as root.\n";
148
149
150
}

#
Robert Ricci's avatar
Robert Ricci committed
151
# The phases are fairly self-explanatory
152
153
#

Robert Ricci's avatar
Robert Ricci committed
154
155
156
Phase "groups", "Creating admin group", sub {
    if (getgrnam("tbadmin")) {
	PhaseSkip("tbadmin group already exists");
157
    }
Robert Ricci's avatar
Robert Ricci committed
158
159
160
161
    ExecQuietFatal("$PW groupadd tbadmin -g 101");
};

Phase "dirs", "Setting directory permissions", sub {
Robert Ricci's avatar
Robert Ricci committed
162
163
    foreach my $dirref (@TESTBED_DIRS) {
	my ($dir, $newmode) = @$dirref;
Robert Ricci's avatar
Robert Ricci committed
164
165
166
167
	Phase $dir, $dir, sub {
	    if (!-d $dir) {
		PhaseFail("Directory $dir does not exist");
	    }
Robert Ricci's avatar
Robert Ricci committed
168
169
170
171
	    # 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
172
173
	    # Fix up the mode (strip file type)
	    $mode = $mode & 0777;
Robert Ricci's avatar
Robert Ricci committed
174
	    if ($mode == eval $newmode && $group eq getgrnam("tbadmin")) {
Robert Ricci's avatar
Robert Ricci committed
175
176
		PhaseSkip("Already done");
	    }
Robert Ricci's avatar
Robert Ricci committed
177
178
	    ExecQuietFatal("$CHGRP tbadmin $realdir");
	    ExecQuietFatal("$CHMOD $newmode $realdir");
Robert Ricci's avatar
Robert Ricci committed
179
	};
180
    }
Robert Ricci's avatar
Robert Ricci committed
181
};
182

183
Phase "ports", "Installing ports", sub {
184
185
186
187
188
189
190
191
192
    Phase "packages", "Installing packages", sub {
	if (!ExecQuiet("$PKG_INFO -e $OPS_PORT")) {
	    PhaseSkip("Ports already installed");
	}
	if (!$packagedir) {
	    PhaseSkip("No package directory provided");
	}
	ExecQuietFatal("$ENV PKG_PATH=$packagedir $PKG_ADD $OPS_PORT");
    };
193
    Phase "pcopy", "Copying ports into place", sub {
194
195
196
	if ($packagedir) {
	    PhaseSkip("Package directory provided");
	}
197
198
199
200
	DoneIfExists("$PORTSDIR/emulab-ops");
	ExecQuietFatal("$SH $SRCDIR/ports/ports-install");
    };
    Phase "pinstall", "Installing ports (may take a while)", sub {
201
	if (!ExecQuiet("$PKG_INFO -e $OPS_PORT")) {
202
203
	    PhaseSkip("Ports already installed");
	}
204
205
206
	if ($packagedir) {
	    PhaseSkip("Package directory provided");
	}
207
208
209
210
211
212
213
214

	#
	# 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: $!";
215
	ExecQuietFatal("make -DBATCH install");
216
217
218
219
	chdir $pwd;
    };
};

Robert Ricci's avatar
Robert Ricci committed
220
221
222
Phase "rc.conf", "Adding testbed content to rc.conf", sub {
    DoneIfEdited($RCCONF);
    AppendToFileFatal($RCCONF,
Robert Ricci's avatar
Robert Ricci committed
223
224
225
226
		      qq|sendmail_enable="YES"|,
		      qq|nfs_server_enable="YES"|,
		      qq|nfs_server_flags="-u -t -n 16"|,
		      qq|syslogd_flags=""|);
Robert Ricci's avatar
Robert Ricci committed
227
};
228

Robert Ricci's avatar
Robert Ricci committed
229
230
231
232
233
234
235
236
237
238
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: $!");
239
240
	    ExecQuietFatal("$CHGRP mailnull $LIST_DIR");
	    ExecQuietFatal("$CHMOD 750 $LIST_DIR");
Robert Ricci's avatar
Robert Ricci committed
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
	};
	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 {
256
	    PhaseSkip("No new aliases") unless @MAILING_LIST_NAMES;
Robert Ricci's avatar
Robert Ricci committed
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
	    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
	#
272
	my @dirs = ('/users','/groups','/proj','/share','/var','/usr/testbed');
Robert Ricci's avatar
Robert Ricci committed
273
274
275
276
277
278
279
280
281
282
283
284
285
286
	@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,
Robert Ricci's avatar
Robert Ricci committed
287
		join(" ",@{$filesystems{$key}}) . "\t$BOSSNODE -maproot=root";
Robert Ricci's avatar
Robert Ricci committed
288
289
290
291
292
293
294
295
296
	}

	#
	# Put them in exports.head, and copy that to /etc/exports
	#
	CreateFileFatal($EXPORTS_HEAD, @exports_lines);
	ExecQuietFatal("cp $EXPORTS_HEAD $EXPORTS_FILE");
    };

297
    # XXX Newhup
Robert Ricci's avatar
Robert Ricci committed
298
299
300
301
302
303
    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");
    };
};
304
305
306
307

#
# Set up syslog
#
Robert Ricci's avatar
Robert Ricci committed
308
309
310
311
312
313
314
315
316
317
318
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>;
319
320
321
	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
322
323
	if (scalar(grep(/^cron/, @sc)) != 1) {
	    PhaseFail("Unable to find marker in /etc/syslog.conf!");
324
325
	}

Robert Ricci's avatar
Robert Ricci committed
326
327
328
329
330
	#
	# Clobber and re-write
	#
	seek(SC,0,0);
	truncate(SC,0);
331

Robert Ricci's avatar
Robert Ricci committed
332
	foreach my $line (@sc) {
333
334
335
336
337
338
339
	    #
	    # 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/;
	    }
Robert Ricci's avatar
Robert Ricci committed
340
	    print SC $line;
341
342
343
344

	    #
	    # Find the cron line, after which we place our auth.info line
	    #
Robert Ricci's avatar
Robert Ricci committed
345
346
347
348
349
350
	    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";
	    }
	}
351

Robert Ricci's avatar
Robert Ricci committed
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
	#
	# 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);
374
		ExecQuietFatal("$CHGRP tbadmin $logfile");
Robert Ricci's avatar
Robert Ricci committed
375
376
377
378
379
380
381
382
383
384
385
386
387
388
		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");
    };
};

389
390
391
392
Phase "cron", "Adding cron jobs", sub {
    Phase "crontab", "Editing $CRONTAB", sub {
	DoneIfEdited($CRONTAB);
	AppendToFileFatal($CRONTAB,
393
	    "0 \t6\t*\t*\t*\troot\t$PREFIX/sbin/quotamail");
394
395
396
397
398
399
400
    };
    Phase "cronhup", "HUPing cron", sub {
	if (PhaseWasSkipped("crontab")) { PhaseSkip("No new crontab"); }
	HUPDaemon("cron");
    };
};

Robert Ricci's avatar
Robert Ricci committed
401
402
403
404
Phase "sudoers", "Editing $SUDOERS", sub {
    DoneIfEdited($SUDOERS);
    AppendToFileFatal($SUDOERS,"%wheel    ALL=(ALL) NOPASSWD: ALL");
};
405

Robert Ricci's avatar
Robert Ricci committed
406
Phase "ssh", "Allowing root ssh", sub {
Robert Ricci's avatar
Robert Ricci committed
407
408
409
410
411
412
413
414
415
416
    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: $!");
    };

Robert Ricci's avatar
Robert Ricci committed
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
};

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");
    };
442
443
};

444
445
Phase "event", "Setting up event system", sub {
    Phase "elvinconf", "Installing elvind config file", sub {
446
	DoneIfIdentical($ELVIND_CONF,$OPS_ELVIND_CONF);
447
448
449
450
	ExecQuietFatal("$CP $OPS_ELVIND_CONF $ELVIND_CONF");
    };
};

451
452
Phase "rc.d", "Setting up rc.d scripts", sub {
    Phase "rc.testbed", "Installing testbed RC scripts", sub {
453
454
455
456
        Phase "elvind.sh", "Removing port version of elvind.sh", sub {
	    DoneIfDoesntExist("$RCDIR/elvind.sh");
            ExecQuietFatal("/bin/rm -f $RCDIR/elvind.sh");
        };
457
458
459
460
461
	DoneIfExists("$RCDIR/2.elvind.sh");
	ExecQuietFatal("$GMAKE -C $TOP_OBJDIR/rc.d control-install");
    };
};

462
463
464
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
465
if (!PhaseWasSkipped("maillists")) {
466
    print "Local mailing lists have been created, with no members, in\n";
Robert Ricci's avatar
Robert Ricci committed
467
    print "$LIST_DIR . Please add members to the following lists:\n";
468
    print map "$_\n", @LOCAL_MAILING_LISTS;
469
}