--- /dev/null
+#!/usr/bin/python
+#
+# Bootstraps the PLC database with a default administrator account and
+# a default site.
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id$
+#
+
+import plcapilib
+(plcapi, moreopts, argv) = plcapilib.plcapi(globals())
+from plc_config import PLCConfiguration
+
+
+def main():
+ cfg = PLCConfiguration()
+ cfg.load()
+ variables = cfg.variables()
+
+ # Load variables into dictionaries
+ (category, variablelist) = variables['plc']
+ plc = dict(zip(variablelist.keys(),
+ [variable['value'] for variable in variablelist.values()]))
+
+ (category, variablelist) = variables['plc_www']
+ plc_www = dict(zip(variablelist.keys(),
+ [variable['value'] for variable in variablelist.values()]))
+
+ (category, variablelist) = variables['plc_api']
+ plc_api = dict(zip(variablelist.keys(),
+ [variable['value'] for variable in variablelist.values()]))
+
+ # Create/update the default administrator account (should be
+ # person_id 2).
+ admin = { 'person_id': 2,
+ 'first_name': "Default",
+ 'last_name': "Administrator",
+ 'email': plc['root_user'],
+ 'password': plc['root_password'] }
+ persons = AdmGetPersons([admin['person_id']])
+ if not persons:
+ person_id = AdmAddPerson(admin['first_name'], admin['last_name'], admin)
+ if person_id != admin['person_id']:
+ # Huh? Someone deleted the account manually from the database.
+ AdmDeletePerson(person_id)
+ raise Exception, "Someone deleted the \"%s %s\" account from the database!" % \
+ (admin['first_name'], admin['last_name'])
+ AdmSetPersonEnabled(person_id, True)
+ else:
+ person_id = persons[0]['person_id']
+ AdmUpdatePerson(person_id, admin)
+
+ # Create/update the default site (should be site_id 0)
+ if plc_www['port'] == '80':
+ url = "http://" + plc_www['host'] + "/"
+ elif plc_www['port'] == '443':
+ url = "https://" + plc_www['host'] + "/"
+ else:
+ url = "http://" + plc_www['host'] + ":" + plc_www['port'] + "/"
+ site = { 'site_id': 1,
+ 'name': plc['name'] + " Central",
+ 'abbreviated_name': plc['name'],
+ 'login_base': plc['slice_prefix'],
+ 'is_public': False,
+ 'url': url,
+ 'max_slices': 100 }
+
+ sites = AdmGetSites([site['site_id']])
+ if not sites:
+ site_id = AdmAddSite(site['name'], site['abbreviated_name'], site['login_base'], site)
+ if site_id != site['site_id']:
+ AdmDeleteSite(site_id)
+ raise Exception, "Someone deleted the \"%s\" site from the database!" % \
+ site['name']
+ else:
+ site_id = sites[0]['site_id']
+ # XXX login_base cannot be updated
+ del site['login_base']
+ AdmUpdateSite(site_id, site)
+
+ # The default administrator account must be associated with a site
+ # in order to login.
+ AdmAddPersonToSite(admin['person_id'], site['site_id'])
+ AdmSetPersonPrimarySite(admin['person_id'], site['site_id'])
+
+ # Grant admin and PI roles to the default administrator account
+ AdmGrantRoleToPerson(admin['person_id'], 10)
+ AdmGrantRoleToPerson(admin['person_id'], 20)
+
+ # XXX Setup default slice attributes and initscripts (copy from PLC)
+
+ # XXX Setup PlanetLabConf entries (copy from PLC)
+
+if __name__ == '__main__':
+ main()
--- /dev/null
+#!/bin/bash
+#
+# Builds a Fedora based PLC image. You should be able to run this
+# script multiple times without a problem.
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id$
+#
+
+PATH=/sbin:/bin:/usr/sbin:/usr/bin
+
+# In a normal CVS environment, the requisite CVS modules (including
+# build/) are located at the same level we are. In a PlanetLab RPM
+# build environment (see the RPM spec file), they are checked out into
+# a subdirectory.
+if [ -d ./build ] ; then
+ PATH=$PATH:./build
+ srcdir=.
+elif [ -d ../build ] ; then
+ PATH=$PATH:../build
+ srcdir=..
+else
+ echo "Error: Could not find sources in either . or .."
+ exit 1
+fi
+
+export PATH
+
+# PLC configuration file
+config=plc_config.xml
+
+# Release and architecture to install
+releasever=2
+basearch=i386
+
+# Data directory base
+usr_share=/usr/share
+
+# Initial size of the image
+size=1000000000
+
+usage()
+{
+ echo "Usage: build.sh [OPTION]..."
+ echo " -c file PLC configuration file (default: $config)"
+ echo " -r release Fedora release number (default: $releasever)"
+ echo " -a arch Fedora architecture (default: $basearch)"
+ echo " -d datadir Data directory base (default: $usr_share)"
+ echo " -s size Approximate size of the installation (default: $size)"
+ echo " -h This message"
+ exit 1
+}
+
+# Get options
+while getopts "c:r:a:d:s:h" opt ; do
+ case $opt in
+ c)
+ config=$OPTARG
+ ;;
+ r)
+ releasever=$OPTARG
+ ;;
+ a)
+ basearch=$OPTARG
+ ;;
+ d)
+ usr_share=$OPTARG
+ ;;
+ s)
+ size=$OPTARG
+ ;;
+ h|*)
+ usage
+ ;;
+ esac
+done
+
+root=fc$releasever
+data=data$releasever
+
+if [ ! -f $root.img ] ; then
+ bs=4096
+ count=$(($size / 4096))
+ dd bs=$bs count=$count if=/dev/zero of=$root.img
+ mkfs.ext3 -j -F $root.img
+fi
+
+mkdir -p $root $data
+mount -o loop $root.img $root
+trap "umount $root; exit 1" ERR
+
+#
+# Build
+#
+
+# Get package list
+while read package ; do
+ packages="$packages -p $package"
+done < <(./plc-config --packages $config)
+
+# Install base system
+mkfedora -v -r $releasever -a $basearch $packages $root
+
+# FC2 minilogd starts up during shutdown and makes unmounting
+# impossible. Just get rid of it.
+rm -f $root/sbin/minilogd
+ln -nsf /bin/true $root/sbin/minilogd
+
+# Build schema
+make -C $srcdir/pl_db
+
+#
+# Install
+#
+
+# Install configuration scripts
+install -D -m 755 plc_config.py $root/tmp/plc_config.py
+chroot $root sh -c 'cd /tmp; python plc_config.py build; python plc_config.py install'
+install -D -m 755 plc-config $root/usr/bin/plc-config
+install -D -m 755 api-config $root/usr/bin/api-config
+
+# Install init script
+install -D -m 755 guest.init $root/etc/init.d/plc
+chroot $root sh -c 'chkconfig --add plc; chkconfig plc on'
+
+# Install DB schema and API code
+mkdir -p $root/usr/share
+rsync -a $srcdir/pl_db $srcdir/plc_api $root/usr/share/
+
+# Install web scripts
+mkdir -p $root/usr/bin
+install -m 755 \
+ plc/scripts/gen-sites-xml.py \
+ plc/scripts/gen-slices-xml-05.py \
+ plc/scripts/gen-static-content.py \
+ $root/usr/bin/
+
+# Install web pages
+mkdir -p $root/var/www/html
+rsync -a $srcdir/plc_www/ $root/var/www/html/
+
+# Install configuration file
+install -D -m 644 $config $data/etc/planetlab/plc_config.xml
+
+# Move "data" directories out of the installation
+datadirs=(
+/etc/planetlab
+/var/lib/pgsql
+/var/www/html/alpina-logs
+/var/www/html/boot
+/var/www/html/download
+/var/www/html/generated
+/var/www/html/install-rpms
+/var/www/html/xml
+)
+
+mkdir -p $root/data
+for datadir in "${datadirs[@]}" ; do
+ mkdir -p ${data}$datadir
+ if [ -d $root/$datadir -a ! -h $root/$datadir ] ; then
+ (cd $root && find ./$datadir | cpio -p -d -u ../$data/)
+ fi
+ rm -rf $root/$datadir
+ mkdir -p $(dirname $root/$datadir)
+ ln -nsf /data$datadir $root/$datadir
+done
+
+# Shrink to 100 MB free space
+kb=$(python <<EOF
+import os
+df = os.statvfs('$root')
+target = 100 * 1024 * 1024 / df.f_bsize
+if df.f_bavail > target:
+ print (df.f_blocks - (df.f_bavail - target)) * df.f_bsize / 1024
+EOF
+)
+
+umount $root
+trap - ERR
+
+if [ -n "$kb" ] ; then
+ # Setup loopback association. Newer versions of losetup have a -f
+ # option which finds an unused loopback device, but we must
+ # support FC2 for now.
+ # dev_loop=$(losetup -f)
+ for i in `seq 1 7` ; do
+ if ! grep -q "^/dev/loop$i" /proc/mounts ; then
+ dev_loop="/dev/loop$i"
+ break
+ fi
+ done
+ losetup $dev_loop $root.img
+ trap "losetup -d $dev_loop" ERR
+
+ # Resize the filesystem
+ e2fsck -f $dev_loop
+ resize2fs $dev_loop ${kb}K
+
+ # Tear down loopback association
+ losetup -d $dev_loop
+ trap - ERR
+
+ # Truncate the image file
+ perl -e "truncate '$root.img', $kb*1024"
+fi
+
+# Write sysconfig
+cat >plc.sysconfig <<EOF
+PLC_ROOT=$usr_share/plc/$root
+PLC_DATA=$usr_share/plc/$data
+#PLC_OPTIONS="-v"
+EOF
+
+exit $RETVAL
--- /dev/null
+#!/bin/bash
+#
+# plc Manages all PLC services on this machine
+#
+# chkconfig: 2345 5 99
+#
+# description: Manages all PLC services on this machine
+#
+# $Id: plc.init,v 1.6 2005/04/24 19:48:11 mlhuang Exp $
+#
+
+PATH=/sbin:/bin:/usr/bin:/usr/sbin
+
+# Source function library.
+. /etc/init.d/functions
+
+# Verbosity
+verbose=0
+
+# Keep in order! All steps should be idempotent. This means that you
+# should be able to run them multiple times without depending on
+# anything previously being run. The idea is that when the
+# configuration changes, "service plc restart" is called, all
+# dependencies are fixed up, and everything just works.
+steps=(
+network
+syslog
+postgresql
+ssl
+gpg
+ssh
+apache
+api
+cron
+)
+nsteps=${#steps[@]}
+
+gethostbyname ()
+{
+ perl -MSocket -e '($a,$b,$c,$d,@addrs) = gethostbyname($ARGV[0]); print inet_ntoa($addrs[0]) . "\n";' $1 2>/dev/null
+}
+
+# Regenerate configuration files
+reload ()
+{
+ # Load configuration
+ plc-config --shell >/etc/planetlab/plc_config
+ . /etc/planetlab/plc_config
+
+ # Generate various defaults
+ if [ -z "$PLC_DB_PASSWORD" ] ; then
+ PLC_DB_PASSWORD=$(uuidgen)
+ plc-config --category=plc_db --variable=password --value="$PLC_DB_PASSWORD" --save
+ fi
+
+ if [ -z "$PLC_API_MAINTENANCE_PASSWORD" ] ; then
+ PLC_API_MAINTENANCE_PASSWORD=$(uuidgen)
+ plc-config --category=plc_api --variable=maintenance_password --value="$PLC_API_MAINTENANCE_PASSWORD" --save
+ fi
+
+ if [ -z "$PLC_API_MAINTENANCE_SOURCES" ] ; then
+ for server in API BOOT WWW ; do
+ eval hostname=\${PLC_${server}_HOST}
+ ip=$(gethostbyname $hostname)
+ if [ -n "$ip" ] ; then
+ if [ -n "$PLC_API_MAINTENANCE_SOURCES" ] ; then
+ PLC_API_MAINTENANCE_SOURCES="$PLC_API_MAINTENANCE_SOURCES $ip"
+ else
+ PLC_API_MAINTENANCE_SOURCES=$ip
+ fi
+ fi
+ done
+ plc-config --category=plc_api --variable=maintenance_sources --value="$PLC_API_MAINTENANCE_SOURCES" --save
+ fi
+
+ # Save configuration
+ mkdir -p /etc/planetlab/php
+ plc-config --php >/etc/planetlab/php/plc_config.php
+ plc-config --shell >/etc/planetlab/plc_config
+
+ # For backward compatibility, until we can convert all code to use
+ # the now standardized variable names.
+
+ # DB constants are all named the same
+ ln -sf plc_config /etc/planetlab/plc_db
+
+ # API constants
+ cat >/etc/planetlab/plc_api <<EOF
+PL_API_SERVER='$PLC_API_HOST'
+PL_API_PATH='$PLC_API_PATH'
+PL_API_PORT=$PLC_API_SSL_PORT
+PL_API_CAPABILITY_AUTH_METHOD='capability'
+PL_API_CAPABILITY_PASS='$PLC_API_MAINTENANCE_PASSWORD'
+PL_API_CAPABILITY_USERNAME='$PLC_API_MAINTENANCE_USER'
+PL_API_TICKET_KEY_FILE='$PLC_API_TICKET_KEY'
+PLANETLAB_SUPPORT_EMAIL='$PLC_MAIL_SUPPORT_ADDRESS'
+BOOT_MESSAGES_EMAIL='$PLC_MAIL_BOOT_ADDRESS'
+WWW_BASE='$PLC_WWW_HOST'
+BOOT_BASE='$PLC_BOOT_HOST'
+EOF
+
+ # The format is
+ #
+ # ip:max_role_id:organization_id:password
+ #
+ # It is unlikely that we will let federated sites use the
+ # maintenance account to access each others' APIs, so we always
+ # set organization_id to -1.
+ (
+ echo -n "PL_API_CAPABILITY_SOURCES='"
+ first=1
+ for ip in $PLC_API_MAINTENANCE_SOURCES ; do
+ if [ $first -ne 1 ] ; then
+ echo -n " "
+ fi
+ first=0
+ echo -n "$ip:-1:-1:$PLC_API_MAINTENANCE_PASSWORD"
+ done
+ echo "'"
+ ) >>/etc/planetlab/plc_api
+
+ cat >/etc/planetlab/php/site_constants.php <<"EOF"
+<?php
+include('plc_config.php');
+
+DEFINE('PL_API_SERVER', PLC_API_HOST);
+DEFINE('PL_API_PATH', PLC_API_PATH);
+DEFINE('PL_API_PORT', PLC_API_SSL_PORT);
+DEFINE('PL_API_CAPABILITY_AUTH_METHOD', 'capability');
+DEFINE('PL_API_CAPABILITY_PASS', PLC_API_MAINTENANCE_PASSWORD);
+DEFINE('PL_API_CAPABILITY_USERNAME', PLC_API_MAINTENANCE_USER);
+DEFINE('WWW_BASE', PLC_WWW_HOST);
+DEFINE('BOOT_BASE', PLC_BOOT_HOST);
+DEFINE('DEBUG', PLC_WWW_DEBUG);
+DEFINE('API_CALL_DEBUG', PLC_API_DEBUG);
+DEFINE('SENDMAIL', PLC_MAIL_ENABLED);
+DEFINE('PLANETLAB_SUPPORT_EMAIL', PLC_NAME . 'Support <' . PLC_MAIL_SUPPORT_ADDRESS . '>');
+DEFINE('PLANETLAB_SUPPORT_EMAIL_ONLY', PLC_MAIL_SUPPORT_ADDRESS);
+?>
+EOF
+}
+
+config_network ()
+{
+ case "$1" in
+ start)
+ # Minimal /etc/hosts
+ (
+ echo "127.0.0.1 localhost.localdomain localhost"
+ for server in API BOOT WWW ; do
+ eval hostname=\${PLC_${server}_HOST}
+ ip=$(gethostbyname $hostname)
+ if [ -n "$ip" ] ; then
+ echo "$ip $hostname"
+ fi
+ done
+ ) >/etc/hosts
+
+ # Set up nameservers
+ (
+ [ -n "$PLC_NET_DNS1" ] && echo "nameserver $PLC_NET_DNS1"
+ [ -n "$PLC_NET_DNS2" ] && echo "nameserver $PLC_NET_DNS2"
+ ) >/etc/resolv.conf
+ ;;
+ esac
+}
+
+config_syslog ()
+{
+ service syslog $1
+ RETVAL=$?
+}
+
+config_postgresql ()
+{
+ # Default locations
+ PGDATA=/var/lib/pgsql/data
+ postgresql_conf=$PGDATA/postgresql.conf
+ pghba_conf=$PGDATA/pg_hba.conf
+
+ case "$1" in
+ start)
+ if [ "$PLC_DB_ENABLED" != "1" ] ; then
+ return 0
+ fi
+
+ # Set data directory and redirect startup output to /var/log/pgsql
+ mkdir -p /etc/sysconfig/pgsql
+ (
+ echo "PGDATA=$PGDATA"
+ echo "PGLOG=/var/log/pgsql"
+ ) >>/etc/sysconfig/pgsql/postgresql
+
+ # PostgreSQL must be started at least once to bootstrap
+ # /var/lib/pgsql/data
+ if [ ! -f $postgresql_conf ] ; then
+ service postgresql start
+ service postgresql stop
+ fi
+
+ # Enable DB server. PostgreSQL >=8.0 defines listen_addresses,
+ # PostgreSQL 7.x uses tcpip_socket.
+ if grep -q listen_addresses $postgresql_conf ; then
+ sed -i -e '/^listen_addresses/d' $postgresql_conf
+ echo "listen_addresses = '*'" >>$postgresql_conf
+ elif grep -q tcpip_socket $postgresql_conf ; then
+ sed -i -e '/^tcpip_socket/d' $postgresql_conf
+ echo "tcpip_socket = true" >>$postgresql_conf
+ fi
+
+ # Disable access to all DBs from all hosts
+ sed -i -e '/^\(host\|local\)/d' $pghba_conf
+
+ # Enable passwordless localhost access
+ echo "local all all trust" >>$pghba_conf
+
+ # Enable access from the API and web servers
+ PLC_API_IP=$(gethostbyname $PLC_API_HOST)
+ PLC_WWW_IP=$(gethostbyname $PLC_WWW_HOST)
+ (
+ echo "host $PLC_DB_NAME $PLC_DB_USER $PLC_API_IP/32 password"
+ echo "host $PLC_DB_NAME $PLC_DB_USER $PLC_WWW_IP/32 password"
+ ) >>$pghba_conf
+
+ # Fix ownership (sed -i changes it)
+ chown postgres:postgres $postgresql_conf $pghba_conf
+
+ # Start up the server
+ service postgresql start
+ RETVAL=$?
+
+ # Create/update the unprivileged database user and password
+ if ! psql -U $PLC_DB_USER -c "" template1 >/dev/null 2>&1 ; then
+ psql -U postgres -c "CREATE USER $PLC_DB_USER PASSWORD '$PLC_DB_PASSWORD'" template1
+ else
+ psql -U postgres -c "ALTER USER $PLC_DB_USER WITH PASSWORD '$PLC_DB_PASSWORD'" template1
+ fi
+
+ # Create the database if necessary
+ if ! psql -U $PLC_DB_USER -c "" $PLC_DB_NAME >/dev/null 2>&1 ; then
+ createdb -U postgres $PLC_DB_NAME
+ psql -U $PLC_DB_USER -f /usr/share/pl_db/plc_schema_3.sql $PLC_DB_NAME
+ fi
+ ;;
+
+ stop)
+ # Drop the current user in case the username changes
+ psql -U postgres -c "DROP USER $PLC_DB_USER" template1
+
+ # WARNING: If the DB name changes, the old DB will be left
+ # intact and a new one will be created. If it changes
+ # back, the old DB will not be re-created.
+
+ # Shut down the server
+ service postgresql stop
+ RETVAL=$?
+ ;;
+ esac
+}
+
+# Generate GPG keys
+config_gpg ()
+{
+ case "$1" in
+ start)
+ # Generate GPG keyrings
+ if [ ! -f $PLC_ROOT_GPG_KEY_PUB -o ! -f $PLC_ROOT_GPG_KEY ] ; then
+ mkdir -p $(dirname $PLC_ROOT_GPG_KEY_PUB)
+ mkdir -p $(dirname $PLC_ROOT_GPG_KEY)
+
+ # Temporarily replace /dev/random with /dev/urandom to
+ # avoid running out of entropy.
+ rm -f /dev/random
+ mknod /dev/random c 1 9
+ gpg --homedir=/root --batch --gen-key <<EOF
+Key-Type: DSA
+Key-Length: 1024
+Subkey-Type: ELG-E
+Subkey-Length: 1024
+Name-Real: $PLC_NAME Central
+Name-Comment: http://$PLC_WWW_HOST/
+Name-Email: $PLC_MAIL_SUPPORT_ADDRESS
+Expire-Date: 0
+%pubring $PLC_ROOT_GPG_KEY_PUB
+%secring $PLC_ROOT_GPG_KEY
+%commit
+EOF
+ RETVAL=$?
+ rm -f /dev/random
+ mknod /dev/random c 1 8
+ chmod 600 $PLC_ROOT_GPG_KEY_PUB $PLC_ROOT_GPG_KEY
+ fi
+ ;;
+ esac
+}
+
+symlink ()
+{
+ mkdir -p $(dirname $2)
+ rm -f $2
+ ln -s $1 $2
+}
+
+# Generate SSL certificates
+config_ssl ()
+{
+ case "$1" in
+ start)
+ # Generate a self-signed SSL certificate. These nice
+ # commands came from the mod_ssl spec file for Fedora Core
+ # 2. We generate only a single certificate for the web
+ # server, then make copies for the API and boot
+ # servers. As always, these certificates may be overridden
+ # later.
+
+ # Generate SSL private key
+ if [ ! -f $PLC_WWW_SSL_KEY ] ; then
+ mkdir -p $(dirname $PLC_WWW_SSL_KEY)
+ 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
+ RETVAL=$(($RETVAL+$?))
+ chmod 600 $PLC_WWW_SSL_KEY
+ fi
+
+ # Generate self-signed certificate
+ if [ ! -f $PLC_WWW_SSL_CRT ] ; then
+ mkdir -p $(dirname $PLC_WWW_SSL_CRT)
+ openssl req -new -x509 -days 365 -set_serial $RANDOM \
+ -key $PLC_WWW_SSL_KEY -out $PLC_WWW_SSL_CRT <<EOF
+--
+State
+City
+Organization
+$PLC_NAME Central
+$PLC_WWW_HOST
+$PLC_MAIL_SUPPORT_ADDRESS
+EOF
+ RETVAL=$(($RETVAL+$?))
+ chmod 644 $PLC_WWW_SSL_CRT
+ fi
+
+ # Make copies for the API and boot servers
+ if [ ! -f $PLC_API_SSL_KEY ] ; then
+ cp -a $PLC_WWW_SSL_KEY $PLC_API_SSL_KEY
+ fi
+ if [ ! -f $PLC_API_SSL_CRT ] ; then
+ cp -a $PLC_WWW_SSL_CRT $PLC_API_SSL_CRT
+ fi
+ if [ ! -f $PLC_API_SSL_KEY ] ; then
+ cp -a $PLC_WWW_SSL_KEY $PLC_API_SSL_KEY
+ fi
+ if [ ! -f $PLC_API_SSL_CRT ] ; then
+ cp -a $PLC_WWW_SSL_CRT $PLC_API_SSL_CRT
+ fi
+
+ # Install into both /etc/pki (Fedora Core 4) and
+ # /etc/httpd/conf (Fedora Core 2). If the API, boot, and
+ # web servers are all running on the same machine, the web
+ # server certificate takes precedence.
+ for server in API BOOT WWW ; do
+ eval enabled=\${PLC_${server}_ENABLED}
+ if [ "$enabled" != "1" ] ; then
+ continue
+ fi
+ eval ssl_crt=\${PLC_${server}_SSL_CRT}
+ eval ssl_key=\${PLC_${server}_SSL_KEY}
+
+ symlink $ssl_crt /etc/pki/tls/certs/localhost.crt
+ symlink $ssl_key /etc/pki/tls/private/localhost.key
+ symlink $ssl_crt /etc/httpd/conf/ssl.crt/server.crt
+ symlink $ssl_key /etc/httpd/conf/ssl.key/server.key
+ done
+ ;;
+ esac
+}
+
+# Generate SSH keys
+config_ssh ()
+{
+ # XXX Could make these configurable
+ KEY_TYPE_ROOT=rsa
+ KEY_LEN_ROOT=1024
+ KEY_TYPE_DEBUG=rsa
+ KEY_LEN_DEBUG=2048
+
+ case "$1" in
+ start)
+ tmp=$(mktemp -d /tmp/ssh.XXXXXX)
+
+ # Generate root SSH key
+ if [ ! -f $PLC_ROOT_SSH_KEY_PUB -o ! -f $PLC_ROOT_SSH_KEY ] ; then
+ ssh-keygen -N "" -C "$PLC_NAME Central <$PLC_MAIL_SUPPORT_ADDRESS>" \
+ -b $KEY_LEN_ROOT -t $KEY_TYPE_ROOT -f $tmp/root
+ RETVAL=$(($RETVAL+$?))
+ install -D -m 600 $tmp/root $PLC_ROOT_SSH_KEY
+ install -D -m 600 $tmp/root.pub $PLC_ROOT_SSH_KEY_PUB
+ fi
+
+ # Generate debug SSH key
+ if [ ! -f $PLC_DEBUG_SSH_KEY_PUB -o ! -f $PLC_DEBUG_SSH_KEY ] ; then
+ ssh-keygen -N "" -C "$PLC_NAME Central <$PLC_MAIL_SUPPORT_ADDRESS>" \
+ -b $KEY_LEN_DEBUG -t $KEY_TYPE_DEBUG -f $tmp/debug
+ RETVAL=$(($RETVAL+$?))
+ install -D -m 600 $tmp/debug $PLC_DEBUG_SSH_KEY
+ install -D -m 600 $tmp/debug.pub $PLC_DEBUG_SSH_KEY_PUB
+ fi
+
+ rm -rf $tmp
+ ;;
+ esac
+}
+
+# Configure Apache web server
+config_apache ()
+{
+ # Default locations
+ DocumentRoot=/var/www/html
+ php_ini=/etc/php.ini
+ httpd_conf=/etc/httpd/conf/httpd.conf
+ ssl_conf=/etc/httpd/conf.d/ssl.conf
+ plc_conf=/etc/httpd/conf.d/plc.conf
+
+ case "$1" in
+ start)
+ if [ "$PLC_API_ENABLED" != "1" -a \
+ "$PLC_BOOT_ENABLED" != "1" -a \
+ "$PLC_WWW_ENABLED" != "1" ] ; then
+ return 0
+ fi
+
+ # Set the default include path
+ include_path=".:$DocumentRoot/includes:$DocumentRoot/generated:/etc/planetlab/php"
+ sed -i -e "s@;include_path = \"\.:.*\"@include_path = \"$include_path\"@" $php_ini
+
+ # Set the port numbers. If the API, boot, and web servers
+ # are all running on the same machine, the web server port
+ # numbers take precedence.
+ for server in API BOOT WWW ; do
+ eval enabled=\${PLC_${server}_ENABLED}
+ if [ "$enabled" != "1" ] ; then
+ continue
+ fi
+ eval http_port=\${PLC_${server}_PORT}
+ eval https_port=\${PLC_${server}_SSL_PORT}
+
+ if [ -n "$http_port" ] ; then
+ sed -i -e "s/^Listen .*/Listen $http_port/" $httpd_conf
+ fi
+ if [ -n "$https_port" ] ; then
+ sed -i -e "s/^Listen .*/Listen $https_port/" $ssl_conf
+ fi
+ done
+
+ # Set custom Apache directives
+ (
+ if [ "$PLC_API_ENABLED" = "1" ] ; then
+ cat <<EOF
+<Location $PLC_API_PATH>
+ SetHandler python-program
+ PythonPath "sys.path + ['/usr/share/plc_api']"
+ PythonHandler mod_pythonXMLRPC
+</Location>
+EOF
+ fi
+
+ cat <<EOF
+<VirtualHost $PLC_WWW_HOST:$PLC_WWW_PORT>
+ Redirect /db https://$PLC_WWW_HOST:$PLC_WWW_SSL_PORT/db
+</VirtualHost>
+EOF
+ ) >$plc_conf
+
+ # Make alpina-logs directory writable for bootmanager log upload
+ chown apache:apache $DocumentRoot/alpina-logs/nodes
+
+ service httpd start
+ RETVAL=$?
+ ;;
+
+ stop)
+ service httpd stop
+ RETVAL=$?
+ ;;
+ esac
+}
+
+config_api ()
+{
+ case "$1" in
+ start)
+ if [ "$PLC_API_ENABLED" -ne 1 ] ; then
+ return
+ fi
+
+ # Update the maintenance account username. This can't be
+ # done through the api-config script since it uses the
+ # maintenance account to access the API. The maintenance
+ # account should be person_id 1 since it is created by the
+ # DB schema itself.
+ psql -U $PLC_DB_USER -c "UPDATE persons SET email='$PLC_API_MAINTENANCE_USER' WHERE person_id=1" $PLC_DB_NAME
+
+ # Bootstrap the DB
+ api-config
+ RETVAL=$?
+ ;;
+ esac
+}
+
+config_cron ()
+{
+ case "$1" in
+ start)
+ cat >/etc/cron.d/plc.cron <<EOF
+SHELL=/bin/bash
+PATH=/sbin:/bin:/usr/sbin:/usr/bin
+MAILTO=$PLC_MAIL_SUPPORT_ADDRESS
+HOME=/
+
+# minute hour day-of-month month day-of-week user command
+*/5 * * * * root gen-slices-xml-05.py
+*/15 * * * * root gen-sites-xml.py
+*/15 * * * * root gen-static-content.py
+EOF
+
+ # Run them once at startup
+ gen-slices-xml-05.py
+ gen-sites-xml.py
+ gen-static-content.py
+
+ service crond start
+ RETVAL=$?
+ ;;
+
+ stop)
+ service crond stop
+ RETVAL=$?
+ ;;
+ esac
+}
+
+usage()
+{
+ echo "Usage: $0 [OPTION]... [COMMAND]"
+ echo " -v Be verbose"
+ echo " -h This message"
+ echo
+ echo "Commands:"
+ echo " start Start all PLC subsystems"
+ echo " stop Stop all PLC subsystems"
+ echo " reload Regenerate configuration files"
+ echo " restart Restart all PLC subsystems"
+ exit 1
+}
+
+# Get options
+while getopts "vh" opt ; do
+ case $opt in
+ v)
+ verbose=1
+ set -x
+ ;;
+ h|*)
+ usage
+ ;;
+ esac
+done
+
+shift $(($OPTIND - 1))
+if [ -z "$1" ] ; then
+ usage
+fi
+
+exec 3>&1
+exec 4>&2
+if [ $verbose -eq 0 ] ; then
+ exec 1>/dev/null
+ exec 2>/dev/null
+fi
+
+# Generate and load configuration
+reload
+. /etc/planetlab/plc_config
+
+RETVAL=0
+
+start ()
+{
+ for step in "${steps[@]}" ; do
+ echo -n $"PLC: Starting $step: " >&3
+ RETVAL=0
+ config_$step start
+ if [ $RETVAL -eq 0 ] ; then
+ success $"PLC: $step startup" >&3
+ else
+ failure $"PLC: $step startup" >&3
+ fi
+ echo >&3
+ done
+}
+
+stop ()
+{
+ for i in $(seq 1 $nsteps) ; do
+ step=${steps[$(($nsteps - $i))]}
+ echo -n $"PLC: Shutting down $step: " >&3
+ RETVAL=0
+ config_$step stop
+ if [ $RETVAL -eq 0 ] ; then
+ success $"PLC: $step shutdown" >&3
+ else
+ failure $"PLC: $step shutdown" >&3
+ fi
+ echo >&3
+ done
+}
+
+case "$1" in
+ start|stop)
+ $1
+ ;;
+
+ restart)
+ stop
+ start
+ ;;
+
+ reload)
+ ;;
+
+ *)
+ usage >&3
+ ;;
+esac
+
+exit $RETVAL
--- /dev/null
+#!/bin/bash
+#
+# plc Manages all PLC services on this machine
+#
+# chkconfig: 2345 99 5
+#
+# description: Manages all PLC services on this machine
+#
+# $Id: plc.init,v 1.6 2005/04/24 19:48:11 mlhuang Exp $
+#
+
+PATH=/sbin:/bin:/usr/bin:/usr/sbin
+
+# Source function library.
+. /etc/init.d/functions
+
+# Source configuration
+if [ -f /etc/sysconfig/plc ] ; then
+ . /etc/sysconfig/plc
+fi
+
+RETVAL=0
+
+# Get options
+while getopts "vh" opt ; do
+ case $opt in
+ v)
+ verbose=1
+ set -x
+ ;;
+ h|*)
+ usage
+ ;;
+ esac
+done
+
+start ()
+{
+ echo -n $"Mounting PLC: "
+
+ if ! grep -q $PLC_ROOT.img /proc/mounts ; then
+ if ! e2fsck -a $PLC_ROOT.img | logger -t "PLC" ; then
+ e2fsck $PLC_ROOT.img
+ fi
+ mount -o loop $PLC_ROOT.img $PLC_ROOT
+ RETVAL=$(($RETVAL+$?))
+ fi
+ if ! grep -q $PLC_DATA /proc/mounts ; then
+ mount -t none -o bind,rw $PLC_DATA $PLC_ROOT/data
+ RETVAL=$(($RETVAL+$?))
+ fi
+ if ! grep -q $PLC_ROOT/proc /proc/mounts ; then
+ mount -t proc none $PLC_ROOT/proc
+ RETVAL=$(($RETVAL+$?))
+ fi
+
+ if [ $RETVAL -eq 0 ]; then
+ success $"PLC mount"
+ else
+ failure $"PLC mount"
+ fi
+ echo
+
+ chroot $PLC_ROOT /sbin/service plc $PLC_OPTIONS start
+ RETVAL=$?
+}
+
+stop ()
+{
+ chroot $PLC_ROOT /sbin/service plc $PLC_OPTIONS stop
+
+ echo -n $"Unmounting PLC: "
+
+ umount $PLC_ROOT/proc
+ RETVAL=$(($RETVAL+$?))
+ umount $PLC_ROOT/data
+ RETVAL=$(($RETVAL+$?))
+ umount $PLC_ROOT
+ RETVAL=$(($RETVAL+$?))
+
+ if [ $RETVAL -eq 0 ]; then
+ success $"PLC unmount"
+ else
+ failure $"PLC unmount"
+ fi
+ echo
+}
+
+restart ()
+{
+ stop
+ start
+}
+
+case "$1" in
+ start|stop|restart)
+ $1
+ ;;
+
+ *)
+ echo "Usage: $0 {start|stop|restart}"
+ RETVAL=1
+ ;;
+esac
+
+exit $RETVAL
--- /dev/null
+# Fedora Core release version to base the installation on. Currently
+# supported: 2, 4.
+%define releasever 2
+
+Vendor: PlanetLab
+Packager: PlanetLab Central <support@planet-lab.org>
+Distribution: PlanetLab 3.0
+URL: http://cvs.planet-lab.org/cvs/myplc
+
+Summary: PlanetLab Central (PLC) Portable Installation
+Name: myplc
+Version: %{releasever}.0
+Release: 1
+License: BSD
+Group: Applications/Systems
+Source0: %{name}-%{version}.tar.gz
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
+
+%define debug_package %{nil}
+
+%description
+MyPLC is a complete PlanetLab Central (PLC) portable installation
+contained within a chroot jail. The default installation consists of a
+web server, an XML-RPC API server, a boot server, and a database
+server: the core components of PLC. The installation may be customized
+through a graphical interface. All PLC services are started up and
+shut down through a single System V init script installed in the host
+system.
+
+%prep
+%setup -q
+
+%build
+cd myplc
+./build.sh -r %{releasever} -d %{_datadir}
+
+# If run under sudo, allow user to delete the build directory
+if [ -n "$SUDO_USER" ] ; then
+ chown -R $SUDO_USER .
+ # Some temporary chroot files like /var/empty/sshd and
+ # /usr/bin/sudo get created with non-readable permissions.
+ find . -not -perm +0600 -exec chmod u+rw {} \;
+fi
+
+%install
+rm -rf $RPM_BUILD_ROOT
+
+cd myplc
+install -d -m 755 $RPM_BUILD_ROOT/%{_datadir}/plc/fc%{releasever}
+install -D -m 644 fc%{releasever}.img $RPM_BUILD_ROOT/%{_datadir}/plc/fc%{releasever}.img
+find data%{releasever} | cpio -p -d -u $RPM_BUILD_ROOT/%{_datadir}/plc/
+install -D -m 755 host.init $RPM_BUILD_ROOT/%{_sysconfdir}/init.d/plc
+install -D -m 644 plc.sysconfig $RPM_BUILD_ROOT/%{_sysconfdir}/sysconfig/plc
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+# If run under sudo, allow user to delete the built RPM
+if [ -n "$SUDO_USER" ] ; then
+ chown $SUDO_USER %{_rpmdir}/%{_arch}/%{name}-%{version}-%{release}.%{_arch}.rpm
+fi
+
+%post
+chkconfig --add plc
+chkconfig plc on
+
+%preun
+# 0 = erase, 1 = upgrade
+if [ $1 -eq 0 ] ; then
+ chkconfig plc off
+ chkconfig --del plc
+fi
+
+%files
+%defattr(-,root,root,-)
+%dir %{_datadir}/plc/fc%{releasever}
+%{_datadir}/plc/fc%{releasever}.img
+%{_datadir}/plc/data%{releasever}
+%{_sysconfdir}/init.d/plc
+%{_sysconfdir}/sysconfig/plc
+
+%changelog
+* Fri Mar 17 2006 Mark Huang <mlhuang@CS.Princeton.EDU> - 0.1-1
+- Initial build.
+
--- /dev/null
+#!/usr/bin/python
+#
+# Script for basic access to the PlanetLab Central (PLC) configuration
+# file store.
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id$
+#
+
+import sys
+import os
+import fcntl
+import getopt
+import signal
+from plc_config import PLCConfiguration
+
+
+def usage():
+ print """
+Script to access the PLC configuration file store.
+
+Usage: %s [OPTION]... [FILES]
+ Conversion:
+
+ --shell Output shell configuration file
+ --python Output Python configuration file
+ --php Output PHP configuration file
+
+ Information:
+
+ --variables List names of all variables
+ --packages List names of all packages
+ --comps List comps.xml from configuration
+
+ Basic variable value manipulation:
+
+ --category= Category identifier
+ --variable= Variable identifier
+ --value= Variable value
+
+ Basic package list manipulation:
+
+ --group= Package group identifier
+ --package= Package name
+ --type= Package type
+
+ Miscellaneous:
+
+ -h, --help This message
+ -s, --save Save changes to first configuration file
+""".lstrip() % sys.argv[0]
+ sys.exit(1)
+
+
+def main():
+ plc = PLCConfiguration()
+ fileobjs = []
+ output = None
+ category = {}
+ variable = {}
+ group = {}
+ package = {}
+ save = False
+
+ # Standard options
+ shortopts = "hs"
+ longopts = ["shell", "bash", "python",
+ "php",
+ "xml",
+ "variables",
+ "packages",
+ "comps",
+ "category=", "variable=", "value=",
+ "group=", "package=", "type=",
+ "help",
+ "save"]
+
+ try:
+ (opts, argv) = getopt.gnu_getopt(sys.argv[1:], shortopts, longopts)
+ except Exception, err:
+ sys.stderr.write("Error: " + str(err) + os.linesep)
+ sys.exit(1)
+
+ for (opt, optval) in opts:
+ if opt == "--shell" or \
+ opt == "--bash" or \
+ opt == "--python":
+ output = plc.output_shell
+ elif opt == "--php":
+ output = plc.output_php
+ elif opt == "--xml":
+ output = plc.output_xml
+ elif opt == "--variables":
+ output = plc.output_variables
+ elif opt == "--packages":
+ output = plc.output_packages
+ elif opt == "--comps":
+ output = plc.output_comps
+ elif opt == "--category":
+ category['id'] = optval
+ elif opt == "--variable":
+ variable['id'] = optval
+ elif opt == "--value":
+ variable['value'] = optval
+ elif opt == "--group":
+ group['id'] = optval
+ elif opt == "--package":
+ package['name'] = optval
+ elif opt == "--type":
+ package['type'] = optval
+ elif opt == '-s' or opt == "--save":
+ save = True
+ elif opt == '-h' or opt == "--help":
+ usage()
+
+ # Try the default
+ if not argv:
+ argv = ["/etc/planetlab/plc_config.xml"]
+
+ # Merge all files
+ for file in argv:
+ try:
+ plc.load(file)
+ except IOError:
+ pass
+ except Exception, err:
+ sys.stderr.write("Error: %s: %s" % (file, str(err)) + os.linesep)
+ sys.exit(1)
+
+ # --category, --variable, --value
+ if category.has_key('id') and variable.has_key('id'):
+ if variable.has_key('value'):
+ plc.set(category, variable)
+ else:
+ (category, variable) = plc.get(category['id'], variable['id'])
+ if variable.has_key('value'):
+ print variable['value']
+
+ # --shell, --php, --xml
+ if output is not None:
+ sys.stdout.write(output())
+
+ # --save
+ if save:
+ plc.save()
+
+
+if __name__ == '__main__':
+ main()
--- /dev/null
+#!/usr/bin/python
+#
+# Merge PlanetLab Central (PLC) configuration files into a variety of
+# output formats. These files represent the global configuration for a
+# PLC installation.
+#
+# Mark Huang <mlhuang@cs.princeton.edu>
+# Copyright (C) 2006 The Trustees of Princeton University
+#
+# $Id$
+#
+
+import xml.dom.minidom
+from StringIO import StringIO
+import time
+import re
+import textwrap
+import codecs
+import os
+import types
+
+
+class PLCConfiguration:
+ """
+ Configuration file store. Optionally instantiate with a file path
+ or object:
+
+ plc = PLCConfiguration()
+ plc = PLCConfiguration(fileobj)
+ plc = PLCConfiguration("/etc/planetlab/plc_config.xml")
+
+ You may load() additional files later, which will be merged into
+ the current configuration:
+
+ plc.load("/etc/planetlab/local.xml")
+
+ You may also save() the configuration. If a file path or object is
+ not specified, the configuration will be written to the file path
+ or object that was first loaded.
+
+ plc.save()
+ plc.save("/etc/planetlab/plc_config.xml")
+ """
+
+ def __init__(self, file = None):
+ impl = xml.dom.minidom.getDOMImplementation()
+ self._dom = impl.createDocument(None, "configuration", None)
+ self._variables = {}
+ self._packages = {}
+ self._files = []
+
+ if file is not None:
+ self.load(file)
+
+
+ def _get_text(self, node):
+ """
+ Get the text of a text node.
+ """
+
+ if node.firstChild and \
+ node.firstChild.nodeType == node.TEXT_NODE:
+ if node.firstChild.data is None:
+ # Interpret simple presence of node as "", not NULL
+ return ""
+ else:
+ return node.firstChild.data
+
+ return None
+
+
+ def _get_text_of_child(self, parent, name):
+ """
+ Get the text of a (direct) child text node.
+ """
+
+ for node in parent.childNodes:
+ if node.nodeType == node.ELEMENT_NODE and \
+ node.tagName == name:
+ return self._get_text(node)
+
+ return None
+
+
+ def _set_text(self, node, data):
+ """
+ Set the text of a text node.
+ """
+
+ if node.firstChild and \
+ node.firstChild.nodeType == node.TEXT_NODE:
+ if data is None:
+ node.removeChild(node.firstChild)
+ else:
+ node.firstChild.data = data
+ elif data is not None:
+ text = TrimText()
+ text.data = data
+ node.appendChild(text)
+
+
+ def _set_text_of_child(self, parent, name, data):
+ """
+ Set the text of a (direct) child text node.
+ """
+
+ for node in parent.childNodes:
+ if node.nodeType == node.ELEMENT_NODE and \
+ node.tagName == name:
+ self._set_text(node, data)
+ return
+
+ child = TrimTextElement(name)
+ self._set_text(child, data)
+ parent.appendChild(child)
+
+
+ def _category_element_to_dict(self, category_element):
+ """
+ Turn a <category> element into a dictionary of its attributes
+ and child text nodes.
+ """
+
+ category = {}
+ category['id'] = category_element.getAttribute('id').lower()
+ for node in category_element.childNodes:
+ if node.nodeType == node.ELEMENT_NODE and \
+ node.tagName in ['name', 'description']:
+ category[node.tagName] = self._get_text_of_child(category_element, node.tagName)
+ category['element'] = category_element
+
+ return category
+
+
+ def _variable_element_to_dict(self, variable_element):
+ """
+ Turn a <variable> element into a dictionary of its attributes
+ and child text nodes.
+ """
+
+ variable = {}
+ variable['id'] = variable_element.getAttribute('id').lower()
+ if variable_element.hasAttribute('type'):
+ variable['type'] = variable_element.getAttribute('type')
+ for node in variable_element.childNodes:
+ if node.nodeType == node.ELEMENT_NODE and \
+ node.tagName in ['name', 'value', 'description']:
+ variable[node.tagName] = self._get_text_of_child(variable_element, node.tagName)
+ variable['element'] = variable_element
+
+ return variable
+
+
+ def _group_element_to_dict(self, group_element):
+ """
+ Turn a <group> element into a dictionary of its attributes
+ and child text nodes.
+ """
+
+ group = {}
+ for node in group_element.childNodes:
+ if node.nodeType == node.ELEMENT_NODE and \
+ node.tagName in ['id', 'name', 'default', 'description', 'uservisible']:
+ group[node.tagName] = self._get_text_of_child(group_element, node.tagName)
+ group['element'] = group_element
+
+ return group
+
+
+ def _packagereq_element_to_dict(self, packagereq_element):
+ """
+ Turns a <packagereq> element into a dictionary of its attributes
+ and child text nodes.
+ """
+
+ package = {}
+ if packagereq_element.hasAttribute('type'):
+ package['type'] = packagereq_element.getAttribute('type')
+ package['name'] = self._get_text(packagereq_element)
+ package['element'] = packagereq_element
+
+ return package
+
+
+ def load(self, file = "/etc/planetlab/plc_config.xml"):
+ """
+ Merge file into configuration store.
+ """
+
+ dom = xml.dom.minidom.parse(file)
+ if type(file) in types.StringTypes:
+ self._files.append(os.path.abspath(file))
+
+ # Parse <variables> section
+ for variables_element in dom.getElementsByTagName('variables'):
+ for category_element in variables_element.getElementsByTagName('category'):
+ category = self._category_element_to_dict(category_element)
+ self.set(category, None)
+
+ for variablelist_element in category_element.getElementsByTagName('variablelist'):
+ for variable_element in variablelist_element.getElementsByTagName('variable'):
+ variable = self._variable_element_to_dict(variable_element)
+ self.set(category, variable)
+
+ # Parse <comps> section
+ for comps_element in dom.getElementsByTagName('comps'):
+ for group_element in comps_element.getElementsByTagName('group'):
+ group = self._group_element_to_dict(group_element)
+ self.add_package(group, None)
+
+ for packagereq_element in group_element.getElementsByTagName('packagereq'):
+ package = self._packagereq_element_to_dict(packagereq_element)
+ self.add_package(group, package)
+
+
+ def save(self, file = None):
+ """
+ Write configuration store to file.
+ """
+
+ if file is None:
+ if self._files:
+ file = self._files[0]
+ else:
+ file = "/etc/planetlab/plc_config.xml"
+
+ if type(file) in types.StringTypes:
+ fileobj = open(file, 'r+')
+ else:
+ fileobj = file
+
+ fileobj.seek(0)
+ fileobj.write(self.output_xml())
+ fileobj.truncate()
+
+ fileobj.close()
+
+
+ def get(self, category_id, variable_id):
+ """
+ Get the specified variable in the specified category.
+
+ Arguments:
+
+ category_id = unique category identifier (e.g., 'plc_www')
+ variable_id = unique variable identifier (e.g., 'port')
+
+ Returns:
+
+ variable = { 'id': "variable_identifier",
+ 'type': "variable_type",
+ 'value': "variable_value",
+ 'name': "Variable name",
+ 'description': "Variable description" }
+ """
+
+ if self._variables.has_key(category_id.lower()):
+ (category, variables) = self._variables[category_id]
+ if variables.has_key(variable_id.lower()):
+ variable = variables[variable_id]
+ else:
+ variable = None
+ else:
+ category = None
+ variable = None
+
+ return (category, variable)
+
+
+ def delete(self, category_id, variable_id):
+ """
+ Delete the specified variable from the specified category. If
+ variable_id is None, deletes all variables from the specified
+ category as well as the category itself.
+
+ Arguments:
+
+ category_id = unique category identifier (e.g., 'plc_www')
+ variable_id = unique variable identifier (e.g., 'port')
+ """
+
+ if self._variables.has_key(category_id.lower()):
+ (category, variables) = self._variables[category_id]
+ if variable_id is None:
+ category['element'].parentNode.removeChild(category['element'])
+ del self._variables[category_id]
+ elif variables.has_key(variable_id.lower()):
+ variable = variables[variable_id]
+ variable['element'].parentNode.removeChild(variable['element'])
+ del variables[variable_id]
+
+
+ def set(self, category, variable):
+ """
+ Add and/or update the specified variable. The 'id' fields are
+ mandatory. If a field is not specified and the category and/or
+ variable already exists, the field will not be updated. If
+ 'variable' is None, only adds and/or updates the specified
+ category.
+
+ Arguments:
+
+ category = { 'id': "category_identifier",
+ 'name': "Category name",
+ 'description': "Category description" }
+
+ variable = { 'id': "variable_identifier",
+ 'type': "variable_type",
+ 'value': "variable_value",
+ 'name': "Variable name",
+ 'description': "Variable description" }
+ """
+
+ if not category.has_key('id') or type(category['id']) not in types.StringTypes:
+ return
+
+ category_id = category['id'].lower()
+
+ if self._variables.has_key(category_id):
+ # Existing category
+ (old_category, variables) = self._variables[category_id]
+
+ # Merge category attributes
+ for tag in ['name', 'description']:
+ if category.has_key(tag):
+ old_category[tag] = category[tag]
+ self._set_text_of_child(old_category['element'], tag, category[tag])
+
+ category_element = old_category['element']
+ else:
+ # Merge into DOM
+ category_element = self._dom.createElement('category')
+ category_element.setAttribute('id', category_id)
+ for tag in ['name', 'description']:
+ if category.has_key(tag):
+ self._set_text_of_child(category_element, tag, category[tag])
+
+ if self._dom.documentElement.getElementsByTagName('variables'):
+ variables_element = self._dom.documentElement.getElementsByTagName('variables')[0]
+ else:
+ variables_element = self._dom.createElement('variables')
+ self._dom.documentElement.appendChild(variables_element)
+ variables_element.appendChild(category_element)
+
+ # Cache it
+ category['element'] = category_element
+ variables = {}
+ self._variables[category_id] = (category, variables)
+
+ if variable is None or not variable.has_key('id') or type(variable['id']) not in types.StringTypes:
+ return
+
+ variable_id = variable['id'].lower()
+
+ if variables.has_key(variable_id):
+ # Existing variable
+ old_variable = variables[variable_id]
+
+ # Merge variable attributes
+ for attribute in ['type']:
+ if variable.has_key(attribute):
+ old_variable[attribute] = variable[attribute]
+ old_variable['element'].setAttribute(attribute, variable[attribute])
+ for tag in ['name', 'value', 'description']:
+ if variable.has_key(tag):
+ old_variable[tag] = variable[tag]
+ self._set_text_of_child(old_variable['element'], tag, variable[tag])
+ else:
+ # Merge into DOM
+ variable_element = self._dom.createElement('variable')
+ variable_element.setAttribute('id', variable_id)
+ for attribute in ['type']:
+ if variable.has_key(attribute):
+ variable_element.setAttribute(attribute, variable[attribute])
+ for tag in ['name', 'value', 'description']:
+ if variable.has_key(tag):
+ self._set_text_of_child(variable_element, tag, variable[tag])
+
+ if category_element.getElementsByTagName('variablelist'):
+ variablelist_element = category_element.getElementsByTagName('variablelist')[0]
+ else:
+ variablelist_element = self._dom.createElement('variablelist')
+ category_element.appendChild(variablelist_element)
+ variablelist_element.appendChild(variable_element)
+
+ # Cache it
+ variable['element'] = variable_element
+ variables[variable_id] = variable
+
+
+ def get_package(self, group_id, package_name):
+ """
+ Get the specified package in the specified package group.
+
+ Arguments:
+
+ group_id - unique group id (e.g., 'plc')
+ package_name - unique package name (e.g., 'postgresql')
+
+ Returns:
+
+ package = { 'name': "package_name",
+ 'type': "mandatory|optional" }
+ """
+
+ if self._packages.has_key(group_id.lower()):
+ (group, packages) = self._packages[group_id]
+ if packages.has_key(package_name):
+ package = packages[package_name]
+ else:
+ package = None
+ else:
+ group = None
+ package = None
+
+ return (group, package)
+
+
+ def delete_package(self, group_id, package_name):
+ """
+ Deletes the specified variable from the specified category. If
+ variable_id is None, deletes all variables from the specified
+ category as well as the category itself.
+
+ Arguments:
+
+ group_id - unique group id (e.g., 'plc')
+ package_name - unique package name (e.g., 'postgresql')
+ """
+
+ if self._packages.has_key(group_id):
+ (group, packages) = self._packages[group_id]
+ if package_name is None:
+ group['element'].parentNode.removeChild(group['element'])
+ del self._packages[group_id]
+ elif packages.has_key(package_name.lower()):
+ package = packages[package_name]
+ package['element'].parentNode.removeChild(package['element'])
+ del packages[package_name]
+
+
+ def add_package(self, group, package):
+ """
+ Add and/or update the specified package. The 'id' and 'name'
+ fields are mandatory. If a field is not specified and the
+ package or group already exists, the field will not be
+ updated. If package is None, only adds/or updates the
+ specified group.
+
+ Arguments:
+
+ group = { 'id': "group_identifier",
+ 'name': "Group name",
+ 'default': "true|false",
+ 'description': "Group description",
+ 'uservisible': "true|false" }
+
+ package = { 'name': "package_name",
+ 'type': "mandatory|optional" }
+ """
+
+ if not group.has_key('id'):
+ return
+
+ group_id = group['id']
+
+ if self._packages.has_key(group_id):
+ # Existing group
+ (old_group, packages) = self._packages[group_id]
+
+ # Merge group attributes
+ for tag in ['id', 'name', 'default', 'description', 'uservisible']:
+ if group.has_key(tag):
+ old_group[tag] = group[tag]
+ self._set_text_of_child(old_group['element'], tag, group[tag])
+
+ group_element = old_group['element']
+ else:
+ # Merge into DOM
+ group_element = self._dom.createElement('group')
+ for tag in ['id', 'name', 'default', 'description', 'uservisible']:
+ if group.has_key(tag):
+ self._set_text_of_child(group_element, tag, group[tag])
+
+ if self._dom.documentElement.getElementsByTagName('comps'):
+ comps_element = self._dom.documentElement.getElementsByTagName('comps')[0]
+ else:
+ comps_element = self._dom.createElement('comps')
+ self._dom.documentElement.appendChild(comps_element)
+ comps_element.appendChild(group_element)
+
+ # Cache it
+ group['element'] = group_element
+ packages = {}
+ self._packages[group_id] = (group, packages)
+
+ if package is None or not package.has_key('name'):
+ return
+
+ package_name = package['name']
+ if packages.has_key(package_name):
+ # Existing package
+ old_package = packages[package_name]
+
+ # Merge variable attributes
+ for attribute in ['type']:
+ if package.has_key(attribute):
+ old_package[attribute] = package[attribute]
+ old_package['element'].setAttribute(attribute, package[attribute])
+ else:
+ # Merge into DOM
+ packagereq_element = TrimTextElement('packagereq')
+ self._set_text(packagereq_element, package_name)
+ for attribute in ['type']:
+ if package.has_key(attribute):
+ packagereq_element.setAttribute(attribute, package[attribute])
+
+ if group_element.getElementsByTagName('packagelist'):
+ packagelist_element = group_element.getElementsByTagName('packagelist')[0]
+ else:
+ packagelist_element = self._dom.createElement('packagelist')
+ group_element.appendChild(packagelist_element)
+ packagelist_element.appendChild(packagereq_element)
+
+ # Cache it
+ package['element'] = packagereq_element
+ packages[package_name] = package
+
+
+ def variables(self):
+ """
+ Return all variables.
+
+ Returns:
+
+ variables = { 'category_id': (category, variablelist) }
+
+ category = { 'id': "category_identifier",
+ 'name': "Category name",
+ 'description': "Category description" }
+
+ variablelist = { 'variable_id': variable }
+
+ variable = { 'id': "variable_identifier",
+ 'type': "variable_type",
+ 'value': "variable_value",
+ 'name': "Variable name",
+ 'description': "Variable description" }
+ """
+
+ return self._variables
+
+
+ def packages(self):
+ """
+ Return all packages.
+
+ Returns:
+
+ packages = { 'group_id': (group, packagelist) }
+
+ group = { 'id': "group_identifier",
+ 'name': "Group name",
+ 'default': "true|false",
+ 'description': "Group description",
+ 'uservisible': "true|false" }
+
+ packagelist = { 'package_name': package }
+
+ package = { 'name': "package_name",
+ 'type': "mandatory|optional" }
+ """
+
+ return self._packages
+
+
+ def _sanitize_variable(self, category_id, variable):
+ assert variable.has_key('id')
+ # Prepend variable name with category label
+ id = category_id + "_" + variable['id']
+ # And uppercase it
+ id = id.upper()
+
+ if variable.has_key('type'):
+ type = variable['type']
+ else:
+ type = None
+
+ if variable.has_key('name'):
+ name = variable['name']
+ else:
+ name = None
+
+ if variable.has_key('value') and variable['value'] is not None:
+ value = variable['value']
+ if type == "int" or type == "double":
+ # bash, Python, and PHP do not require that numbers be quoted
+ pass
+ elif type == "boolean":
+ # bash, Python, and PHP can all agree on 0 and 1
+ if value == "true":
+ value = "1"
+ else:
+ value = "0"
+ else:
+ # bash, Python, and PHP all support strong single quoting
+ value = "'" + value.replace("'", "\\'") + "'"
+ else:
+ value = None
+
+ if variable.has_key('description') and variable['description'] is not None:
+ description = variable['description']
+ # Collapse consecutive whitespace
+ description = re.sub(r'\s+', ' ', description)
+ # Wrap comments at 70 columns
+ wrapper = textwrap.TextWrapper()
+ comments = wrapper.wrap(description)
+ else:
+ comments = None
+
+ return (id, name, value, comments)
+
+
+ def _header(self):
+ header = """
+DO NOT EDIT. This file was automatically generated at
+%s from:
+
+%s
+""" % (time.asctime(), os.linesep.join(self._files))
+
+ # Get rid of the surrounding newlines
+ return header.strip().split(os.linesep)
+
+
+ def output_shell(self, encoding = "utf-8"):
+ """
+ Return variables as a shell script.
+ """
+
+ buf = codecs.lookup(encoding)[3](StringIO())
+ buf.writelines(["# " + line + os.linesep for line in self._header()])
+
+ for (category_id, (category, variables)) in self._variables.iteritems():
+ for variable in variables.values():
+ (id, name, value, comments) = self._sanitize_variable(category_id, variable)
+ buf.write(os.linesep)
+ if name is not None:
+ buf.write("# " + name + os.linesep)
+ if comments is not None:
+ buf.writelines(["# " + line + os.linesep for line in comments])
+ # bash does not have the concept of NULL
+ if value is not None:
+ buf.write(id + "=" + value + os.linesep)
+
+ return buf.getvalue()
+
+
+ def output_php(self, encoding = "utf-8"):
+ """
+ Return variables as a PHP script.
+ """
+
+ buf = codecs.lookup(encoding)[3](StringIO())
+ buf.write("<?php" + os.linesep)
+ buf.writelines(["// " + line + os.linesep for line in self._header()])
+
+ for (category_id, (category, variables)) in self._variables.iteritems():
+ for variable in variables.values():
+ (id, name, value, comments) = self._sanitize_variable(category_id, variable)
+ buf.write(os.linesep)
+ if name is not None:
+ buf.write("// " + name + os.linesep)
+ if comments is not None:
+ buf.writelines(["// " + line + os.linesep for line in comments])
+ if value is None:
+ value = 'NULL'
+ buf.write("DEFINE('%s', %s);" % (id, value) + os.linesep)
+
+ buf.write("?>" + os.linesep)
+
+ return buf.getvalue()
+
+
+ def output_xml(self, encoding = "utf-8"):
+ """
+ Return variables in original XML format.
+ """
+
+ buf = codecs.lookup(encoding)[3](StringIO())
+ self._dom.writexml(buf, addindent = " ", indent = "", newl = "\n", encoding = encoding)
+
+ return buf.getvalue()
+
+
+ def output_variables(self, encoding = "utf-8"):
+ """
+ Return list of all variable names.
+ """
+
+ buf = codecs.lookup(encoding)[3](StringIO())
+
+ for (category_id, (category, variables)) in self._variables.iteritems():
+ for variable in variables.values():
+ (id, name, value, comments) = self._sanitize_variable(category_id, variable)
+ buf.write(id + os.linesep)
+
+ return buf.getvalue()
+
+
+ def output_packages(self, encoding = "utf-8"):
+ """
+ Return list of all packages.
+ """
+
+ buf = codecs.lookup(encoding)[3](StringIO())
+
+ for (group, packages) in self._packages.values():
+ buf.write(os.linesep.join(packages.keys()))
+
+ if buf.tell():
+ buf.write(os.linesep)
+
+ return buf.getvalue()
+
+
+ def output_comps(self, encoding = "utf-8"):
+ """
+ Return <comps> section of configuration.
+ """
+
+ if self._dom is None or \
+ not self._dom.getElementsByTagName("comps"):
+ return
+ comps = self._dom.getElementsByTagName("comps")[0]
+
+ impl = xml.dom.minidom.getDOMImplementation()
+ doc = impl.createDocument(None, "comps", None)
+
+ buf = codecs.lookup(encoding)[3](StringIO())
+
+ # Pop it off the DOM temporarily
+ parent = comps.parentNode
+ parent.removeChild(comps)
+
+ doc.replaceChild(comps, doc.documentElement)
+ doc.writexml(buf, encoding = encoding)
+
+ # Put it back
+ parent.appendChild(comps)
+
+ return buf.getvalue()
+
+
+# xml.dom.minidom.Text.writexml adds surrounding whitespace to textual
+# data when pretty-printing. Override this behavior.
+class TrimText(xml.dom.minidom.Text):
+ def writexml(self, writer, indent="", addindent="", newl=""):
+ xml.dom.minidom.Text.writexml(self, writer, "", "", "")
+
+
+class TrimTextElement(xml.dom.minidom.Element):
+ def writexml(self, writer, indent="", addindent="", newl=""):
+ writer.write(indent)
+ xml.dom.minidom.Element.writexml(self, writer, "", "", "")
+ writer.write(newl)
+
+
+if __name__ == '__main__':
+ import sys
+ if len(sys.argv) > 1 and sys.argv[1] in ['build', 'install']:
+ from distutils.core import setup
+ setup(py_modules=["plc_config"])
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE configuration PUBLIC "-//PlanetLab Central//DTD PLC configuration//EN" "configuration.dtd">
+
+<configuration>
+ <variables>
+ <category id="plc">
+ <name>System</name>
+ <description>Basic system variables. Be sure that the values of
+ these variables are the same across all machines in your
+ installation.</description>
+
+ <variablelist>
+ <variable id="name" type="string">
+ <name>Name</name>
+ <value>PlanetLab Test</value>
+ <description>The name of this PLC installation. It is used in
+ the name of the default system site (e.g., PlanetLab Central)
+ and in the names of various administrative entities (e.g.,
+ PlanetLab Support).</description>
+ </variable>
+
+ <variable id="slice_prefix" type="string">
+ <name>Slice Prefix</name>
+ <value>pl</value>
+ <description>The abbreviated name of this PLC
+ installation. It is used as the prefix for system slices
+ (e.g., pl_conf). Warning: Currently, this variable should
+ not be changed once set.</description>
+ </variable>
+
+ <variable id="root_user" type="password">
+ <name>Root Account</name>
+ <value>root@test.planet-lab.org</value>
+ <description>The name of the initial administrative
+ account. We recommend that this account be used only to create
+ additional accounts associated with real
+ administrators, then disabled.</description>
+ </variable>
+
+ <variable id="root_password" type="password">
+ <name>Root Password</name>
+ <value>root</value>
+ <description>The password of the initial administrative
+ account. Also the password of the root account on the Boot
+ CD.</description>
+ </variable>
+
+ <!-- The following are not actually meant to be configurable
+ as variables. The web interface should allow the file to
+ be downloaded, or its contents replaced by a file upload,
+ but the actual <value> shouldn't need to be changed. -->
+
+ <variable id="root_ssh_key_pub" type="file">
+ <name>Root SSH Public Key</name>
+ <value>/etc/planetlab/root_ssh_key.pub</value>
+ <description>The SSH public key used to access the root
+ account on your nodes.</description>
+ </variable>
+
+ <variable id="root_ssh_key" type="file">
+ <name>Root SSH Private Key</name>
+ <value>/etc/planetlab/root_ssh_key.rsa</value>
+ <description>The SSH private key used to access the root
+ account on your nodes.</description>
+ </variable>
+
+ <variable id="debug_ssh_key_pub" type="file">
+ <name>Debug SSH Public Key</name>
+ <value>/etc/planetlab/debug_ssh_key.pub</value>
+ <description>The SSH public key used to access the root
+ account on your nodes when they are in Debug mode.</description>
+ </variable>
+
+ <variable id="debug_ssh_key" type="file">
+ <name>Debug SSH Private Key</name>
+ <value>/etc/planetlab/debug_ssh_key.rsa</value>
+ <description>The SSH private key used to access the root
+ account on your nodes when they are in Debug mode.</description>
+ </variable>
+
+ <variable id="root_gpg_key_pub" type="file">
+ <name>Root GPG Public Keyring</name>
+ <value>/etc/planetlab/pubring.gpg</value>
+ <description>The GPG public keyring used to sign the Boot
+ Manager and all node packages.</description>
+ </variable>
+
+ <variable id="root_gpg_key" type="file">
+ <name>Root GPG Private Keyring</name>
+ <value>/etc/planetlab/secring.gpg</value>
+ <description>The SSH private key used to access the root
+ account on your nodes.</description>
+ </variable>
+ </variablelist>
+ </category>
+
+ <category id="plc_net">
+ <name>Network</name>
+ <description>Network environment.</description>
+
+ <variablelist>
+ <variable id="dns1" type="ip">
+ <name>Primary DNS Server</name>
+ <value>128.112.136.10</value>
+ <description>Primary DNS server address.</description>
+ </variable>
+
+ <variable id="dns2" type="ip">
+ <name>Secondary DNS Server</name>
+ <value>128.112.136.12</value>
+ <description>Secondary DNS server address.</description>
+ </variable>
+ </variablelist>
+ </category>
+
+ <category id="plc_mail">
+ <name>Mail</name>
+ <description>Many maintenance scripts, as well as the API and
+ web site themselves, send e-mail notifications and
+ warnings.</description>
+
+ <variablelist>
+ <variable id="enabled" type="boolean">
+ <name>Enable Mail</name>
+ <value>false</value>
+ <description>Set to false to suppress all e-mail notifications
+ and warnings.</description>
+ </variable>
+
+ <variable id="support_address">
+ <name>Support Address</name>
+ <value>root@localhost</value>
+ <description>This address is used for support
+ requests. Support requests may include traffic complaints,
+ security incident reporting, web site malfunctions, and
+ general requests for information. We recommend that the
+ address be aliased to a ticketing system such as Request
+ Tracker.</description>
+ </variable>
+
+ <variable id="boot_address">
+ <name>Boot Messages Address</name>
+ <value>root@localhost</value>
+ <description>The API will notify this address when a problem
+ occurs during node installation or boot. If a domain is not
+ specified, the default system domain will be used
+ name.</description>
+ </variable>
+ </variablelist>
+ </category>
+
+ <category id="plc_db">
+ <name>Database Server</name>
+ <description>Database server definitions.</description>
+
+ <variablelist>
+ <variable id="enabled" type="boolean">
+ <name>Enabled</name>
+ <value>true</value>
+ <description>Enable the database server on this
+ machine.</description>
+ </variable>
+
+ <variable id="type" type="string">
+ <name>Type</name>
+ <value>postgresql</value>
+ <description>The type of database server. Currently, only
+ postgresql is supported.</description>
+ </variable>
+
+ <variable id="host" type="hostname">
+ <name>Hostname</name>
+ <value>localhost</value>
+ <description>The fully qualified hostname or IP address of
+ the database server. This hostname must be resolvable and
+ reachable by the rest of your installation.</description>
+ </variable>
+
+ <variable id="name" type="string">
+ <name>Database Name</name>
+ <value>planetlab3</value>
+ <description>The name of the database to access.</description>
+ </variable>
+
+ <variable id="user" type="string">
+ <name>Database Username</name>
+ <value>pgsqluser</value>
+ <description>The username to use when accessing the
+ database.</description>
+ </variable>
+
+ <variable id="password" type="password">
+ <name>Database Password</name>
+ <value></value>
+ <description>The password to use when accessing the
+ database. If left blank, one will be
+ generated.</description>
+ </variable>
+ </variablelist>
+ </category>
+
+ <category id="plc_api">
+ <name>API Server</name>
+ <description>API (XML-RPC) server definitions.</description>
+
+ <variablelist>
+ <variable id="enabled" type="boolean">
+ <name>Enabled</name>
+ <value>true</value>
+ <description>Enable the API server on this
+ machine.</description>
+ </variable>
+
+ <variable id="debug" type="boolean">
+ <name>Debug</name>
+ <value>false</value>
+ <description>Enable verbose API debugging. Do not enable on
+ a production system!</description>
+ </variable>
+
+ <variable id="host" type="hostname">
+ <name>Hostname</name>
+ <value>localhost</value>
+ <description>The fully qualified hostname or IP address of
+ the API server. This hostname must be resolvable and
+ reachable by the rest of your installation, as well as your
+ nodes.</description>
+ </variable>
+
+ <variable id="ssl_port" type="int">
+ <name>Port</name>
+ <value>80</value>
+ <description>The TCP port number through which the API
+ should be accessed. Warning: SSL (port 443) access is not
+ fully supported by the website code yet. We recommend that
+ port 80 be used for now and that the API server either run
+ on the same machine as the web server, or that they both be
+ on a secure wired network.</description>
+ </variable>
+
+ <variable id="path" type="string">
+ <name>Path</name>
+ <value>/PLCAPI/</value>
+ <description>The base path of the API URL.</description>
+ </variable>
+
+ <variable id="maintenance_user" type="string">
+ <name>Maintenance User</name>
+ <value>maint@test.planet-lab.org</value>
+ <description>The username of the maintenance account. This
+ account is used by local scripts that perform automated
+ tasks, and cannot be used for normal logins.</description>
+ </variable>
+
+ <variable id="maintenance_password" type="password">
+ <name>Maintenance Password</name>
+ <value></value>
+ <description>The password of the maintenance account. If
+ left blank, one will be generated. We recommend that the
+ password be changed periodically.</description>
+ </variable>
+
+ <variable id="maintenance_sources" type="hostname">
+ <name>Authorized Hosts</name>
+ <value></value>
+ <description>A space-separated list of IP addresses allowed
+ to access the API through the maintenance account. If left
+ blank, the API, web, and boot servers are
+ allowed.</description>
+ </variable>
+
+ <!-- The following are not actually meant to be configurable
+ as variables. The web interface should allow the file to
+ be downloaded, or its contents replaced by a file upload,
+ but the actual <value> shouldn't need to be changed. -->
+
+ <variable id="ssl_crt" type="file">
+ <name>SSL Certificate</name>
+ <value>/etc/planetlab/api_ssl.crt</value>
+ <description>The signed SSL certificate to use for HTTPS
+ access. If not specified or non-existent, a self-signed
+ certificate will be generated.</description>
+ </variable>
+
+ <variable id="ssl_key" type="file">
+ <name>SSL Key</name>
+ <value>/etc/planetlab/api_ssl.key</value>
+ <description>The corresponding SSL private key. If not
+ specified or non-existent, a self-signed certificate will be
+ generated.</description>
+ </variable>
+
+ <variable id="ticket_key" type="file">
+ <name>Slice Ticket Private Key</name>
+ <value>/etc/planetlab/slice-ticket-key-nopass.pem</value>
+ <description>The private PEM key file used to sign slice
+ tickets.</description>
+ </variable>
+
+ <variable id="ticket_key_pub" type="file">
+ <name>Slice Ticket Public Key</name>
+ <value>/etc/planetlab/slice-ticket-key-public.pem</value>
+ <description>The public PEM key file used to verify signed
+ slice tickets.</description>
+ </variable>
+ </variablelist>
+ </category>
+
+ <category id="plc_www">
+ <name>Web Server</name>
+ <description>Web server definitions.</description>
+
+ <variablelist>
+ <variable id="enabled" type="boolean">
+ <name>Enabled</name>
+ <value>true</value>
+ <description>Enable the web server on this
+ machine.</description>
+ </variable>
+
+ <variable id="debug" type="boolean">
+ <name>Debug</name>
+ <value>false</value>
+ <description>Enable debugging output on web pages. Do not
+ enable on a production system!</description>
+ </variable>
+
+ <variable id="host" type="hostname">
+ <name>Hostname</name>
+ <value>localhost</value>
+ <description>The fully qualified hostname or IP address of
+ the web server. This hostname must be resolvable and
+ reachable by the rest of your installation, as well as your
+ nodes.</description>
+ </variable>
+
+ <variable id="port" type="int">
+ <name>Port</name>
+ <value>80</value>
+ <description>The TCP port number through which the
+ unprotected portions of the web site should be
+ accessed.</description>
+ </variable>
+
+ <variable id="ssl_port" type="int">
+ <name>SSL Port</name>
+ <value>443</value>
+ <description>The TCP port number through which the protected
+ portions of the web site should be accessed.</description>
+ </variable>
+
+ <!-- The following are not actually meant to be configurable
+ as variables. The web interface should allow the file to
+ be downloaded, or its contents replaced by a file upload,
+ but the actual <value> shouldn't need to be changed. -->
+
+ <variable id="ssl_crt" type="file">
+ <name>SSL Certificate</name>
+ <value>/etc/planetlab/www_ssl.crt</value>
+ <description>The signed SSL certificate to use for HTTPS
+ access. If not specified or non-existent, a self-signed
+ certificate will be generated.</description>
+ </variable>
+
+ <variable id="ssl_key" type="file">
+ <name>SSL Key</name>
+ <value>/etc/planetlab/www_ssl.key</value>
+ <description>The corresponding SSL private key. If not
+ specified or non-existent, a self-signed certificate will be
+ generated.</description>
+ </variable>
+ </variablelist>
+ </category>
+
+ <category id="plc_boot">
+ <name>Boot Server</name>
+ <description>Boot server definitions. Multiple boot servers
+ may be brought up for load balancing, but we recommend that a
+ single DNS round-robin system be implemented so that the
+ following variables are the same across all of
+ them.</description>
+
+ <variablelist>
+ <variable id="enabled" type="boolean">
+ <name>Enabled</name>
+ <value>true</value>
+ <description>Enable the boot server on this
+ machine.</description>
+ </variable>
+
+ <variable id="host" type="hostname">
+ <name>Hostname</name>
+ <value>localhost</value>
+ <description>The fully qualified hostname or IP address of
+ the boot server. This hostname must be resolvable and
+ reachable by the rest of your installation, as well as your
+ nodes.</description>
+ </variable>
+
+ <variable id="port" type="int">
+ <name>Port</name>
+ <value>80</value>
+ <description>The TCP port number through which the
+ unprotected portions of the boot server should be
+ accessed.</description>
+ </variable>
+
+ <variable id="ssl_port" type="int">
+ <name>SSL Port</name>
+ <value>443</value>
+ <description>The TCP port number through which the protected
+ portions of the boot server should be
+ accessed.</description>
+ </variable>
+
+ <!-- The following are not actually meant to be configurable
+ as variables. The web interface should allow the file to
+ be downloaded, or its contents replaced by a file upload,
+ but the actual <value> shouldn't need to be changed. -->
+
+ <variable id="ssl_crt" type="binary">
+ <name>SSL Certificate</name>
+ <value>/etc/planetlab/boot_ssl.crt</value>
+ <description>The signed SSL certificate to use for HTTPS
+ access. If not specified, or non-existent a self-signed
+ certificate will be generated.</description>
+ </variable>
+
+ <variable id="ssl_key" type="binary">
+ <name>SSL Key</name>
+ <value>/etc/planetlab/boot_ssl.key</value>
+ <description>The corresponding SSL private key. If not
+ specified or non-existent, a self-signed certificate will be
+ generated.</description>
+ </variable>
+ </variablelist>
+ </category>
+ </variables>
+
+ <comps>
+ <group>
+ <id>plc</id>
+ <name>PlanetLab Central</name>
+ <default>true</default>
+ <description>PlanetLab Central Packages</description>
+ <uservisible>true</uservisible>
+ <packagelist>
+ <!-- Sending mail -->
+ <packagereq type="mandatory">sendmail</packagereq>
+ <packagereq type="mandatory">sendmail-cf</packagereq>
+
+ <!-- (Optional) Synchronizing with PLC -->
+ <packagereq type="mandatory">rsync</packagereq>
+
+ <!-- Cron jobs -->
+ <packagereq type="mandatory">vixie-cron</packagereq>
+
+ <!-- Other utilities -->
+ <packagereq type="mandatory">cvs</packagereq>
+ <packagereq type="mandatory">curl</packagereq>
+ <packagereq type="mandatory">wget</packagereq>
+
+ <!-- yum >=2.2 uses a new repository format -->
+ <packagereq type="mandatory">createrepo</packagereq>
+
+ <!-- For mkpasswd -->
+ <packagereq type="mandatory">expect</packagereq>
+
+ <!-- Almost all scripts are written in Python -->
+ <packagereq type="mandatory">python</packagereq>
+
+ <!-- For various Python scripts that access the API -->
+ <packagereq type="mandatory">plcapilib</packagereq>
+
+ <!-- Database server -->
+ <packagereq type="mandatory">postgresql</packagereq>
+ <packagereq type="mandatory">postgresql-server</packagereq>
+ <packagereq type="mandatory">postgresql-python</packagereq>
+
+ <!-- (Secure) web server -->
+ <packagereq type="mandatory">httpd</packagereq>
+ <packagereq type="mandatory">mod_ssl</packagereq>
+
+ <!-- Web pages are written primarily in PHP. A few pages still
+ access the DB directly. -->
+ <packagereq type="mandatory">php</packagereq>
+ <packagereq type="mandatory">php-pgsql</packagereq>
+ <packagereq type="mandatory">php-xmlrpc</packagereq>
+
+ <!-- Need GD for ImageCreate(), etc. -->
+ <packagereq type="mandatory">gd</packagereq>
+ <packagereq type="mandatory">php-gd</packagereq>
+
+ <!-- API server is implemented in mod_python -->
+ <packagereq type="mandatory">mod_python</packagereq>
+
+ <!-- API server uses a few non-standard packages -->
+ <packagereq type="mandatory">PyXML</packagereq>
+
+ <!-- API server uses SSL to sign tickets -->
+ <packagereq type="mandatory">xmlsec1</packagereq>
+ <packagereq type="mandatory">xmlsec1-openssl</packagereq>
+ <packagereq type="mandatory">openssl</packagereq>
+
+ <!-- bootcd is generated using mkisofs -->
+ <packagereq type="mandatory">mkisofs</packagereq>
+
+ <!-- bootcd and bootmanager images are signed using GPG -->
+ <packagereq type="mandatory">gnupg</packagereq>
+
+ <!-- bootmanager requires uuencode -->
+ <packagereq type="mandatory">sharutils</packagereq>
+ </packagelist>
+ </group>
+
+ </comps>
+
+</configuration>