#!/bin/bash #author/maintainer klaus@merlinux.de DESCRIPTION="create and delete zope-2 instances" USAGE="USAGE: zope-instance (list installed instances and their tcp ports) zope-instance help (these lines) zope-instance install 2.8.1 intranet (install+start a new instance with zope version 2.8.1) zope-instance use 2.8.3 intranet (upgrade to zope version 2.8.3) zope-instance deinstall intranet (delete instance) zope-instance deactivate intranet (stop instance and do not start at bootup) zope-instance activate intranet (start instance and start at bootup (DEFAULT)) (intranet is just an example name, 2.8.1 and 2.8.3 are just example versions) you need root rights to change anything " set -e #exit on any error INSTANCEBASEDIR=/www/zope-instances USERDIR=/cell/zope-instances #users will find stuff more easily in here BINBASEDIR=/opt/zope- #Zope binaries, parentdir and dirname prefix SRCBASEDIR=/opt/src PWFILE=".root" PWGENERATOR=/admin/bin/makepassword DOWNLOADER=/admin/package/zope-instance/zopedownload GROUPINFO="/admin/bin/cellinfo group" DEBUG=1 #1: extra output 0: no extra output VERSION="2.3, fixed finding free portbase" FQDN=`/admin/bin/cellinfo fqdn` [ -z "$FQDN" ] && FQDN=localhost UNIXUSER=zope #run the instance as this user if ! OWNINGGROUP=`$GROUPINFO` 2>/dev/null ; then # ^^^ do not confuse with: if ! [ $OWNINGGROUP = `$GROUPINFO` ] OWNINGGROUP=zope fi OWNER="zope:$OWNINGGROUP" DEPENDS_ON="unix-user $UNIXUSER (primary group should be: $OWNINGGROUP), $GROUPINFO, $PWGENERATOR, $DOWNLOADER" ZOPEADMINUSER=root #internal zope admin, not unix user (even if the same name) main() { case $1 in ""|status) status ;; help|-h|--help) note "$USAGE" ;; activate) activate "$2" ;; deactivate) deactivate "$2" ;; install) install "$2" "$3" ;; use) use "$2" "$3" ;; deinstall) deinstall "$2" ;; *) note "unkwnown input, call me without options to get usage help" esac } status() { note "INSTALLED INSTANCES: " find "$INSTANCEBASEDIR" -mindepth 1 -maxdepth 1 -type d|while read instance; do if [ ! -e "$instance"/etc/zope.conf ] ; then continue; fi USED=`grep -E '^port-base ' "$instance"/etc/zope.conf|sed 's/^port-base[ ]*\([0-9][0-9]*\).*/\1/'` if [ -z "$USED" ]; then note "$instance with tcp port 8080" else note "$instance with tcp port $((USED + 8080))" fi done note " see options with $0 help" #TODO: show whether running and activated } activate() { NAME="$1" INSTANCEDIR="$INSTANCEBASEDIR"/"$NAME" [ -d "$INSTANCEDIR" ] || errorexit "does not exist: $INSTANCEDIR" if [ -e /etc/init.d/"$NAME" ] ; then /etc/init.d/"$NAME" start else note " WARNING: /etc/init.d/$NAME was not found" fi note "- preparing automatic start during boot" rc-update add "$NAME" default &>/dev/null } deactivate() { #TODO: sometimes processes are not stopped (zdaemon/zdrun.p...) NAME="$1" INSTANCEDIR="$INSTANCEBASEDIR"/"$NAME" [ -d "$INSTANCEDIR" ] || errorexit "does not exist: $INSTANCEDIR" note "- stopping instance $NAME" if [ -e /etc/init.d/"$NAME" ] ; then /etc/init.d/"$NAME" stop 2>/dev/null || true else note " WARNING: /etc/init.d/$NAME was not found" fi note "- preventing automatic start during boot" rc-update del "$NAME" default &>/dev/null } use() { #check input [ -z "$1" ] && errorexit "version not supported, usage: \"zope-instance install 2.x.y INSTANCENAME\"" echo "$1"|grep "^2\." &>/dev/null || errorexit "version $1 not supported, usage: \"zope-instance install 2.x.y INSTANCENAME\"" echo "$1"|grep "[^0-9.]" &>/dev/null && errorexit "version $1 not supported, usage: \"zope-instance install 2.x.y INSTANCENAME\"" VERSION=$1 [ -z "$2" ] && errorexit "name missing, usage: \"zope-instance use VERSION INSTANCENAME\"" NAME=$2 #check whether binaries are ready BINDIR="$BINBASEDIR"$VERSION note " zope binary directory: $BINDIR" if [ ! -d "$BINDIR" ] ; then makebinaries "$BINDIR" "$VERSION" elif [ `ls -1 --color=no "$BINDIR"|wc -l` = 0 ] ; then makebinaries "$BINDIR" "$VERSION" fi deactivate "$NAME" BINMASKDIR="${BINBASEDIR}${NAME}" note " masking $BINDIR as $BINMASKDIR to make instances more independent of zope binary version, ease upgrading" note " zope binary dir used: $BINMASKDIR" ln -snf "$BINDIR" "$BINMASKDIR" activate "$NAME" } install() { #check input [ -z "$1" ] && errorexit "version not supported, usage: \"zope-instance install 2.x.y INSTANCENAME\"" echo "$1"|grep "^2\." &>/dev/null || errorexit "version $1 not supported, usage: \"zope-instance install 2.x.y INSTANCENAME\"" echo "$1"|grep "[^0-9.]" &>/dev/null && errorexit "version $1 not supported, usage: \"zope-instance install 2.x.y INSTANCENAME\"" VERSION=$1 [ -z "$2" ] && errorexit "name missing, usage: \"zope-instance install VERSION INSTANCENAME\"" NAME=$2 #here is the instance INSTANCEDIR="$INSTANCEBASEDIR"/"$NAME" [ -e "$INSTANCEDIR" ] && errorexit "already used: $NAME" mkdir -p "$INSTANCEDIR" &>/dev/null ||true note " instance-directory: $INSTANCEDIR (also reachable in $USERDIR/$NAME)" #check whether binaries are ready BINDIR="$BINBASEDIR"$VERSION debug "zope-directory: $BINDIR" if [ ! -d "$BINDIR" ] ; then makebinaries "$BINDIR" "$VERSION" elif [ `ls -1 --color=no "$BINDIR"|wc -l` = 0 ] ; then makebinaries "$BINDIR" "$VERSION" fi makeinstance "$BINDIR" "$INSTANCEDIR" "$VERSION" "$NAME" } notempty() { note " WARNING: $1 is not empty" select I in proceed cancel ; do echo "$I"; return; done } makeinstance() { BINDIR="$1" INSTANCEDIR="$2" VERSION="$3" NAME="$4" BINMASKDIR="${BINBASEDIR}${NAME}" #change zope version easy lateron note " zope binary dir used: $BINMASKDIR" ZOPEROOTPW=`$PWGENERATOR` echo "$ZOPEROOTPW" > "${INSTANCEDIR}"/"${PWFILE}" note "- setting admin login: root with password: $ZOPEROOTPW (saved in ${INSTANCEDIR}/${PWFILE})" note "- populating instance home" ln -snf "$BINDIR" "$BINMASKDIR" "$BINMASKDIR"/bin/mkzopeinstance.py --user ${ZOPEADMINUSER}:"$ZOPEROOTPW" --dir "$INSTANCEDIR" chown -R "$OWNER" "$INSTANCEDIR" chmod -R g+rwX "$INSTANCEDIR" chmod g+xs "$INSTANCEDIR" "$INSTANCEDIR"/var "$INSTANCEDIR"/log setuser "$INSTANCEDIR" PORT=`settcpport "$INSTANCEDIR" "$NAME"` initadd "$INSTANCEDIR" "$NAME" #also starts the zope instance chmod -R g+rwX "$INSTANCEDIR" #for files created by the first start chown -R "$OWNER" "$INSTANCEDIR" apache_conf "$PORT" "$NAME" } apache_conf() { PORT="$1" NAME="$2" APACHEFILE="$INSTANCEBASEDIR"/"$NAME".apacheconf debug "creating $APACHEFILE" touch "$APACHEFILE" chown "$OWNER" "$APACHEFILE" chmod 664 "$APACHEFILE" echo " RewriteEngine On RewriteRule ^/$NAME(.*) http://localhost:$PORT/VirtualHostBase/https/$FQDN:443/VirtualHostRoot/_vh_${NAME}\$1 [P] # alternative methods: ##Virtual Host Monster and ProxyPass: #ProxyPass /$NAME http://localhost:$PORT/VirtualHostBase/https/$FQDN:443/VirtualHostRoot/_vh_$NAME ##working but ssl is reverted to non-ssl after first klick: #ProxyPass /$NAME http://localhost:$PORT/VirtualHostBase/http/$FQDN:80/VirtualHostRoot/_vh_$NAME ##Siteroot and ProxyPass: #ProxyPass /$NAME http://$FQDN:$PORT/ #ProxyPassReverse /$NAME http://$FQDN:$PORT/ #ProxyPass /misc_ http://$FQDN:$PORT/misc_ #ProxyPass /p_ http://$FQDN:$PORT/p_ ##to let all links in zope start with a given base, ##add a siteroot in Zope (e.g. Base: http://$FQDN and Path: /$NAME) ##Otherwise above Proxypass rules ##will only disguise the real URL with its TCP-Port ##until the first click on an internal link " >> "$APACHEFILE" note " wrote $USERDIR/$NAME.apacheconf" note " apache configured to show zope on http://$FQDN/$NAME" note " - but superior redirections may override" note " - and you have to add a VirtualHostMonster inside zope to make it work" apache2ctl configtest && /etc/init.d/apache2 restart } setuser() { INSTANCEDIR="$1" cp "$INSTANCEDIR"/etc/zope.conf "$INSTANCEDIR"/etc/zope.conf.default cat "$INSTANCEDIR"/etc/zope.conf.default|sed 's/^[# ]*effective-user.*/effective-user '${UNIXUSER}'/' > "$INSTANCEDIR"/etc/zope.conf } note() { echo "$@" >&2 } settcpport() { INSTANCEDIR="$1" NAME="$2" note "- Setting Port" PORTBASE=`getfreeportbase "$NAME"` if [ $PORTBASE -gt 0 ] ; then cp "$INSTANCEDIR"/etc/zope.conf "$INSTANCEDIR"/etc/zope.conf.backup cat "$INSTANCEDIR"/etc/zope.conf.backup|sed 's/^[# ]*port-base.*/port-base '${PORTBASE}'/' > "$INSTANCEDIR"/etc/zope.conf fi note " zope instance is at http://$FQDN:$((PORTBASE + 8080))/manage" echo $((PORTBASE + 8080)) } getfreeportbase() { NAME="$1" BASE=1000 while [ is`is_port_used $BASE "$NAME"` = isused ]; do debug "portbase is used: $BASE" BASE=$((BASE + 1000)) done debug "portbase is free: $BASE" echo $BASE } is_port_used() { SELF="$INSTANCEBASEDIR"/"$2" try=$1 debug "trying portrange $try" find "$INSTANCEBASEDIR" -mindepth 1 -maxdepth 1 -type d|while read instance; do if [ "$instance" = "$SELF" ] ; then continue; fi if [ ! -e "$instance"/etc/zope.conf ] ; then continue; fi USED=`grep -E '^port-base ' "$instance"/etc/zope.conf|sed 's/^[ ]*port-base[ ]*\([0-9][0-9]*\).*/\1/'` debug "$instance uses \"$USED\"" >&2 if [ -z "$USED" ]; then if [ $try = 0 ] ; then echo used break fi continue fi if [ $USED = $try ] ; then echo used debug "portbase $try is already used" break fi done } debug() { [ "$DEBUG" = 1 ] && echo " DEBUG: $@" >&2 } initadd() { INSTANCEDIR="$1" NAME="$2" SCRIPTPATH=/etc/init.d/"$2" #make init script note "- creating init script" echo "#!/sbin/runscript depend() { use net after logger } start() { ebegin \"Starting zope instance ${INSTANCEDIR}\" sudo -u zope ${INSTANCEDIR}/bin/zopectl start eend $? } stop() { ebegin \"Stopping zope instance ${INSTANCEDIR}\" sudo -u zope ${INSTANCEDIR}/bin/zopectl stop eend $? } " > "$SCRIPTPATH" chmod 744 "$SCRIPTPATH" note " system init script (root only): $SCRIPTPATH" #note " adding to default runlevel" #rc-update add "$NAME" default >/dev/null activate "$NAME" } makebinaries() { BINDIR="$1" VERSION="$2" mkdir -p "$BINDIR" mkdir -p "$SRCBASEDIR" &>/dev/null ||true cd "$SRCBASEDIR" SRCDIR=Zope-"$VERSION" SRCDIR=`find . -maxdepth 1 -type d -iname "$SRCDIR*"|tail -n 1` if [ ! -d "$SRCDIR" ] || [ `ls -1 --color=no "$SRCDIR"|wc -l` = 0 ] ; then SRCFILE=`find . -maxdepth 1 -type f -iname "Zope-${VERSION}*.*z*"|tail -n 1` if [ ! -e "$SRCFILE" ] ; then #download note "- downloading $VERSION" SRCFILE=`"$DOWNLOADER" "$VERSION"` [ -z "$SRCFILE" ] && errorexit "download failed" fi PACK=${SRCFILE##*.} #unpack src note "- unpacking zope source $SRCFILE" [ $PACK = tgz ] && tar zxf "$SRCFILE" [ $PACK = gz ] && tar zxf "$SRCFILE" [ $PACK = bz2 ] && tar jxf "$SRCFILE" SRCDIR=Zope-"$VERSION" SRCDIR=`find . -maxdepth 1 -type d -iname "$SRCDIR*"|tail -n 1` fi cd "$SRCDIR" note " sourcedir is `pwd`" note "- configuring" configure "$BINDIR" note "- building" make > /dev/null note "- copying files (make install)" make install > /dev/null } configure() { tmp=/tmp/confout.$$ BINDIR="$1" ./configure --prefix="$BINDIR" 2>&1 > "$tmp" || BAD=1 if [ "$BAD" = 1 ] ; then if grep -i -A 2 "No .* Python version found" "$tmp" ; then PYTHON=`choosepython "$tmp"|head -n 1` PYV=`pythonversion "$PYTHON"` note "- Trying to convince Zope to work with $PYTHON ($PYV)" BAD=0 ./configure --prefix="$BINDIR" --with-python="$PYTHON" 2>&1 > "$tmp" || BAD=1 if [ $BAD = 1 ] ; then note "ERROR: ./configure --prefix=$BINDIR --with-python=$VERSION" note "(in `pwd`)" note "see $tmp for details. Last 5 lines:" note tail -n 5 "$tmp" exit 1 fi else note "ERROR: ./configure --prefix=$BINDIR" note "(in `pwd`)" note "see $tmp for details. Last 5 lines:" note tail -n 5 "$tmp" exit 1 fi fi rm "$tmp" } choosepython() { REQUEST="$1" HAVE=/usr/bin/python #last resort, may not work perfectly WANT=`grep -i "Python version .* before continuing" "$REQUEST" |sed 's/.*Python version[ ]*//;s/[ ]*before.*//' ` debug "wanted python version: $WANT" WANT=`echo "$WANT"|sed 's/\([0-9]\.[0-9]\).*/\1/'` debug "searching python version: $WANT" for TRY in /usr/bin/python*.* /usr/local/bin/python*.*; do [ -e "$TRY" ] || continue VERSION=`echo "$TRY"|sed 's/.*python//'` debug "comparing installed version $VERSION with searched $WANT" if [ "$VERSION" = "$WANT" ] ; then debug "found fitting version of $TRY" echo "$TRY" fi done echo "$HAVE" } pythonversion() { FULL="$1" #full path and executable name if [ -x "$FULL" ]; then CMD="import string,sys;a=string.split(sys.version)[0]" # Strip trailing + from version number CMD="$CMD;a=(a[-1]=='+')and(a[:-1])or(a);print a" VERSION=`"$FULL" -c "$CMD"` fi echo "$VERSION" } deinstall() { NAME="$1" INSTANCEDIR="$INSTANCEBASEDIR"/"$NAME" [ -d "$INSTANCEDIR" ] || errorexit "does not exist: $INSTANCEDIR" deactivate "$NAME" note "- removing initscript" rm /etc/init.d/"$NAME" &>/dev/null || true note "- removing instance files" cd /tmp #a bit safer rm -rf "$INSTANCEDIR" rm "$INSTANCEBASEDIR"/"$NAME".apacheconf rm "${BINBASEDIR}${NAME}" } errorexit() { echo "ERROR: $@, exiting" >&2 exit 1 } main $@