- fix typos
[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.8 2006/03/29 17:03: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     # Export so that we do not have to specify -p to psql invocations
188     export PGPORT=$PLC_DB_PORT
189
190     case "$1" in
191         start)
192             if [ "$PLC_DB_ENABLED" != "1" ] ; then
193                 return 0
194             fi
195
196             # Set data directory and redirect startup output to /var/log/pgsql
197             mkdir -p /etc/sysconfig/pgsql
198             (
199                 echo "PGDATA=$PGDATA"
200                 echo "PGLOG=/var/log/pgsql"
201                 echo "PGPORT=$PLC_DB_PORT"
202             ) >>/etc/sysconfig/pgsql/postgresql
203
204             # Fix ownership (rpm installation may have changed it)
205             chown -R -H postgres:postgres $(dirname $PGDATA)
206
207             # PostgreSQL must be started at least once to bootstrap
208             # /var/lib/pgsql/data
209             if [ ! -f $postgresql_conf ] ; then
210                 service postgresql start
211                 service postgresql stop
212             fi
213
214             # Enable DB server. PostgreSQL >=8.0 defines listen_addresses,
215             # PostgreSQL 7.x uses tcpip_socket.
216             if grep -q listen_addresses $postgresql_conf ; then
217                 sed -i -e '/^listen_addresses/d' $postgresql_conf
218                 echo "listen_addresses = '*'" >>$postgresql_conf
219             elif grep -q tcpip_socket $postgresql_conf ; then
220                 sed -i -e '/^tcpip_socket/d' $postgresql_conf
221                 echo "tcpip_socket = true" >>$postgresql_conf
222             fi
223
224             # Disable access to all DBs from all hosts
225             sed -i -e '/^\(host\|local\)/d' $pghba_conf
226
227             # Enable passwordless localhost access
228             echo "local all all trust" >>$pghba_conf
229
230             # Enable access from the API and web servers
231             PLC_API_IP=$(gethostbyname $PLC_API_HOST)
232             PLC_WWW_IP=$(gethostbyname $PLC_WWW_HOST)
233             (
234                 echo "host $PLC_DB_NAME $PLC_DB_USER $PLC_API_IP/32 password"
235                 echo "host $PLC_DB_NAME $PLC_DB_USER $PLC_WWW_IP/32 password"
236             ) >>$pghba_conf
237
238             # Fix ownership (sed -i changes it)
239             chown postgres:postgres $postgresql_conf $pghba_conf
240
241             # Start up the server
242             service postgresql start
243             # /etc/init.d/postgresql always returns 0, even on failure
244             status postmaster && [ -f /var/lock/subsys/postgresql ]
245             check
246
247             # Create/update the unprivileged database user and password
248             if ! psql -U $PLC_DB_USER -c "" template1 >/dev/null 2>&1 ; then
249                 psql -U postgres -c "CREATE USER $PLC_DB_USER PASSWORD '$PLC_DB_PASSWORD'" template1
250             else
251                 psql -U postgres -c "ALTER USER $PLC_DB_USER WITH PASSWORD '$PLC_DB_PASSWORD'" template1
252             fi
253
254             # Create the database if necessary
255             if ! psql -U $PLC_DB_USER -c "" $PLC_DB_NAME >/dev/null 2>&1 ; then
256                 createdb -U postgres $PLC_DB_NAME
257                 psql -U $PLC_DB_USER -f /usr/share/pl_db/plc_schema_3.sql $PLC_DB_NAME
258             fi
259             ;;
260
261         stop)
262             # Drop the current user in case the username changes
263             psql -U postgres -c "DROP USER $PLC_DB_USER" template1
264
265             # WARNING: If the DB name changes, the old DB will be left
266             # intact and a new one will be created. If it changes
267             # back, the old DB will not be re-created.
268
269             # Shut down the server
270             service postgresql stop
271             check
272             ;;
273     esac
274 }
275
276 # Generate GPG keys
277 config_gpg ()
278 {
279     case "$1" in
280         start)
281             # Generate GPG keyrings
282             if [ ! -f $PLC_ROOT_GPG_KEY_PUB -o ! -f $PLC_ROOT_GPG_KEY ] ; then
283                 mkdir -p $(dirname $PLC_ROOT_GPG_KEY_PUB)
284                 mkdir -p $(dirname $PLC_ROOT_GPG_KEY)
285
286                 # Temporarily replace /dev/random with /dev/urandom to
287                 # avoid running out of entropy.
288                 rm -f /dev/random
289                 mknod /dev/random c 1 9
290                 gpg --homedir=/root --batch --gen-key <<EOF
291 Key-Type: DSA
292 Key-Length: 1024
293 Subkey-Type: ELG-E
294 Subkey-Length: 1024
295 Name-Real: $PLC_NAME Central
296 Name-Comment: http://$PLC_WWW_HOST/
297 Name-Email: $PLC_MAIL_SUPPORT_ADDRESS
298 Expire-Date: 0
299 %pubring $PLC_ROOT_GPG_KEY_PUB
300 %secring $PLC_ROOT_GPG_KEY
301 %commit
302 EOF
303                 check
304                 rm -f /dev/random
305                 mknod /dev/random c 1 8
306                 chmod 600 $PLC_ROOT_GPG_KEY_PUB $PLC_ROOT_GPG_KEY
307             fi
308             ;;
309     esac
310 }
311
312 symlink ()
313 {
314     mkdir -p $(dirname $2)
315     rm -f $2
316     ln -s $1 $2
317 }
318
319 # Generate SSL certificates
320 config_ssl ()
321 {
322     case "$1" in
323         start)
324             # Generate a self-signed SSL certificate. These nice
325             # commands came from the mod_ssl spec file for Fedora Core
326             # 2. We generate only a single certificate for the web
327             # server, then make copies for the API and boot
328             # servers. As always, these certificates may be overridden
329             # later.
330
331             # Generate SSL private key
332             if [ ! -f $PLC_WWW_SSL_KEY ] ; then
333                 mkdir -p $(dirname $PLC_WWW_SSL_KEY)
334                 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
335                 check
336                 chmod 600 $PLC_WWW_SSL_KEY
337             fi
338
339             # Generate self-signed certificate
340             if [ ! -f $PLC_WWW_SSL_CRT ] ; then
341                 mkdir -p $(dirname $PLC_WWW_SSL_CRT)
342                 openssl req -new -x509 -days 365 -set_serial $RANDOM \
343                     -key $PLC_WWW_SSL_KEY -out $PLC_WWW_SSL_CRT <<EOF
344 --
345 State
346 City
347 Organization
348 $PLC_NAME Central
349 $PLC_WWW_HOST
350 $PLC_MAIL_SUPPORT_ADDRESS
351 EOF
352                 check
353                 chmod 644 $PLC_WWW_SSL_CRT
354             fi
355
356             # Make copies for the API and boot servers
357             if [ ! -f $PLC_API_SSL_KEY ] ; then
358                 cp -a $PLC_WWW_SSL_KEY $PLC_API_SSL_KEY
359             fi
360             if [ ! -f $PLC_API_SSL_KEY_PUB ] ; then
361                 openssl rsa -pubout <$PLC_API_SSL_KEY >$PLC_API_SSL_KEY_PUB
362                 check
363             fi
364             if [ ! -f $PLC_API_SSL_CRT ] ; then
365                 cp -a $PLC_WWW_SSL_CRT $PLC_API_SSL_CRT
366             fi
367             if [ ! -f $PLC_BOOT_SSL_KEY ] ; then
368                 cp -a $PLC_WWW_SSL_KEY $PLC_BOOT_SSL_KEY
369             fi
370             if [ ! -f $PLC_BOOT_SSL_CRT ] ; then
371                 cp -a $PLC_WWW_SSL_CRT $PLC_BOOT_SSL_CRT
372             fi
373
374             # Install into both /etc/pki (Fedora Core 4) and
375             # /etc/httpd/conf (Fedora Core 2). If the API, boot, and
376             # web servers are all running on the same machine, the web
377             # server certificate takes precedence.
378             for server in API BOOT WWW ; do
379                 eval enabled=\${PLC_${server}_ENABLED}
380                 if [ "$enabled" != "1" ] ; then
381                     continue
382                 fi
383                 eval ssl_crt=\${PLC_${server}_SSL_CRT}
384                 eval ssl_key=\${PLC_${server}_SSL_KEY}
385
386                 symlink $ssl_crt /etc/pki/tls/certs/localhost.crt
387                 symlink $ssl_key /etc/pki/tls/private/localhost.key
388                 symlink $ssl_crt /etc/httpd/conf/ssl.crt/server.crt
389                 symlink $ssl_key /etc/httpd/conf/ssl.key/server.key
390             done
391             ;;
392     esac
393 }
394
395 # Generate SSH keys
396 config_ssh ()
397 {
398     # XXX Could make these configurable
399     KEY_TYPE_ROOT=rsa
400     KEY_LEN_ROOT=1024
401     KEY_TYPE_DEBUG=rsa
402     KEY_LEN_DEBUG=2048  
403
404     case "$1" in
405         start)
406             tmp=$(mktemp -d /tmp/ssh.XXXXXX)
407
408             # Generate root SSH key
409             if [ ! -f $PLC_ROOT_SSH_KEY_PUB -o ! -f $PLC_ROOT_SSH_KEY ] ; then
410                 ssh-keygen -N "" -C "$PLC_NAME Central <$PLC_MAIL_SUPPORT_ADDRESS>" \
411                     -b $KEY_LEN_ROOT -t $KEY_TYPE_ROOT -f $tmp/root
412                 check
413                 install -D -m 600 $tmp/root $PLC_ROOT_SSH_KEY
414                 install -D -m 600 $tmp/root.pub $PLC_ROOT_SSH_KEY_PUB
415             fi
416
417             # Generate debug SSH key
418             if [ ! -f $PLC_DEBUG_SSH_KEY_PUB -o ! -f $PLC_DEBUG_SSH_KEY ] ; then
419                 ssh-keygen -N "" -C "$PLC_NAME Central <$PLC_MAIL_SUPPORT_ADDRESS>" \
420                     -b $KEY_LEN_DEBUG -t $KEY_TYPE_DEBUG -f $tmp/debug
421                 check
422                 install -D -m 600 $tmp/debug $PLC_DEBUG_SSH_KEY
423                 install -D -m 600 $tmp/debug.pub $PLC_DEBUG_SSH_KEY_PUB
424             fi
425
426             rm -rf $tmp
427             ;;
428     esac
429 }
430
431 # Configure Apache web server
432 config_apache ()
433 {
434     # Default locations
435     DocumentRoot=/var/www/html
436     php_ini=/etc/php.ini
437     httpd_conf=/etc/httpd/conf/httpd.conf
438     ssl_conf=/etc/httpd/conf.d/ssl.conf
439     plc_conf=/etc/httpd/conf.d/plc.conf
440
441     case "$1" in
442         start)
443             if [ "$PLC_API_ENABLED" != "1" -a \
444                  "$PLC_BOOT_ENABLED" != "1" -a \
445                  "$PLC_WWW_ENABLED" != "1" ] ; then
446                 return 0
447             fi
448
449             # Set the default include path
450             include_path=".:$DocumentRoot/includes:$DocumentRoot/generated:/etc/planetlab/php"
451             sed -i -e "s@;include_path = \"\.:.*\"@include_path = \"$include_path\"@" $php_ini
452
453             # Set the port numbers. If the API, boot, and web servers
454             # are all running on the same machine, the web server port
455             # numbers take precedence.
456             for server in API BOOT WWW ; do
457                 eval enabled=\${PLC_${server}_ENABLED}
458                 if [ "$enabled" != "1" ] ; then
459                     continue
460                 fi
461                 eval http_port=\${PLC_${server}_PORT}
462                 eval https_port=\${PLC_${server}_SSL_PORT}
463
464                 if [ -n "$http_port" ] ; then
465                     sed -i -e "s/^Listen .*/Listen $http_port/" $httpd_conf
466                 fi
467                 if [ -n "$https_port" ] ; then
468                     sed -i \
469                         -e "s/^Listen .*/Listen $https_port/" \
470                         -e "s/<VirtualHost _default_:.*>/<VirtualHost _default_:$https_port>/" \
471                         $ssl_conf
472                 fi
473             done
474                     
475             # Set custom Apache directives
476             (
477                 if [ "$PLC_API_ENABLED" = "1" ] ; then
478                     # XXX We should only support non-SSL access to the
479                     # API by writing this to conf.d/plc_ssl.conf, then
480                     # writing "Include conf.d/plc_ssl.conf" to
481                     # conf.d/ssl.conf. Once oldapi, which does not
482                     # support SSL, is removed from the web pages, we
483                     # can do this.
484                     cat <<EOF
485 <Location $PLC_API_PATH>
486     SetHandler python-program
487     PythonPath "sys.path + ['/usr/share/plc_api']"
488     PythonHandler mod_pythonXMLRPC
489 </Location>
490 EOF
491                 fi
492
493                 if [ "$PLC_WWW_ENABLED" = "1" ] ; then
494                     cat <<EOF
495 <VirtualHost *:$PLC_WWW_PORT>
496     Redirect /db https://$PLC_WWW_HOST:$PLC_WWW_SSL_PORT/db
497 </VirtualHost>
498 EOF
499                 else
500                     cat <<EOF
501 <Location /db>
502     Deny from all
503 </Location>
504 EOF
505                 fi
506             ) >$plc_conf
507
508             # Make alpina-logs directory writable for bootmanager log upload
509             chown apache:apache $DocumentRoot/alpina-logs/nodes
510
511             service httpd start
512             check
513             ;;
514
515         stop)
516             service httpd stop
517             check
518             ;;
519     esac
520 }
521
522 config_api ()
523 {
524     case "$1" in
525         start)
526             if [ "$PLC_API_ENABLED" != "1" ] ; then
527                 return
528             fi
529
530             # Update the maintenance account username. This can't be
531             # done through the api-config script since it uses the
532             # maintenance account to access the API. The maintenance
533             # account should be person_id 1 since it is created by the
534             # DB schema itself.
535             psql -U $PLC_DB_USER -c "UPDATE persons SET email='$PLC_API_MAINTENANCE_USER' WHERE person_id=1" $PLC_DB_NAME
536
537             # Bootstrap the DB
538             api-config
539             check
540             ;;
541     esac
542 }
543
544 config_cron ()
545 {
546     case "$1" in
547         start)
548             if [ "$PLC_MAIL_ENABLED" = "1" ] ; then
549                 MAILTO=$PLC_MAIL_SUPPORT_ADDRESS
550             else
551                 MAILTO=
552             fi
553             cat >/etc/cron.d/plc.cron <<EOF
554 SHELL=/bin/bash
555 PATH=/sbin:/bin:/usr/sbin:/usr/bin
556 MAILTO=$MAILTO
557 HOME=/
558
559 # minute hour day-of-month month day-of-week user command
560 */5 * * * * root gen-slices-xml-05.py
561 */15 * * * * root gen-sites-xml.py
562 */15 * * * * root gen-static-content.py
563 EOF
564
565             # Run them once at startup
566             gen-slices-xml-05.py
567             check
568             gen-sites-xml.py
569             check
570             gen-static-content.py
571             check
572
573             service crond start
574             check
575             ;;
576
577         stop)
578             service crond stop
579             check
580             ;;
581     esac
582 }
583
584 usage()
585 {
586     echo "Usage: $0 [OPTION]... [COMMAND]"
587     echo "      -v              Be verbose"
588     echo "      -h              This message"
589     echo
590     echo "Commands:"
591     echo "      start           Start all PLC subsystems"
592     echo "      stop            Stop all PLC subsystems"
593     echo "      reload          Regenerate configuration files"
594     echo "      restart         Restart all PLC subsystems"
595     exit 1
596 }
597
598 # Get options
599 while getopts "vh" opt ; do
600     case $opt in
601         v)
602             verbose=1
603             set -x
604             ;;
605         h|*)
606             usage
607             ;;
608     esac
609 done
610
611 shift $(($OPTIND - 1))
612 if [ -z "$1" ] ; then
613     usage
614 fi
615
616 exec 3>&1
617 exec 4>&2
618 if [ $verbose -eq 0 ] ; then
619     exec 1>>/var/log/boot.log
620     exec 2>>/var/log/boot.log
621 fi
622
623 # Generate and load configuration
624 reload
625 . /etc/planetlab/plc_config
626
627 RETVAL=0
628
629 start ()
630 {
631     for step in "${steps[@]}" ; do
632         echo -n $"PLC: Starting $step: " >&3
633         RETVAL=$ERRORS
634         config_$step start
635         if [ $RETVAL -eq $ERRORS ] ; then
636             success $"PLC: $step startup" >&3
637         else
638             failure $"PLC: $step startup" >&3
639         fi
640         echo >&3
641     done
642 }
643
644 stop ()
645 {
646     for i in $(seq 1 $nsteps) ; do
647         step=${steps[$(($nsteps - $i))]}
648         echo -n $"PLC: Shutting down $step: " >&3
649         RETVAL=$ERRORS
650         config_$step stop
651         if [ $RETVAL -eq $ERRORS ] ; then
652             success $"PLC: $step shutdown" >&3
653         else
654             failure $"PLC: $step shutdown" >&3
655         fi
656         echo >&3
657     done
658 }
659
660 case "$1" in
661     start|stop)
662         $1
663         ;;
664
665     restart)
666         stop
667         start
668         ;;
669
670     reload)
671         ;;
672
673     *)
674         usage >&3
675         ;;
676 esac
677
678 exit $RETVAL