From 6ba56d57f045f3809d569248488b44b18d13ad83 Mon Sep 17 00:00:00 2001 From: Mark Huang Date: Mon, 27 Mar 2006 17:36:46 +0000 Subject: [PATCH 1/1] MyPLC: portable self-contained PLC installation --- api-config | 97 +++++++ build.sh | 216 ++++++++++++++ guest.init | 634 ++++++++++++++++++++++++++++++++++++++++ host.init | 106 +++++++ myplc.spec | 85 ++++++ plc-config | 151 ++++++++++ plc_config.py | 773 +++++++++++++++++++++++++++++++++++++++++++++++++ plc_config.xml | 518 +++++++++++++++++++++++++++++++++ 8 files changed, 2580 insertions(+) create mode 100755 api-config create mode 100755 build.sh create mode 100755 guest.init create mode 100755 host.init create mode 100644 myplc.spec create mode 100755 plc-config create mode 100644 plc_config.py create mode 100644 plc_config.xml diff --git a/api-config b/api-config new file mode 100755 index 0000000..5901a6f --- /dev/null +++ b/api-config @@ -0,0 +1,97 @@ +#!/usr/bin/python +# +# Bootstraps the PLC database with a default administrator account and +# a default site. +# +# Mark Huang +# 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() diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..d172a84 --- /dev/null +++ b/build.sh @@ -0,0 +1,216 @@ +#!/bin/bash +# +# Builds a Fedora based PLC image. You should be able to run this +# script multiple times without a problem. +# +# Mark Huang +# 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 < 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 </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 <>/etc/planetlab/plc_api + + cat >/etc/planetlab/php/site_constants.php <<"EOF" +'); +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 <$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 <" \ + -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 < + SetHandler python-program + PythonPath "sys.path + ['/usr/share/plc_api']" + PythonHandler mod_pythonXMLRPC + +EOF + fi + + cat < + Redirect /db https://$PLC_WWW_HOST:$PLC_WWW_SSL_PORT/db + +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 <&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 diff --git a/host.init b/host.init new file mode 100755 index 0000000..0c101e2 --- /dev/null +++ b/host.init @@ -0,0 +1,106 @@ +#!/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 diff --git a/myplc.spec b/myplc.spec new file mode 100644 index 0000000..131e079 --- /dev/null +++ b/myplc.spec @@ -0,0 +1,85 @@ +# Fedora Core release version to base the installation on. Currently +# supported: 2, 4. +%define releasever 2 + +Vendor: PlanetLab +Packager: PlanetLab Central +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 - 0.1-1 +- Initial build. + diff --git a/plc-config b/plc-config new file mode 100755 index 0000000..840ee46 --- /dev/null +++ b/plc-config @@ -0,0 +1,151 @@ +#!/usr/bin/python +# +# Script for basic access to the PlanetLab Central (PLC) configuration +# file store. +# +# Mark Huang +# 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() diff --git a/plc_config.py b/plc_config.py new file mode 100644 index 0000000..b3ae90d --- /dev/null +++ b/plc_config.py @@ -0,0 +1,773 @@ +#!/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 +# 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 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 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 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 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 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 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("" + 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 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"]) diff --git a/plc_config.xml b/plc_config.xml new file mode 100644 index 0000000..7c85cf3 --- /dev/null +++ b/plc_config.xml @@ -0,0 +1,518 @@ + + + + + + + System + Basic system variables. Be sure that the values of + these variables are the same across all machines in your + installation. + + + + Name + PlanetLab Test + 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). + + + + Slice Prefix + pl + 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. + + + + Root Account + root@test.planet-lab.org + 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. + + + + Root Password + root + The password of the initial administrative + account. Also the password of the root account on the Boot + CD. + + + + + + Root SSH Public Key + /etc/planetlab/root_ssh_key.pub + The SSH public key used to access the root + account on your nodes. + + + + Root SSH Private Key + /etc/planetlab/root_ssh_key.rsa + The SSH private key used to access the root + account on your nodes. + + + + Debug SSH Public Key + /etc/planetlab/debug_ssh_key.pub + The SSH public key used to access the root + account on your nodes when they are in Debug mode. + + + + Debug SSH Private Key + /etc/planetlab/debug_ssh_key.rsa + The SSH private key used to access the root + account on your nodes when they are in Debug mode. + + + + Root GPG Public Keyring + /etc/planetlab/pubring.gpg + The GPG public keyring used to sign the Boot + Manager and all node packages. + + + + Root GPG Private Keyring + /etc/planetlab/secring.gpg + The SSH private key used to access the root + account on your nodes. + + + + + + Network + Network environment. + + + + Primary DNS Server + 128.112.136.10 + Primary DNS server address. + + + + Secondary DNS Server + 128.112.136.12 + Secondary DNS server address. + + + + + + Mail + Many maintenance scripts, as well as the API and + web site themselves, send e-mail notifications and + warnings. + + + + Enable Mail + false + Set to false to suppress all e-mail notifications + and warnings. + + + + Support Address + root@localhost + 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. + + + + Boot Messages Address + root@localhost + 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. + + + + + + Database Server + Database server definitions. + + + + Enabled + true + Enable the database server on this + machine. + + + + Type + postgresql + The type of database server. Currently, only + postgresql is supported. + + + + Hostname + localhost + The fully qualified hostname or IP address of + the database server. This hostname must be resolvable and + reachable by the rest of your installation. + + + + Database Name + planetlab3 + The name of the database to access. + + + + Database Username + pgsqluser + The username to use when accessing the + database. + + + + Database Password + + The password to use when accessing the + database. If left blank, one will be + generated. + + + + + + API Server + API (XML-RPC) server definitions. + + + + Enabled + true + Enable the API server on this + machine. + + + + Debug + false + Enable verbose API debugging. Do not enable on + a production system! + + + + Hostname + localhost + 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. + + + + Port + 80 + 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. + + + + Path + /PLCAPI/ + The base path of the API URL. + + + + Maintenance User + maint@test.planet-lab.org + The username of the maintenance account. This + account is used by local scripts that perform automated + tasks, and cannot be used for normal logins. + + + + Maintenance Password + + The password of the maintenance account. If + left blank, one will be generated. We recommend that the + password be changed periodically. + + + + Authorized Hosts + + 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. + + + + + + SSL Certificate + /etc/planetlab/api_ssl.crt + The signed SSL certificate to use for HTTPS + access. If not specified or non-existent, a self-signed + certificate will be generated. + + + + SSL Key + /etc/planetlab/api_ssl.key + The corresponding SSL private key. If not + specified or non-existent, a self-signed certificate will be + generated. + + + + Slice Ticket Private Key + /etc/planetlab/slice-ticket-key-nopass.pem + The private PEM key file used to sign slice + tickets. + + + + Slice Ticket Public Key + /etc/planetlab/slice-ticket-key-public.pem + The public PEM key file used to verify signed + slice tickets. + + + + + + Web Server + Web server definitions. + + + + Enabled + true + Enable the web server on this + machine. + + + + Debug + false + Enable debugging output on web pages. Do not + enable on a production system! + + + + Hostname + localhost + 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. + + + + Port + 80 + The TCP port number through which the + unprotected portions of the web site should be + accessed. + + + + SSL Port + 443 + The TCP port number through which the protected + portions of the web site should be accessed. + + + + + + SSL Certificate + /etc/planetlab/www_ssl.crt + The signed SSL certificate to use for HTTPS + access. If not specified or non-existent, a self-signed + certificate will be generated. + + + + SSL Key + /etc/planetlab/www_ssl.key + The corresponding SSL private key. If not + specified or non-existent, a self-signed certificate will be + generated. + + + + + + Boot Server + 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. + + + + Enabled + true + Enable the boot server on this + machine. + + + + Hostname + localhost + 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. + + + + Port + 80 + The TCP port number through which the + unprotected portions of the boot server should be + accessed. + + + + SSL Port + 443 + The TCP port number through which the protected + portions of the boot server should be + accessed. + + + + + + SSL Certificate + /etc/planetlab/boot_ssl.crt + The signed SSL certificate to use for HTTPS + access. If not specified, or non-existent a self-signed + certificate will be generated. + + + + SSL Key + /etc/planetlab/boot_ssl.key + The corresponding SSL private key. If not + specified or non-existent, a self-signed certificate will be + generated. + + + + + + + + plc + PlanetLab Central + true + PlanetLab Central Packages + true + + + sendmail + sendmail-cf + + + rsync + + + vixie-cron + + + cvs + curl + wget + + + createrepo + + + expect + + + python + + + plcapilib + + + postgresql + postgresql-server + postgresql-python + + + httpd + mod_ssl + + + php + php-pgsql + php-xmlrpc + + + gd + php-gd + + + mod_python + + + PyXML + + + xmlsec1 + xmlsec1-openssl + openssl + + + mkisofs + + + gnupg + + + sharutils + + + + + + -- 2.43.0