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