dfbbed374bac7d5b6d7307dc8d59204020fc75de
[myplc.git] / guest.init
1 #!/bin/bash
2 #
3 # plc   Manages all PLC services on this machine
4 #
5 # chkconfig: 2345 5 99
6 #
7 # description:  Manages all PLC services on this machine
8 #
9 # $Id: guest.init,v 1.6 2006/03/29 02:56:15 mlhuang Exp $
10 #
11
12 PATH=/sbin:/bin:/usr/bin:/usr/sbin
13
14 # Source function library.
15 . /etc/init.d/functions
16
17 # Verbosity
18 verbose=0
19
20 # Keep in order! All steps should be idempotent. This means that you
21 # should be able to run them multiple times without depending on
22 # anything previously being run. The idea is that when the
23 # configuration changes, "service plc restart" is called, all
24 # dependencies are fixed up, and everything just works.
25 steps=(
26 network
27 syslog
28 postgresql
29 ssl
30 gpg
31 ssh
32 apache
33 api
34 cron
35 )
36 nsteps=${#steps[@]}
37
38 # Total number of errors
39 ERRORS=0
40
41 # Count the exit status of the last command
42 check ()
43 {
44     ERRORS=$(($ERRORS+$?))
45 }
46
47 # Return IP address of hostname if resolvable
48 gethostbyname ()
49 {
50     perl -MSocket -e '($a,$b,$c,$d,@addrs) = gethostbyname($ARGV[0]); print inet_ntoa($addrs[0]) . "\n";' $1 2>/dev/null
51 }
52
53 # Regenerate configuration files
54 reload ()
55 {
56     # Load configuration
57     plc-config --shell >/etc/planetlab/plc_config
58     . /etc/planetlab/plc_config
59
60     # Generate various defaults
61     if [ -z "$PLC_DB_PASSWORD" ] ; then
62         PLC_DB_PASSWORD=$(uuidgen)
63         plc-config --category=plc_db --variable=password --value="$PLC_DB_PASSWORD" --save
64     fi
65
66     if [ -z "$PLC_API_MAINTENANCE_PASSWORD" ] ; then
67         PLC_API_MAINTENANCE_PASSWORD=$(uuidgen)
68         plc-config --category=plc_api --variable=maintenance_password --value="$PLC_API_MAINTENANCE_PASSWORD" --save
69     fi
70
71     # Need to configure network before resolving hostnames
72     config_network start
73
74     PLC_API_MAINTENANCE_SOURCES=$(
75         for server in API BOOT WWW ; do
76             eval hostname=\${PLC_${server}_HOST}
77             gethostbyname $hostname
78         done | sort -u
79     )
80     plc-config --category=plc_api --variable=maintenance_sources --value="$PLC_API_MAINTENANCE_SOURCES" --save
81
82     # Save configuration
83     mkdir -p /etc/planetlab/php
84     plc-config --php >/etc/planetlab/php/plc_config.php
85     plc-config --shell >/etc/planetlab/plc_config
86
87     # For backward compatibility, until we can convert all code to use
88     # the now standardized variable names.
89
90     # DB constants are all named the same
91     ln -sf plc_config /etc/planetlab/plc_db
92
93     # API constants
94     cat >/etc/planetlab/plc_api <<EOF
95 PL_API_SERVER='$PLC_API_HOST'
96 PL_API_PATH='$PLC_API_PATH'
97 PL_API_PORT=$PLC_API_PORT
98 PL_API_CAPABILITY_AUTH_METHOD='capability'
99 PL_API_CAPABILITY_PASS='$PLC_API_MAINTENANCE_PASSWORD'
100 PL_API_CAPABILITY_USERNAME='$PLC_API_MAINTENANCE_USER'
101 PL_API_TICKET_KEY_FILE='$PLC_API_SSL_KEY'
102 PLANETLAB_SUPPORT_EMAIL='$PLC_MAIL_SUPPORT_ADDRESS'
103 BOOT_MESSAGES_EMAIL='$PLC_MAIL_BOOT_ADDRESS'
104 WWW_BASE='$PLC_WWW_HOST'
105 BOOT_BASE='$PLC_BOOT_HOST'
106 EOF
107
108     # The format is
109     #
110     # ip:max_role_id:organization_id:password
111     #
112     # It is unlikely that we will let federated sites use the
113     # maintenance account to access each others' APIs, so we always
114     # set organization_id to -1.
115     (
116         echo -n "PL_API_CAPABILITY_SOURCES='"
117         first=1
118         for ip in $PLC_API_MAINTENANCE_SOURCES ; do
119             if [ $first -ne 1 ] ; then
120                 echo -n " "
121             fi
122             first=0
123             echo -n "$ip:-1:-1:$PLC_API_MAINTENANCE_PASSWORD"
124         done
125         echo "'"
126     ) >>/etc/planetlab/plc_api
127
128     cat >/etc/planetlab/php/site_constants.php <<"EOF"
129 <?php
130 include('plc_config.php');
131
132 DEFINE('PL_API_SERVER', PLC_API_HOST);
133 DEFINE('PL_API_PATH', PLC_API_PATH);
134 DEFINE('PL_API_PORT', PLC_API_PORT);
135 DEFINE('PL_API_CAPABILITY_AUTH_METHOD', 'capability');
136 DEFINE('PL_API_CAPABILITY_PASS', PLC_API_MAINTENANCE_PASSWORD);
137 DEFINE('PL_API_CAPABILITY_USERNAME', PLC_API_MAINTENANCE_USER);
138 DEFINE('WWW_BASE', PLC_WWW_HOST);
139 DEFINE('BOOT_BASE', PLC_BOOT_HOST);
140 DEFINE('DEBUG', PLC_WWW_DEBUG);
141 DEFINE('API_CALL_DEBUG', PLC_API_DEBUG);
142 DEFINE('SENDMAIL', PLC_MAIL_ENABLED);
143 DEFINE('PLANETLAB_SUPPORT_EMAIL', PLC_NAME . 'Support <' . PLC_MAIL_SUPPORT_ADDRESS . '>');
144 DEFINE('PLANETLAB_SUPPORT_EMAIL_ONLY', PLC_MAIL_SUPPORT_ADDRESS);
145 ?>
146 EOF
147 }
148
149 config_network ()
150 {
151     case "$1" in
152         start)
153             # Minimal /etc/hosts
154             echo "127.0.0.1     localhost.localdomain localhost" >/etc/hosts
155             (
156                 for server in API BOOT WWW ; do
157                     eval hostname=\${PLC_${server}_HOST}
158                     ip=$(gethostbyname $hostname)
159                     if [ -n "$ip" ] ; then
160                         echo "$ip       $hostname"
161                     fi
162                 done
163             ) >>/etc/hosts
164
165             # Set up nameservers
166             (
167                 [ -n "$PLC_NET_DNS1" ] && echo "nameserver $PLC_NET_DNS1"
168                 [ -n "$PLC_NET_DNS2" ] && echo "nameserver $PLC_NET_DNS2"
169             ) >/etc/resolv.conf
170             ;;
171     esac
172 }
173
174 config_syslog ()
175 {
176     service syslog $1
177     check
178 }
179
180 config_postgresql ()
181 {
182     # Default locations
183     PGDATA=/var/lib/pgsql/data
184     postgresql_conf=$PGDATA/postgresql.conf
185     pghba_conf=$PGDATA/pg_hba.conf
186
187     case "$1" in
188         start)
189             if [ "$PLC_DB_ENABLED" != "1" ] ; then
190                 return 0
191             fi
192
193             # Set data directory and redirect startup output to /var/log/pgsql
194             mkdir -p /etc/sysconfig/pgsql
195             (
196                 echo "PGDATA=$PGDATA"
197                 echo "PGLOG=/var/log/pgsql"
198             ) >>/etc/sysconfig/pgsql/postgresql
199
200             # Fix ownership (rpm installation may have changed it)
201             chown -R -H postgres:postgres $(dirname $PGDATA)
202
203             # PostgreSQL must be started at least once to bootstrap
204             # /var/lib/pgsql/data
205             if [ ! -f $postgresql_conf ] ; then
206                 service postgresql start
207                 service postgresql stop
208             fi
209
210             # Enable DB server. PostgreSQL >=8.0 defines listen_addresses,
211             # PostgreSQL 7.x uses tcpip_socket.
212             if grep -q listen_addresses $postgresql_conf ; then
213                 sed -i -e '/^listen_addresses/d' $postgresql_conf
214                 echo "listen_addresses = '*'" >>$postgresql_conf
215             elif grep -q tcpip_socket $postgresql_conf ; then
216                 sed -i -e '/^tcpip_socket/d' $postgresql_conf
217                 echo "tcpip_socket = true" >>$postgresql_conf
218             fi
219
220             # Disable access to all DBs from all hosts
221             sed -i -e '/^\(host\|local\)/d' $pghba_conf
222
223             # Enable passwordless localhost access
224             echo "local all all trust" >>$pghba_conf
225
226             # Enable access from the API and web servers
227             PLC_API_IP=$(gethostbyname $PLC_API_HOST)
228             PLC_WWW_IP=$(gethostbyname $PLC_WWW_HOST)
229             (
230                 echo "host $PLC_DB_NAME $PLC_DB_USER $PLC_API_IP/32 password"
231                 echo "host $PLC_DB_NAME $PLC_DB_USER $PLC_WWW_IP/32 password"
232             ) >>$pghba_conf
233
234             # Fix ownership (sed -i changes it)
235             chown postgres:postgres $postgresql_conf $pghba_conf
236
237             # Start up the server
238             service postgresql start
239             # /etc/init.d/postgresql always returns 0, even on failure
240             status postmaster
241             check
242
243             # Create/update the unprivileged database user and password
244             if ! psql -U $PLC_DB_USER -c "" template1 >/dev/null 2>&1 ; then
245                 psql -U postgres -c "CREATE USER $PLC_DB_USER PASSWORD '$PLC_DB_PASSWORD'" template1
246             else
247                 psql -U postgres -c "ALTER USER $PLC_DB_USER WITH PASSWORD '$PLC_DB_PASSWORD'" template1
248             fi
249
250             # Create the database if necessary
251             if ! psql -U $PLC_DB_USER -c "" $PLC_DB_NAME >/dev/null 2>&1 ; then
252                 createdb -U postgres $PLC_DB_NAME
253                 psql -U $PLC_DB_USER -f /usr/share/pl_db/plc_schema_3.sql $PLC_DB_NAME
254             fi
255             ;;
256
257         stop)
258             # Drop the current user in case the username changes
259             psql -U postgres -c "DROP USER $PLC_DB_USER" template1
260
261             # WARNING: If the DB name changes, the old DB will be left
262             # intact and a new one will be created. If it changes
263             # back, the old DB will not be re-created.
264
265             # Shut down the server
266             service postgresql stop
267             check
268             ;;
269     esac
270 }
271
272 # Generate GPG keys
273 config_gpg ()
274 {
275     case "$1" in
276         start)
277             # Generate GPG keyrings
278             if [ ! -f $PLC_ROOT_GPG_KEY_PUB -o ! -f $PLC_ROOT_GPG_KEY ] ; then
279                 mkdir -p $(dirname $PLC_ROOT_GPG_KEY_PUB)
280                 mkdir -p $(dirname $PLC_ROOT_GPG_KEY)
281
282                 # Temporarily replace /dev/random with /dev/urandom to
283                 # avoid running out of entropy.
284                 rm -f /dev/random
285                 mknod /dev/random c 1 9
286                 gpg --homedir=/root --batch --gen-key <<EOF
287 Key-Type: DSA
288 Key-Length: 1024
289 Subkey-Type: ELG-E
290 Subkey-Length: 1024
291 Name-Real: $PLC_NAME Central
292 Name-Comment: http://$PLC_WWW_HOST/
293 Name-Email: $PLC_MAIL_SUPPORT_ADDRESS
294 Expire-Date: 0
295 %pubring $PLC_ROOT_GPG_KEY_PUB
296 %secring $PLC_ROOT_GPG_KEY
297 %commit
298 EOF
299                 check
300                 rm -f /dev/random
301                 mknod /dev/random c 1 8
302                 chmod 600 $PLC_ROOT_GPG_KEY_PUB $PLC_ROOT_GPG_KEY
303             fi
304             ;;
305     esac
306 }
307
308 symlink ()
309 {
310     mkdir -p $(dirname $2)
311     rm -f $2
312     ln -s $1 $2
313 }
314
315 # Generate SSL certificates
316 config_ssl ()
317 {
318     case "$1" in
319         start)
320             # Generate a self-signed SSL certificate. These nice
321             # commands came from the mod_ssl spec file for Fedora Core
322             # 2. We generate only a single certificate for the web
323             # server, then make copies for the API and boot
324             # servers. As always, these certificates may be overridden
325             # later.
326
327             # Generate SSL private key
328             if [ ! -f $PLC_WWW_SSL_KEY ] ; then
329                 mkdir -p $(dirname $PLC_WWW_SSL_KEY)
330                 openssl genrsa -rand /proc/apm:/proc/cpuinfo:/proc/dma:/proc/filesystems:/proc/interrupts:/proc/ioports:/proc/pci:/proc/rtc:/proc/uptime 1024 >$PLC_WWW_SSL_KEY
331                 check
332                 chmod 600 $PLC_WWW_SSL_KEY
333             fi
334
335             # Generate self-signed certificate
336             if [ ! -f $PLC_WWW_SSL_CRT ] ; then
337                 mkdir -p $(dirname $PLC_WWW_SSL_CRT)
338                 openssl req -new -x509 -days 365 -set_serial $RANDOM \
339                     -key $PLC_WWW_SSL_KEY -out $PLC_WWW_SSL_CRT <<EOF
340 --
341 State
342 City
343 Organization
344 $PLC_NAME Central
345 $PLC_WWW_HOST
346 $PLC_MAIL_SUPPORT_ADDRESS
347 EOF
348                 check
349                 chmod 644 $PLC_WWW_SSL_CRT
350             fi
351
352             # Make copies for the API and boot servers
353             if [ ! -f $PLC_API_SSL_KEY ] ; then
354                 cp -a $PLC_WWW_SSL_KEY $PLC_API_SSL_KEY
355             fi
356             if [ ! -f $PLC_API_SSL_KEY_PUB ] ; then
357                 openssl rsa -pubout <$PLC_API_SSL_KEY >$PLC_API_SSL_KEY_PUB
358                 check
359             fi
360             if [ ! -f $PLC_API_SSL_CRT ] ; then
361                 cp -a $PLC_WWW_SSL_CRT $PLC_API_SSL_CRT
362             fi
363             if [ ! -f $PLC_API_SSL_KEY ] ; then
364                 cp -a $PLC_WWW_SSL_KEY $PLC_API_SSL_KEY
365             fi
366             if [ ! -f $PLC_API_SSL_CRT ] ; then
367                 cp -a $PLC_WWW_SSL_CRT $PLC_API_SSL_CRT
368             fi
369
370             # Install into both /etc/pki (Fedora Core 4) and
371             # /etc/httpd/conf (Fedora Core 2). If the API, boot, and
372             # web servers are all running on the same machine, the web
373             # server certificate takes precedence.
374             for server in API BOOT WWW ; do
375                 eval enabled=\${PLC_${server}_ENABLED}
376                 if [ "$enabled" != "1" ] ; then
377                     continue
378                 fi
379                 eval ssl_crt=\${PLC_${server}_SSL_CRT}
380                 eval ssl_key=\${PLC_${server}_SSL_KEY}
381
382                 symlink $ssl_crt /etc/pki/tls/certs/localhost.crt
383                 symlink $ssl_key /etc/pki/tls/private/localhost.key
384                 symlink $ssl_crt /etc/httpd/conf/ssl.crt/server.crt
385                 symlink $ssl_key /etc/httpd/conf/ssl.key/server.key
386             done
387             ;;
388     esac
389 }
390
391 # Generate SSH keys
392 config_ssh ()
393 {
394     # XXX Could make these configurable
395     KEY_TYPE_ROOT=rsa
396     KEY_LEN_ROOT=1024
397     KEY_TYPE_DEBUG=rsa
398     KEY_LEN_DEBUG=2048  
399
400     case "$1" in
401         start)
402             tmp=$(mktemp -d /tmp/ssh.XXXXXX)
403
404             # Generate root SSH key
405             if [ ! -f $PLC_ROOT_SSH_KEY_PUB -o ! -f $PLC_ROOT_SSH_KEY ] ; then
406                 ssh-keygen -N "" -C "$PLC_NAME Central <$PLC_MAIL_SUPPORT_ADDRESS>" \
407                     -b $KEY_LEN_ROOT -t $KEY_TYPE_ROOT -f $tmp/root
408                 check
409                 install -D -m 600 $tmp/root $PLC_ROOT_SSH_KEY
410                 install -D -m 600 $tmp/root.pub $PLC_ROOT_SSH_KEY_PUB
411             fi
412
413             # Generate debug SSH key
414             if [ ! -f $PLC_DEBUG_SSH_KEY_PUB -o ! -f $PLC_DEBUG_SSH_KEY ] ; then
415                 ssh-keygen -N "" -C "$PLC_NAME Central <$PLC_MAIL_SUPPORT_ADDRESS>" \
416                     -b $KEY_LEN_DEBUG -t $KEY_TYPE_DEBUG -f $tmp/debug
417                 check
418                 install -D -m 600 $tmp/debug $PLC_DEBUG_SSH_KEY
419                 install -D -m 600 $tmp/debug.pub $PLC_DEBUG_SSH_KEY_PUB
420             fi
421
422             rm -rf $tmp
423             ;;
424     esac
425 }
426
427 # Configure Apache web server
428 config_apache ()
429 {
430     # Default locations
431     DocumentRoot=/var/www/html
432     php_ini=/etc/php.ini
433     httpd_conf=/etc/httpd/conf/httpd.conf
434     ssl_conf=/etc/httpd/conf.d/ssl.conf
435     plc_conf=/etc/httpd/conf.d/plc.conf
436
437     case "$1" in
438         start)
439             if [ "$PLC_API_ENABLED" != "1" -a \
440                  "$PLC_BOOT_ENABLED" != "1" -a \
441                  "$PLC_WWW_ENABLED" != "1" ] ; then
442                 return 0
443             fi
444
445             # Set the default include path
446             include_path=".:$DocumentRoot/includes:$DocumentRoot/generated:/etc/planetlab/php"
447             sed -i -e "s@;include_path = \"\.:.*\"@include_path = \"$include_path\"@" $php_ini
448
449             # Set the port numbers. If the API, boot, and web servers
450             # are all running on the same machine, the web server port
451             # numbers take precedence.
452             for server in API BOOT WWW ; do
453                 eval enabled=\${PLC_${server}_ENABLED}
454                 if [ "$enabled" != "1" ] ; then
455                     continue
456                 fi
457                 eval http_port=\${PLC_${server}_PORT}
458                 eval https_port=\${PLC_${server}_SSL_PORT}
459
460                 if [ -n "$http_port" ] ; then
461                     sed -i -e "s/^Listen .*/Listen $http_port/" $httpd_conf
462                 fi
463                 if [ -n "$https_port" ] ; then
464                     sed -i \
465                         -e "s/^Listen .*/Listen $https_port/" \
466                         -e "s/<VirtualHost _default_:.*>/<VirtualHost _default_:$https_port>/" \
467                         $ssl_conf
468                 fi
469             done
470                     
471             # Set custom Apache directives
472             (
473                 if [ "$PLC_API_ENABLED" = "1" ] ; then
474                     # XXX We should only support non-SSL access to the
475                     # API by writing this to conf.d/plc_ssl.conf, then
476                     # writing "Include conf.d/plc_ssl.conf" to
477                     # conf.d/ssl.conf. Once oldapi, which does not
478                     # support SSL, is removed from the web pages, we
479                     # can do this.
480                     cat <<EOF
481 <Location $PLC_API_PATH>
482     SetHandler python-program
483     PythonPath "sys.path + ['/usr/share/plc_api']"
484     PythonHandler mod_pythonXMLRPC
485 </Location>
486 EOF
487                 fi
488
489                 if [ "$PLC_WWW_ENABLED" = "1" ] ; then
490                     cat <<EOF
491 <VirtualHost *:$PLC_WWW_PORT>
492     Redirect /db https://$PLC_WWW_HOST:$PLC_WWW_SSL_PORT/db
493 </VirtualHost>
494 EOF
495                 else
496                     cat <<EOF
497 <Location /db>
498     Deny from all
499 </Location>
500 EOF
501                 fi
502             ) >$plc_conf
503
504             # Make alpina-logs directory writable for bootmanager log upload
505             chown apache:apache $DocumentRoot/alpina-logs/nodes
506
507             service httpd start
508             check
509             ;;
510
511         stop)
512             service httpd stop
513             check
514             ;;
515     esac
516 }
517
518 config_api ()
519 {
520     case "$1" in
521         start)
522             if [ "$PLC_API_ENABLED" != "1" ] ; then
523                 return
524             fi
525
526             # Update the maintenance account username. This can't be
527             # done through the api-config script since it uses the
528             # maintenance account to access the API. The maintenance
529             # account should be person_id 1 since it is created by the
530             # DB schema itself.
531             psql -U $PLC_DB_USER -c "UPDATE persons SET email='$PLC_API_MAINTENANCE_USER' WHERE person_id=1" $PLC_DB_NAME
532
533             # Bootstrap the DB
534             api-config
535             check
536             ;;
537     esac
538 }
539
540 config_cron ()
541 {
542     case "$1" in
543         start)
544             if [ "$PLC_MAIL_ENABLED" = "1" ] ; then
545                 MAILTO=$PLC_MAIL_SUPPORT_ADDRESS
546             else
547                 MAILTO=
548             fi
549             cat >/etc/cron.d/plc.cron <<EOF
550 SHELL=/bin/bash
551 PATH=/sbin:/bin:/usr/sbin:/usr/bin
552 MAILTO=$MAILTO
553 HOME=/
554
555 # minute hour day-of-month month day-of-week user command
556 */5 * * * * root gen-slices-xml-05.py
557 */15 * * * * root gen-sites-xml.py
558 */15 * * * * root gen-static-content.py
559 EOF
560
561             # Run them once at startup
562             gen-slices-xml-05.py
563             check
564             gen-sites-xml.py
565             check
566             gen-static-content.py
567             check
568
569             service crond start
570             check
571             ;;
572
573         stop)
574             service crond stop
575             check
576             ;;
577     esac
578 }
579
580 usage()
581 {
582     echo "Usage: $0 [OPTION]... [COMMAND]"
583     echo "      -v              Be verbose"
584     echo "      -h              This message"
585     echo
586     echo "Commands:"
587     echo "      start           Start all PLC subsystems"
588     echo "      stop            Stop all PLC subsystems"
589     echo "      reload          Regenerate configuration files"
590     echo "      restart         Restart all PLC subsystems"
591     exit 1
592 }
593
594 # Get options
595 while getopts "vh" opt ; do
596     case $opt in
597         v)
598             verbose=1
599             set -x
600             ;;
601         h|*)
602             usage
603             ;;
604     esac
605 done
606
607 shift $(($OPTIND - 1))
608 if [ -z "$1" ] ; then
609     usage
610 fi
611
612 exec 3>&1
613 exec 4>&2
614 if [ $verbose -eq 0 ] ; then
615     exec 1>>/var/log/boot.log
616     exec 2>>/var/log/boot.log
617 fi
618
619 # Generate and load configuration
620 reload
621 . /etc/planetlab/plc_config
622
623 RETVAL=0
624
625 start ()
626 {
627     for step in "${steps[@]}" ; do
628         echo -n $"PLC: Starting $step: " >&3
629         RETVAL=$ERRORS
630         config_$step start
631         if [ $RETVAL -eq $ERRORS ] ; then
632             success $"PLC: $step startup" >&3
633         else
634             failure $"PLC: $step startup" >&3
635         fi
636         echo >&3
637     done
638 }
639
640 stop ()
641 {
642     for i in $(seq 1 $nsteps) ; do
643         step=${steps[$(($nsteps - $i))]}
644         echo -n $"PLC: Shutting down $step: " >&3
645         RETVAL=$ERRORS
646         config_$step stop
647         if [ $RETVAL -eq $ERRORS ] ; then
648             success $"PLC: $step shutdown" >&3
649         else
650             failure $"PLC: $step shutdown" >&3
651         fi
652         echo >&3
653     done
654 }
655
656 case "$1" in
657     start|stop)
658         $1
659         ;;
660
661     restart)
662         stop
663         start
664         ;;
665
666     reload)
667         ;;
668
669     *)
670         usage >&3
671         ;;
672 esac
673
674 exit $RETVAL