summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGunnar Wrobel <wrobel@gentoo.org>2007-02-21 22:11:48 +0000
committerGunnar Wrobel <wrobel@gentoo.org>2007-02-21 22:11:48 +0000
commitd3ea96e12549b4f75e314418de97a6820a07466f (patch)
treec6443803a46269a49cc0bf92cc6d61b6288f6fe6
downloadwebapp-config-d3ea96e12549b4f75e314418de97a6820a07466f.tar.gz
webapp-config-d3ea96e12549b4f75e314418de97a6820a07466f.tar.bz2
webapp-config-d3ea96e12549b4f75e314418de97a6820a07466f.zip
Added current version
svn path=/trunk/webapp-config/; revision=2
-rw-r--r--.svn.ignore2
-rw-r--r--AUTHORS.txt22
-rw-r--r--CHANGES.txt1046
-rw-r--r--ChangeLog443
-rw-r--r--MANIFEST.in7
-rw-r--r--WebappConfig/.svn.ignore1
-rw-r--r--WebappConfig/__init__.py1
-rw-r--r--WebappConfig/config.py1357
-rw-r--r--WebappConfig/content.py752
-rw-r--r--WebappConfig/db.py721
-rw-r--r--WebappConfig/debug.py501
-rw-r--r--WebappConfig/dotconfig.py282
-rw-r--r--WebappConfig/ebuild.py340
-rw-r--r--WebappConfig/filetype.py231
-rw-r--r--WebappConfig/permissions.py282
-rw-r--r--WebappConfig/protect.py245
-rw-r--r--WebappConfig/sandbox.py114
-rw-r--r--WebappConfig/server.py344
-rw-r--r--WebappConfig/tests/.svn.ignore1
-rw-r--r--WebappConfig/tests/dtest.py31
-rw-r--r--WebappConfig/tests/testfiles/contents/.webapp-test-1.012
-rw-r--r--WebappConfig/tests/testfiles/contents/.webapp-test-1.112
-rw-r--r--WebappConfig/tests/testfiles/contents/app/.webapp-test-1.09
-rw-r--r--WebappConfig/tests/testfiles/contents/app/dir1/.keep0
-rw-r--r--WebappConfig/tests/testfiles/contents/app/dir1/webapp_test0
-rw-r--r--WebappConfig/tests/testfiles/contents/app/dir2/webapp_test0
-rw-r--r--WebappConfig/tests/testfiles/contents/app/dir3/webapp_test0
-rw-r--r--WebappConfig/tests/testfiles/contents/app/dir4/webapp_test0
-rw-r--r--WebappConfig/tests/testfiles/contents/app/test11
-rw-r--r--WebappConfig/tests/testfiles/contents/app/test21
l---------WebappConfig/tests/testfiles/contents/app/test31
-rw-r--r--WebappConfig/tests/testfiles/contents/app/test41
-rw-r--r--WebappConfig/tests/testfiles/contents/app/test51
-rw-r--r--WebappConfig/tests/testfiles/contents/app/test71
-rw-r--r--WebappConfig/tests/testfiles/contents/app2/.webapp-test-1.05
-rw-r--r--WebappConfig/tests/testfiles/contents/app2/dir1/webapp_test0
-rw-r--r--WebappConfig/tests/testfiles/contents/app2/dir2/test10
-rw-r--r--WebappConfig/tests/testfiles/contents/app2/dir2/webapp_test0
-rw-r--r--WebappConfig/tests/testfiles/contents/app2/test10
-rw-r--r--WebappConfig/tests/testfiles/contents/app2/test20
l---------WebappConfig/tests/testfiles/contents/app2/test31
-rw-r--r--WebappConfig/tests/testfiles/htdocs/complain/.webapp26
-rw-r--r--WebappConfig/tests/testfiles/htdocs/complain/.webapp-cool-1.1.10
-rw-r--r--WebappConfig/tests/testfiles/htdocs/horde/.webapp26
-rw-r--r--WebappConfig/tests/testfiles/installtest/dir21
-rw-r--r--WebappConfig/tests/testfiles/installtest/test21
-rw-r--r--WebappConfig/tests/testfiles/installtest/test31
-rw-r--r--WebappConfig/tests/testfiles/protect/complex/._cfg0001_test0
-rw-r--r--WebappConfig/tests/testfiles/protect/complex/._cfg0033_test0
-rw-r--r--WebappConfig/tests/testfiles/protect/complex/._cfg0800_test0
-rw-r--r--WebappConfig/tests/testfiles/protect/empty/keep0
-rw-r--r--WebappConfig/tests/testfiles/protect/simple/._cfg0000_test0
-rw-r--r--WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/config-files2
-rwxr-xr-xWebappConfig/tests/testfiles/share-webapps/horde/3.0.5/hooks/test3
-rw-r--r--WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/htdocs/dir1/test10
-rw-r--r--WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/htdocs/dir2/webapp_test0
-rw-r--r--WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/htdocs/test10
-rw-r--r--WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/htdocs/test20
-rw-r--r--WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/installed_by_webapp_eclass0
-rw-r--r--WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/postinst-en.txt33
-rw-r--r--WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/server-owned-files2
-rw-r--r--WebappConfig/tests/testfiles/share-webapps/installtest/1.0/config-files1
-rw-r--r--WebappConfig/tests/testfiles/share-webapps/installtest/1.0/htdocs/dir1/webapp_test0
-rw-r--r--WebappConfig/tests/testfiles/share-webapps/installtest/1.0/htdocs/dir2/webapp_test0
-rw-r--r--WebappConfig/tests/testfiles/share-webapps/installtest/1.0/htdocs/test11
-rw-r--r--WebappConfig/tests/testfiles/share-webapps/installtest/1.0/htdocs/test21
-rw-r--r--WebappConfig/tests/testfiles/share-webapps/installtest/1.0/htdocs/test31
-rw-r--r--WebappConfig/tests/testfiles/share-webapps/installtest/1.0/htdocs/test41
-rw-r--r--WebappConfig/tests/testfiles/share-webapps/installtest/1.0/installed_by_webapp_eclass0
-rw-r--r--WebappConfig/tests/testfiles/share-webapps/installtest/1.0/server-owned-files1
-rw-r--r--WebappConfig/tests/testfiles/share-webapps/uninstalled/6.6.6/installed_by_webapp_eclass0
-rw-r--r--WebappConfig/tests/testfiles/webapps/gallery/1.4.4_p6/installs1
-rw-r--r--WebappConfig/tests/testfiles/webapps/gallery/2.0_rc2/installs0
-rw-r--r--WebappConfig/tests/testfiles/webapps/horde/3.0.5/installs1
-rw-r--r--WebappConfig/tests/testfiles/webapps/phpldapadmin/0.9.7_alpha4/installs1
-rw-r--r--WebappConfig/version.py19
-rw-r--r--WebappConfig/worker.py563
-rw-r--r--WebappConfig/wrapper.py110
-rwxr-xr-xconfig/webapp-config229
-rw-r--r--doc/.svn.ignore4
-rw-r--r--doc/Makefile43
-rw-r--r--doc/webapp-config.5.xml216
-rw-r--r--doc/webapp-config.8.xml573
-rw-r--r--doc/webapp-eclass.xml567
-rw-r--r--doc/webapp.eclass.5.xml444
-rw-r--r--ebuild/webapp-config-1.50.16.ebuild58
-rw-r--r--eclass/webapp.eclass565
-rw-r--r--examples/phpmyadmin-2.5.4-r1.ebuild93
-rw-r--r--examples/phpmyadmin-2.5.4-r2.ebuild92
-rw-r--r--examples/postinstall-en.txt13
-rwxr-xr-xsbin/webapp-cleaner144
-rwxr-xr-xsbin/webapp-config44
-rwxr-xr-xsetup.py21
93 files changed, 10670 insertions, 0 deletions
diff --git a/.svn.ignore b/.svn.ignore
new file mode 100644
index 0000000..0ca67d0
--- /dev/null
+++ b/.svn.ignore
@@ -0,0 +1,2 @@
+dist
+MANIFEST
diff --git a/AUTHORS.txt b/AUTHORS.txt
new file mode 100644
index 0000000..70669c6
--- /dev/null
+++ b/AUTHORS.txt
@@ -0,0 +1,22 @@
+AUTHORS
+-------
+
+Original design came from GLEP 11:
+
+ http://glep.gentoo.org/
+
+Code authors:
+
+ Stuart Herbert <stuart@gentoo.org>
+ Renat Lumpau <rl03@gentoo.org>
+ Gunnar Wrobel <php@gunnarwrobel.de>
+
+Significant contributors (testing, review, design suggestions):
+
+ dju`
+ Dietrich Heise
+ Rob Holland
+ Martin Holzner
+ Robin H. Johnson
+ Max Kalika
+ Sven Wegener
diff --git a/CHANGES.txt b/CHANGES.txt
new file mode 100644
index 0000000..6237597
--- /dev/null
+++ b/CHANGES.txt
@@ -0,0 +1,1046 @@
+Deferred For webapp-config-v2
+-----------------------------
+
+ * sqlite database for contents db?
+ * enhancement: introduce metadata.xml support
+ * vhost-config support
+ * support for multiple webapps installed into the same directory
+ (Gentoo Bug #56364)
+
+Version 1.50.15 - Released 19th July 2006
+=========================================
+
+ * enhancement: Added script webapp-cleaner.
+
+Version 1.50.14 - Released 20th April 2006
+==========================================
+
+ * bug fix: Added types import (Gentoo bug #126534)
+ * bug fix: Added support for splitting hostnames into
+ subdomains. (Gentoo bug #126486)
+ * enhancement: Added cherokee support (Gentoo bug #129359)
+ * bug fix: Added -D and -E description. Fixed upgrade
+ instructions (Gentoo bug #129167)
+
+Version 1.50.13 - Released 06th March 2006
+==========================================
+
+ * bug fix: amd64 libdir fix (Gentoo bug #125156)
+
+Version 1.50.12 - Released 06th March 2006
+==========================================
+
+ * bug fix: amd64 libdir fix (Gentoo bug #125156)
+
+Version 1.50.11 - Released 04th March 2006
+==========================================
+
+ * bug fix: Fixed package detection.
+
+Version 1.50.10 - Released 30th January 2006
+============================================
+
+ * bug fix: Both vhost_config_virtual_files and
+ vhost_config_default_dirs were ignored. Fixed.
+ * bug fix: Fixed pass-through of the server variable and modified
+ the uid/gid extraction to result in readable names.
+ * bug fix: Showing the instructions now includes the server
+ argument.
+
+Version 1.50.9 - Released 27th January 2006
+===========================================
+
+ * enhancement: Allowing config-server-owned.
+ * bug fix: Fix for recognizing config protected files.
+ * bug fix: Fixed error message for a problematic configuration
+ configuration file.
+ * bug fix: Fixed behaviour for upgrades. Now shows upgrade
+ instructions.
+
+Version 1.50.7 - Released 9th January 2006
+==========================================
+
+ * bug fix: Another correction to symlinks. We now support broken
+ links.
+ * bug fix: Fix for variables VHOST_SERVER_xID.
+ * bug fix: Adapted running hooks
+ * bug fix: Fixed documentation of "--list-installs"
+
+Version 1.50.6 - Released 4th January 2006
+==========================================
+
+ * bug fix: Removed a problematic doctest so that src_test will work.
+
+Version 1.50.5 - Released 4th January 2006
+==========================================
+
+ * bug fix: Fixed trailing slashes for the -d flag (Gentoo bug
+ #115765)
+ * enhancement: Support for symlinked directories.
+ * enhancement: Ebuild now provides src_test.
+
+Version 1.50.3 - Released 12th December 2005
+============================================
+
+ * bug fix: Removed "root" references for BSD.
+ * bug fix: Fixed handling of content file between upgrades.
+
+Version 1.50.2 - Released 05th December 2005
+============================================
+
+ * bug fix: Reverted hook logic.
+ * bug fix: Added MY_HOSTROOTDIR to the exported variables.
+
+Version 1.50.1 - Released 05th December 2005
+============================================
+
+ * bug fix: fixed problem with missing hook directories.
+
+Version 1.50.0 - Released 24th November 2005
+============================================
+
+ * rewrite in python
+
+Version 1.11-r1 - Released 20st July 2005
+=========================================
+
+ * bug fix: webapp-config removes all '//' occurances from the install
+ directory name
+ - Ensures we don't add otherwise duplicate strings into the database
+ of what is installed where
+ * bug fix: webapp-config no longer reads .webapp files twice during
+ upgrades
+ - Should fix problems of an upgrade removing the wrong version of
+ an app
+ * bug fix: webapp-config no longer appends to .webapp files
+ - Should fix numerous reported problems
+ * bug fix: $PN and $PVR are exported for hook scripts
+ * bug fix: --pretend no longer modifies the .webapp file
+ * bug fix: users can change VHOSTS_HTDOCS_INSECURE, and webapp-config will
+ notice
+ * bug fix: users can change VHOSTS_HTDOCS_SECURE, and webapp-config will
+ notice
+ * bug fix: webapp-config will select the correct target directory when
+ --secure is used
+
+Version 1.11 - Released 30th May 2005
+=====================================
+
+ * r12, r13 and r14 were released separately by Gentoo
+ * v1.11 incorporates the changes made by Gentoo
+ - fix for creating path-to-dir w/ permissions 777
+ - replaced with own fix
+ - fix for insecure creation of temporary files
+ - replaced with fix from vapier@gentoo.org
+ - replaced deprecated qpkg with equery (yuck)
+ - added as-is
+ * bug fix: no longer uses 'id -g' to work out the default group
+ - updated sbin/webapp-config
+ - see Gentoo bug #60155
+ - tested & ready for release
+ * bug fix: change 'etc-upgrade' to 'etc-update' in the man page
+ - updated doc/webapp-config.8.xml
+ - see Gentoo bug #53390
+ - tested & ready for release
+ * bug fix: webapp-config runs with the default umask, except when
+ adding files into their destination directories
+ - updated sbin/webapp-config
+ - see Gentoo bug #53390
+ - also fixes Gentoo bug #91785
+ * bug fix: changed default permissions on .webapp files to be '0600'
+ - also changes default permissions for files in /var/db/webapps
+ - see Gentoo bug #71228
+ - tested & ready for release
+ * bug fix: make the default output more verbose than it currently is
+ - updated sbin/webapp-config
+ - updated lib/servers/basic.sh
+ - see Gentoo bug #64684
+ - tested & ready for release
+ * bug fix: --list-servers works now
+ - updated sbin/webapp-config
+ - see Gentoo bug #69463
+ - tested & ready for release
+ * bug fix: cope with too many protected files
+ - updated sbin/webapp-config
+ - see Gentoo bug #73520
+ - tested & ready for release
+ * bug fix: --pretend does not create any directories any more
+ - updated sbin/webapp-config
+ - see Gentoo bug #80535
+ - tested & ready for release
+ * bug fix: --bug-report needs to automatically switch on --verbose
+ - updated sbin/webapp-config
+ - see Gentoo bug #82655
+ - tested & ready for release
+ * bug fix? ensure we overwrite old files left behind by broken users
+ - see Gentoo bug #89821
+ * bug fix: incorrect instructions for removing an app
+ - updated doc/webapp-config.8.xml
+ - see Gentoo bug #90157
+ - tested & ready for release
+ * bug fix: webapp-config no longer goes around resetting the write
+ permissions of directories it is not responsible for
+ - bug added in v1.10-r14
+ - see Gentoo bug #92388
+ - tested & ready for release
+ * bug fix: webapp.eclass installs SQL files as only readable by root
+ - prevents non-root users stealing passwords listed in these files
+ - updated eclass/webapp.eclass
+ - tested & ready for release
+ * bug fix: --list-installs output included cruft which broke emerge -C
+ - updated sbin/webapp-config
+ * enhancement: added support for postupgrade-en.txt files
+ - updated sbin/webapp-config, doc/webapp-config.5.xml
+ - see Gentoo bug #53585
+ * enhancement: added support for other webservers installed under gentoo
+ - added lib/servers/aolserver.sh
+ - added lib/servers/lighttpd.sh
+ - added a generic 'basic.sh' for servers which seem to run as root
+ - moved lib/servers/apache-basic.sh to be lib/servers/apache.sh
+ - updated config/webapp-config to default to 'apache'
+ - tested & ready for release
+ * enhancement: added short aliases for some of the longer options
+ - updated sbin/webapp-config
+ - updated doc/webapp-config.8.xml
+ - see Gentoo bug #76767
+ - tested & ready for release
+ * enhancement: removed support for 'runbycgibin'
+ - never really worked in the first place
+ - this is best done by setting up an appropriate handler in your webserver
+ - updated sbin/webapp-config
+ - updated config/webapp-config
+ - updated doc/webapp-config.eclass.5.xml
+
+Version 1.10-r11 - Released 19th September 2004
+===============================================
+
+ * version bump: changed version to 1.10-r11
+ - updated sbin/webapp-config
+ * enhancement: support for amd64 platform
+ - updated sbin/webapp-config
+ * bug fix: missing libsh_einfo on line 359
+ - see http://bugs.gentoo.org/62845
+ - updated sbin/webapp-config
+
+Version 1.10-r10 - Released 19th August 2004
+============================================
+
+ * version bump: changed version to 1.10-r10
+ - updated sbin/webapp-config
+ - updated config/webapp-config
+ * enhancement: VHOST_SERVER variable now exported
+ - contains whatever's passed to webapp-config w/ the -s switch
+ - no use to anyone, as -s can only contain 'apache-basic' right now :)
+ - updated sbin/webapp-config
+ - updated docs/webapp.eclass.5.xml
+ * bug fix: VHOST_PERMS misspelt as VHOSTS_PERMS :(
+ - updated config/webapp-config
+ * bug fix: default for VHOST_PERMS_VIRTUAL_FILE was 'o-x', should be 'o-w'
+ - updated sbin/webapp-config
+ - updated config/webapp-config to be config v5
+
+Version 1.10-r9 - Released 19th August 2004
+===========================================
+
+ * enhancement: you can now control the permissions used for the directory
+ that webapp-config installs into by changing the config file
+ - updated sbin/webapp-config
+ - updated config/webapp-config
+ - updated lib/servers/apache-basic.sh
+ - updated doc/webapp-config.5.xml
+ * enhancement: we now export more variables for use in hook functions
+ and the post-installation instructions
+ - updated sbin/webapp-config
+ - updated doc/webapp-config.8.xml
+ * feature change: webapp-config is no longer chatty
+ - by popular request
+ - updated sbin/webapp-config
+ - updated lib/libsh.sh
+ - updated lib/servers/apache-basic.sh
+ * bug fix: no more installing virtual files with '0' permissions
+ - we now pick up the permissions from the master copy of the file
+ - updated sbin/webapp-config
+ * bug fix: only add directories that we really need to the contents file
+ - updated sbin/webapp-config
+
+Version 1.10-r8 - Released 16th August 2004
+===========================================
+
+ * version changed to 1.10-r8
+ - updated sbin/webapp-config
+ * bug fix: installed 'virtual' files cannot always be set '644'
+ - prevents executables installed in the cgi-bin directory from working
+ - updated sbin/webapp-config
+ - updated config/webapp-config
+
+Version 1.10-r7 - Released 12th August 2004
+===========================================
+
+A big thanks to dju` (http://dju.elegiac.net/) for pointing out that these
+changes were needed.
+
+ * version changed to 1.10-r7
+ - updated sbin/webapp-config
+ * enhancement: permissions for files and directories can now be set from
+ the config file
+ - updated sbin/webapp-config
+ - updated config/webapp-config
+ - updated doc/webapp-config.5.xml
+ * bug fix: .webapp files are no longer created 666
+ - updated sbin/webapp-config
+
+Version 1.10-r6 - Released 8th August 2004
+==========================================
+
+8th August 2004
+---------------
+
+ * version bump to 1.10-r6
+ - updated sbin/webapp-config
+
+ * bug fix: files containing a '+' in the name are now uninstalled correctly
+ - bug was caused by using egrep in fn_getcontentsline()
+ - updated sbin/webapp-config to use regular grep
+ - thanks to Dietrich Heise <dietrich@pegestorf.de> for the bug report
+
+6th August 2004
+---------------
+
+ * reverted the configuration change for the default user and group of
+ config-owned files
+ - the 'nobody' user is reserved for NFS root_squash
+
+31st July 2004
+--------------
+
+ * configuration change: the default user and group for config-owned files
+ is now 'nobody'
+ - updated config/webapp-config
+ - updated sbin/webapp-config
+
+28th July 2004
+--------------
+
+ * bug fix: when creating a directory, test for a 'virtual' parent *before*
+ trying to delete any directories from disk
+ - updated sbin/webapp-config
+
+22nd July 2004
+--------------
+
+ * enhancement: directories that already exist are now handled better
+ - ownership and permissions on existing directories are now changed
+ - removing warning messages
+ - updated sbin/webapp-config
+
+18th July 2004
+--------------
+
+ * bug fix: fix fir 'libse_enotice: command not found'
+ - updated sbin/webapp-config
+
+Version 1.10-r5 - Released 11th July 2004
+=========================================
+
+11th July 2004
+--------------
+
+ * bug fix: config-file protection wasn't working
+ - never worked if you were re-installing the same package
+ - broken for everyone else when I made webapp-config safe to use with
+ directories containing spaces in them
+ - updated sbin/webapp-config
+
+8th July 2004
+-------------
+
+ * version bump: 1.10-r5
+ * enhancement: many more variables exported for hook scripts, and now for use
+ in post-install texts too
+ - updated sbin/webapp-config
+ - updated eclass/webapp.eclass.5
+
+Version 1.10-r4 - Released 7th July 2004
+========================================
+
+7th July 2004
+-------------
+
+ Hrm. All the fixes listed below as being in -r3 never made it into the
+ final tarball. This was due to a mixup between my laptop (where all the
+ dev work is done) and my desktop (where testing and releases occur).
+
+ So -r4 is what -r3 should have been.
+
+Version 1.10-r3 - Released 28th June 2004
+=========================================
+
+28th June 2004
+--------------
+
+ * bug fix: hook scripts were called with parameters of 'start' and 'stop'
+ instead of 'install' and 'clean'
+ - updated sbin/webapp-config
+
+27th June 2004
+--------------
+
+ * version changed to 1.10-r3
+ - updated sbin/webapp-config
+
+ * bug fix: tightening up umasks
+ - server-owned files are now created with mode 660
+ - updated sbin/webapp-config
+
+ * bug fix: incorrect references to 'webapp.ebuild' in SEE ALSO sections
+ of the manuals
+ - updated doc/webapp-config.5.xml
+ - updated doc/webapp.eclass.5.xml
+
+ * bug fix: changing VHOST_HTDOCS_INSECURE broke webapp-config
+ - updated config/webapp-config
+
+23rd June 2004
+--------------
+
+ * version change to v1.10-r2
+ - updated sbin/webapp-config
+ * bug fix: --secure attempts to install files from /usr/share/webapps/<pn>/
+ <pvr>/htdocs-secure, which doesn't exist (Gentoo bug #??)
+ - updated config/webapp-config
+ * bug fix: the examples for webapp-config -I used -d with full directory
+ paths (Gentoo bug #52888)
+ - the XML was correct
+ - re-generated the man pages (grrr)
+ * bug fix: you can't use webapp-config -U to upgrade from one unrelated
+ package to another (Gentoo bug #?)
+ - you can only upgrade to a different version of the same package
+ - updated sbin/webapp-config
+
+14th June 2004
+--------------
+
+ * bug fix: fn_add_contents() didn't cope with files and directories that
+ contained spaces (Gentoo bug #53215)
+ - updated sbin/webapp-config
+ - thanks to Sven Wegener for the fix
+
+ * enhancement: internal file-type cache re-written to improve performance
+ - also simplifies support for files & directories with spaces in the
+ name
+ - also makes XML metadata file support easier to add
+ - updated sbin/webapp-config
+
+13th June 2004
+--------------
+
+ * bug fix: files always copied, never hard-linked (Gentoo bug #53809)
+ - fixed in sbin/webapp-config
+ - thanks to Sven Wegener for the bug report & fix
+
+ * bug fix: --help broke (Gentoo bug #53794)
+ - fixed in sbin/webapp-config
+ - thanks to Sven Wegener for the bug report & fix
+
+ * bug fix: config-owned / server-owned files always installed as virtual
+ (Gentoo bug #53806)
+ - fixed in sbin/webapp-config
+ - fixed in eclass/webapp.eclass
+ - thanks to Sven Wegener for the bug report once more!
+
+11th June 2004
+--------------
+
+ * bug fix: the examples for webapp-config -I used -d with full directory
+ paths (Gentoo bug #52888)
+ - updated doc/webapp-config.8.xml
+
+Version 1.10 - Released 11th June 2004
+======================================
+
+11th June 2004
+--------------
+
+ * bug fix: webapp-config needs to support installing files w/
+ spaces in the name (Gentoo bug #53215)
+ - wow - this needed fixing all over the place. Quite an eye-opener on
+ just how many unquoted parameters were being used
+ - updated sbin/webapp-config
+ - updated lib/libsh.sh
+ - updated eclass/webapp.eclass
+ - updated lib/servers/apache-basic.sh
+
+ * bug fix: webapp-config needs to set umask 0 when running
+ (Gentoo bug #53390)
+ - updated sbin/webapp-config
+
+ * documentation improvement:
+ - standardised on 'master copy' and 'virtual copy' as the terms to
+ use to mean files under /usr/share/webapps and /var/www respectively
+ - updated doc/webapp-config.8.xml
+
+ * enhancement: hard linking now documented
+ - updated doc/webapp-config.8.xml to cover --soft switch
+ - updated doc/webapp-config.8.xml to explain how to get the most out
+ of hard linking
+
+ * enhancement: --secure switch to install into /var/www/<host>/htdocs-secure
+ (Gentoo bug #53384)
+ - updated doc/webapp-config.8.xml to list --secure switch
+ - updated sbin/webapp-config to support --secure
+ - updated config/webapp-config to support VHOST_HTDOCS_INSECURE and
+ VHOST_HTDOCS_SECURE config options
+ - updated doc/webapp-config.5.xml to document VHOST_HTDOCS_INSECURE and
+ VHOST_HTDOCS_SECURE config options
+
+4th June 2004
+-------------
+
+ * enhancement: --bug-report now runs all mode-specific checks before
+ quitting
+
+ * enhancement: hard linking now supported
+ - by default, webapp-config will now try to hard-link in a file, and
+ will then try to copy the file if the hard link fails
+
+ many users have found the use of symlinks confusing
+ many users have objected to having cgi-bin's symlinked in
+ some popular packages will not work when their files are symlinked
+ rather than hard-linked
+
+ softlinking now has to be explicitly asked for with a --soft switch
+ (I'll make it so that this can also be set in the config file)
+
+ - updated sbin/webapp-config
+ - support hard-linking files by default
+ - new --soft switch if you would prefer to use symlinks instead
+ - TODO: update doc/webapp-config.8.xml to document new --soft option
+
+ - updated config/webapp-config to add new VHOST_LINK_TYPE config variable
+ - TODO: update doc/webapp-config.8.xml to add a new section discussing
+ soft links vs hard links
+
+28th May 2004
+-------------
+
+ * version changed to 1.10dev
+
+ * enhancement: checks to ensure that the config file matches the version
+ of webapp-config
+ - sanity checks to ensure that the user is not trying to use webapp-config
+ with a broken config-file ... or an older config file that hasn't been
+ updated when webapp-config was updated!
+ - added WA_CONFIG_VERSION to config/webapp-config
+ - added --check-config to sbin/webapp-config
+ - updated sbin/webapp-config to always do --check-config
+
+ * cosmetic: fatal error message is now prefixed with 'Fatal error: ' string
+ to make it even more obvious what the problem is
+ - updated lib/libsh.sh
+
+27th May 2004
+-------------
+
+ * bug fix: documentation errors
+ - webapp_pkg_setup() was documented as preparing ${D} for ebuilds. This
+ used to be the case, but is now done in webapp_pkg_preinst().
+ - examples and documentation go into /usr/share/doc, not /usr/doc
+ - updated doc/webapp.eclass.5.xml
+
+Version 1.9-r3 - 25th May 2004
+------------------------------
+
+ * version change to 1.9-r3
+ - updated sbin/webapp-config
+
+ * enhancement: export more environment variables for the hook scripts
+ - updated sbin/webapp-config to export a few VHOST_* variables
+ - updated doc/webapp.eclass.5.xml to document the exported variables
+
+22nd May 2004
+-------------
+
+ * workaround: portage-2.0.51 does not call src_install() and pkg_postinst()
+ inside the same shell instance
+ - updated webapp.eclass to create an empty file during src_install(),
+ and to look for that file at the start of pkg_postinst()
+ * behaviour change: if you already have something in /var/www/localhost/$PN,
+ webapp.eclass will now overwrite whatever is in there
+ - requested after discussion on gentoo-core mailing list
+
+20th May 2004 (changes for webapp-config-1.10)
+----------------------------------------------
+
+ * Work in progress: XML support
+ - added xslt/to-bash.xsl: stylesheet for transforming contents of the
+ XML metadata file into a set of bash function calls
+ - next step is to implement the callback functions
+ * Enhancement: use hard-linking if available
+ - lots of updates to sbin/webapp-config to support hard-linking
+ - we use hard-linking if we can, and fallback to symlinks if we
+ cannot
+ * Enhancement: support copying files instead of linking them
+ - some apps (in particular some PHP-based ones) don't work correctly
+ if they are symlinked in
+ - updated sbin/webapp-config to support copying 'virtual' files rather
+ than linking to them
+ - update sbin/webapp-config to turn 'virtual' directories into real
+ ones
+ - update eclass/webapp.eclass to allow setting meta options on packages
+ such as the new virtualise option
+ - update doc/webapp.eclass.5.xml to document the new metadata
+
+Version 1.9-r2 - 19th May 2004
+==============================
+
+A bug fix release.
+
+ * Version changed to 1.9-r2
+ - updated sbin/webapp-config
+
+ * Bug fix: fn_run_hooks() now makes sure that the script is executable
+ before trying to execute it
+ - updated sbin/webapp-config
+
+Version 1.9-r1 - 18th May 2004
+==============================
+
+A bug-fix release.
+
+ * Version changed to 1.9-r1
+ - updated sbin/webapp-config
+
+ * Fix for fn_run_hooks() looking in the wrong place
+ - updated sbin/webapp-config - now uses MY_HOOKSCRIPTSDIR rather
+ than MY_HOOKSCRIPTDIRS
+ - thanks to Sven Wegener <sven.wegener@stealer.net> for spotting
+ this one
+
+ * Fix for fn_run_hooks() not exporting an environment to the hook scripts
+ - updated sbin/webapp-config - now exports a few variables which should
+ help the people who need this
+ - updated doc/webapp.eclass.5.xml to document the variables exported to
+ the hook scripts
+
+Version 1.9 - 17th May 2004
+===========================
+
+17th May 2004
+-------------
+
+ * Version changed to 1.9
+ - updated sbin/webapp-config
+
+ * Enhancement: post-install (and pre-remove) script hook
+ - you can supply a script that will be executed after the installation
+ of a virtual copy, and immediately before the removal of a virtual copy
+ - updated eclass/webapp.eclas - add new function webapp_hook_script()
+ - updated config/webapp-config - add MY_HOOKSCRIPTSDIR variable
+ - updated sbin/webapp-config - call webapp_hook_script post-install, and
+ pre-clean
+ - updated doc/webapp.eclass.5.xml - document webapp_hook_script()
+
+ * Enhancement: you can now configure which user & group will own files
+ that are not owned by the web server or the config user
+ - because we install symlinks everywhere, this is something you have
+ to configure before installing the master copy with emerge
+ - updated config/webapp-config to include VHOST_DEFAULT_UID and
+ VHOST_DEFAULT_GID
+ - updated sbin/webapp-config to chown directories to VHOST_DEFAULT_UID:
+ VHOST_DEFAULT_GID. Also all symlinks created by webapp-config will
+ be owned by this uid/gid combo too
+ - updated doc/webapp-config.5.xml to document VHOST_DEFAULT_UID and
+ VHOST_DEFAULT_GID
+ - updated eclass/webapp.eclass to chown files and directories to
+ VHOST_CONFIG_UID and VHOST_CONFIG_GID
+
+ * Work In Progress: xml configuration files
+ - the plan is to replace the various metadata files in
+ /usr/share/webapps/$PN/$PVR with a single metadata.xml. This will
+ ease the transition to webapp-config v2, and give us new capabilities
+ that we currently don't have
+ - step 1 of this plan is to create some xsl stylesheets that we can
+ apply to the metadata.xml. we'll feed these through xsltproc, and
+ feed the output of that into webapp-config's internal metadata cache
+ - step 2 of this plan is to create some xsl stylesheets that will allow
+ us to add files and directories to metadata.xml
+ - step 3 of this plan is to create some xsl stylesheets that will allow
+ us to add metadata to metadata.xml
+ - at this point, we'll have enough capabilities to replace webapp-config's
+ metadata files with a single metadata.xml file ;-)
+
+14th May 2004
+-------------
+
+ * Enhancement: hook into pkg_prerm(), to remove all installed copies
+ of the package that Portage is about to remove
+ - requires a change to the contents of the .webapp file that lives
+ in the install directory of virtual copies
+ - we can't remove any virtual copies created by webapp-config-1.8 or
+ less using this method. Maybe I should rethink how this will work?
+ - updated sbin/webapp-config to include install host and directory
+ in future .webapp files
+ - updated eclass/webapp.eclass to look in the .webapp files, and if
+ possible to remove all the virtual copies (not working yet!!)
+
+ * Enhancement: webapp-config should never die() after verifying all params
+ - this means that it *will* overwrite files not under it's control
+ - added by popular request
+ - updated sbin/webapp-config
+
+ * Bug fix: webapp-config --list-installs only worked when used with
+ wildcards
+ - updated sbin/webapp-config
+
+13th May 2004
+-------------
+
+ * Bug fix: if installing into a directory that does NOT contain a webapp,
+ output a warning and refuse to install
+ - updated pkg_setup() in config/webapp.eclass
+ * Bug fix: no longer delete the master copy directory when upgrading
+ - updated webapp_getinstalltype() in config/webapp.eclass
+ * Enhancement: if USE=-vhosts, packages are upgraded properly
+ - old versions of the package are auto-removed
+
+12th May 2004
+-------------
+
+ * Enhancement: tell people what to do when USE=vhosts is on
+ - updated eclass/webapp.eclass
+
+ * Bug fix: webapp-config now allows you to set $MY_HTDOCSDIR to be
+ server-owned or config-owned as necessary.
+ - updated sbin/webapp-config
+
+11th May 2004
+-------------
+
+ * Version changed to 1.9dev
+ - updated sbin/webapp-config
+
+ * Enhancement: generate HTML versions of the man pages
+ - updated doc/Makefile to build HTML as well as man pages
+ - I would build PDF as well, but Gentoo's version of xmlto seems to be
+ missing a dependency or two for that
+
+ * Enhancement: drop-in webserver config support
+ - packages (such as trac) that need to add configuration files for
+ webservers can do so by calling the webapp_server_config() function
+ in the ebuild.
+ - updated eclass/webapp-config to add new webapp_server_config() function
+ - updated doc/webapp.eclass.5.xml to document new function
+
+Version 1.8 - 11th May 2004
+===========================
+
+11th May 2004
+-------------
+
+ * Urgent bugfix - webapp-config -U broken
+ - updated sbin/webapp-config to only call fn_setinstalldir when not
+ upgrading
+
+ * Added subversion tags for webapp-config 1.6 and webapp-config 1.7
+
+ * Version updated to 1.8
+
+10th May 2004
+-------------
+
+ * Maintenance - ebuilds that use the webapp eclass now depend on
+ webapp-config v1.7
+ - updated eclass/webapp.eclass
+
+2nd May 2004
+------------
+
+ * Enhancement - catch common ebuild problems in webapp.eclass
+ - catches ebuilds that set SLOT themselves
+ - catches ebuilds that do not call webapp_src_install()
+
+Version 1.7 - 1st May 2004
+==========================
+
+30th April 2004
+---------------
+
+ * Bug fix - directories were always installed as 'root-owned'
+ - updated sbin/webapp-config
+ - another feature broken by the ${D} changes in webapp-config-1.3
+
+ * Enhancement - old packages are removed when USE-vhosts
+ - portage wouldn't normally remove them, because the eclass SLOTs
+ every installed package (deliberately)
+ - updated eclass/webapp.eclass
+
+Version 1.6 - 29th April 2004
+=============================
+
+29th April 2004
+---------------
+
+ * Bug fix - correctly select install or upgrade mode when USE=-vhosts
+ - updated webapp_pkg_postinst in eclass/webapp-config
+
+ * Bug fix - install config files as files, not symlinks
+ - updated eclass/webapp.config. This got broken when we changed how
+ we used ${D} in webapp-config-1.3
+
+ * Bug fix - install into /var/www/localhost when USE=-vhosts
+ - grrr - I must be more careful when moving code between my machines
+ - updated eclass/webapp.eclass to use the -h switch once more
+
+ * Bug fix - examples/phpmyadmin* ebuilds did not call webapp_src_preinst
+
+ * Bug fix - added missing uid and gids for Apache server
+ - updated lib/servers/apache-basic.sh
+
+ * Changed behaviour - -d is now relative from htdocs dir
+ - requested by too many to count ;-)
+ - updated sbin/webapp-config
+ - updated doc/webapp-config.8.xml
+ - updated config/webapp-config
+ - updated eclass/webapp.eclass
+ - updated lib/servers/apache-basic.sh
+
+ * New feature - --show-postinst
+ - requested by Rob Holland
+ - re-displays the post-installation instructions for an installation
+ - required changes to sbin/webapp-config
+ - updated doc/webapp-config.8.xml to match
+
+ * New feature - --pretend mode
+ - shows where the install would occur, and then bails
+ - updated sbin/webapp-config to make --pretend an alias for --bug-report
+ - updated doc/webapp-config.8.xml
+
+Version 1.5 - 28th April 2004
+=============================
+
+28th April 2004
+---------------
+
+ * Bug fix - do not attempt to install cgi-bin's twice
+ - updated lib/servers/apache-basic.sh to only install from htdocs
+ and hostroot. The cgi-bin directory is under hostroot, and will
+ always be installed when hostroot is installed
+
+ * Eclass now requires webapp-config-1.5
+ - the eclass is shipped as part of the Portage tree
+
+26th April 2004
+---------------
+
+ * Version change to 1.5dev
+
+ * Bug fix - package upgrades work if you use USE=-vhost
+ - updated sbin/webapp-config to add new --show-installed switch
+ - updated eclass/webapp.eclass's pkg_postinst() function to detect
+ a previously-installed copy of the package, and to change the
+ flags to webapp-config appropriately
+ - updated doc/webapp-config.8.xml to add new --show-installed switch
+
+Version 1.4 - Released 23rd April 2004
+======================================
+
+ * Version change to 1.4
+
+ * Bug fix - portage removes ${D} between a compile and an install
+ - updated eclass/webapp.eclass - added new function webapp_src_preinst()
+ to create the necessary directories in ${D}
+ - updated doc/webapp.eclass.5.xml to cover webapp_src_preinst()
+ - updated examples/phpmyadmin* to use webapp_src_preinst()
+
+ * Bug fix - coping with ${D}
+ - updated eclass/webapp.eclass so that webapp_checkfileexists() now
+ uses $2 like it is supposed to
+
+Version 1.3 - Released 23rd April 2004
+======================================
+
+23rd April 2004
+---------------
+
+ * Version change to 1.3
+
+ * Removed the automatic ${D} from variables defined in the config file
+ - removed all trace of ${D} from config/webapp-config
+ - updated eclass/webapp.eclass to prepend ${D} where necessary
+ - updated examples/phpmyadmin-2.5.4-r1 to use ${D} where necessary
+ - updated examples/phpmyadmin-2.5.4-r2 to use ${D} where necessary too
+
+ * Another fix for USE=-vhosts
+ - updated eclass/webapp.eclass to always install webapps into
+ /var/www/localhost/htdocs; ie VHOST_HOSTNAME is ignored
+
+ * Auto-install webapp-config support
+ - updated eclass/webapp.eclass to DEPEND on net-www/webapp-config 1.3
+ or later.
+
+Version 1.2 - Released 23rd April 2004
+======================================
+
+20th April 2004
+---------------
+
+ * Version change to 1.2
+ - updated in sbin/webapp-config
+
+ * Bug fix for webapp.eclass
+ - moved the test for /etc/vhosts/webapp-config from the global scope
+ into the pkg_setup function
+
+ * Bug fix for webapp.eclass(5)
+ - changed the SYNOPSIS from 'inherit webapp.eclass' to 'inherit eclass'
+
+ * Improved output for fatal errors
+ - updated sbin/webapp-config to always accurately trap when -d is missing
+ from a -C operation
+ - updated lib/libsh.sh to leave the user in no doubt whatsoever that
+ the script has exited with a fatal error
+
+ * Improved error handling for unusual cases
+ - updated sbin/webapp-config to ensure that missing contents files and
+ install databases are trapped and handled correctly
+
+Version 1.1 - Released 14th April 2004
+======================================
+
+14th April 2004
+---------------
+
+ * Released webapp-config 1.1
+
+13th April 2004
+---------------
+
+ * Completed webapp.eclass(5) man page. Not the best document I've ever
+ written, but it is a start. Hopefully someone will submit patches or
+ something to improve it.
+
+ * Improved webapp.eclass, to match the documentation
+ - now exports pkg_postinst(), rather than pkg_config() (doh!)
+
+ * New support for post-installation instructions
+ - new webapp_postinst_txt() in the eclass, which adds a text file
+ containing post-install instructions
+ - updated sbin/webapp-config to display any post-install instructions
+ that exist
+ - updated example ebuilds to support the new post-install instructions
+ - this support will need reviewing; output the file to the screen at the
+ end of the ebuild sucks a bit, tbh - but at least it's a start
+
+ * Moved /var/cache/webapps to /var/db/webapps
+ - /var/cache is for temporary files only
+ - updated config/webapp-config
+
+ * Support for NOT having the vhosts USE flag set didn't work. It does now.
+ - fixed pkg_postinst() in eclass/webapp.eclass
+
+7th April 2004
+--------------
+
+ * Completed webapp-config(5) man page
+
+6th April 2004
+--------------
+
+ * Completed webapp-config(8) man page
+
+ * Changed behaviour of install (-I mode)
+
+ - updated sbin/webapp-config so that --virtual-files and --force-virtual
+ have no effect on config files. They are always copied into the install
+ directory.
+
+5th April 2004
+--------------
+
+ * The example phpmyadmin ebuild now SLOTs correctly (doh)
+
+ * Changed the behaviour of upgrade (-U mode)
+
+ - update sbin/webapp-config to only automatically overwrite all files
+ that have never ever been changed. If the md5 is different, the
+ file is config-protected instead.
+ - timestamps are no longer used to decide whether upgrade can replace
+ a file or not. Only the md5 is used. We still record timestamps
+ in the contents file, but for the moment they are no longer used.
+ - updated webapp-config.8.xml man page to match
+
+ * Fixed a bug in upgrades (-U mode)
+
+ - Loops that performed tests on parent directories became infinite loops
+ if they were given absolute paths to examine. This has been fixed.
+
+ * Man page gets ever closer to completion
+
+ - finished the Protected Configuration Files section
+ - added new Contents File section
+
+31st March 2004
+---------------
+
+ * Manual page for webapp-config(8) drafted
+ * Added new -V switch (alias for --verbose)
+
+29th March 2004
+---------------
+
+ * Continuing work for new 'installs' file
+
+ - updated eclass/webapp.eclass to create /var/cache/webapps/${PN}/${PVR}
+ directory. This is where the 'installs' file will now live, so that
+ /usr can be mounted read-only
+ - updated config/webapp-config to look for the 'installs' file under
+ the /var/cache/webapps/${PN}/${PVR} directory
+ - updated sbin/webapp-config to look for the 'installs' file under
+ the /var/cache/webapps/${PN}/${PVR} directory
+ - added new 'libsh_file_isempty()' function to test whether a file
+ is empty (zero length) or not
+ - empty 'installs' files are now removed during webapp-config -C
+ - I'm happy that --list-installs is now working as intended
+
+ * Fixed --bug-report switch (it works now ;-)
+
+26th March 2004
+---------------
+
+ * Bug fix for --list-servers not working at all
+
+ * Continuing work for new 'installs' file
+
+ - added new switch --list-installs to webapp-config
+
+ * Added new 'verbose' output mode. The idea is that, without this switch,
+ as much output as possible should be fit to feed into other command-line
+ programs.
+
+ - added new global G_VERBOSE to webapp-config
+ - added new --verbose switch to webapp-config
+ - added libsh_everbose to lib/libsh.sh
+
+24th March 2004
+---------------
+
+ * Added support for new 'installs' file inside the master copy, for keeping
+ track of the virtual installs that have been made
+
+ - file is created, updated, and destroyed by webapp-config. We can't
+ do this in the webapp.eclass, because Portage (correctly) will notice
+ that it has been changed and won't remove it during an emerge -C.
+ - added a new G_INSTALL_TIMESTAMP global to webapp-config, to tell us
+ when an install was made
+ - the fn_ws_install_verify() and fn_ws_clean_verify() now make sure that
+ G_INSTALLDIR is an absolute path, even if the user passed it in as
+ a relative path. We use the install path as the primary key inside
+ our 'installs' file
+ - added --list-installs to the help output. Function not implemented
+ yet.
+
+ * Updated webapp.eclass to install a webapp into $VHOSTS_ROOT when the
+ 'vhosts' USE flag is NOT set. Previously the path was hard-coded.
+
+ * Moved the default location of the webapp-config config file from
+ /etc/conf.d to /etc/vhosts.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..0b31841
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,443 @@
+2006-12-30 Gunnar Wrobel <wrobel@pardus.de>
+
+ * WebappConfig/*.py:
+
+ Fixed copyright years.
+
+ * WebappConfig/content.py (Contents.get_canremove):
+
+ Fixed doc testing for older get_canremove fix.
+
+ * WebappConfig/ebuild.py (Ebuild.run_vars):
+
+ Fixed doc testing for uid/gid fixes.
+
+ * WebappConfig/config.py (Config.__init__, Config.get_perm):
+
+ Fixed uid/gid BDS problems (bugs #104652 and #149939)
+
+ Fixed type in get_perm (bug #133530)
+
+ * WebappConfig/permissions.py (PermissionMap):
+
+ Fixed permission map parsing (bug #146059)
+
+ * WebappConfig/wrapper.py (package_installed):
+
+ Added support for package.provided when detecting installed
+ packages (bug #139965)
+
+2006-04-21 <post@gunnarwrobel.de>
+
+ * doc/webapp-config.8.xml: Added -D and -E description.
+ Fixed upgrade instructions. Bug #129167
+
+ * config/webapp-config (vhost_root): Added another
+ vhost_root option.
+ (vhost_hostname): Added cherokee.
+
+ * doc/webapp-config.5.xml: Description of subdomain
+ variables.
+
+ * WebappConfig/server.py (Cherokee): Added cherokee
+ support. Bug #129359
+
+ * WebappConfig/config.py (Config.split_hostname):
+ Added support for splitting hostnames into subdomains.
+ Bug #126486
+
+ * WebappConfig/wrapper.py: Added types import.
+ Bug #126534
+
+2006-03-06 <post@gunnarwrobel.de>
+
+ * WebappConfig/version.py: Version bump to 1.50.13
+
+ * WebappConfig/wrapper.py: Fix for the libdir
+ problem (see bug #125156)
+
+ * WebappConfig/sandbox.py (Sandbox.__init__): Second
+ correction for the amd64 libdir problem.
+
+ * WebappConfig/version.py: Version bump to 1.50.12
+
+ * WebappConfig/wrapper.py: Added libdir retrieval for
+ amd64
+
+ * WebappConfig/sandbox.py (Sandbox.__init__): Added
+ possible amd64 libdir fix
+
+2006-02-28 <post@gunnarwrobel.de>
+
+ * WebappConfig/db.py (WebappSource.packageavail):
+ Adapted to change in package_installed
+
+ * WebappConfig/wrapper.py (package_installed):
+ Fixed logic to allow for non-exact checks
+
+ * WebappConfig/server.py (Basic.__init__): Added check
+ for installed web server.
+ (Basic.supported): Added package check.
+
+2006-01-30 <post@gunnarwrobel.de>
+
+ * WebappConfig/db.py (WebappSource.get_source_files):
+ Added array sorting.
+ (WebappSource.get_source_directories):
+ Added array sorting.
+
+ * WebappConfig/ebuild.py (Ebuild.run_vars): Fixed
+ pass-through of the server variable and modified
+ the uid/gid extraction to result in readable names.
+
+ * WebappConfig/server.py (Basic.install): Showing the
+ instructions now includes the server argument.
+
+2006-01-29 <post@gunnarwrobel.de>
+
+ * WebappConfig/config.py (Config.run): Both
+ vhost_config_virtual_files and
+ vhost_config_default_dirs were ignored. Fixed.
+
+2006-01-27 <post@gunnarwrobel.de>
+
+ * WebappConfig/version.py: Version bump to 1.50.9
+
+ * WebappConfig/server.py (Basic): Fixed
+ behaviour for upgrades. Now shows upgrade instructions
+
+ * WebappConfig/config.py (BashConfigParser.get):
+ Fixed error message for a problematic configuration
+ configuration file.
+
+2006-01-13 <post@gunnarwrobel.de>
+
+ * WebappConfig/content.py (Contents.get_canremove):
+ Fix for recognizing config protected files
+
+ * WebappConfig/worker.py (WebappAdd.mkfile):
+ Fix for recognizing config protected files
+
+ * WebappConfig/filetype.py (FileType.__init__): New
+ return value: config-server-owned
+
+ * WebappConfig/content.py (Contents.read): Allowing
+ config-server-owned
+
+ * WebappConfig/server.py (Basic.__init__): Setting
+ server permissions for config-server-owned
+
+ * WebappConfig/config.py (Config.create_permissions):
+ Added permissions for config-server-owned
+
+2006-01-09 <post@gunnarwrobel.de>
+
+ * WebappConfig/version.py: Releasing .7
+
+ * doc/webapp-config.8.xml: Fixed documentation of
+ "--list-installs". Ticket #37
+
+ * WebappConfig/server.py (Basic.install): Adapted
+ running hooks (necessary for ticket #40)
+
+ * WebappConfig/ebuild.py (Ebuild.run_vars): Fixed
+ VHOST_SERVER_UID (see ticket #40 -> www.vhost-tools.org)
+
+2006-01-05 <post@gunnarwrobel.de>
+
+ * WebappConfig/worker.py (WebappAdd.mkfile): Fix to
+ support broken symlinks.
+
+2006-01-04 <post@gunnarwrobel.de>
+
+ * WebappConfig/db.py (WebappSource.reportpackageavail):
+ Removed problematic doc tests.
+
+ * WebappConfig/version.py: Version 1.50.5
+
+ * WebappConfig/tests/testfiles: Fixed a number of empty
+ directories that are not included by distutils.
+
+ * WebappConfig/worker.py (WebappAdd): Fixed doctest for
+ distutil changes.
+
+ * WebappConfig/content.py (Contents.__init__): Added
+ generic ignored file.
+
+ * WebappConfig/dotconfig.py (DotConfig): Doctest fix.
+
+ * ebuild/webapp-config-1.50.4.ebuild (src_test): Added
+ testing function.
+
+2005-12-28 <post@gunnarwrobel.de>
+
+ * WebappConfig/content.py (Contents.read): Fixed a
+ problem that I introduced with 1.50.3. The cleaning of
+ the hostroot dir did not work anymore.
+
+ * WebappConfig/version.py: Version 1.50.4
+
+ * WebappConfig/db.py (WebappSource.get_source_files):
+ Tried to fix the symlinking problem.
+
+ * WebappConfig/config.py (Config.setinstalldir):
+ Fixed trailing slashes for the installdir (bug #115765)
+
+2005-12-09 <post@gunnarwrobel.de>
+
+ * WebappConfig/content.py (Contents.write): Fixed
+ handling of content file between upgrades.
+
+2005-12-07 <post@gunnarwrobel.de>
+
+ * WebappConfig/config.py (Config.create_permissions):
+ Removed "root" reference and modified group owner
+ from "root" to config user gid. Unclear why the bash
+ version used root here.
+
+ * WebappConfig/server.py (Basic.set_server_user):
+ Removed "root" reference.
+
+2005-11-30 <post@gunnarwrobel.de>
+
+ * WebappConfig/ebuild.py (Ebuild.run_vars): Added
+ MY_HOSTROOTDIR to the exported variables.
+
+ Added doctests for the last two changes.
+
+2005-11-27 <post@gunnarwrobel.de>
+
+ * WebappConfig/ebuild.py (Ebuild.run_hooks): Reverted
+ hook logic. Stupid mistake. Thanks Renat!
+
+2005-11-07 <post@gunnarwrobel.de>
+
+ * WebappConfig/db.py (AppHierarchy.list_locations):
+ Improved handling of situations where the user only
+ specifies the project name.
+ (AppHierarchy.list_locations):
+ Fixes for --listunsed which did not report unused
+ packages.
+
+ * WebappConfig/config.py (Config.run): Fixed call for
+ list-installs
+
+ * WebappConfig/server.py (Basic.upgrade): Added support
+ for switching packages during upgrading
+
+ * WebappConfig/config.py: Removed upgrade check, added
+ warnings.
+
+ * Many changes to remove --force-virtual support
+
+2005-11-06 <post@gunnarwrobel.de>
+
+ * WebappConfig/server.py (Basic.install): Fixed
+ creation of the root directory
+
+ * WebappConfig/content.py (Contents.check_installdir):
+ Moved check for the installation directory to a better
+ place
+
+ * WebappConfig/config.py: Fixed permission issues
+
+ * WebappConfig/worker.py (WebappAdd.mkfile): Fixed
+ messages while linking.
+
+ * WebappConfig/config.py (Config.create_server):
+ Fixed soft linking.
+
+ * WebappConfig/protect.py: Fixed some bugs concerning
+ the handling of config protected files.
+
+ * WebappConfig/content.py (Contents.appdb): Fixed path
+ to the content file. Patches upgrading.
+
+ * WebappConfig/filetype.py: Fixed double slashes.
+
+ * WebappConfig/server.py: Adapted to the new user/group
+ handliing
+
+ * WebappConfig/wrapper.py: Fixed import statement from
+ gentoolkit
+
+ * WebappConfig/config.py: Adapted to the new user/group
+ handliing
+
+ * WebappConfig/worker.py: Adapted to the new user/group
+ handliing
+
+ * WebappConfig/permissions.py: Added functions get_user and
+ get_group to handle numerical uid's and gid's
+
+ * doc/webapp-config.8.xml: Fixed instructions concerning
+ numerical uid and gid
+
+ * config/webapp-config: Removed fixmes concerning numerical
+ uid and gid
+
+2005-11-05 <post@gunnarwrobel.de>
+
+ * WebappConfig/permissions.py: Added the permission handler.
+
+ * sbin/webapp-config: pylintification.
+
+2005-11-03 <post@gunnarwrobel.de>
+
+ * WebappConfig/content.py: Removed pretend check for file
+ operations.
+ (Contents.read): Incorrect check for relative flag fixed.
+
+ * WebappConfig/config.py (Config.create_permissions):
+ Incorrect permissions fixed.
+
+ * WebappConfig/dotconfig.py (DotConfig.write): Added quotes
+ that I forgot before
+
+ * WebappConfig/db.py: Doctest fix.
+ (WebappDB.add): Fixed handling of newlines in the "install"
+ files
+
+ * WebappConfig/content.py (Contents.add): Handling absoulte
+ paths was incorrect. Fixed.
+
+ * WebappConfig/worker.py: Fixed handling absolute paths.
+
+ * WebappConfig/server.py (Basic.install): Fixed handling
+ absolute paths.
+
+ * WebappConfig/config.py (Config.run): Fixed creating the
+ virtual install location.
+
+2005-11-02 <post@gunnarwrobel.de>
+
+ * WebappConfig/db.py: Another doctest fix.
+
+ * WebappConfig/worker.py: Fixing doctests.
+
+ * WebappConfig/content.py: Fixed doctests.
+
+ * WebappConfig/db.py: Fixed doctests.
+
+ * WebappConfig/ebuild.py: Fixed doctests.
+
+ * WebappConfig/debug.py: Added support for debugging class
+ variables, extended status message function (not needed
+ by w-c)
+
+2005-11-01 <post@gunnarwrobel.de>
+
+ * WebappConfig/version.py: Version 1.22
+
+ * WebappConfig/config.py: Pretending to install and clean
+ runs through now but still has bugs.
+
+ * WebappConfig/server.py: Pretending to install and clean
+ runs through now but still has bugs.
+
+ * WebappConfig/worker.py
+ (WebappRemove.remove_dirs)
+ (WebappRemove.remove_files):
+ Fixed return value (anything left behind)
+
+ * WebappConfig/dotconfig.py (DotConfig.__getitem__): Added
+ function to retrieve red values.
+
+ * WebappConfig/server.py: Adapted to the new source
+ organization.
+
+ * WebappConfig/config.py: Adapted to the new source
+ organization.
+
+2005-10-31 <post@gunnarwrobel.de>
+
+ * WebappConfig/content.py (Contents.add): Fixed action
+ when pretending.
+ (Contents.kill): Added function to remove the content file.
+
+ * WebappConfig/db.py (WebappSource): Added some debugging
+ statements.
+
+ * WebappConfig/worker.py: Final structure. Doctests added.
+ One left.
+ (WebappAdd): Finished doc tests. Fixed class. => Completed.
+
+ * WebappConfig/content.py: Fixed variable typo.
+
+ * WebappConfig/db.py
+ (WebappSource.source_exists):
+ (WebappSource.get_source_directories):
+ (WebappSource.get_source_files):
+ Added functions.
+
+ (WebappSource.read):
+ Added support for filetypes from whithin the source handler.
+
+ * WebappConfig/worker.py: Splitted the worker in adding and
+ removing. Nearly finished doctesting.
+
+2005-10-30 <post@gunnarwrobel.de>
+
+ * WebappConfig/content.py: Finished doctesting the module.
+
+2005-10-29 <post@gunnarwrobel.de>
+
+ * WebappConfig/tests/testfiles: Added a number of test
+ files for the doc tests.
+
+ * WebappConfig/content.py: Handler for the contents
+ file (split from worker.py)
+
+ * WebappConfig/db.py: Moved functions for package
+ availability into the Webapps handler
+
+ * WebappConfig/protect.py: Handler for config protected
+ files (split from worker.py)
+
+ * WebappConfig/dotconfig.py: Handler for the dotConfig file
+ in virtual install locations (split from worker.py/config.py)
+
+ * WebappConfig/ebuild.py: Handler for the ebuild postinstall
+ files and script hooks. (split from worker.py)
+
+2005-10-28 <post@gunnarwrobel.de>
+
+ * WebappConfig/content.py: Handler for the contents of
+ a virtual install. (split from worker.py)
+
+ * WebappConfig/db.py: Handler for /var/db/webapps and
+ /usr/share/webapps (split from worker.py)
+
+ * WebappConfig/filetype.py: Handler for different file types.
+ (split from worker.py)
+
+ * WebappConfig/worker.py: Splitting file.
+
+ * WebappConfig/wrapper.py: Fixed for debug.py
+
+ * WebappConfig/server.py: Fixed for debug.py
+
+ * WebappConfig/debug.py: Renamed util.py to debug.py
+ and incorporated updates from other project.
+
+ * WebappConfig/tests/testfiles: Added a number of test
+ files for the doc tests.
+
+ * BUGS.txt: Added file (r45:47)
+
+ * TODO.txt: Merged in revision (r45:47)
+
+2005-10-27 <post@gunnarwrobel.de>
+
+ * WebappConfig/config.py: Merged revisions (r41:42)
+
+ * WebappConfig/worker.py: Merged revisions (r37:41)
+
+ * TODO.txt: Merged TODO items (r36:37)
+
+ * ChangeLog: Added personal changelog to keep a record on this
+ branch.
+
+ * branch/gunnar: Created separate branch based on revision 36.
+
+
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..452fe8a
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,7 @@
+include config/*
+include doc/*
+include eclass/*
+include examples/*
+include xslt/*
+include *.txt
+recursive-include WebappConfig/tests *
diff --git a/WebappConfig/.svn.ignore b/WebappConfig/.svn.ignore
new file mode 100644
index 0000000..0d20b64
--- /dev/null
+++ b/WebappConfig/.svn.ignore
@@ -0,0 +1 @@
+*.pyc
diff --git a/WebappConfig/__init__.py b/WebappConfig/__init__.py
new file mode 100644
index 0000000..f3a72d8
--- /dev/null
+++ b/WebappConfig/__init__.py
@@ -0,0 +1 @@
+#Make this a python package
diff --git a/WebappConfig/config.py b/WebappConfig/config.py
new file mode 100644
index 0000000..de59d48
--- /dev/null
+++ b/WebappConfig/config.py
@@ -0,0 +1,1357 @@
+#!/usr/bin/python -O
+#
+# /usr/sbin/webapp-config
+# Python script for managing the deployment of web-based
+# applications
+#
+# Originally written for the Gentoo Linux distribution
+#
+# Copyright (c) 1999-2006 Gentoo Foundation
+# Released under v2 of the GNU GPL
+#
+# Author(s) Stuart Herbert <stuart@gentoo.org>
+# Renat Lumpau <rl03@gentoo.org>
+# Gunnar Wrobel <php@gunnarwrobel.de>
+#
+# ========================================================================
+
+# ========================================================================
+# Dependencies
+# ------------------------------------------------------------------------
+
+import sys, os, os.path, optparse, ConfigParser, re, shlex, socket, time
+
+from ConfigParser import MAX_INTERPOLATION_DEPTH, DEFAULTSECT, \
+ ParsingError, InterpolationMissingOptionError, \
+ InterpolationSyntaxError, \
+ InterpolationDepthError
+
+import WebappConfig.server
+import WebappConfig.permissions as Perm
+import WebappConfig.wrapper as wrapper
+
+from optparse import OptionParser, OptionGroup
+from WebappConfig.debug import OUT
+from WebappConfig.version import WCVERSION
+
+from WebappConfig.permissions import PermissionMap
+
+# ========================================================================
+# BashParser class
+# ------------------------------------------------------------------------
+
+class BashConfigParser(ConfigParser.SafeConfigParser):
+
+ _interpvar_match = re.compile(r"(%\(([^)]+)\)s|\$\{([^}]+)\})").match
+
+ def get(self, section, option):
+ try:
+ return ConfigParser.SafeConfigParser.get(self, section, option)
+ except Exception, e:
+ OUT.die('\nThere is a problem with your configuration file.\n'
+ 'webapp-config tried to read the variable "'
+ + str(option) + '"\nand received the following error:'
+ '\n\n' + str(e) + '\nPlease note that webapp-config i'
+ 's not written in bash anymore\nand that you cannot u'
+ 'se the bash scripting features.')
+ return ''
+
+ def _interpolate_some(self, option, accum, rest, section, map, depth):
+ if depth > MAX_INTERPOLATION_DEPTH:
+ raise InterpolationDepthError(option, section, rest)
+ while rest:
+ p = rest.find("%")
+ if p < 0:
+ p = rest.find("$")
+ if p < 0:
+ accum.append(rest)
+ return
+ if p > 0:
+ accum.append(rest[:p])
+ rest = rest[p:]
+ # p is no longer used
+ c = rest[1:2]
+
+ OUT.debug('Parsing', 7)
+
+ if c == "%" or c == "$":
+ accum.append(c)
+ rest = rest[2:]
+ elif c == "(" or c == "{":
+ m = self._interpvar_match(rest)
+ if m is None:
+ raise InterpolationSyntaxError(option, section,
+ "bad interpolation variable reference %r" % rest)
+ var = m.group(2)
+ if not var:
+ var = m.group(3)
+ var = self.optionxform(var)
+ rest = rest[m.end():]
+ try:
+ v = map[var]
+ except KeyError:
+ raise InterpolationMissingOptionError(
+ option, section, rest, var)
+ if "%" in v or "$" in v:
+ self._interpolate_some(option, accum, v,
+ section, map, depth + 1)
+ else:
+ accum.append(v)
+ else:
+ raise InterpolationSyntaxError(
+ option, section,
+ "'" + c + "' must be followed by '" + c + "', '{', or '(', found: %r" % (rest,))
+
+ OPTCRE = re.compile(
+ r'(?P<option>[^:=\s][^:=]*)' # very permissive!
+ r'\s*(?P<vi>[:=])\s*' # any number of space/tab,
+ # followed by separator
+ # (either : or =), followed
+ # by any # space/tab
+ r'(?P<value>.*)$' # everything up to eol
+ )
+
+ def _read(self, fp, fpname):
+ """Parse a sectioned setup file.
+
+ The sections in setup file contains a title line at the top,
+ indicated by a name in square brackets (`[]'), plus key/value
+ options lines, indicated by `name: value' format lines.
+ Continuations are represented by an embedded newline then
+ leading whitespace. Blank lines, lines beginning with a '#',
+ and just about everything else are ignored.
+ """
+ cursect = None # None, or a dictionary
+ optname = None
+ lineno = 0
+ e = None # None, or an exception
+
+ # Read everything into the "USER" section
+ if 'USER' in self._sections:
+ cursect = self._sections['USER']
+ else:
+ cursect = {'__name__': 'USER'}
+ self._sections['USER'] = cursect
+
+ while True:
+ line = fp.readline()
+ if not line:
+ break
+ lineno = lineno + 1
+ # comment or blank line?
+ if line.strip() == '' or line[0] in '#;':
+ continue
+ if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR":
+ # no leading whitespace
+ continue
+ # continuation line?
+ if line[0].isspace() and cursect is not None and optname:
+ value = line.strip()
+ if value:
+ cursect[optname] = "%s\n%s" % (cursect[optname], value)
+ # a section header or option header?
+ else:
+ # an option line?
+ mo = self.OPTCRE.match(line)
+ if mo:
+ optname, vi, optval = mo.group('option', 'vi', 'value')
+ if vi in ('=', ':') and ';' in optval:
+ # ';' is a comment delimiter only if it follows
+ # a spacing character
+ pos = optval.find(';')
+ if pos != -1 and optval[pos-1].isspace():
+ optval = optval[:pos]
+ optval = optval.strip()
+ # allow empty values
+ if optval == '""':
+ optval = ''
+ if optval[0] == '"' and optval[-1] == '"' :
+ optval = optval[1:-1]
+ optname = self.optionxform(optname.rstrip())
+ cursect[optname] = optval
+ else:
+ # a non-fatal parsing error occurred. set up the
+ # exception but keep going. the exception will be
+ # raised at the end of the file and will contain a
+ # list of all bogus lines
+ if not e:
+ e = ParsingError(fpname)
+ e.append(lineno, repr(line))
+ # if any parsing errors occurred, raise an exception
+ if e:
+ raise e
+
+# ========================================================================
+# Config class
+# ------------------------------------------------------------------------
+
+class Config:
+
+ # --------------------------------------------------------------------
+ # Init functions
+
+ def __init__(self):
+
+ ## These are the webapp-config default configuration
+ ## values.
+
+ self.__root = wrapper.get_root()
+
+ hostname = 'localhost'
+ try:
+ hostname = socket.gethostbyaddr(socket.gethostname())[0]
+ except:
+ pass
+
+ self.__d = {
+ 'config_protect' : wrapper.config_protect,
+ # Necessary to load the config file
+ 'my_etcconfig' : '/etc/vhosts/webapp-config',
+ 'my_dotconfig' : '.webapp',
+ 'my_version' : WCVERSION,
+ 'my_conf_version' : '7',
+ 'my_bugsurl' : wrapper.bugs_link,
+ 'g_myname' : sys.argv[0],
+ 'g_orig_installdir' : '/',
+ 'g_installdir' : '/',
+ 'g_link_options' : '',
+ 'g_link_type' : 'hard',
+ 'g_configprefix' : '._cfg',
+ 'g_perms_dotconfig' : '0600',
+ # USER section (only 'get' these variables from
+ # the USER section)
+ 'vhost_hostname' : hostname,
+ 'vhost_server' : 'apache',
+ 'vhost_default_uid' : '0',
+ 'vhost_default_gid' : '0',
+ 'vhost_config_gid' : str(os.getgid()),
+ 'vhost_config_uid' : str(os.getuid()),
+ 'vhost_config_virtual_files' : 'virtual',
+ 'vhost_config_default_dirs' : 'default-owned',
+ 'vhost_config_dir' : '%(vhost_root)s/conf',
+ 'vhost_htdocs_insecure' : 'htdocs',
+ 'vhost_htdocs_secure' : 'htdocs-secure',
+ 'vhost_perms_serverowned_dir' : '0775',
+ 'vhost_perms_serverowned_file' : '0664',
+ 'vhost_perms_configowned_dir' : '0755',
+ 'vhost_perms_configowned_file' : '0644',
+ 'vhost_perms_defaultowned_dir' : '0755',
+ 'vhost_perms_virtualowned_file': 'o-w',
+ 'vhost_perms_installdir' : '0755',
+ # FIXME: I added the following two as default values so that the
+ # commands --show-postinst and --show-postupgrade do not
+ # need a fully initialized server instance. That would make
+ # things more complex. On the other hand these parameters
+ # will not have the correct server values when running
+ # --show-post*. Think about it again.
+ #
+ # -- wrobel
+ 'vhost_server_uid' : 'root',
+ 'vhost_server_gid' : 'root',
+ 'vhost_root' : '/var/www/%(vhost_hostname)s',
+ 'g_htdocsdir' : '%(vhost_root)s/%(my_htdocsbase)s',
+ 'my_appdir' : '%(my_approot)s/%(my_appsuffix)s',
+ 'my_htdocsdir' : '%(my_appdir)s/htdocs',
+ 'my_persistdir' : '%(my_persistroot)s/%(my_appsuffix)s',
+ 'my_hostrootdir' : '%(my_appdir)s/%(my_hostrootbase)s',
+ 'my_cgibindir' : '%(my_hostrootdir)s/%(my_cgibinbase)s',
+ 'my_iconsdir' : '%(my_hostrootdir)s/%(my_iconsbase)s',
+ 'my_errorsdir' : '%(my_hostrootdir)s/%(my_errorsbase)s',
+ 'my_approot' : '/usr/share/webapps',
+ 'my_appsuffix' : '%(pn)s/%(pvr)s',
+ 'my_persistroot' : '/var/db/webapps',
+ 'my_hostrootbase' : 'hostroot',
+ 'my_cgibinbase' : 'cgi-bin',
+ 'my_iconsbase' : 'icons',
+ 'my_errorsbase' : 'error',
+ 'my_sqlscriptsdir' : '%(my_appdir)s/sqlscripts',
+ 'g_cgibindir' : '%(vhost_root)s/%(my_cgibinbase)s',
+ 'my_hookscriptsdir' : '%(my_appdir)s/hooks',
+ 'my_serverconfigdir': '%(my_appdir)s/conf',
+ 'wa_configlist' : '%(my_appdir)s/config-files',
+ 'wa_solist' : '%(my_appdir)s/server-owned-files',
+ 'wa_virtuallist' : '%(my_appdir)s/virtuals',
+ 'wa_installsbase' : 'installs',
+ 'wa_installs' : '%(my_persistdir)s/%(wa_installsbase)s',
+ 'wa_postinstallinfo':
+ '%(my_appdir)s/post-install-instructions.txt',
+ }
+
+ # Setup basic defaults
+ self.config = BashConfigParser(self.__d)
+ self.config.add_section('USER')
+
+ # Setup the command line parser
+ self.setup_parser()
+
+ self.work = 'help'
+
+ self.flag_dir = False
+
+ def setup_parser(self):
+
+ self.parser = OptionParser(
+ usage = '%prog [-ICU] [-dghus] <APPLICATION VERSION>',
+ version = self.config.get('USER', 'my_version'),
+ add_help_option = False)
+
+ #-----------------------------------------------------------------
+ # Usage
+
+ group = OptionGroup(self.parser, 'APPLICATION VERSION',
+ 'The name and version number of the web appli'
+ 'cation to install e.g. phpmyadmin 2.5.4. The'
+ 'APPLICATION must have already been installed'
+ ' into the '
+ + self.config.get('USER', 'my_approot') +
+ ' directory tree using emerge')
+
+ self.parser.add_option_group(group)
+
+ #-----------------------------------------------------------------
+ # Main Options
+
+ group = OptionGroup(self.parser, '<Main Options>')
+
+ group.add_option('-I',
+ '--install',
+ action = 'store_true',
+ help = 'Install a web application')
+
+ group.add_option('-C',
+ '--clean',
+ action = 'store_true',
+ help = 'Remove a web application')
+
+ group.add_option('-U',
+ '--upgrade',
+ action = 'store_true',
+ help = 'Upgrade a web application')
+
+ self.parser.add_option_group(group)
+
+ #-----------------------------------------------------------------
+ # Path Options
+
+ group = OptionGroup(self.parser, '<Installation Location>')
+
+ group.add_option('-h',
+ '--host',
+ help = 'The hostname to configure this applicati'
+ 'on to serve. Also affects where some files go.'
+ ' If you get this setting wrong, you may need to'
+ ' re-install the application to correct the pro'
+ 'blem! Default is HOST = '
+ + self.config.get('USER', 'vhost_hostname') +
+ '. To change the default, change the value of "v'
+ 'host_hostname" in '
+ + self.config.get('USER', 'my_etcconfig') +
+ ' <NOTE>: if the default value is currently "loc'
+ 'alhost", that probably means that this computer'
+ '\'s /etc/hosts file is not correctly configured'
+ )
+
+ group.add_option('-d', '--dir',
+ help = 'Install <application> into DIR under the'
+ ' htdocs dir. The default is '
+ + self.config.get('USER', 'g_installdir'))
+
+ group.add_option('-s', '--server',
+ help = 'Specify which web SERVER to install the '
+ 'application to run under. Use webapp-config --l'
+ 'ist-servers to see supported web servers. The d'
+ 'efault is -s '
+ + self.config.get('USER', 'vhost_server') +
+ '. To change the default, change the value of VH'
+ 'OST_SERVER in '
+ + self.config.get('USER', 'my_etcconfig'))
+
+ group.add_option('--secure', action='store_true',
+ help = 'Install, upgrade, or clean files in htdo'
+ 'cs-secure rather than in the htdocs directory.')
+
+ self.parser.add_option_group(group)
+
+ #-----------------------------------------------------------------
+ # Installation Options
+
+ group = OptionGroup(self.parser, '<Installation Options>')
+
+ group.add_option('-u',
+ '--user',
+ help = 'Install config files so that they can be'
+ ' edited by USER. USER can be a username.Numeric'
+ 'al user ids are NOT supported. Default is USER '
+ '= '
+ + self.config.get('USER', 'vhost_config_uid') +
+ ' To change the default, change the value of VHO'
+ 'ST_CONFIG_UID in '
+ + self.config.get('USER', 'my_etcconfig'))
+
+ group.add_option('-g',
+ '--group',
+ help = 'Install config files so that they can be'
+ ' edited by GROUP. GROUP can be a group name. Nu'
+ 'merical group ids are NOT supported.Default is '
+ 'GROUP = '
+ + self.config.get('USER', 'vhost_config_gid') +
+ 'To change the default, change the value of VHOS'
+ 'T_CONFIG_GID in '
+ + self.config.get('USER', 'my_etcconfig'))
+
+ group.add_option('--soft',
+ action='store_true',
+ help = 'Use symbolic links instead of hard links'
+ ' when creating virtual files. <NOTE>: some pack'
+ 'ages will not work if you use this option')
+
+ group.add_option('--virtual-files',
+ '--vf',
+ type = 'choice',
+ choices = ['server-owned',
+ 'config-owned',
+ 'virtual'],
+ help = 'Decide what happens when we\'re installi'
+ 'ng a file that could be shared (ie, one we woul'
+ 'dn\'t normally create a local copy of). VIRTUAL'
+ '_FILES must be one of: "server-owned" [files ar'
+ 'e owned by the user and group thatthe web-serve'
+ 'r runs under], "config-owned" [files are owned '
+ 'by the user and group specified by the -u and -'
+ 'g switches to this script],"virtual" [files are'
+ ' shared; a local copy is not created]. Default '
+ 'is '
+ + self.config.get('USER',
+ 'vhost_config_virtual_files') +
+ '. To change these defaults, change the value of'
+ ' VHOST_CONFIG_VIRTUAL_FILES in '
+ + self.config.get('USER', 'my_etcconfig') +
+ ' <NOTE>: Some -s <server> options may not suppo'
+ 'rt all values of VIRTUAL_FILES and will report '
+ 'an error')
+
+ group.add_option('--default-dirs',
+ '--dd',
+ type = 'choice',
+ choices = ['server-owned',
+ 'config-owned',
+ 'default-owned'],
+ help = 'Decide what happens when we\'re installi'
+ 'ng a directory that could be shared (ie, one we'
+ ' wouldn\'t normally create a local copy of). DE'
+ 'FAULT_DIRS must be one of: "server-owned" [dirs'
+ ' are owned by the user and group thatthe web-se'
+ 'rver runs under], "config-owned" [dirs are owne'
+ 'd by the user and group specified by the -u and'
+ ' -g switches to this script],"default-owned" [d'
+ 'irs are owned by the user specified in VHOST_DE'
+ 'FAULT_UID:VHOST_DEFAULT_GID]. Default is '
+ + self.config.get('USER',
+ 'vhost_config_default_dirs') +
+ '. To change these defaults, change the value of'
+ ' VHOST_CONFIG_DEFAULT_DIRS in '
+ + self.config.get('USER', 'my_etcconfig') +
+ ' <NOTE>: Some -s <server> options may not suppo'
+ 'rt all values of DEFAULT_DIRS and will report a'
+ 'n error')
+
+ self.parser.add_option_group(group)
+
+ #-----------------------------------------------------------------
+ # Information Options
+
+ group = OptionGroup(self.parser, '<Information Options>')
+
+ group.add_option('--pretend',
+ action='store_true',
+ help = 'Output information about what webapp-con'
+ 'fig would do, then quit without actually doing '
+ 'it')
+
+ group.add_option('-V',
+ '--verbose',
+ action='store_true',
+ help = 'Output even more information than normal'
+ )
+
+ group.add_option('--list-servers',
+ '--ls',
+ action='store_true',
+ help = 'List all web servers currently supported'
+ ' by webapp-config')
+
+ group.add_option('--list-installs',
+ '--li',
+ action='store_true',
+ help = 'List all current virtual installs for <a'
+ 'pplication>. Use * for the package name and/or '
+ 'version number to list more thanone package / v'
+ 'ersion of a package. Remember to include the *'
+ ' in single quotes to stop your shell from expan'
+ 'ding it first!!')
+
+ group.add_option('--list-unused-installs',
+ '--lui',
+ action='store_true',
+ help = 'List all master images which currently a'
+ 're not used. Use * for the package name and/or '
+ 'version number to list more than one package / '
+ 'version of a package. Remember to include the *'
+ ' in single quotes to stop your shell from expan'
+ 'ding it first!!')
+
+ group.add_option('--show-installed',
+ '--si',
+ action='store_true',
+ help = 'Show what application is installed in DI'
+ 'R')
+
+ group.add_option('--show-postinst',
+ '--spi',
+ action='store_true',
+ help = 'Show the post-installation instructions '
+ 'for <application>. Very handy if you\'ve lost t'
+ 'he instructions when they were shown to you ;-)'
+ )
+
+ group.add_option('--show-postupgrade',
+ '--spu',
+ action='store_true')
+
+ group.add_option('--query',
+ action='store_true')
+
+ self.parser.add_option_group(group)
+
+ #-----------------------------------------------------------------
+ # Other Options
+
+ group = OptionGroup(self.parser, '<Other Options>')
+
+ group.add_option('-D',
+ '--define',
+ action='append',
+ help = 'Allows to name a <KEY>=<VALUE> pair that'
+ 'will be imported into the configuration variabl'
+ 'es of webapp-config. This allows you to provide'
+ ' customized variables which can be used in the '
+ 'configuration file. This can also be used to te'
+ 'mporarily overwrite variables from the configur'
+ 'ation file.'
+ )
+
+ group.add_option('-E',
+ '--envvar',
+ action='append',
+ help = 'Allows to name single environment variab'
+ 'le that will be imported by webapp-config. Thi'
+ 's allows you to provide customized variables wh'
+ 'ich can be used in the configuration file. This'
+ ' can also be used to temporarily overwrite vari'
+ 'ables from the configuration file.'
+ )
+
+ group.add_option('--envall',
+ action='store_true',
+ help = 'Imports all environment variables and ov'
+ 'erwrites configurations read from the configura'
+ 'tion file. Setting this switch is not recommend'
+ 'ed since you might have environment variables s'
+ 'et to values that cannot be parsed.')
+
+ group.add_option('-?', '--help', action='help',
+ help = 'Show this help')
+
+ self.parser.add_option_group(group)
+
+ #-----------------------------------------------------------------
+ # Debug Options
+
+ OUT.cli_opts(self.parser)
+
+ #-----------------------------------------------------------------
+ # Bug Options
+
+ group = OptionGroup(self.parser,
+ '<Reporting Bugs>',
+ 'To report bugs about webapp-config, please g'
+ 'o to '
+ + self.config.get('USER', 'my_bugsurl')
+ + '. Include the output of webapp-config --bu'
+ 'g-report <your parameters here> to help us t'
+ 'o help you')
+
+ group.add_option('--bug-report',
+ action='store_true')
+
+ self.parser.add_option_group(group)
+
+ # --------------------------------------------------------------------
+ # Variable functions
+
+ def maybe_get(self, option, section = 'USER'):
+
+ OUT.debug('Retrieving option ' + option, 7)
+
+ if not self.config.has_option(section, option):
+
+ OUT.debug('Missing option ' + option, 7)
+
+ return ''
+ return self.config.get(section, option)
+
+ def get_perm(self, permission):
+ result = None
+ try:
+ result = PermissionMap(self.maybe_get(permission))
+ except Exception, e:
+ OUT.die('You did specify an invalid permission value for the'
+ ' variable "' + permission + "'")
+ return result
+
+ def get_user(self, user):
+ result = None
+ try:
+ result = Perm.get_user(self.maybe_get(user))
+ except KeyError, e:
+ OUT.die('You did specify an invalid user value for the'
+ ' variable "' + user + "'")
+ return result
+
+ def get_group(self, group):
+ result = None
+ try:
+ result = Perm.get_group(self.maybe_get(group))
+ except KeyError, e:
+ OUT.die('You did specify an invalid group value for the'
+ ' variable "' + group + "'")
+ return result
+
+ def installdir(self):
+ return self.maybe_get('g_installdir')
+
+ def packagename(self, sep='-'):
+ return self.maybe_get('pn') + sep + self.maybe_get('pvr')
+
+ def maybe_getboolean(self, option, section = 'USER'):
+
+ OUT.debug('Retrieving boolean option ' + option, 7)
+
+ if not self.config.has_option(section, option):
+
+ OUT.debug('Missing boolean option ' + option, 7)
+
+ return False
+
+ return self.config.getboolean(section, option)
+
+ def removing(self):
+ return self.maybe_getboolean('g_remove')
+
+ def installing(self):
+ return self.maybe_getboolean('g_install')
+
+ def upgrading(self):
+ return self.maybe_getboolean('g_upgrade')
+
+ def verbose(self):
+ return self.maybe_getboolean('g_verbose')
+
+ def pretend(self):
+ return self.maybe_getboolean('g_pretend')
+
+ # --------------------------------------------------------------------
+ # fn_parseparams()
+ #
+ # Parse the command-line parameters, ready to verify them
+ #
+ # Inputs:
+ # $* - the command-line the script was called with
+ #
+ # Outputs
+ # None
+
+ def parseparams (self):
+
+ OUT.debug('Parsing all configuration parameters', 6)
+
+ # we import /etc/vhosts/webapp-config so that we can snag the
+ # defaults to embed in this output
+
+ if not os.access(self.__d['my_etcconfig'], os.R_OK):
+ OUT.die('The configuration file ' + self.__d['my_etcconfig'] +
+ ' is not accessible!')
+
+ try:
+ self.config.read(self.__d['my_etcconfig'])
+ except Exception, e:
+ OUT.die('The config file '
+ + self.config.get('USER', 'my_etcconfig') +
+ ' cannot be read by the configuration parser.'
+ '.\nMaybe you need to etc-update?\nError was: ' + str(e))
+
+ # check the version id in the config file
+ #
+ # if they don't match, it's probably because the user hasn't had
+ # the chance to etc-update yet
+
+ if (self.config.get('USER', 'wa_conf_version') !=
+ self.config.get('USER', 'my_conf_version')):
+
+ OUT.die('The config file '
+ + self.config.get('USER', 'my_etcconfig') +
+ ' appears to be for an older version of webapp-config'
+ '.\nMaybe you need to etc-update?\n'
+ + self.config.get('USER', 'my_etcconfig') +
+ ' needs updating!')
+
+
+ OUT.debug('Successfully parsed configuration file options', 7)
+
+ # Parse the command line
+ (options, args) = self.parser.parse_args()
+
+ OUT.debug('Successfully parsed command line options', 7)
+
+ # handle debugging
+ OUT.cli_handle(options)
+
+ # Second config level are environment variables
+
+ # Handle -E
+ envmap = []
+
+ if (options.__dict__.has_key('envall') and
+ options.__dict__['envall']):
+ envmap = 'all'
+
+ elif (options.__dict__.has_key('envvar') and
+ options.__dict__['envvar']):
+ envmap = [x.lower() for x in options.__dict__['envvar']]
+
+ OUT.debug('Trying to import environment variables', 7)
+
+ if envmap:
+ for (key, value) in os.environ.items():
+
+ if envmap == 'all' or key.lower() in envmap:
+
+ OUT.debug('Adding environment variable', 8)
+
+ self.config.set('USER',
+ key.lower(),
+ value)
+
+ if (options.__dict__.has_key('define') and
+ options.__dict__['define']):
+ for i in options.__dict__['define']:
+ if '=' in i:
+ self.config.set('USER',
+ i.split('=')[0].lower(),
+ i.split('=')[1])
+
+ # Indicate that --dir was found
+ if options.__dict__.has_key('dir'):
+ self.flag_dir = True
+
+ # Map command line options into the configuration
+ option_to_config = {'host' : 'vhost_hostname',
+ 'dir' : 'g_installdir',
+ 'server' : 'vhost_server',
+ 'secure' : 'g_secure',
+ 'user' : 'vhost_config_uid',
+ 'group' : 'vhost_config_gid',
+ 'soft' : 'g_soft',
+ 'virtual_files': 'vhost_config_virtual_files',
+ 'default_dirs' : 'vhost_config_default_dirs',
+ 'pretend' : 'g_pretend',
+ 'verbose' : 'g_verbose',
+ 'bug_report' : 'g_bugreport'}
+
+ for i in option_to_config.keys():
+ if options.__dict__.has_key(i) and options.__dict__[i]:
+ self.config.set('USER', option_to_config[i],
+ str(options.__dict__[i]))
+
+ # handle verbosity
+ if (options.__dict__.has_key('pretend')
+ and options.__dict__['pretend']):
+
+ self.config.set('USER', 'g_verbose', 'True')
+
+ if self.verbose():
+
+ OUT.debug('Setting verbose', 7)
+
+ OUT.set_info_level(4)
+
+ else:
+
+ OUT.debug('Setting quiet', 7)
+
+ OUT.set_info_level(1)
+
+ # store original installdir
+ self.config.set('USER', 'g_orig_installdir',
+ self.config.get('USER', 'g_installdir'))
+
+ # Provide simple subdomain support
+ self.split_hostname()
+
+ # support --secure
+ if not self.config.has_option('USER', 'my_htdocsbase'):
+
+ OUT.debug('Setting "my_htdocsbase"', 7)
+
+ if (self.config.has_option('USER', 'g_secure') and
+ self.config.getboolean('USER', 'g_secure')):
+ self.config.set('USER', 'my_htdocsbase',
+ '%(vhost_htdocs_secure)s')
+ else:
+ self.config.set('USER', 'my_htdocsbase',
+ '%(vhost_htdocs_insecure)s')
+
+ # set the action to be performed
+ work = ['install', 'clean', 'upgrade', 'list_installs',
+ 'list_servers', 'list_unused_installs',
+ 'show_installed', 'show_postinst',
+ 'show_postupgrade', 'check_config', 'query']
+
+ for i in work:
+ if options.__dict__.get(i):
+ self.work = i
+ break
+
+ OUT.debug('Checking command line arguments', 1)
+
+ if len(args) > 0:
+ self.config.set('USER', 'pn', args[0])
+
+ if len(args) > 1:
+ self.config.set('USER', 'pvr', args[-1])
+
+
+
+ # --------------------------------------------------------------------
+ # Helper functions
+
+ def check_package_set(self):
+ if not self.config.has_option('USER', 'pn'):
+ self.parser.print_help()
+ OUT.die('You need to specify at least the application you'
+ ' would like to handle!')
+
+ def check_version_set(self):
+ if not self.config.has_option('USER', 'pvr'):
+ OUT.die('You did not specify which version to handle.\n Use "'
+ + self.config.get('USER','g_myname') +
+ ' --help" for usage')
+
+ def split_hostname(self):
+
+ hostname = self.config.get('USER', 'vhost_hostname')
+
+ subdomains = hostname.split('.')
+
+ j = len(subdomains)
+ for i in subdomains:
+ self.config.set('USER', 'vhost_subdomain_' + str(j), i)
+
+ OUT.debug('Storing subdomain name', 8)
+
+ j -= 1
+
+ def setinstalldir(self):
+
+ # set our install directory
+ #
+ # the sed is to make sure we don't have any '//' or '///' and so
+ # on in the final directory string
+ #
+ # this makes sure we don't write rubbish into the installs list
+
+ installpath = self.config.get('USER', 'g_htdocsdir') + '/' + \
+ self.config.get('USER', 'g_installdir')
+
+ installpath = re.compile('/+').sub('/', self.__root + installpath)
+
+ while installpath[-1] == '/':
+ installpath = installpath[:-1]
+
+ OUT.info('Install directory is: ' + installpath)
+
+ self.config.set('USER', 'g_installdir', installpath)
+
+ def checkconfig (self):
+
+ OUT.debug('Running checkconfig', 6)
+
+ # handle the softlink support
+
+ if ((self.config.has_option('USER', 'vhost_link_type') and
+ self.config.get('USER', 'vhost_link_type') == 'soft') or
+ (self.config.has_option('USER', 'g_soft') and
+ self.config.getboolean('USER', 'g_soft'))):
+
+ OUT.debug('Selecting soft links' , 7)
+
+ self.config.set('USER', 'g_link_type', 'soft')
+ else:
+
+ OUT.debug('Selecting hard links' , 7)
+
+ self.config.set('USER', 'g_link_type', 'hard')
+
+ # here, we output some useful information that might make a
+ # difference when dealing with a bug report
+
+ OUT.info('\nParameters from section "USER":\n')
+ usr_list = []
+ for i in self.config.options('USER'):
+
+ OUT.debug('Reporting parameter' , 7)
+
+ OUT.info(' Parameter ' + OUT.maybe_color('turquoise', i)
+ + ': "' + self.config.get('USER', i) + '"')
+ usr_list.append(i)
+
+ OUT.info('\nParameters from section "USER":\n')
+ for i in self.config.options('USER'):
+ if not i in usr_list:
+ OUT.info(' Parameter ' + OUT.maybe_color('turquoise', i)
+ + ': "' + self.config.get('USER', i) + '"')
+
+ # if we're running with --bug-report, time to quit
+
+ if self.config.has_option('USER', 'g_bugreport') and \
+ self.config.getboolean('USER', 'g_bugreport'):
+ sys.exit(0)
+
+ # if we get to here, then all is well
+
+ OUT.info('All config file checks successfully passed')
+
+ # --------------------------------------------------------------------
+ # Main functions
+
+ def run(self):
+
+ OUT.debug('Handling ' + self.work, 6)
+
+ if self.work == 'help':
+ self.parser.print_help()
+ sys.exit(0)
+
+ if self.work == 'list_servers':
+ from WebappConfig.server import listservers
+ # List the supported servers
+ listservers()
+
+ if self.work == 'query':
+
+ # The user needs to specify package and version
+ # for this action
+ self.check_package_set()
+ self.check_version_set()
+
+ # List all variables in bash format for the eclass
+ for i in self.config.options('USER'):
+ if not i in ['pn', 'pvr']:
+ try:
+ print i.upper() + '="' + self.config.get('USER', i) + '"'
+ except InterpolationSyntaxError,e:
+ print '# Failed to evaluate: ' + i.upper()
+
+ sys.exit(0)
+
+ if self.work == 'list_unused_installs':
+ # Get the handler for the virtual install db
+ db = self.create_webapp_db(self.maybe_get('USER', 'pn'),
+ self.maybe_get('USER', 'pvr'))
+
+ # Compare this against the installed web applications
+ self.create_webapp_source().listunused(db)
+
+ if self.work == 'list_installs':
+ # Get the handler for the virtual install db and list the
+ # virtual installations
+ self.create_webapp_db(self.maybe_get('pn'),
+ self.maybe_get('pvr')).listinstalls()
+
+ if self.work == 'show_installed':
+
+ # This reads a .webap file in the specified
+ # installdir.
+ self.setinstalldir()
+ self.create_dotconfig().show_installed()
+
+ if self.work == 'show_postinst':
+
+ # The user needs to specify package and version
+ # for this action
+ self.check_package_set()
+ self.check_version_set()
+
+ # The package must be installed
+ self.create_webapp_source().reportpackageavail()
+
+ # Show the post install file
+ self.create_ebuild().show_postinst()
+
+ if self.work == 'show_postupgrade':
+
+ # The user needs to specify package and version
+ # for this action
+ self.check_package_set()
+ self.check_version_set()
+
+ # The package must be installed
+ self.create_webapp_source().reportpackageavail()
+
+ # Show the post upgrade file
+ self.create_ebuild().show_postupgrade()
+
+ if self.work == 'install':
+
+ # This function's job is to perform *all* checks necessary to ensure
+ # that we can go ahead and install the application.
+ #
+ # If any of the tests fail, this function should abort by calling
+ # libsh_die()
+ #
+ # If this function returns, the caller can assume it is safe to
+ # proceed with the installation attempt
+ #
+ # This function is called from fn_verifyparams, *after* the
+ # command-line parameters have been parsed, and assigned to the
+ # $G_... variables.
+
+ # The user needs to specify package and version
+ # for this action
+ self.check_package_set()
+ self.check_version_set()
+
+ # Check that all configurations are valid
+ self.checkconfig()
+
+ # Check package availability and read config/server-owned files
+ ws = self.create_webapp_source()
+ ws.reportpackageavail()
+ ws.read(
+ virtual_files = self.config.get('USER', 'vhost_config_virtual_files'),
+ default_dirs = self.config.get('USER', 'vhost_config_default_dirs')
+ )
+
+ # Set the installation directory
+ self.setinstalldir()
+
+ # Check if there is a conflicting package
+ OUT.info('Is there already a package installed in '
+ + self.config.get('USER', 'g_installdir') + '?')
+
+ old = self.create_dotconfig()
+
+ if old.has_dotconfig():
+ old.read()
+ OUT.die('Package ' + old.packagename() + ' is already in'
+ 'stalled here.\nUse webapp-config -C to uninstall'
+ ' it first.\nInstall directory already contains a'
+ ' web application!')
+
+ OUT.info('No, there isn\'t. I can install into there safely.'
+ )
+
+ # check install location
+ if (os.path.basename(self.installdir()) ==
+ self.maybe_get('my_htdocsbase')):
+ OUT.warn('\nYou may be installing into the website\'s root di'
+ 'rectory.\nIs this what you meant to do?\n')
+
+ # Now we can install
+ self.create_server(self.create_content(self.maybe_get('pn'),
+ self.maybe_get('pvr')),
+ ws,
+ self.config.get('USER', 'pn'),
+ self.config.get('USER', 'pvr')).install()
+
+ if self.work == 'clean':
+
+ # This function's job is to perform *all* checks necessary to ensure
+ # that we can successfully remove an installed application.
+ #
+ # If any tests fail, this function should abort by calling libsh_die().
+ #
+ # This function is called from fn_verify(), and runs after the command-
+ # line parameters have been parsed.
+
+ # special case
+ #
+ # if a package has been specified, then chances are that they forgot
+ # to add the '-d' switch
+ if not self.flag_dir:
+ OUT.die('Missing switch -d <dir>')
+
+ if not self.upgrading():
+ self.setinstalldir()
+
+ old = self.create_dotconfig()
+
+ if not old.has_dotconfig():
+ OUT.die('Cannot clean!\n'
+ 'No package installed in ' + self.installdir())
+ old.read()
+
+ if not os.path.isdir(self.installdir()):
+ OUT.die('Directory "'
+ + self.__destd
+ + '" does not appear to exist')
+
+ if self.verbose():
+ OUT.warn('clean() called')
+
+ OUT.info('Removing installed files and directories')
+
+ self.config.set('USER', 'pn', old['WEB_PN'])
+ self.config.set('USER', 'pvr', old['WEB_PVR'])
+
+ # we don't want to read the .webapp file if we're upgrading,
+ # because we've just written the *new* app's details into the file!!
+ #
+ # we've already read the *old* app's details before we began the
+ # upgrade
+
+ # okay - what do we have?
+
+ OUT.info('Removing '
+ + old['WEB_PN'] + '-'
+ + old['WEB_PVR'] + ' from '
+ + self.installdir() + '\n'
+ + ' Installed by '
+ + old['WEB_INSTALLEDBY'] + ' on '
+ + old['WEB_INSTALLEDDATE'] + '\n'
+ +' Config files owned by '
+ + old['WEB_INSTALLEDFOR'], 1)
+
+ content = self.create_content(old['WEB_PN'], old['WEB_PVR'])
+ content.read()
+
+ self.create_server(content,
+ self.create_webapp_source(),
+ old['WEB_PN'], old['WEB_PVR']).clean()
+
+ if self.work == 'upgrade':
+
+ # This function's job is to perform *all* the checks necessary before
+ # we go off and upgrade an installed application
+ #
+ # If any of the tests fail, this function should abort the script by
+ # calling the libsh_die() function
+ #
+ # If this function returns to the caller, the caller should assume it's
+ # safe to start the upgrade :-)
+
+ # catch errors caused by missing <app-name> or <app-version>
+ #
+ # see Gentoo bug 98638
+
+ # The user needs to specify package and version
+ # for this action
+ self.check_package_set()
+ self.check_version_set()
+
+ # Check that all configurations are valid
+ self.checkconfig()
+
+ # Check package availability and read config/server-owned files
+ ws = self.create_webapp_source()
+ ws.reportpackageavail()
+ ws.read(
+ virtual_files = self.config.get('USER', 'vhost_config_virtual_files'),
+ default_dirs = self.config.get('USER', 'vhost_config_default_dirs')
+ )
+
+ # Set the installation directory
+ self.setinstalldir()
+
+ old = self.create_dotconfig()
+
+ if not old.has_dotconfig():
+ OUT.die('Cannot clean!\n'
+ 'No package installed in ' + self.installdir())
+ old.read()
+
+ # special case
+ #
+ # are we trying to upgrade to the same package?
+ # if so, we don't allow that
+
+ # FIXME: Should we check that we are not upgrading from
+ # package x to package y? In principle we are just running
+ # -C followed by -I so it does not matter too much what
+ # we remove and add. Why did the originial bash version
+ # invert romving and adding?
+ if (self.config.get('USER', 'pn') == old['WEB_PN'] and
+ self.config.get('USER', 'pvr') == old['WEB_PVR']):
+ OUT.warn('Do you really want to upgrade to the same pac'
+ 'kage?\nWaiting for 8 seconds...\nPress Ctrl-c i'
+ 'f you are uncertain.')
+ time.sleep(8)
+
+ if (self.config.get('USER', 'pn') != old['WEB_PN']):
+ OUT.warn('Do you really want to switch the installation'
+ ' from package "' + old['WEB_PN'] + '" to packag'
+ 'e "' + self.config.get('USER', 'pn') + '"?\nWai'
+ 'ting for 8 seconds... \nPress Ctrl-c if you are'
+ ' uncertain.')
+ time.sleep(8)
+
+ # okay, what do we have?
+
+ OUT.info('Upgrading '
+ + old['WEB_PN'] + '-'
+ + old['WEB_PVR'] + ' to '
+ + self.packagename() + '\n'
+ + ' Installed by '
+ + old['WEB_INSTALLEDBY'] + ' on '
+ + old['WEB_INSTALLEDDATE'] + '\n'
+ +' Config files owned by '
+ + old['WEB_INSTALLEDFOR'], 1)
+
+ content = self.create_content(old['WEB_PN'], old['WEB_PVR'])
+ content.read()
+
+ self.create_server(content,
+ ws,
+ old['WEB_PN'],
+ old['WEB_PVR']).upgrade(self.config.get('USER', 'pn'),
+ self.config.get('USER', 'pvr'))
+
+
+ def create_webapp_db(self, package, version):
+
+ from WebappConfig.db import WebappDB
+
+ return WebappDB(self.maybe_get('my_persistroot'),
+ package,
+ version,
+ self.maybe_get('wa_installsbase'),
+ #FIXME: I am confused by the bash version here
+ # The bash version uses the following command to
+ # create missing directories in the install db:
+ # mkdir -p "`dirname ${WA_INSTALLS}`" -m ${G_PERMS_DOTCONFIG}
+ # G_PERMS_DOTCONFIG defaults to "0600". I
+ # don't understand why I have only 755 directories
+ # my install db. Choosing 0755 here should be safe.
+ PermissionMap('0755'),
+ self.get_perm('g_perms_dotconfig'),
+ self.verbose(),
+ self.pretend())
+
+ def create_webapp_source(self):
+
+ from WebappConfig.db import WebappSource
+
+ return WebappSource(self.maybe_get('my_approot'),
+ self.maybe_get('pn'),
+ self.maybe_get('pvr'))
+
+ def create_dotconfig(self):
+
+ from WebappConfig.dotconfig import DotConfig
+
+ return DotConfig(self.installdir(),
+ self.maybe_get('my_dotconfig'),
+ self.get_perm('g_perms_dotconfig'),
+ self.pretend())
+
+ def create_ebuild(self):
+
+ from WebappConfig.ebuild import Ebuild
+
+ return Ebuild(self)
+
+
+ def create_content(self, package, version):
+
+ from WebappConfig.content import Contents
+
+ return Contents(self.installdir(),
+ package,
+ version,
+ self.get_perm('g_perms_dotconfig'),
+ self.maybe_get('my_dotconfig'),
+ self.verbose(),
+ self.pretend())
+
+ def create_server(self, content, webapp_source, package, version):
+
+ # handle server type
+
+ allowed_servers = {'apache' : WebappConfig.server.Apache,
+ 'lighttpd' : WebappConfig.server.Lighttpd,
+ 'aolserver': WebappConfig.server.Aolserver,
+ 'cherokee' : WebappConfig.server.Cherokee}
+
+ server = self.config.get('USER', 'vhost_server')
+
+ if not server in allowed_servers:
+ OUT.die('I don\'t support the "' + server + '" web server.')
+
+ from WebappConfig.protect import Protection
+
+ directories = {'source' : 'htdocs',
+ 'destination' : self.installdir(),
+ 'hostroot' : self.maybe_get('my_hostrootbase'),
+ 'vhostroot' : self.maybe_get('vhost_root')}
+
+ handlers = {'source' : webapp_source,
+ 'dotconfig' : self.create_dotconfig(),
+ 'ebuild' : self.create_ebuild(),
+ 'db' : self.create_webapp_db(package, version),
+ 'protect' : Protection(),
+ 'content' : content}
+
+ flags = {'linktype' : self.maybe_get('g_link_type'),
+ 'host' : self.maybe_get('vhost_hostname'),
+ 'orig' : self.maybe_get('g_orig_installdir'),
+ 'upgrade' : self.upgrading(),
+ 'verbose' : self.verbose(),
+ 'pretend' : self.pretend()}
+
+ return allowed_servers[server](directories,
+ self.create_permissions(),
+ handlers,
+ flags)
+
+ def create_permissions(self):
+
+ return {'file' : {'virtual' : [self.get_user('vhost_default_uid'),
+ self.get_group('vhost_default_gid'),
+ self.get_perm('vhost_perms_virtualowned_file')],
+ # These will be re-set by the servers
+ 'server-owned' : [None,
+ None,
+ self.get_perm('vhost_perms_serverowned_file')],
+ 'config-owned' : [self.get_user('vhost_config_uid'),
+ self.get_group('vhost_config_gid'),
+ self.get_perm('vhost_perms_configowned_file')],
+ 'config-server-owned' : [self.get_user('vhost_config_uid'),
+ None,
+ self.get_perm('vhost_perms_serverowned_file')],},
+ 'dir' : {'default-owned' :[self.get_user('vhost_default_uid'),
+ self.get_group('vhost_default_gid'),
+ self.get_perm('vhost_perms_defaultowned_dir')],
+ # These will be re-set by the servers
+ 'server-owned' : [None,
+ None,
+ self.get_perm('vhost_perms_serverowned_dir')],
+ 'config-owned' : [self.get_user('vhost_config_uid'),
+ self.get_group('vhost_config_gid'),
+ self.get_perm('vhost_perms_configowned_dir')],
+ 'config-server-owned' : [self.get_user('vhost_config_uid'),
+ None,
+ self.get_perm('vhost_perms_serverowned_dir')],
+ 'install-owned': [self.get_user('vhost_default_uid'),
+ self.get_group('vhost_default_gid'),
+ self.get_perm('vhost_perms_installdir')],},
+ }
diff --git a/WebappConfig/content.py b/WebappConfig/content.py
new file mode 100644
index 0000000..dc55d16
--- /dev/null
+++ b/WebappConfig/content.py
@@ -0,0 +1,752 @@
+#!/usr/bin/python -O
+#
+# /usr/sbin/webapp-config
+# Python script for managing the deployment of web-based
+# applications
+#
+# Originally written for the Gentoo Linux distribution
+#
+# Copyright (c) 1999-2006 Gentoo Foundation
+# Released under v2 of the GNU GPL
+#
+# Author(s) Stuart Herbert <stuart@gentoo.org>
+# Renat Lumpau <rl03@gentoo.org>
+# Gunnar Wrobel <php@gunnarwrobel.de>
+#
+# ========================================================================
+''' This class handles the contents file of a virtual install
+location. This file records all files and directories of the
+installation. '''
+
+__version__ = "$Id: content.py 245 2006-01-13 16:57:29Z wrobel $"
+
+# ========================================================================
+# Dependencies
+# ------------------------------------------------------------------------
+
+import md5, re, os, os.path
+
+from WebappConfig.debug import OUT
+from WebappConfig.permissions import PermissionMap
+import WebappConfig.wrapper as wrapper
+
+# ========================================================================
+# Content handler
+# ------------------------------------------------------------------------
+
+class Contents:
+ '''
+ This class records the contents for virtual install locations.
+ '''
+
+#self.worker.get_config('g_perms_dotconfig')
+ def __init__(self,
+ installdir,
+ package = '',
+ version = '',
+ permission = PermissionMap('0600'),
+ dbfile = '.webapp',
+ verbose = False,
+ pretend = False):
+
+ self.__root = wrapper.get_root()
+ self.__re = re.compile('/+')
+ self.__installdir = installdir
+
+ self.__pn = package
+ self.__pvr = version
+ self.__dbfile = dbfile
+
+ self.__perm = permission
+ self.__v = verbose
+ self.__p = pretend
+
+ self.__content = {}
+
+ # Ignore specific files while removing contents
+
+ # Added "webapp-test" to the list of ignored files. This
+ # type of file will be used to mark directories that should
+ # be included into the distutils manifest.
+ self.ignore = ['webapp_test']
+
+ def package_name(self):
+ ''' Return the package name for the virtual install.'''
+ return self.__pn + '-' + self.__pvr
+
+ def set_package(self, package):
+ ''' Set the package name.'''
+ self.__pn = package
+
+ def set_version(self, version):
+ ''' Set the package version.'''
+ self.__pvr = version
+
+ def appdb(self):
+ ''' Return the full path to the contents file.'''
+ return self.__installdir + '/' + self.__dbfile + '-' \
+ + self.package_name()
+
+ def db_print(self):
+ ''' Print all enties of the contents file.'''
+ entries = self.get_sorted_files()
+ values = []
+ for i in entries:
+ # Fix relative entry
+ s = self.__content[i]
+ s[1] = str(int(s[1]))
+ values.append(' '.join(s))
+ OUT.notice('\n'.join(values))
+
+ def check_installdir(self):
+ if not os.path.isdir(self.__installdir) and not self.__p:
+ OUT.die('"' + self.__installdir + '" specifies no directory! '
+ 'webapp-config needs a valid directory to store/retri'
+ 'eve information. Please correct your settings.')
+
+ def kill(self):
+ ''' Remove the contents file.'''
+ if not self.__p:
+ try:
+ dbpath = self.appdb()
+ self.check_installdir()
+ os.unlink(dbpath)
+ self.__content = {}
+ return True
+ except:
+ OUT.warn('Failed to remove ' + self.appdb() + '!')
+ return False
+ else:
+ OUT.info('Would have removed ' + self.appdb())
+ return True
+
+ def read(self):
+ '''
+ Reads the contents database.
+
+ Some content files have been provided for test purposes:
+
+ >>> import os.path
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+
+ This one should succeed:
+
+ >>> a = Contents(here + '/tests/testfiles/contents/',
+ ... package = 'test', version = '1.0')
+ >>> a.read()
+ >>> a.db_print()
+ file 1 virtual util/icon_browser.php 1124612216 9ffb2ca9ccd2db656b97cd26a1b06010
+ file 1 config-owned inc/prefs.php 1124612215 ffae752dba7092cd2d1553d04a0f0045
+ file 1 virtual lib/prefs.php 1124612215 ffae752dba7092cd2d1553d04a0f0045
+ file 1 virtual signup.php 1124612220 dc838bc375b3d02dafc414f8e71a2aec
+ file 1 server-owned data.php 1117009618 0
+ sym 1 virtual test 1124612220 dc838bc375b3d02dafc414f8e71a2aec /I link / to a very / strange location
+ dir 1 default-owned util 1117009618 0
+ dir 1 config-owned inc 1117009618 0
+ dir 1 default-owned lib 1117009618 0
+ dir 0 default-owned /var/www/localhost/cgi-bin 1124577741 0
+ dir 0 default-owned /var/www/localhost/error 1124577740 0
+ dir 0 default-owned /var/www/localhost/icons 1124577741 0
+
+ >>> a.get_directories() #doctest: +ELLIPSIS
+ ['.../contents//util', '.../contents//inc', '.../contents//lib', '/var/www/localhost/cgi-bin', '/var/www/localhost/error', '/var/www/localhost/icons']
+
+ This is a corrupted file that checks all fail safes:
+
+ >>> OUT.color_off()
+ >>> a = Contents(here + '/tests/testfiles/contents/',
+ ... package = 'test', version = '1.1')
+ >>> a.read() #doctest: +ELLIPSIS
+ * Invalid line in content file (dir 1 default-owned). Ignoring!
+ * Content file .../tests/testfiles/contents//.webapp-test-1.1 has an invalid line:
+ * dir 1 nobody-owned 1117009618 0
+ * Invalid owner: nobody-owned
+ * Invalid line in content file (dir 1 nobody-owned 1117009618 0). Ignoring!
+ * Content file .../tests/testfiles/contents//.webapp-test-1.1 has an invalid line:
+ * garbage 1 virtual 1124612215 ffae752dba7092cd2d1553d04a0f0045
+ * Invalid file type: garbage
+ * Invalid line in content file (garbage 1 virtual 1124612215 ffae752dba7092cd2d1553d04a0f0045). Ignoring!
+ * Invalid line in content file (file 1 virtual). Ignoring!
+ * Content file .../tests/testfiles/contents//.webapp-test-1.1 has an invalid line:
+ * file 1 virtual
+ * Not enough entries.
+ * Invalid line in content file (file 1 virtual ). Ignoring!
+ * Content file .../tests/testfiles/contents//.webapp-test-1.1 has an invalid line:
+ * file 31 config-owned 1124612215 ffae752dba7092cd2d1553d04a0f0045
+ * Invalid relative flag: 31
+ * Invalid line in content file (file 31 config-owned 1124612215 ffae752dba7092cd2d1553d04a0f0045). Ignoring!
+ '''
+
+ dbpath = self.appdb()
+
+ if not dbpath or not os.access(dbpath, os.R_OK):
+ OUT.die('Content file ' + dbpath + ' is missing or not accessibl'
+ 'e!')
+
+ content = open(dbpath).readlines()
+
+ for i in content:
+
+ i = i.strip()
+
+ rfn = re.compile('"(.*)"')
+ rfs = rfn.search(i)
+ if not rfs:
+ ok = False
+ else:
+ fn = rfs.group(1)
+ i = rfn.sub('', i)
+ line_split = i.split(' ')
+ line_split[3] = fn
+
+ OUT.debug('Adding content line', 10)
+
+ ok = True
+
+ if len(line_split) < 6:
+ ok = False
+ OUT.warn('Content file ' + dbpath + ' has an invalid line'
+ ':\n' + i + '\nNot enough entries.')
+
+ if ok and not line_split[0] in ['file', 'sym', 'dir']:
+ ok = False
+ OUT.warn('Content file ' + dbpath + ' has an invalid line'
+ ':\n' + i + '\nInvalid file type: '
+ + line_split[0])
+
+ if ok and not line_split[1] in ['0', '1']:
+ ok = False
+ OUT.warn('Content file ' + dbpath + ' has an invalid line'
+ ':\n' + i + '\nInvalid relative flag: '
+ + line_split[1])
+
+ if ok and not line_split[2] in ['virtual',
+ 'server-owned',
+ 'config-owned',
+ 'default-owned',
+ 'config-server-owned',
+ # Still need that in case an
+ # application was installed
+ # with w-c-1.11
+ 'root-owned']:
+ ok = False
+ OUT.warn('Content file ' + dbpath + ' has an invalid line'
+ ':\n' + i + '\nInvalid owner: '
+ + line_split[2])
+
+ if ok and line_split[0] == 'sym' and len(line_split) == 6:
+ OUT.warn('Content file ' + dbpath + ' has an invalid line'
+ ':\n' + i + '\nMissing link target! ')
+
+ if len(line_split) == 6:
+ line_split.append('')
+
+ # I think this could happen if the link target contains
+ # spaces
+ # -- wrobel
+ if len(line_split) > 7:
+ line_split = line_split[0:6] \
+ + [' '.join(line_split[6:])]
+
+ if ok:
+ if line_split[1] == '0':
+ self.__content[line_split[3]] = line_split
+ else:
+ self.__content[self.__installdir + '/'
+ + line_split[3]] = line_split
+
+ else:
+ OUT.warn('Invalid line in content file (' + i + '). Ignor'
+ 'ing!')
+
+ def write(self):
+ '''
+ Write the contents file.
+
+ A short test:
+
+ >>> import os.path
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> a = Contents(here + '/tests/testfiles/contents/',
+ ... package = 'test', version = '1.0',
+ ... pretend = True)
+ >>> a.read()
+ >>> OUT.color_off()
+ >>> a.write() #doctest: +ELLIPSIS
+ * Would have written content file .../tests/testfiles/contents//.webapp-test-1.0!
+ '''
+
+ dbpath = self.appdb()
+
+ if not dbpath:
+ OUT.die('No package specified!')
+
+ self.check_installdir()
+
+ values = [' '.join(i) for i in self.__content.values()]
+
+ if not self.__p:
+ try:
+ fd = os.open(self.appdb(), os.O_WRONLY | os.O_CREAT,
+ self.__perm(0600))
+
+ os.write(fd, '\n'.join(values))
+
+ os.close(fd)
+ except Exception, e:
+ OUT.warn('Failed to write content file ' + dbpath + '!\n'
+ + 'Error was: ' + str(e))
+ else:
+ OUT.info('Would have written content file ' + dbpath + '!')
+
+ def delete(self, entry):
+ '''
+ Delete a database entry.
+ '''
+ del self.__content[entry]
+
+ def add(self,
+ dsttype,
+ ctype,
+ destination,
+ path,
+ relative = True):
+ '''
+ Add an entry to the contents file.
+
+ Just like Portage, when we install an app, we create a contents
+ file to say what we installed and when. We use this contents
+ file to help us safely remove & upgrade apps.
+
+ CONTENTS file format:
+
+ <what> <rel> <type> <filename> <timestamp> <sum> [<optional>]
+
+ where
+
+ <what> is one of dir|sym|file|hardlink
+
+ <rel> is 1 for relative filenames, 0 for absolute
+ filenames
+
+ <type> is one of
+ server-owned|default-owned|config-owned|virtual
+
+ <timestamp> is the timestamp when the file was installed
+
+ <sum> is the md5sum of the file
+ (this is 0 for directories and symlinks)
+
+ <filename> is the actual name of the file we have installed
+
+ <optional> is additional data that depends upon <what>
+
+ NOTE:
+ Filenames used to be on the end of the line. This made
+ the old bash version more complicated, and
+ prone to failure. So I have moved the filename into the
+ middle of the line. -- Stuart
+
+ Portage uses absolute names for its files, dirs, and symlinks.
+ We do not.
+ In theory, you can move a directory containing a web-based app,
+ and
+
+ a) the app itself will not break, and
+ b) webapp-config will still work on that directory
+ for upgrades and cleans.
+
+ Position-independence *is* a design constraint that all future
+ changes to this script need to honour.
+
+ Inputs:
+
+ dsttype - type to add (one of dir|sym|file|hardlink)
+ ctype - internal webapp-config type
+ - (server-owned | config-owned | virtual)
+ destination - install dir (normally $G_INSTALLDIR)
+ entry - filename inside 'destination'
+ relative - 1 for storing a relative filename, 0 otherwise
+
+ >>> OUT.color_off()
+ >>> import os.path
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+
+ One for pretending:
+
+ >>> a = Contents(here + '/tests/testfiles/contents/app/',
+ ... package = 'test', version = '1.0',
+ ... pretend = True)
+
+ And this one is for real:
+
+ >>> b = Contents(here + '/tests/testfiles/contents/app/',
+ ... package = 'test', version = '1.0')
+
+ Pretend to add a file:
+
+ >>> a.add('file', 'config-owned',
+ ... destination = here + '/tests/testfiles/contents/app/',
+ ... path = '/test1', relative = True)
+ * pretending to add: file 1 config-owned "test1"
+
+ Lets not pretend this time:
+
+ >>> b.add('file', 'config-owned',
+ ... destination = here + '/tests/testfiles/contents/app/',
+ ... path = '/test1', relative = True)
+ >>> b.entry(here + '/tests/testfiles/contents/app/test1') #doctest: +ELLIPSIS
+ 'file 1 config-owned "test1" ... d8e8fca2dc0f896fd7cb4cb0031ba249 '
+
+ Lets produce an error with a file that does not exist:
+
+ >>> b.add('file', 'config-owned',
+ ... destination = here + '/tests/testfiles/contents/app/',
+ ... path = '/nothere', relative = True) #doctest: +ELLIPSIS
+ * Cannot access file .../tests/testfiles/contents/app/nothere to add it as installation content. This should not happen!
+
+ Other file types:
+
+ >>> b.add('hardlink', 'config-owned',
+ ... destination = here + '/tests/testfiles/contents/app/',
+ ... path = '/test2', relative = True)
+ >>> b.entry(here + '/tests/testfiles/contents/app/test2') #doctest: +ELLIPSIS
+ 'file 1 config-owned "test2" ... d8e8fca2dc0f896fd7cb4cb0031ba249 '
+ >>> b.add('dir', 'default-owned',
+ ... destination = here + '/tests/testfiles/contents/app/',
+ ... path = '/dir1', relative = True)
+ >>> b.entry(here + '/tests/testfiles/contents/app/dir1') #doctest: +ELLIPSIS
+ 'dir 1 default-owned "dir1" ... 0 '
+ >>> b.add('dir', 'default-owned', destination = here + '/tests/testfiles/contents/app',
+ ... path = '/dir1',
+ ... relative = False)
+ >>> b.entry(here + '/tests/testfiles/contents/app/dir1') #doctest: +ELLIPSIS
+ 'dir 0 default-owned ".../tests/testfiles/contents/app/dir1" ... 0 '
+
+ Q: Is the full link to the target what we want?
+ A: Yes, since the link will still be ok even if we move the directory.
+
+ >>> b.add('sym', 'virtual',
+ ... destination = here + '/tests/testfiles/contents/app/',
+ ... path = '/test3', relative = True)
+ >>> b.entry(here + '/tests/testfiles/contents/app/test3') #doctest: +ELLIPSIS
+ 'sym 1 virtual "test3" ... 0 .../tests/testfiles/contents/app/test1'
+
+ >>> b.db_print() #doctest: +ELLIPSIS
+ file 1 config-owned "test1" ... d8e8fca2dc0f896fd7cb4cb0031ba249
+ file 1 config-owned "test2" ... d8e8fca2dc0f896fd7cb4cb0031ba249
+ sym 1 virtual "test3" ... 0 .../tests/testfiles/contents/app/test1
+ dir 0 default-owned ".../tests/testfiles/contents/app/dir1" ... 0
+
+ '''
+
+ OUT.debug('Adding entry to content dictionary', 6)
+
+ # Build the full path that we use as index in the contents list
+ while path[0] == '/':
+ path = path[1:]
+ while destination[-1] == '/':
+ destination = destination[:-1]
+
+ entry = destination + '/' + path
+
+ # special case - we don't add entries for '.'
+
+ if os.path.basename(entry) == '.':
+ return
+
+ if (not self.__p
+ and not os.path.islink(entry)
+ and (not os.path.exists(entry)
+ or not os.access(entry, os.R_OK))):
+ OUT.warn('Cannot access file ' + entry + ' to add it as'
+ ' installation content. This should not happen!')
+ return
+
+ allowed_types = {
+ 'file' : [ 'file', self.file_md5, self.file_null ],
+ 'hardlink': [ 'file', self.file_md5, self.file_null ],
+ 'dir' : [ 'dir', self.file_zero, self.file_null ],
+ 'sym' : [ 'sym', self.file_zero, self.file_link ],
+ }
+
+ if not dsttype in allowed_types.keys():
+ OUT.die('Oops, webapp-config bug. "dsttype" is ' + dsttype)
+
+ # Generate handler for file attributes
+ a = allowed_types[dsttype]
+
+ # For absolute entries the path must match the entry
+ if not relative:
+ path = entry
+
+ OUT.debug('Adding entry', 7)
+
+ # report if pretending
+ if self.__p:
+
+ OUT.info(' pretending to add: ' +
+ ' '.join([dsttype,
+ str(int(relative)),
+ ctype,
+ '"' + path + '"']))
+ else:
+
+ # Only the path is enclosed in quotes, NOT the link targets
+ self.__content[entry] = [ a[0],
+ str(int(relative)),
+ ctype,
+ '"' + path + '"',
+ self.file_time(entry),
+ a[1](entry),
+ a[2](entry)]
+
+ if self.__v:
+ msg = path
+ if msg[0] == "/":
+ msg = self.__root + msg
+ msg = self.__re.sub('/', msg)
+ OUT.notice('>>> ' + a[0] + ' ' * (4 - len(a[0])) + ' (' \
+ + ctype + ') ' + msg)
+
+
+ def file_zero(self, filename):
+ ''' Just return a zero value.'''
+ return '0'
+
+ def file_null(self, filename):
+ ''' Just return an empty value.'''
+ return ''
+
+ def file_md5(self, filename):
+ ''' Return the md5 hash for the file content.'''
+ return str(md5.md5(open(filename).read()).hexdigest())
+
+ def file_time(self, filename):
+ ''' Return the last modification time.'''
+ if os.path.islink(filename):
+ return str(os.lstat(filename)[8])
+ else:
+ return str(os.stat(filename)[8])
+
+ def file_link(self, filename):
+ ''' Return the path of the link target.'''
+ return os.path.realpath(filename)
+
+ def get_sorted_files(self):
+ ''' Get a list of files. This is returned as a list sorted according
+ to length, so that files lower in the hierarchy can be removed
+ first.'''
+ def lencmp(x, y):
+ zx = len(x)
+ zy = len(y)
+ if zx > zy:
+ return -1
+ if zy > zx:
+ return 1
+ return cmp(x, y)
+
+ installed = self.__content.keys()
+ installed.sort(lencmp)
+
+ return installed
+
+ def get_directories(self):
+ ''' Get only the directories as a sorted list.'''
+ return [i
+ for i in self.get_sorted_files()
+ if self.__content[i][0] == 'dir']
+
+ def get_files(self):
+ ''' Get only files as a sorted list.'''
+ return [i
+ for i in self.get_sorted_files()
+ if self.__content[i][0] in ['sym', 'file']]
+
+
+ def get_canremove(self, entry):
+ '''
+ Determines if an entry can be removed.
+
+ Returns a string if the entry may not be removed. The
+ string will describe the reason why the entry should
+ not be removed.
+
+ In case the entry can be removed nothing will be
+ returned.
+
+ >>> import os.path
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+
+ Trying to remove the contents:
+
+ >>> a = Contents(here + '/tests/testfiles/contents/app/',
+ ... package = 'test', version = '1.0')
+ >>> a.read()
+ >>> a.ignore += ['.svn']
+ >>> for i in a.get_directories():
+ ... a.get_canremove(i)
+ '!dir test7'
+ '!empty dir1'
+
+ >>> for i in a.get_files():
+ ... a.get_canremove(i)
+ '!time test2'
+ '!time test4'
+ '!found test6'
+ '!sym dir3'
+ '!file dir4'
+
+ # Disabled
+ #'!target test3'
+
+ '''
+
+ OUT.debug('Checking if the file can be removed', 6)
+
+ # Path not found.
+ # Cannot remove -> return False
+ if not os.path.exists(entry) and not os.path.islink(entry):
+
+ OUT.debug('Did not find the file.', 7)
+
+ return '!found ' + self.epath(entry)
+
+ entry_type = self.etype(entry)
+
+ if entry_type == 'sym':
+ # Should be a link but is not.
+ if not os.path.islink(entry):
+ return '!sym ' + self.epath(entry)
+
+ # Expected link location does not match with
+ # current target.
+ # this results in leaving broken symlinks behind
+ # if self.file_link(entry) != self.etarget(entry):
+ # return '!target ' + self.epath(entry)
+
+ if entry_type == 'file':
+
+ # Expected file, path is not a file
+ if not os.path.isfile(entry):
+ return '!file ' + self.epath(entry)
+
+ # This is config protected. Do not remove!
+ #if self.eowner(entry)[0:6] == 'config':
+ # return '!cfgpro ' + self.epath(entry)
+
+ # Modification time does not match. Refuse to remove.
+ if self.file_time(entry) != self.etime(entry):
+ return '!time ' + self.epath(entry)
+
+ # Content has different hash. Do not remove.
+ if self.file_md5(entry) != self.emd5(entry):
+ return '!sum ' + self.epath(entry)
+
+ if entry_type == 'dir':
+
+ # Expected directory, path is not a directory.
+ if not os.path.isdir(entry):
+ return '!dir ' + self.epath(entry)
+
+ # the rules are simple
+ #
+ # if the directory is empty, it can go
+ # if the directory is not empty, it cannot go
+
+ # get a directory listing
+
+ entry_list = os.listdir(entry)
+
+ # Support for ignoring file. Currently only needed
+ # to enable doctests in the subversion repository
+ if self.ignore:
+ entry_list = [i for i in entry_list
+ if not i in self.ignore]
+
+ OUT.debug('Remaining files', 7)
+
+ # Directory not empty
+ # Cannot remove -> return False
+ if entry_list:
+ return '!empty ' + self.epath(entry)
+
+ # All checks passed? Remove!
+
+ def entry(self, entry):
+ ''' Return a complete entry.'''
+ if entry in self.__content.keys():
+ return ' '.join(self.__content[entry])
+ else:
+ raise Exception('Unknown file "' + entry + '"')
+
+ def etype(self, entry):
+ '''
+ Returns the entry type.
+ '''
+ if entry in self.__content.keys():
+ return self.__content[entry][0]
+ else:
+ raise Exception('Unknown file "' + entry + '"')
+
+ def erelative(self, entry):
+ '''
+ Returns if the entry is relative or not.
+ '''
+ if entry in self.__content.keys():
+ return bool(int(self.__content[entry][1]))
+ else:
+ raise Exception('Unknown file "' + entry + '"')
+
+ def eowner(self, entry):
+ '''
+ Returns the owner of the entry.
+ '''
+ if entry in self.__content.keys():
+ return self.__content[entry][2]
+ else:
+ raise Exception('Unknown file "' + entry + '"')
+
+ def epath(self, entry):
+ '''
+ Returns the (possibly relative) path of the entry.
+ '''
+ if entry in self.__content.keys():
+ msg = self.__content[entry][3]
+ if msg[0] == "/":
+ msg = self.__root + msg
+ msg = self.__re.sub('/', msg)
+ return msg
+ else:
+ raise Exception('Unknown file "' + entry + '"')
+
+ def etime(self, entry):
+ '''
+ Returns the recorded modification time of the entry.
+ '''
+ if entry in self.__content.keys():
+ return self.__content[entry][4]
+ else:
+ raise Exception('Unknown file "' + entry + '"')
+
+ def emd5(self, entry):
+ '''
+ Returns the recorded md5 hash of the entry.
+ '''
+ if entry in self.__content.keys():
+ return self.__content[entry][5]
+ else:
+ raise Exception('Unknown file "' + entry + '"')
+
+ def etarget(self, entry):
+ '''
+ Returns the recorded target of the link.
+ '''
+ if entry in self.__content.keys():
+ return self.__content[entry][6]
+ else:
+ raise Exception('Unknown file "' + entry + '"')
+
+if __name__ == '__main__':
+ import doctest, sys
+ doctest.testmod(sys.modules[__name__])
diff --git a/WebappConfig/db.py b/WebappConfig/db.py
new file mode 100644
index 0000000..b8757bb
--- /dev/null
+++ b/WebappConfig/db.py
@@ -0,0 +1,721 @@
+#!/usr/bin/python -O
+#
+# /usr/sbin/webapp-config
+# Python script for managing the deployment of web-based
+# applications
+#
+# Originally written for the Gentoo Linux distribution
+#
+# Copyright (c) 1999-2006 Gentoo Foundation
+# Released under v2 of the GNU GPL
+#
+# Author(s) Stuart Herbert <stuart@gentoo.org>
+# Renat Lumpau <rl03@gentoo.org>
+# Gunnar Wrobel <php@gunnarwrobel.de>
+#
+# ========================================================================
+''' This module provides handlers for the web application database as
+well as the database of virtual installs. '''
+
+__version__ = "$Id: db.py 264 2006-02-28 14:07:26Z wrobel $"
+
+# ========================================================================
+# Dependencies
+# ------------------------------------------------------------------------
+
+import time, os, os.path, re
+
+import WebappConfig.wrapper as wrapper
+
+from WebappConfig.debug import OUT
+from WebappConfig.permissions import PermissionMap
+
+
+# ========================================================================
+# Reduced base class
+# ------------------------------------------------------------------------
+
+class AppHierarchy:
+ '''
+ This base class provides a few common classes shared between the db
+ handler for /var/db/webapps and /usr/share/webapps.
+
+ Doctests can be found in the derived classes.
+ '''
+
+
+ def __init__(self,
+ root,
+ package = '',
+ version = '',
+ dbfile = 'installs'):
+
+ self.__r = wrapper.get_root()
+ self.root = self.__r + root
+ self.root = re.compile('/+').sub('/', self.root)
+
+ if not os.path.isdir(self.root):
+ OUT.die('"' + self.root + '" specifies no directory! webapp'
+ '-config needs a valid directory to store/retrieve in'
+ 'formation. Please correct your settings.')
+
+ self.pn = package
+ self.pvr = version
+ self.dbfile = dbfile
+
+ def package_name(self):
+ ''' Returns the package name in case the database has been initialized
+ with a specific name and version.'''
+ return self.pn + '-' + self.pvr
+
+ def set_package(self, package):
+ ''' Set the package name.'''
+ self.pn = package
+
+ def set_version(self, version):
+ ''' Set the package version.'''
+ self.pvr = version
+
+ def approot(self):
+ ''' Return the root directory of the package.'''
+ if self.pn:
+ result = self.root + '/' + self.pn
+ return re.compile('/+').sub('/', result)
+
+ def appdir(self):
+ ''' Return specific package directory (name + version).'''
+ if self.pvr and self.approot():
+ result = self.approot() + '/' + self.pvr
+ return re.compile('/+').sub('/', result)
+
+ def appdb(self):
+ ''' Return the complete path to the db file.'''
+ if self.appdir():
+ result = self.appdir() + '/' + self.dbfile
+ return re.compile('/+').sub('/', result)
+
+ def list_locations(self):
+ ''' List all available db files.'''
+
+ OUT.debug('Retrieving hierarchy locations', 6)
+
+ dbpath = self.appdb()
+
+ if dbpath and os.path.isfile(dbpath):
+ return {dbpath : [ self.pn, self.pvr]}
+
+ if dbpath and not os.path.isfile(dbpath):
+ OUT.debug('Package "' + self.package_name()
+ + '" not listed in the hierarchy (file "'
+ + dbpath + ' is missing)!', 8)
+ return {}
+
+ locations = {}
+
+ if self.pn:
+ packages = [self.pn]
+ else:
+ packages = os.listdir(self.root)
+
+ for i in packages:
+
+ OUT.debug('Checking package', 8)
+
+ if os.path.isdir(self.root + '/' + i):
+
+ OUT.debug('Checking version', 8)
+
+ versions = os.listdir(self.root + '/' + i)
+
+ for j in versions:
+ appdir = self.root + '/' + i + '/' + j
+ location = appdir + '/' + self.dbfile
+ if (os.path.isdir(appdir) and
+ os.path.isfile(location)):
+ locations[location] = [ i, j ]
+
+ return locations
+
+# ========================================================================
+# Handler for /var/db/webapps
+# ------------------------------------------------------------------------
+
+class WebappDB(AppHierarchy):
+ '''
+ The DataBase class handles a file-oriented data base that stores
+ information about virtual installs of web applications.
+
+ Some test files are needed to test the functionality. This localizes
+ the current position:
+
+ >>> import os.path
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+
+ Deactivates color output which is bad for the doctest
+
+ >>> OUT.color_off()
+
+ Initialize the class:
+
+ >>> a = WebappDB(here + '/tests/testfiles/webapps')
+
+ This lists the database:
+ >>> a.listinstalls()
+ /var/www/localhost/htdocs/gallery
+ /var/www/localhost/htdocs/horde
+ /var/www/localhost/htdocs/phpldapadmin
+
+ Which is also possible in a more user friendly way:
+
+ >>> b = WebappDB(here + '/tests/testfiles/webapps', verbose = True)
+ >>> b.listinstalls()
+ * Installs for gallery-1.4.4_p6
+ * /var/www/localhost/htdocs/gallery
+ * Installs for horde-3.0.5
+ * /var/www/localhost/htdocs/horde
+ * Installs for phpldapadmin-0.9.7_alpha4
+ * /var/www/localhost/htdocs/phpldapadmin
+
+ The function 'get_inst_files' handles the file locations within the
+ database. If no package has been specified while initializing
+ the database, the funtion will return all files available:
+
+ (code will only return package and varsion since the actual path
+ varies whith your code location)
+
+ >>> sb = [i[1] for i in b.list_locations().items()]
+ >>> sb.sort(lambda x,y: cmp(x[0]+x[1],y[0]+y[1]))
+ >>> sb
+ [['gallery', '1.4.4_p6'], ['gallery', '2.0_rc2'], ['horde', '3.0.5'], ['phpldapadmin', '0.9.7_alpha4']]
+
+ >>> c = WebappDB(here + '/tests/testfiles/webapps',
+ ... package = 'horde', version = '3.0.5')
+ >>> [i[1] for i in c.list_locations().items()]
+ [['horde', '3.0.5']]
+
+ Package specifiers that do not map to an install file will yield
+ an empty result and a warning.
+
+ The warning is turned off for the example:
+ >>> OUT.warn_off()
+
+ >>> c = WebappDB(here + '/tests/testfiles/webapps',
+ ... package = 'garbish', version = '3.0.5')
+ >>> [i[1] for i in c.list_locations().items()]
+ []
+
+ Package specifiers that do not map to an install file will yield
+ an empty result and a warning:
+ >>> c = WebappDB(here + '/tests/testfiles/webapps',
+ ... package = 'horde', version = '8.1.1')
+ >>> [i[1] for i in c.list_locations().items()]
+ []
+
+ The warning is turned off for the example:
+ >>> OUT.warn_on()
+
+ Virtual installs can be added or removed using the corresponding
+ functions (the example will just pretend to write):
+
+ >>> d = WebappDB(here + '/tests/testfiles/webapps', pretend = True,
+ ... package = 'horde', version = '3.0.5')
+ >>> d.add('/my/really/weird/hierarchy/for/horde', #doctest: +ELLIPSIS
+ ... user = 'me', group = 'me')
+ * Pretended to append installation /my/really/weird/hierarchy/for/horde
+ * Entry:
+ * ... me me /my/really/weird/hierarchy/for/horde
+ *
+ >>> d.remove('/var/www/localhost/htdocs/horde')
+ * Pretended to remove installation /var/www/localhost/htdocs/horde
+ * Final DB content:
+ *
+ *
+
+ >>> d.remove('/my/really/weird/hierarchy/for/horde') #doctest: +ELLIPSIS
+ * Installation at "/my/really/weird/hierarchy/for/horde" could not be found in the database file. Check the entries in ".../tests/testfiles/webapps/horde/3.0.5/installs"!
+ * Pretended to remove installation /my/really/weird/hierarchy/for/horde
+ * Final DB content:
+ * 1124612110 root root /var/www/localhost/htdocs/horde
+ *
+
+ '''
+
+ def __init__(self,
+ root = '/var/db/webapps',
+ package = '',
+ version = '',
+ installs = 'installs',
+ dir_perm = PermissionMap('0755'),
+ file_perm = PermissionMap('0600'),
+ verbose = False,
+ pretend = False):
+
+ AppHierarchy.__init__(self,
+ root,
+ package,
+ version,
+ dbfile = installs)
+
+ self.__dir_perm = dir_perm
+ self.__file_perm = file_perm
+ self.__v = verbose
+ self.__p = pretend
+
+ def remove(self, installdir):
+ '''
+ Remove a record from the list of virtual installs.
+
+ installdir - the installation directory
+ '''
+ if not installdir:
+ OUT.die('The installation directory must be specified!')
+
+ dbpath = self.appdb()
+
+ if not dbpath:
+ OUT.die('No package specified!')
+
+ if not os.access(dbpath, os.R_OK):
+ OUT.warn('Unable to read the install database ' + dbpath)
+ return
+
+ # Read db file
+ fdb = open(dbpath)
+ entries = fdb.readlines()
+ fdb.close()
+
+ newentries = []
+ found = False
+
+ for i in entries:
+
+ j = i.strip().split(' ')
+
+ if j:
+
+ if len(j) != 4:
+
+ # Remove invalid entry
+ OUT.warn('Invalid line "' + i.strip() + '" remo'
+ 'ved from the database file!')
+ elif j[3] != installdir:
+
+ OUT.debug('Keeping entry', 7)
+
+ # Keep valid entry
+ newentries.append(i.strip())
+
+ elif j[3] == installdir:
+
+ # Remove entry, indicate found
+ found = True
+
+ if not found:
+ OUT.warn('Installation at "' + installdir + '" could not be '
+ 'found in the database file. Check the entries in "'
+ + dbpath + '"!')
+
+ if not self.__p:
+ installs = open(dbpath, 'w')
+ installs.write('\n'.join(newentries) + '\n')
+ else:
+ OUT.info('Pretended to remove installation ' + installdir)
+ OUT.info('Final DB content:\n' + '\n'.join(newentries) + '\n')
+
+ def add(self, installdir, user, group):
+ '''
+ Add a record to the list of virtual installs.
+
+ installdir - the installation directory
+ '''
+
+ if not installdir:
+ OUT.die('The installation directory must be specified!')
+
+ if not str(user):
+ OUT.die('Please specify a valid user!')
+
+ if not str(group):
+ OUT.die('Please specify a valid group!')
+
+ OUT.debug('Adding install record', 6)
+
+ dbpath = self.appdb()
+
+ if not dbpath:
+ OUT.die('No package specified!')
+
+ if not self.__p and not os.path.isdir(os.path.dirname(dbpath)):
+ os.mkdir(os.path.dirname(dbpath), self.__dir_perm(0755))
+
+ fd = None
+
+ if not self.__p:
+ fd = os.open(dbpath,
+ os.O_WRONLY | os.O_APPEND | os.O_CREAT,
+ self.__file_perm(0600))
+
+ entry = str(int(time.time())) + ' ' + str(user) + ' ' + str(group)\
+ + ' ' + installdir + '\n'
+
+ OUT.debug('New record', 7)
+
+ if not self.__p:
+ os.write(fd, entry)
+ os.close(fd)
+ else:
+ OUT.info('Pretended to append installation ' + installdir)
+ OUT.info('Entry:\n' + entry)
+
+
+ def read_db(self):
+ '''
+ Returns the db content.
+ '''
+
+ files = self.list_locations()
+
+ if not files:
+ return {}
+
+ result = {}
+
+ for j in files.keys():
+
+ p = files[j][0] + '/' + files[j][1]
+ add = []
+
+ installs = open(j).readlines()
+
+ for i in installs:
+ if len(i.split(' ')) == 4:
+ add.append(i.split(' '))
+
+ if add:
+ result[p] = add
+
+ return result
+
+ def has_installs(self):
+ ''' Return True in case there are any virtual install locations
+ listed in the db file '''
+ if self.read_db():
+ return True
+ return False
+
+ def listinstalls(self):
+ '''
+ Outputs a list of what has been installed so far.
+ '''
+
+ loc = self.read_db()
+
+ if not loc and self.__v:
+ OUT.die('No virtual installs found!')
+
+ keys = loc.keys()
+ keys.sort()
+
+ for j in keys:
+ # The verbose output is meant to be readable for the user
+ if self.__v:
+ OUT.info('Installs for ' + '-'.join(j.split('/')), 4)
+
+ for i in loc[j]:
+ if self.__v:
+ # The verbose output is meant to be readable for
+ # the user
+ OUT.info(' ' + i[3].strip(), 1)
+ else:
+ # This is a simplified form for the webapp.eclass
+ print i[3].strip()
+
+# ========================================================================
+# Handler for /usr/share/webapps
+# ------------------------------------------------------------------------
+
+class WebappSource(AppHierarchy):
+ '''
+ The WebappSource class handles a web application hierarchy under
+ /usr/share/webapps.
+
+ Some test files are needed to test the functionality. This localizes
+ the current position:
+
+ >>> import os.path
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+
+ Initialize the class:
+
+ >>> a = WebappSource(here + '/tests/testfiles/share-webapps',)
+
+ A WebappDB class is needed to retrive information about installed
+ packages:
+ >>> b = WebappDB(here + '/tests/testfiles/webapps')
+
+ This lists the database:
+ >>> a.listunused(b)
+ installtest-1.0
+ uninstalled-6.6.6
+
+ '''
+
+ def __init__(self,
+ root = '/usr/share/webapps',
+ package = '',
+ version = '',
+ installed = 'installed_by_webapp_eclass'):
+
+ AppHierarchy.__init__(self,
+ root,
+ package,
+ version,
+ dbfile = installed)
+
+ self.__types = None
+
+ # Ignore specific files from the install location
+ self.ignore = []
+
+ def read(self,
+ config_owned = 'config-files',
+ server_owned = 'server-owned-files',
+ virtual_files = 'virtual',
+ default_dirs = 'default-owned'):
+ '''
+ Initialize the type cache.
+
+ >>> import os.path
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> a = WebappSource(here + '/tests/testfiles/share-webapps',
+ ... 'horde', '3.0.5')
+
+ >>> a.read()
+ >>> a.filetype('test1')
+ 'config-owned'
+
+ >>> a.filetype('test2')
+ 'server-owned'
+
+ '''
+ import WebappConfig.filetype
+
+ server_files = []
+ config_files = []
+
+ if os.access(self.appdir() + '/' + config_owned, os.R_OK):
+ flist = open(self.appdir() + '/' + config_owned)
+ config_files = flist.readlines()
+
+ OUT.debug('Identified config-protected files.', 7)
+
+ flist.close()
+
+ if os.access(self.appdir() + '/' + server_owned, os.R_OK):
+ flist = open(self.appdir() + '/' + server_owned)
+ server_files = flist.readlines()
+
+ OUT.debug('Identified config-protected files.', 7)
+
+ flist.close()
+
+ self.__types = WebappConfig.filetype.FileType(config_files,
+ server_files,
+ virtual_files,
+ default_dirs)
+
+ def filetype(self, filename):
+ ''' Determine filetype for the given file.'''
+ if self.__types:
+
+ OUT.debug('Returning file type', 7)
+
+ return self.__types.filetype(filename)
+
+ def dirtype(self, directory):
+ ''' Determine filetype for the given directory.'''
+ if self.__types:
+
+ OUT.debug('Returning directory type', 7)
+
+ return self.__types.dirtype(directory)
+
+ def source_exists(self, directory):
+ '''
+ Checks if the specified source directory exists within the
+ application directory.
+
+ >>> import os.path
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> a = WebappSource(here + '/tests/testfiles/share-webapps',
+ ... 'horde', '3.0.5')
+
+ >>> a.source_exists('htdocs')
+ True
+
+ >>> a.source_exists('test')
+ False
+ '''
+ if self.appdir() and os.path.isdir(self.appdir()
+ + '/' + directory):
+ return True
+ return False
+
+ def get_source_directories(self, directory):
+ '''
+ Lists the directories provided by the source directory
+ 'directory'
+
+ >>> import os.path
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> a = WebappSource(here + '/tests/testfiles/share-webapps',
+ ... 'horde', '3.0.5')
+ >>> d = a.get_source_directories('htdocs')
+ >>> [i for i in d if i != '.svn']
+ ['dir1', 'dir2']
+ '''
+ dirs = []
+
+ if self.source_exists(directory):
+ source_dir = self.appdir() + '/' + directory
+ dir_entries = os.listdir(source_dir)
+ for i in dir_entries:
+ if (not os.path.islink(source_dir + '/' + i)
+ and os.path.isdir(source_dir + '/' + i)):
+ dirs.append(i)
+
+ # Support for ignoring entries. Currently only needed
+ # to enable doctests in the subversion repository
+ if self.ignore:
+ dirs = [i for i in dirs
+ if not i in self.ignore]
+
+ dirs.sort()
+
+ return dirs
+
+ def get_source_files(self, directory):
+ '''
+ Lists the files provided by the source directory
+ 'directory'
+
+ >>> import os.path
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> a = WebappSource(here + '/tests/testfiles/share-webapps',
+ ... 'horde', '3.0.5')
+ >>> a.get_source_files('htdocs')
+ ['test1', 'test2']
+ '''
+
+ files = []
+
+ if self.source_exists(directory):
+ source_dir = self.appdir() + '/' + directory
+ dir_entries = os.listdir(source_dir)
+ for i in dir_entries:
+ if (os.path.isfile(source_dir + '/' + i)
+ or os.path.islink(source_dir + '/' + i)):
+ files.append(i)
+
+ # Support for ignoring files. Currently only needed
+ # to enable doctests in the subversion repository
+ if self.ignore:
+ files = [i for i in files
+ if not i in self.ignore]
+
+ files.sort()
+
+ return files
+
+ def listunused(self, db):
+ '''
+ Outputs a list of what has not been installed so far
+ '''
+
+ packages = self.list_locations()
+
+ if not packages:
+ OUT.die('No packages found!')
+
+ keys = packages.keys()
+ keys.sort()
+
+ OUT.debug('Check for unused web apps', 7)
+
+ for i in keys:
+
+ db.set_package(packages[i][0])
+ db.set_version(packages[i][1])
+
+ if not db.has_installs():
+ OUT.notice(packages[i][0] + '-' + packages[i][1])
+
+
+ def packageavail(self):
+ '''
+ Check to see whether the given package has been installed or not.
+
+ These checks are carried out by using wrapper.py to facilitate
+ distribution independant handling of the task.
+
+ Outputs:
+ 0 - on success
+ 1 - package not found
+ 2 - no package to find
+ 3 - package isn't webapp-config compatible '
+
+ >>> import os.path
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+
+ Does not exist:
+
+ >>> a = WebappSource(here + '/tests/testfiles/share-webapps',
+ ... 'nothere', '1')
+ >>> a.packageavail()
+ 1
+
+ Incompatible cannot be tested since that would require a
+ oackage (including version number) that is installed on
+ all systems.
+ '''
+
+ OUT.debug('Verifying package ' + self.package_name(), 6)
+
+ if not wrapper.package_installed('=' + self.package_name()):
+ return 1
+
+ # unfortunately, just because a package has been installed, it
+ # doesn't mean that the package itself is webapp-compatible
+ #
+ # we need to check that the package has an entry in the
+ # application repository
+
+ if not self.appdb():
+ return 3
+ else:
+ return 0
+
+ def reportpackageavail(self):
+ '''
+ This is a simple wrapper around packageavail() that outputs
+ user-friendly error messages if an error occurs
+
+ Cannot test the rest, do not want to die.
+ '''
+
+ OUT.info('Do we have ' + self.package_name() + ' available?')
+
+ available = self.packageavail()
+
+ if available == 0:
+ OUT.info(' Yes, we do')
+ if available == 1:
+ OUT.die(' Please emerge ' + self.package_name() + ' first.')
+ if available == 3:
+ OUT.die(' ' + self.package_name() + ' is not compatible with '
+ 'webapp-config.\nIf it should be, report this at '
+ + wrapper.bugs_link)
+
+if __name__ == '__main__':
+ import doctest, sys
+ doctest.testmod(sys.modules[__name__])
diff --git a/WebappConfig/debug.py b/WebappConfig/debug.py
new file mode 100644
index 0000000..18cea6f
--- /dev/null
+++ b/WebappConfig/debug.py
@@ -0,0 +1,501 @@
+#################################################################################
+# WEBAPP-CONFIG - DEBUGGING FUNCTIONS
+#################################################################################
+# debug.py -- Utility function for debugging
+# Copyright 2005 Gunnar Wrobel
+# Distributed under the terms of the GNU General Public License v2
+
+__version__ = "$Id: debug.py 159 2005-11-10 09:48:25Z wrobel $"
+
+#################################################################################
+##
+## Dependancies
+##
+#################################################################################
+
+import sys, inspect
+
+from optparse import OptionGroup
+
+#################################################################################
+##
+## Color codes (taken from portage)
+##
+#################################################################################
+
+esc_seq = '\x1b['
+
+codes = {}
+codes['reset'] = esc_seq + '39;49;00m'
+codes['red'] = esc_seq + '31;01m'
+codes['green'] = esc_seq + '32;01m'
+codes['yellow'] = esc_seq + '33;01m'
+codes['turquoise'] = esc_seq + '36;01m'
+
+#################################################################################
+##
+## Message Class
+##
+#################################################################################
+
+class Message:
+ #FIXME: Think about some simple doctests before you modify this class the
+ # next time.
+
+ def __init__(self, module = '',
+ err = sys.stderr,
+ dbg = sys.stderr,
+ debugging_level = 4,
+ debugging_verbosity = 2,
+ info_level = 4,
+ warn_level = 4,
+ col = True,
+ mth = ['*'],
+ obj = ['*'],
+ var = ['*']):
+
+ # A description of the module that is being debugged
+ self.debug_env = module
+
+ # Where should the debugging output go? This can also be a file
+ self.debug_out = dbg
+
+ # Where should the error output go? This can also be a file
+ self.error_out = err
+
+ # The higher the level the more information you will get
+ self.warn_lev = warn_level
+
+ # The higher the level the more information you will get
+ self.info_lev = info_level
+
+ # The highest level of debugging messages acceptable for output
+ # The higher the level the more output you will get
+ self.debug_lev = debugging_level
+
+ # The debugging output can range from very verbose (3) to
+ # very compressed (1)
+ self.debug_vrb = debugging_verbosity
+
+ # Which methods should actually be debugged?
+ # Use '*' to indicate 'All methods'
+ self.debug_mth = mth
+
+ # Which objects should actually be debugged?
+ # Use '*' to indicate 'All objects'
+ self.debug_obj = obj
+
+ # Which variables should actually be debugged?
+ # Use '*' to indicate 'All variables'
+ self.debug_var = var
+
+ # Exclude class variables by default
+ self.show_class_variables = False
+
+ # Should the output be colored?
+ self.use_color = col
+
+ self.has_error = False
+
+
+ ############################################################################
+ # Add command line options
+
+ def cli_opts(self, parser):
+
+ group = OptionGroup(parser,
+ '<Debugging options>',
+ 'Control the debugging features of '
+ + self.debug_env)
+
+ group.add_option('--debug',
+ action = 'store_true',
+ help = 'Activates debugging features.')
+
+ group.add_option('--debug-level',
+ action = 'store',
+ type = 'int',
+ help = 'A value between 0 and 10. 0 means no debugging '
+ 'messages will be selected, 10 selects all debugging me'
+ 'ssages. Default is "4".')
+
+ group.add_option('--debug-verbose',
+ action = 'store',
+ type = 'int',
+ help = 'A value between 1 and 3. Lower values yield les'
+ 's verbose debugging output. Default is "2".')
+
+ group.add_option('--debug-methods',
+ action = 'store',
+ help = 'Limits the methods that will return debugging o'
+ 'utput. The function name is sufficient and there is no'
+ 'difference between class methods or general functions.'
+ ' Several methods can be specified by seperating them w'
+ ' with a comma. Default is "*" which specifies all meth'
+ 'ods.')
+
+ group.add_option('--debug-classes',
+ action = 'store',
+ help = 'Limits the classes that will return debugging o'
+ 'utput. Specify only the class name not including the m'
+ 'odules in which the class is defined (e.g. MyModule.ma'
+ 'in.Main should only be represented by "Main"). Several'
+ 'classes can be specified by seperating them with a com'
+ 'ma. Default is "*" which specifies all classes.')
+
+ group.add_option('--debug-variables',
+ action = 'store',
+ help = 'Limits the variables that will return debugging'
+ ' output. Several variables can be specified by seperat'
+ 'ing them with a comma. Default is "*" which specifies '
+ 'all variables.')
+
+ group.add_option('--debug-class-vars',
+ action = 'store_true',
+ help = 'In default mode the debugging code will only re'
+ 'turn information on the local variable which does not '
+ 'include the class variables. Use this switch to add al'
+ 'l values that are provided by "self".')
+
+ group.add_option('--debug-nocolor',
+ action = 'store_true',
+ help = 'Deactivates colors in the debugging output.')
+
+ parser.add_option_group(group)
+
+
+ #############################################################################
+ # Handle command line options
+
+ def cli_handle(self, options):
+
+ if (options.__dict__.has_key('debug')
+ and options.__dict__['debug']):
+ self.debug_on()
+ else:
+ self.debug_off()
+ return
+
+ if (options.__dict__.has_key('debug_class_vars')
+ and options.__dict__['debug_class_vars']):
+ self.class_variables_on()
+ else:
+ self.class_variables_off()
+
+ if (options.__dict__.has_key('debug_nocolor')
+ and options.__dict__['debug_nocolor']):
+ self.color_off()
+ else:
+ self.color_on()
+
+ if (options.__dict__.has_key('debug_level') and
+ options.__dict__['debug_level']):
+ dbglvl = int(options.__dict__['debug_level'])
+ if dbglvl < 0:
+ dbglvl = 0
+ if dbglvl > 10:
+ dbglvl = 10
+ self.set_debug_level(dbglvl)
+
+ if (options.__dict__.has_key('debug_verbose') and
+ options.__dict__['debug_verbose']):
+ dbgvrb = int(options.__dict__['debug_verbose'])
+ if dbgvrb < 1:
+ dbgvrb = 1
+ if dbgvrb > 3:
+ dbgvrb = 3
+ self.set_debug_verbosity(dbgvrb)
+
+ for i in [('debug_methods', self.set_debug_methods),
+ ('debug_classes', self.set_debug_classes),
+ ('debug_variables', self.set_debug_variables),]:
+
+ if (options.__dict__.has_key(i[0]) and
+ options.__dict__[i[0]]):
+ i[1](options.__dict__[i[0]])
+
+
+ #############################################################################
+ ## Helper Functions
+
+ def set_module(self, module):
+
+ self.debug_env = module
+
+ def set_debug_methods(self, methods):
+
+ methods = methods.split(',')
+
+ if methods:
+ self.debug_mth = methods
+
+ def set_debug_classes(self, classes):
+
+ classes = classes.split(',')
+
+ if classes:
+ self.debug_obj = classes
+
+ def set_debug_variables(self, variables):
+
+ variables = variables.split(',')
+
+ if variables:
+ self.debug_var = variables
+
+ def maybe_color (self, col, text):
+ if self.use_color:
+ return codes[col] + text + codes['reset']
+ return text
+
+ def set_info_level(self, info_level = 4):
+ self.info_lev = info_level
+
+ def info_off(self):
+ self.set_info_level(0)
+
+ def info_on(self, info_level = 4):
+ self.set_info_level(info_level)
+
+ def set_warn_level(self, warn_level = 4):
+ self.warn_lev = warn_level
+
+ def warn_off(self):
+ self.set_warn_level(0)
+
+ def warn_on(self, warn_level = 4):
+ self.set_warn_level(warn_level)
+
+ def set_debug_level(self, debugging_level = 4):
+ self.debug_lev = debugging_level
+
+ def set_debug_verbosity(self, debugging_verbosity = 2):
+ self.debug_vrb = debugging_verbosity
+
+ def debug_off(self):
+ self.set_debug_level(0)
+
+ def debug_on(self):
+ self.set_debug_level()
+
+ def color_off(self):
+ self.use_color = False
+
+ def color_on(self):
+ self.use_color = True
+
+ def class_variables_off(self):
+ self.show_class_variables = False
+
+ def class_variables_on(self):
+ self.show_class_variables = True
+
+ #############################################################################
+ ## Output Functions
+
+ def notice (self, note):
+ print note
+
+ def info (self, info, level = 4):
+
+ info = str(info)
+
+ if level > self.info_lev:
+ return
+
+ for i in info.split('\n'):
+ print self.maybe_color('green', '* ') + i
+
+ def status (self, message, status, info = 'ignored'):
+
+ message = str(message)
+
+ lines = message.split('\n')
+
+ if not lines:
+ return
+
+ for i in lines[0:-1]:
+ print self.maybe_color('green', '* ') + i
+
+ i = lines[-1]
+
+ if len(i) > 58:
+ i = i[0:57]
+
+ if status == 1:
+ result = '[' + self.maybe_color('green', 'ok') + ']'
+ elif status == 0:
+ result = '[' + self.maybe_color('red', 'failed') + ']'
+ else:
+ result = '[' + self.maybe_color('yellow', info) + ']'
+
+ print self.maybe_color('green', '* ') + i + ' ' + '.' * (58 - len(i)) \
+ + ' ' + result
+
+ def warn (self, warn, level = 4):
+
+ warn = str(warn)
+
+ if level > self.warn_lev:
+ return
+
+ for i in warn.split('\n'):
+ print self.maybe_color('yellow', '* ') + i
+
+ def error (self, error):
+
+ error = str(error)
+
+ for i in error.split('\n'):
+ print >> self.error_out, self.maybe_color('red', '* ') + i
+ self.has_error = True
+
+ def die (self, error):
+
+ error = str(error)
+
+ for i in error.split('\n'):
+ self.error(self.maybe_color('red', 'Fatal error: ') + i)
+ self.error(self.maybe_color('red', 'Fatal error(s) - aborting'))
+ sys.exit(1)
+
+ def debug (self, message, level = 4):
+ '''
+ This is a generic debugging method.
+ '''
+ ## Check the debug level first. This is the most inexpensive check.
+ if level > self.debug_lev:
+ return
+
+ ## Maybe this should be debugged. So get the stack first.
+ stack = inspect.stack()
+
+ ## This can probably never happen but does not harm to check
+ ## that there is actually something calling this function
+ if len(stack) < 2:
+ return
+
+ ## Get the stack length to determine indentation of the debugging output
+ stacklength = len(stack)
+ ls = ' ' * stacklength
+
+ ## Get the information about the caller
+ caller = stack[1]
+
+ ## The function name of the calling frame is the fourth item in the list
+ callermethod = caller[3]
+
+ ## Is this actually one of the methods that should be debugged?
+ if not '*' in self.debug_mth and not callermethod in self.debug_mth:
+ return
+
+ ## Still looks like this should be debugged. So retrieve the dictionary
+ ## of local variables from the caller
+ callerlocals = inspect.getargvalues(caller[0])[3]
+
+ ## Is the caller an obejct? If so he provides 'self'
+ if 'self' in callerlocals.keys():
+ callerobject = callerlocals['self']
+ del callerlocals['self']
+ if self.show_class_variables:
+ cv = inspect.getmembers(callerobject,
+ lambda x: not inspect.ismethod(x))
+ callerlocals.update(cv)
+ else:
+ callerobject = None
+
+ # Remove variables not requested
+ if not '*' in self.debug_var:
+ callerlocals = dict([i for i in callerlocals.items()
+ if i[0] in self.debug_var])
+
+ ## Is the object among the list of objects to debug?
+ if (not '*' in self.debug_obj and
+ not str(callerobject.__class__.__name__) in self.debug_obj):
+ return
+
+ message = str(message)
+
+ def breaklines(x):
+ '''
+ Helper function to keep width of the debugging output.
+
+ This may look ugly for arrays but it is acceptable and not
+ breaking the line would break the output format
+ '''
+ ## Get the number of lines we need (rounded down)
+ lines = len(x) // 60
+ if lines > 0:
+ for j in range(lines):
+ ## Print line with continuation marker
+ print >> self.debug_out, ls + '// ' + x[0:60] + ' \\'
+ ## Remove printed characters from output
+ x = x[60:]
+ ## Print final line
+ print >> self.debug_out, ls + '// ' + x
+
+ if self.debug_vrb == 1:
+ # Top line indicates class and method
+ c = ''
+ if callerobject:
+ c += 'Class: ' + str(callerobject.__class__.__name__) + ' | '
+ if callermethod:
+ c += 'Method: ' + str(callermethod)
+ print >> self.debug_out, '// ' + c
+ # Selected variables follow
+ if callerlocals:
+ for i,j in callerlocals.items():
+ print >> self.debug_out, '// ' \
+ + self.maybe_color('turquoise', str(i)) + ':' + str(j)
+ # Finally the message
+ print >> self.debug_out, self.maybe_color('yellow', message)
+ return
+
+ if self.debug_vrb == 3:
+ print >> self.debug_out, ls + '/////////////////////////////////' + \
+ '////////////////////////////////'
+
+ # General information about what is being debugged
+ #(module name or similar)
+ print >> self.debug_out, ls + '// ' + self.debug_env
+ print >> self.debug_out, ls + '//-----------------------------------' + \
+ '----------------------------'
+
+ ## If the caller is a class print the name here
+ if callerobject:
+ print >> self.debug_out, ls + \
+ '// Object Class: ' + str(callerobject.__class__.__name__)
+
+ ## If the method has been extracted print it here
+ if callermethod:
+ print >> self.debug_out, ls + '// ' \
+ + self.maybe_color('green', 'Method: ') + str(callermethod)
+ if self.debug_vrb == 3:
+ print >> self.debug_out, ls + '//---------------------------' + \
+ '------------------------------------'
+
+ ## Print the information on all available local variables
+ if callerlocals:
+ if self.debug_vrb == 3:
+ print >> self.debug_out, ls + '//'
+ print >> self.debug_out, ls + '// VALUES '
+ for i,j in callerlocals.items():
+ print >> self.debug_out, ls + '// ------------------> ' \
+ + self.maybe_color('turquoise', str(i)) + ':'
+ breaklines(str(j))
+ if self.debug_vrb == 3:
+ print >> self.debug_out, ls + '//------------------------------'\
+ '---------------------------------'
+
+ # Finally print the message
+ breaklines(self.maybe_color('yellow', message))
+
+ if self.debug_vrb == 3:
+ print >> self.debug_out, ls + '//-------------------------------' + \
+ '--------------------------------'
+ print >> self.debug_out, ls + '/////////////////////////////////' + \
+ '////////////////////////////////'
+
+## gloabal message handler
+OUT = Message('webapp-config')
diff --git a/WebappConfig/dotconfig.py b/WebappConfig/dotconfig.py
new file mode 100644
index 0000000..5292d1b
--- /dev/null
+++ b/WebappConfig/dotconfig.py
@@ -0,0 +1,282 @@
+#!/usr/bin/python -O
+#
+# /usr/sbin/webapp-config
+# Python script for managing the deployment of web-based
+# applications
+#
+# Originally written for the Gentoo Linux distribution
+#
+# Copyright (c) 1999-2006 Gentoo Foundation
+# Released under v2 of the GNU GPL
+#
+# Author(s) Stuart Herbert <stuart@gentoo.org>
+# Renat Lumpau <rl03@gentoo.org>
+# Gunnar Wrobel <php@gunnarwrobel.de>
+#
+# ========================================================================
+''' Handles the information stored within the virtual install location
+about the type of package installed. '''
+
+__version__ = "$Id: dotconfig.py 219 2006-01-04 20:13:38Z wrobel $"
+
+# ========================================================================
+# Dependencies
+# ------------------------------------------------------------------------
+
+import pwd, shlex, os.path
+
+from time import strftime
+from WebappConfig.debug import OUT
+from WebappConfig.permissions import PermissionMap
+
+# ========================================================================
+# Handler for dotConfig files
+# ------------------------------------------------------------------------
+
+class DotConfig:
+ '''
+ This class handles the dotconfig file that will be written to all
+ virtual install locations.
+
+ A virtual install location has been prepared in the testfiles
+ directory:
+
+ >>> import os.path
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> a = DotConfig(here + '/tests/testfiles/htdocs/horde')
+ >>> a.has_dotconfig()
+ True
+
+ This directory contains no virtual install:
+
+ >>> b = DotConfig(here + '/tests/testfiles/htdocs/empty')
+ >>> b.has_dotconfig()
+ False
+
+ The horde install directory is empty:
+
+ >>> a.is_empty()
+
+ This install location has another web application installed::
+
+ >>> b = DotConfig(here + '/tests/testfiles/htdocs/complain')
+ >>> b.is_empty()
+ '!morecontents .webapp-cool-1.1.1'
+
+ This prints what is installed in the horde directory (and
+ tests the read() function):
+
+ >>> a.show_installed()
+ horde 3.0.5
+
+ This will pretend to write a .webapp file (this test has too many ellipsis):
+
+ >>> OUT.color_off()
+ >>> a = DotConfig('/nowhere', pretend = True)
+ >>> a.write('horde', '5.5.5', 'localhost', '/horde3', 'me:me') #doctest: +ELLIPSIS
+ * Would have written the following information into /nowhere/.webapp:
+ * # .webapp
+ ...
+ *
+ * WEB_PN="horde"
+ * WEB_PVR="5.5.5"
+ * WEB_INSTALLEDBY="..."
+ * WEB_INSTALLEDDATE="..."
+ * WEB_INSTALLEDFOR="me:me"
+ * WEB_HOSTNAME="localhost"
+ * WEB_INSTALLDIR="/horde3"
+
+ Delete the .webapp file if possible:
+
+ >>> a = DotConfig(here + '/tests/testfiles/htdocs/horde', pretend = True)
+ >>> a.kill() #doctest: +ELLIPSIS
+ * Would have removed .../tests/testfiles/htdocs/horde/.webapp
+ True
+
+ '''
+
+ def __init__(self,
+ installdir,
+ dot_config = '.webapp',
+ permission = PermissionMap('0600'),
+ pretend = False):
+
+ self.__instdir = installdir
+ self.__file = dot_config
+ self.__perm = permission
+ self.__p = pretend
+ self.__data = {}
+ self.__tokens = ['WEB_PN',
+ 'WEB_PVR',
+ 'WEB_INSTALLEDBY',
+ 'WEB_INSTALLEDDATE',
+ 'WEB_INSTALLEDFOR',
+ 'WEB_HOSTNAME',
+ 'WEB_INSTALLDIR']
+
+ def __getitem__(self, key):
+ if key in self.__data.keys():
+ return self.__data[key]
+
+ def __dot_config(self):
+ ''' Returns the full path to the dot config file.'''
+ return self.__instdir + '/' + self.__file
+
+ def has_dotconfig(self):
+ ''' Return True if the install location already has a dotconfig
+ file.'''
+ dotconfig = self.__dot_config()
+
+ OUT.debug('Verifying path', 7)
+
+ if not os.path.isfile(dotconfig):
+ return False
+
+ if not os.access(dotconfig, os.R_OK):
+ return False
+
+ return True
+
+ def is_empty(self):
+ '''
+ Checks if there are more files '.webapp-*' in the directory.
+ Returns empty if there are none.
+ '''
+ if not os.path.isdir(self.__instdir):
+ return '!dir ' + self.__instdir
+
+ # get a directory listing
+ entries = os.listdir(self.__instdir)
+
+ for i in entries:
+ if i[:len(self.__file)] == self.__file and i != self.__file:
+ return '!morecontents ' + i
+
+ def show_installed(self):
+ ''' Show which application has been installed in the install
+ location.'''
+ if not self.has_dotconfig():
+ OUT.die('No ' + self.__file + ' file in ' + self.__instdir
+ + '; unable to continue')
+
+ self.read()
+
+ OUT.notice(self.__data['WEB_PN'] + ' ' +
+ self.__data['WEB_PVR'])
+
+ def packagename(self):
+ ''' Retrieve the package name from the values specified in the dot
+ config file'''
+
+ OUT.debug('Trying to retrieve package name', 6)
+
+ if ('WEB_PN' in self.__data.keys()
+ and 'WEB_PVR' in self.__data.keys()):
+ return self.__data['WEB_PN'] + '-' + self.__data['WEB_PVR']
+ return ''
+
+ def read(self):
+ ''' Read the contents of the dot config file.'''
+ dotconfig = self.__dot_config()
+
+ OUT.debug('Checking for dotconfig ', 6)
+
+ if not self.has_dotconfig():
+ raise Exception('Cannot read file ' + dotconfig)
+
+ tokens = shlex.shlex(open(dotconfig))
+
+ while True:
+ a = tokens.get_token()
+ b = tokens.get_token()
+ c = tokens.get_token()
+
+ OUT.debug('Reading token', 8)
+
+ if (a in self.__tokens and
+ b == '=' and c):
+
+ if c[0] == '"':
+ c = c[1:]
+
+ if c[-1] == '"':
+ c = c[:-1]
+
+ self.__data[a] = c
+
+ else:
+ break
+
+ def write(self,
+ package,
+ version,
+ host,
+ original_installdir,
+ user_group):
+ '''
+ Output the .webapp file, that tells us in future what has been installed
+ into this directory.
+ '''
+ self.__data['WEB_PN'] = package
+ self.__data['WEB_PVR'] = version
+ self.__data['WEB_INSTALLEDBY'] = pwd.getpwuid(os.getuid())[0]
+ self.__data['WEB_INSTALLEDDATE'] = strftime('%Y-%m-%d %H:%M:%S')
+ self.__data['WEB_INSTALLEDFOR'] = user_group
+ self.__data['WEB_HOSTNAME'] = host
+ self.__data['WEB_INSTALLDIR'] = original_installdir
+
+
+ info = ['# ' + self.__file,
+ '# config file for this copy of '
+ + package + '-' + version,
+ '#',
+ '# automatically created by Gentoo\'s webapp-config',
+ '# do NOT edit this file by hand',
+ '',]
+
+ for i in self.__tokens:
+ info.append(i + '="' + self.__data[i] + '"')
+
+ if not self.__p:
+ try:
+
+ fd = os.open(self.__dot_config(),
+ os.O_WRONLY | os.O_CREAT,
+ self.__perm(0600))
+
+ os.write(fd, '\n'.join(info))
+ os.close(fd)
+
+ except Exception, e:
+
+ OUT.die('Unable to write to ' + self.__dot_config()
+ + '\nError was: ' + str(e))
+ else:
+ OUT.info('Would have written the following information into '
+ + self.__dot_config() + ':\n' + '\n'.join(info))
+
+ def kill(self):
+ ''' Remove the dot config file.'''
+
+ empty = self.is_empty()
+
+ OUT.debug('Trying to removing .webapp file', 7)
+
+ if not empty:
+ if not self.__p:
+ try:
+ os.unlink(self.__dot_config())
+ except:
+ OUT.warn('Failed to remove '
+ + self.__dot_config() + '!')
+ return False
+ else:
+ OUT.info('Would have removed ' + self.__dot_config())
+ return True
+ else:
+ OUT.notice('--- ' + empty)
+ return False
+
+if __name__ == '__main__':
+ import doctest, sys
+ doctest.testmod(sys.modules[__name__])
diff --git a/WebappConfig/ebuild.py b/WebappConfig/ebuild.py
new file mode 100644
index 0000000..9c08d3d
--- /dev/null
+++ b/WebappConfig/ebuild.py
@@ -0,0 +1,340 @@
+#!/usr/bin/python -O
+#
+# /usr/sbin/webapp-config
+# Python script for managing the deployment of web-based
+# applications
+#
+# Originally written for the Gentoo Linux distribution
+#
+# Copyright (c) 1999-2006 Gentoo Foundation
+# Released under v2 of the GNU GPL
+#
+# Author(s) Stuart Herbert <stuart@gentoo.org>
+# Renat Lumpau <rl03@gentoo.org>
+# Gunnar Wrobel <php@gunnarwrobel.de>
+#
+# ========================================================================
+''' Provides a class that handles ebuild related tasks. '''
+
+__version__ = "$Id: ebuild.py 260 2006-01-29 23:21:56Z wrobel $"
+
+# ========================================================================
+# Dependencies
+# ------------------------------------------------------------------------
+
+import os.path, re, pwd, grp
+
+from WebappConfig.debug import OUT
+import WebappConfig.wrapper as wrapper
+from WebappConfig.sandbox import Sandbox
+
+# ========================================================================
+# Handler for ebuild related tasks
+# ------------------------------------------------------------------------
+
+class Ebuild:
+ '''
+ This class handles all ebuild related task. Currently this includes
+ displaying the post install instruction as well as running hooks
+ provided by the ebuild.
+
+ This creates the basic configuration defaults:
+
+ >>> import WebappConfig.config
+ >>> config = WebappConfig.config.Config()
+
+ This needs to be completed with some parameters
+ that would be usually provided when parsing the
+ commandline:
+
+ >>> config.config.set('USER', 'my_htdocsbase', 'htdocs')
+ >>> config.config.set('USER', 'pn', 'horde')
+ >>> config.config.set('USER', 'pvr', '3.0.5')
+ >>> config.config.set('USER', 'vhost_server_uid', 'apache')
+ >>> config.config.set('USER', 'vhost_server_gid', 'apache')
+
+ And the application directory needs to be set
+ to the testfile reporitory
+
+ >>> import os.path
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> config.config.set('USER', 'my_approot', here +
+ ... '/tests/testfiles/share-webapps')
+
+ Time to create the ebuild handler:
+
+ >>> a = Ebuild(config)
+
+ Run a hook script:
+
+ >>> from WebappConfig.server import Basic
+ >>> basic = Basic({'source': '', 'destination': '', 'hostroot': '', 'vhostroot':''},
+ ... config.create_permissions(),
+ ... {'source':'','content':'','protect':'','dotconfig':'','ebuild':'','db':''},
+ ... {'verbose':False,'pretend':True})
+ >>> a.run_hooks('test', basic)
+ Called with test
+ <BLANKLINE>
+
+ The same on a directory that misses a hook dir:
+
+ >>> config.config.set('USER', 'pn', 'empty')
+ >>> config.config.set('USER', 'pvr', '1.0')
+ >>> a = Ebuild(config)
+ >>> a.run_hooks('test', basic)
+
+ This app has a hook dir but no script:
+
+ >>> config.config.set('USER', 'pn', 'uninstalled')
+ >>> config.config.set('USER', 'pvr', '6.6.6')
+ >>> a = Ebuild(config)
+ >>> a.run_hooks('test', basic)
+
+
+ '''
+
+ def __init__(self, config):
+
+ self.__root = wrapper.get_root()
+ self.config = config
+ self.__re = re.compile('/+')
+ self.__sourced = self.__re.sub('/', self.__root
+ + self.get_config('my_appdir'))
+ self.__hooksd = self.__re.sub('/', self.__root
+ + self.get_config('my_hookscriptsdir'))
+
+ def get_config(self, option):
+ ''' Return a config option.'''
+ return self.config.config.get('USER', option)
+
+ def run_hooks(self, type, server):
+ '''
+ Run the hook scripts - if there are any
+ '''
+
+ if self.config.pretend():
+ return
+
+ sandbox = Sandbox(self.config)
+
+ if sandbox.start():
+ OUT.debug('Disabling hooks')
+ return
+
+ self.run_vars(server)
+
+ if os.path.isdir(self.__hooksd):
+ for x in os.listdir(self.__hooksd):
+
+ if (os.path.isfile(self.__hooksd + '/' + x) and
+ os.access(self.__hooksd + '/' + x, os.X_OK)):
+
+ OUT.debug('Running hook script', 7)
+
+ fi, fo, fe = os.popen3(self.__hooksd + '/' + x + ' '
+ + type)
+ fi.close()
+ result_lines = fo.readlines()
+ error_lines = fe.readlines()
+ fo.close()
+ fe.close()
+
+ if result_lines:
+ for i in result_lines:
+ OUT.notice(i)
+
+ if error_lines:
+ for i in error_lines:
+ OUT.warn(i)
+
+ sandbox.stop()
+
+ def show_post(self, filename, ptype, server = None):
+ '''
+ Display one of the post files.
+ '''
+
+ post_file = self.__sourced + '/' + filename
+
+ OUT.debug('Check for instruction file', 7)
+
+ if not os.path.isfile(post_file):
+ return
+
+ self.run_vars(server)
+
+ post_instructions = open(post_file).readlines()
+
+ OUT.debug('Read post instructions', 7)
+
+ post = [
+ '',
+ '=================================================================',
+ 'POST-' + ptype.upper() + ' INSTRUCTIONS',
+ '=================================================================',
+ '']
+
+ for i in post_instructions:
+ i = i.replace('"', '\\"')
+ post.append(os.popen('echo -n "' + i + '"\n').read()[:-1])
+
+ post = post + [
+ '',
+ '=================================================================',
+ '']
+
+ for i in post:
+ OUT.notice(i)
+
+ def show_postinst(self, server = None):
+ '''
+ Display any post-installation instructions, if there are any.
+ '''
+
+ OUT.debug('Running show_postinst', 6)
+
+ self.show_post(filename = 'postinst-en.txt', ptype = 'install', server = server)
+
+ def show_postupgrade(self, server = None):
+ '''
+ Display any post-upgrade instructions, if there are any.
+ '''
+
+ OUT.debug('Running show_postupgrade', 6)
+
+ self.show_post(filename = 'postupgrade-en.txt', ptype = 'upgrade', server = server)
+
+ def run_vars(self, server = None):
+ '''
+ This function exports the necessary variables to the shell
+ environment so that they are accessible within the shell scripts
+ and/or files provided by the ebuild.
+
+ The procedure from above is repeated to set up the default
+ environment:
+
+ >>> import WebappConfig.config
+ >>> config = WebappConfig.config.Config()
+ >>> config.config.set('USER', 'my_htdocsbase', 'htdocs')
+ >>> config.config.set('USER', 'pn', 'horde')
+ >>> config.config.set('USER', 'pvr', '3.0.5')
+ >>> import os.path
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> config.config.set('USER', 'my_approot', here +
+ ... '/tests/testfiles/share-webapps')
+
+ Time to create the ebuild handler:
+
+ >>> a = Ebuild(config)
+
+ The dummy post-install file should display all the variables
+ that are exported here:
+
+ >>> a.show_postinst() #doctest: +ELLIPSIS
+ <BLANKLINE>
+ =================================================================
+ POST-INSTALL INSTRUCTIONS
+ =================================================================
+ <BLANKLINE>
+ MY_HOSTROOTDIR: .../tests/testfiles/share-webapps/horde/3.0.5/hostroot
+ MY_HTDOCSDIR: .../tests/testfiles/share-webapps/horde/3.0.5/htdocs
+ MY_CGIBINDIR: .../tests/testfiles/share-webapps/horde/3.0.5/hostroot/cgi-bin
+ MY_INSTALLDIR: /
+ MY_ICONSDIR: .../tests/testfiles/share-webapps/horde/3.0.5/hostroot/icons
+ MY_SERVERCONFIGDIR: .../tests/testfiles/share-webapps/horde/3.0.5/conf
+ MY_ERRORSDIR: .../tests/testfiles/share-webapps/horde/3.0.5/hostroot/error
+ MY_SQLSCRIPTSDIR: .../tests/testfiles/share-webapps/horde/3.0.5/sqlscripts
+ VHOST_ROOT: /var/www/...
+ VHOST_HTDOCSDIR: /var/www/.../htdocs
+ VHOST_CGIBINDIR: /var/www/.../cgi-bin
+ VHOST_CONFDIR: /var/www/.../
+ VHOST_ERRORSDIR: /var/www/.../error
+ VHOST_ICONSDIR: /var/www/.../icons
+ VHOST_HOSTNAME: ...
+ VHOST_SERVER: apache
+ VHOST_APPDIR: /
+ VHOST_CONFIG_UID: ...
+ VHOST_CONFIG_GID: ...
+ VHOST_SERVER_UID: ...
+ VHOST_SERVER_GID: ...
+ VHOST_DEFAULT_UID: 0
+ VHOST_DEFAULT_GID: 0
+ VHOST_PERMS_SERVEROWNED_DIR: 0775
+ VHOST_PERMS_SERVEROWNED_FILE: 0664
+ VHOST_PERMS_CONFIGOWNED_DIR: 0755
+ VHOST_PERMS_CONFIGOWNED_FILE: 0644
+ VHOST_PERMS_DEFAULTOWNED_DIR: 0755
+ VHOST_PERMS_VIRTUALOWNED_FILE: o-w
+ VHOST_PERMS_INSTALLDIR: 0755
+ ROOT: /
+ PN: horde
+ PVR: 3.0.5
+ <BLANKLINE>
+ =================================================================
+ <BLANKLINE>
+ '''
+
+ v_root = self.get_config('vhost_root')
+ v_cgi = self.get_config('g_cgibindir')
+ v_conf = v_root + '/'
+ v_err = v_root + '/' + self.get_config('my_errorsbase')
+ v_icon = v_root + '/' + self.get_config('my_iconsbase')
+
+ g_inst = self.get_config('g_installdir')
+ g_htd = self.get_config('g_htdocsdir')
+ g_orig = self.get_config('g_orig_installdir')
+
+ vsu = None
+ vsg = None
+ if server:
+ vsu = pwd.getpwuid(server.vhost_server_uid)[0]
+ vsg = grp.getgrgid(server.vhost_server_gid)[0]
+
+ OUT.debug('Exporting variables', 7)
+
+ export_map = {'MY_HOSTROOTDIR' : None,
+ 'MY_HTDOCSDIR' : None,
+ 'MY_CGIBINDIR' : None,
+ 'MY_INSTALLDIR' : g_inst,
+ 'MY_ICONSDIR' : None,
+ 'MY_SERVERCONFIGDIR' : None,
+ 'MY_ERRORSDIR' : None,
+ 'MY_SQLSCRIPTSDIR' : None,
+ 'VHOST_ROOT' : None,
+ 'VHOST_HTDOCSDIR' : g_htd,
+ 'VHOST_CGIBINDIR' : v_cgi,
+ 'VHOST_CONFDIR' : v_conf,
+ 'VHOST_ERRORSDIR' : v_err,
+ 'VHOST_ICONSDIR' : v_icon,
+ 'VHOST_HOSTNAME' : None,
+ 'VHOST_SERVER' : None,
+ 'VHOST_APPDIR' : g_orig,
+ 'VHOST_CONFIG_UID' : None,
+ 'VHOST_CONFIG_GID' : None,
+ 'VHOST_SERVER_UID' : vsu,
+ 'VHOST_SERVER_GID' : vsg,
+ 'VHOST_DEFAULT_UID' : None,
+ 'VHOST_DEFAULT_GID' : None,
+ 'VHOST_PERMS_SERVEROWNED_DIR' : None,
+ 'VHOST_PERMS_SERVEROWNED_FILE' : None,
+ 'VHOST_PERMS_CONFIGOWNED_DIR' : None,
+ 'VHOST_PERMS_CONFIGOWNED_FILE' : None,
+ 'VHOST_PERMS_DEFAULTOWNED_DIR' : None,
+ 'VHOST_PERMS_VIRTUALOWNED_FILE': None,
+ 'VHOST_PERMS_INSTALLDIR' : None,
+ 'ROOT' : wrapper.get_root(),
+ 'PN' : None,
+ 'PVR': None}
+
+ for i in export_map.keys():
+
+ value = export_map[i]
+
+ if not value:
+ value = self.get_config(i.lower())
+
+ os.putenv(i, str(value))
+
+if __name__ == '__main__':
+ import doctest, sys
+ doctest.testmod(sys.modules[__name__])
diff --git a/WebappConfig/filetype.py b/WebappConfig/filetype.py
new file mode 100644
index 0000000..c7da10b
--- /dev/null
+++ b/WebappConfig/filetype.py
@@ -0,0 +1,231 @@
+#!/usr/bin/python -O
+#
+# /usr/sbin/webapp-config
+# Python script for managing the deployment of web-based
+# applications
+#
+# Originally written for the Gentoo Linux distribution
+#
+# Copyright (c) 1999-2006 Gentoo Foundation
+# Released under v2 of the GNU GPL
+#
+# Author(s) Stuart Herbert <stuart@gentoo.org>
+# Renat Lumpau <rl03@gentoo.org>
+# Gunnar Wrobel <php@gunnarwrobel.de>
+#
+# ========================================================================
+''' A class that returns the file type for a given path.'''
+
+__version__ = "$Id: filetype.py 245 2006-01-13 16:57:29Z wrobel $"
+
+# ========================================================================
+# Dependencies
+# ------------------------------------------------------------------------
+
+import os.path, re
+
+from WebappConfig.debug import OUT
+
+# ========================================================================
+# Handler for File Types
+# ------------------------------------------------------------------------
+
+class FileType:
+ '''
+ A helper class to determine file and directory types.
+
+ The file type is determined based on two initial lists:
+
+ - a list of all files and directories owned by the config user
+ - a list of all files and directories owned by the server user
+
+ This creates such lists:
+
+ >>> config_owned = [ 'a', 'a/b/c/d', '/e', '/f/', '/g/h/', 'i\\n']
+ >>> server_owned = [ 'j', 'k/l/m/n', '/o', '/p/', '/q/r/', 's\\n']
+
+ The class is initialized with these two arrays:
+
+ >>> a = FileType(config_owned, server_owned)
+
+ This class provides three functions to retrieve information about
+ the file or directory type.
+
+ File types
+ ----------
+
+ >>> a.filetype('a')
+ 'config-owned'
+ >>> a.filetype('a/b/c/d')
+ 'config-owned'
+
+ >>> a.filetype('j')
+ 'server-owned'
+ >>> a.filetype('/o')
+ 'server-owned'
+
+ File names - whether specified as input in the
+ {config,server}_owned lists or as key for retrieving the type - may
+ have leading or trailing whitespace. It will be removed. Trailing
+
+ >>> a.filetype('\\n s')
+ 'server-owned'
+ >>> a.filetype('/g/h\\n')
+ 'config-owned'
+
+ Unspecified files will result in a virtual type:
+
+ >>> a.filetype('unspecified.txt')
+ 'virtual'
+
+ This behaviour can be influenced by setting the 'virtual_files'
+ option for the class (which corresponds to the --virtual-files command
+ line option):
+
+ >>> b = FileType(config_owned, server_owned,
+ ... virtual_files = 'server-owned')
+ >>> b.filetype('unspecified.txt')
+ 'server-owned'
+
+ Directory types
+ ---------------
+
+ The class does not know if the given keys are files or directories.
+ This is specified using the correct function for them. So the same
+ keys that were used above can also be used here:
+
+ >>> a.dirtype('a')
+ 'config-owned'
+ >>> a.dirtype('j')
+ 'server-owned'
+
+ The same whitespace and trailing slash fixing rules apply for
+ directory names:
+
+ >>> a.dirtype('\\n s')
+ 'server-owned'
+ >>> a.dirtype('/g/h\\n')
+ 'config-owned'
+
+ Unspecified directories are 'default-owned' and not marked 'virtual':
+
+ >>> a.dirtype('unspecified.txt')
+ 'default-owned'
+
+ '''
+
+ def __init__(self,
+ config_owned,
+ server_owned,
+ virtual_files = 'virtual',
+ default_dirs = 'default-owned'):
+ '''
+ Populates the cache with the file types as provided by the
+ ebuild.
+ '''
+
+ self.__cache = {}
+
+ # Validity of entries are checked by the command line parser
+ self.__virtual_files = virtual_files
+ self.__default_dirs = default_dirs
+
+ # populate cache
+ for i in config_owned:
+
+ OUT.debug('Adding config-owned file', 8)
+
+ self.__cache[self.__fix(i)] = 'config-owned'
+
+ for i in server_owned:
+
+ if self.__fix(i) in self.__cache.keys():
+
+ OUT.debug('Adding config-server-owned file', 8)
+
+ self.__cache[self.__fix(i).strip()] = 'config-server-owned'
+
+ else:
+
+ OUT.debug('Adding server-owned file', 8)
+
+ self.__cache[self.__fix(i).strip()] = 'server-owned'
+
+
+ def filetype(self, filename):
+ '''
+ Inputs:
+
+ filename - the file that we need a decision about
+
+ returns one of these:
+
+ server-owned - file needs to be owned by the webserver user
+ (and needs to be a local copy)
+ config-owned - file needs to be owned by the config user
+ (and needs to be a local copy)
+ virtual - we do not need a local copy of the file
+
+ NOTE:
+ Use get_dirtype(directory) for directories
+
+ NOTE:
+ the user can use --virtual-files on the command-line to change
+ what type virtual files are really reported as
+ '''
+
+ # remove any whitespace and trailing /
+ filename = self.__fix(filename)
+
+ # look for config-protected files in the cache
+ if filename in self.__cache.keys():
+ return self.__cache[filename]
+
+ # unspecified file (and thus virtual)
+ return self.__virtual_files
+
+ def dirtype(self, directory):
+ '''
+ Inputs:
+
+ directory - the directory that we need a decision about
+
+ returns one of these:
+
+ server-owned - dir needs to be owned by the webserver user
+ config-owned - dir needs to be owned by the config user
+ default-owned - we need a local copy, owned by root
+
+ NOTE:
+ Use get_filetype(filename) for files
+
+ NOTE:
+ the user can use --default-dirs on the command-line to change
+ what type default directories are really reported as
+ '''
+
+ # remove any whitespace and trailing /
+ directory = self.__fix(directory)
+
+ # check the cache
+ if directory in self.__cache.keys():
+ return self.__cache[directory]
+
+ # unspecified directories are default-owned
+ return self.__default_dirs
+
+ def __fix(self, filename):
+ ''' Removes trailing slash and whitespace from a path '''
+ filename = filename.strip()
+ while filename[-1] == '/':
+ filename = filename[:-1]
+
+ # Fix double slashes
+ filename = re.compile('/+').sub('/', filename)
+
+ return filename
+
+
+if __name__ == '__main__':
+ import doctest, sys
+ doctest.testmod(sys.modules[__name__])
diff --git a/WebappConfig/permissions.py b/WebappConfig/permissions.py
new file mode 100644
index 0000000..9abba4f
--- /dev/null
+++ b/WebappConfig/permissions.py
@@ -0,0 +1,282 @@
+#!/usr/bin/python -O
+#
+# /usr/sbin/webapp-config
+# Python script for managing the deployment of web-based
+# applications
+#
+# Originally written for the Gentoo Linux distribution
+#
+# Copyright (c) 1999-2006 Gentoo Foundation
+# Released under v2 of the GNU GPL
+#
+# Author(s) Stuart Herbert <stuart@gentoo.org>
+# Renat Lumpau <rl03@gentoo.org>
+# Gunnar Wrobel <php@gunnarwrobel.de>
+#
+# ========================================================================
+'''
+webapp-config needs a permission handling that allows to combine original
+file permission settings with values supplied by the user.
+'''
+
+__version__ = "$Id: permissions.py 129 2005-11-06 12:46:31Z wrobel $"
+
+# ========================================================================
+# Dependencies
+# ------------------------------------------------------------------------
+
+import re, types, grp, pwd
+
+# ========================================================================
+# Permission Helper
+# ------------------------------------------------------------------------
+
+# Mask
+
+ALL = 7
+READ = 4
+WRITE = 2
+EXECUTE = 1
+
+# Shifting
+
+USER = 6
+GROUP = 3
+OTHER = 0
+
+class PermissionMap:
+ ''' Permission Helper class that is initialized with a permission
+ mask. Subsequently the class can be used to combine this mask
+ with file permissions to generate a merged target value. '''
+
+ # Describes valid permission masks
+
+ valid = re.compile('^([ugoa]{1,4})([+-=])([rwx]{0,3})$')
+
+ def __call__(self, permissions):
+ ''' "permissions" represents the last 9 bits of the permission
+ map as a normal integer (a value between 0 and 512).
+
+ Test the simple case:
+
+ >>> a = PermissionMap('0777')
+ >>> a(0644)
+ 511
+ >>> a(0000)
+ 511
+ >>> a = PermissionMap('o+x')
+ >>> a(0000)
+ 1
+ >>> a = PermissionMap('ugo+rwx')
+ >>> a(0000)
+ 511
+ >>> a = PermissionMap('u=rwx,g=x,o=w')
+ >>> a(0644)
+ 458
+ >>> a = PermissionMap('u-rw,g=x,o+x')
+ >>> a(0644)
+ 13
+ >>> a = PermissionMap('u-rwx,g-rwx,o-x')
+ >>> a(0751)
+ 0
+ >>> a = PermissionMap('u=rw,g=r,o=')
+ >>> a(0000)
+ 416
+ '''
+
+ # Check if the permissions are absolute
+
+ if self.__absolute:
+ return self.__permissions
+ else:
+
+ for i in self.__permissions:
+
+ # Permission mask. More complex
+
+ entity = self.valid.match(i).group(1)
+ operator = self.valid.match(i).group(2)
+ perm = self.valid.match(i).group(3)
+
+ # Generate the permission bits
+
+ perm_bit = 0
+
+ for i in perm:
+ if i == 'r':
+ perm_bit |= READ
+ if i == 'w':
+ perm_bit |= WRITE
+ if i == 'x':
+ perm_bit |= EXECUTE
+
+ for i in entity:
+ if i == 'u':
+ shift = [ USER ]
+ if i == 'g':
+ shift = [ GROUP ]
+ if i == 'o':
+ shift = [ OTHER ]
+ if i == 'a':
+ shift = [ USER, GROUP, OTHER ]
+
+ for j in shift:
+ if operator == '=':
+ permissions &= ~(ALL << j)
+ if operator == '-':
+ permissions &= ~(perm_bit << j)
+ if operator == '+' or operator == '=':
+ permissions |= (perm_bit << j)
+
+ return permissions
+
+ def __init__(self, permissions):
+ '''Check that the given permission map evaluates to something
+ useful.
+
+ "permissions" must be a string. It can either specify
+ absolute permissions as an octal number or it uses the
+ syntax for the unix chmod command (only
+ [ugoa]{1,4}[+-=][rxw]{1,3} as a comma-seperated list).
+
+ Check a few values:
+ >>> a = PermissionMap('0777')
+
+ >>> a = PermissionMap('0788')
+ Traceback (most recent call last):
+ ...
+ Exception: Invalid permission string "0788"
+
+ >>> a = PermissionMap('u+r, go-wx')
+
+ >>> a = PermissionMap('u=+r,go-wx')
+ Traceback (most recent call last):
+ ...
+ Exception: Invalid permission string "u=+r,go-wx"
+
+ >>> a = PermissionMap('u=rw,g=r,o=')
+ '''
+
+ self.__permissions = 0
+
+ # Simple case: absolute octal permission
+
+ if re.compile('[0-7]{4}').match(permissions):
+ self.__absolute = True
+ self.__permissions = eval(permissions)
+
+ else:
+ # Split on commas first
+
+ splitted_permissions = [ x.strip()
+ for x in permissions.split(',') ]
+
+ # Now analyze each part for correct structure
+
+ for i in splitted_permissions:
+ if not self.valid.match(i):
+ raise Exception('Invalid permission string "'
+ + permissions + '"')
+
+ self.__permissions = splitted_permissions
+ self.__absolute = False
+
+def get_group(group):
+ '''
+ Specify a group id either as integer, as string that can
+ be transformed into an integer or a string that matches
+ a group name.
+
+ >>> get_group(0)
+ 0
+ >>> get_group('0')
+ 0
+ >>> get_group('root')
+ 0
+ >>> get_group('does_not_exist')
+ Traceback (most recent call last):
+ ...
+ KeyError: 'The given group "does_not_exist" does not exist!'
+ '''
+
+ gid = -1
+ ngroup = -1
+
+ # Can we transform the value to an integer?
+ if type(group) != types.IntType:
+ try:
+ ngroup = int(group)
+ except ValueError, e:
+ ngroup = -1
+ else:
+ # The value is an integer
+ ngroup = group
+
+ # Value was specified as integer or was transformed
+ if ngroup != -1:
+ try:
+ # Try to match the integer to a group id
+ gid = grp.getgrgid(ngroup)[2]
+ except KeyError, e:
+ pass
+
+ if gid == -1:
+ # No success yet. Try to match to the group name
+ try:
+ gid = grp.getgrnam(str(group))[2]
+ except KeyError, e:
+ raise KeyError('The given group "' + str(group)
+ + '" does not exist!')
+
+ return gid
+
+def get_user(user):
+ '''
+ Specify a user id either as integer, as string that can
+ be transformed into an integer or a string that matches
+ a user name.
+
+ >>> get_user(0)
+ 0
+ >>> get_user('0')
+ 0
+ >>> get_user('root')
+ 0
+ >>> get_user('does_not_exist')
+ Traceback (most recent call last):
+ ...
+ KeyError: 'The given user "does_not_exist" does not exist!'
+ '''
+ uid = -1
+ nuser = -1
+
+ # Can we transform the value to an integer?
+ if type(user) != types.IntType:
+ try:
+ nuser = int(user)
+ except ValueError, e:
+ nuser = -1
+ else:
+ # The value is an integer
+ nuser = user
+
+ # Value was specified as integer or was transformed
+ if nuser != -1:
+ try:
+ # Try to match the integer to a user id
+ uid = pwd.getpwuid(nuser)[2]
+ except KeyError, e:
+ pass
+
+ if uid == -1:
+ # No success yet. Try to match to the user name
+ try:
+ uid = pwd.getpwnam(str(user))[2]
+ except KeyError, e:
+ raise KeyError('The given user "' + str(user)
+ + '" does not exist!')
+ return uid
+
+if __name__ == '__main__':
+ import doctest, sys
+ doctest.testmod(sys.modules[__name__])
diff --git a/WebappConfig/protect.py b/WebappConfig/protect.py
new file mode 100644
index 0000000..b5818bc
--- /dev/null
+++ b/WebappConfig/protect.py
@@ -0,0 +1,245 @@
+#!/usr/bin/python -O
+#
+# /usr/sbin/webapp-config
+# Python script for managing the deployment of web-based
+# applications
+#
+# Originally written for the Gentoo Linux distribution
+#
+# Copyright (c) 1999-2006 Gentoo Foundation
+# Released under v2 of the GNU GPL
+#
+# Author(s) Stuart Herbert <stuart@gentoo.org>
+# Renat Lumpau <rl03@gentoo.org>
+# Gunnar Wrobel <php@gunnarwrobel.de>
+#
+# ========================================================================
+''' Helper functions for config protected files.'''
+
+__version__ = "$Id: protect.py 133 2005-11-06 14:34:04Z wrobel $"
+
+# ========================================================================
+# Dependencies
+# ------------------------------------------------------------------------
+
+import re, os, os.path
+
+import WebappConfig.wrapper
+
+from WebappConfig.debug import OUT
+
+# ========================================================================
+# Config protection helper class
+# ------------------------------------------------------------------------
+
+class Protection:
+ '''
+ A small helper class to handle config protection.
+ '''
+
+ def __init__(self):
+ '''
+ This is distribution specific so the information is provided by
+ wrapper.py
+ '''
+ self.config_protect = WebappConfig.wrapper.config_protect
+ self.protect_prefix = WebappConfig.wrapper.protect_prefix
+ self.update_command = WebappConfig.wrapper.update_command
+
+ # ------------------------------------------------------------------------
+ # Outputs:
+ # $my_return = the new mangled name (that you can use instead of
+ # $2)
+
+ def get_protectedname(self,
+ destination,
+ filename):
+ '''
+ Woh. Somewhere, we've decided that we're trying to overwrite a file
+ that we really want to save. So, we need a new name for the file that
+ we want to install - which is where we come in.
+
+ NOTE:
+ The filename that we produce is compatible with Gentoo's
+ etc-update tool. This is deliberate.
+
+ Inputs:
+ destination - the directory that the file is being installed into
+ filename - the original name of the file
+
+ Let's test the code on some examples:
+
+ >>> import os.path
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+
+ >>> a = Protection()
+ >>> a.get_protectedname(here + '/tests/testfiles/protect/empty',
+ ... 'test')#doctest: +ELLIPSIS
+ '.../tests/testfiles/protect/empty//._cfg0000_test'
+
+ >>> a.get_protectedname(here + '/tests/testfiles/protect/simple',
+ ... 'test')#doctest: +ELLIPSIS
+ '.../tests/testfiles/protect/simple//._cfg0001_test'
+
+ >>> a.get_protectedname(here + '/tests/testfiles/protect/complex',
+ ... 'test')#doctest: +ELLIPSIS
+ '.../tests/testfiles/protect/complex//._cfg0801_test'
+
+ '''
+
+ my_file = os.path.basename(filename)
+ my_filedir = destination + '/' + os.path.dirname(filename)
+
+ # find the highest numbered protected file that already
+ # exists, and increment it by one
+
+ entries = os.listdir(my_filedir)
+
+ OUT.debug('Identifying possible file number', 7)
+
+ numbers = []
+ prefix = self.protect_prefix
+ rep = re.compile(prefix.replace('.','\.') + '(\d{4})_')
+
+ for i in entries:
+ rem = rep.match(i)
+ if rem:
+ numbers.append(int(rem.group(1)))
+
+ if numbers:
+ max_n = max(numbers) + 1
+ else:
+ max_n = 0
+
+ return my_filedir + '/%s%.4d_%s' % (prefix, max_n, my_file)
+
+
+ def dirisconfigprotected(self, installdir):
+ '''
+ Traverses the path of parent directories for the
+ given install dir and checks if any matches the list
+ of config protected files.
+
+ >>> a = Protection()
+
+ Add a virtual config protected directory:
+
+ >>> a.config_protect += ' /my/strange/htdocs/'
+ >>> a.dirisconfigprotected('/my/strange/htdocs/where/i/installed/x')
+ True
+ >>> a.dirisconfigprotected('/my/strange/htdocs/where/i/installed/x/')
+ True
+ >>> a.config_protect += ' /my/strange/htdocs'
+ >>> a.dirisconfigprotected('/my/strange/htdocs/where/i/installed/x')
+ True
+ >>> a.dirisconfigprotected('/my/strange/htdocs/where/i/installed/x/')
+ True
+
+ >>> a.config_protect += ' bad_user /my/strange/htdocs'
+ >>> a.dirisconfigprotected('/my/bad_user/htdocs/where/i/installed/x')
+ False
+ >>> a.dirisconfigprotected('/my/strange/htdocs/where/i/installed/x/')
+ True
+
+ >>> a.dirisconfigprotected('/')
+ False
+ '''
+
+ my_master = []
+ for i in self.config_protect.split(' '):
+ if i[0] == '/':
+ if i[-1] == '/':
+ my_master.append(i[:-1])
+ else:
+ my_master.append(i)
+
+ if installdir[0] != '/':
+ OUT.die('BUG! Don\'t call this with a relative path.')
+
+ if installdir[-1] == '/':
+ my_dir = installdir[:-1]
+ else:
+ my_dir = installdir
+
+ while my_dir:
+
+ if my_dir == '.' or my_dir == '/':
+ return False
+
+ for x in my_master:
+ if my_dir == x:
+ return True
+
+ my_dir = os.path.dirname(my_dir)
+
+ # nope, the directory isn't config-protected at this time
+ return False
+
+ def how_to_update(self, dirs):
+ '''
+ Instruct the user how to update the application.
+
+ >>> OUT.color_off()
+ >>> a = Protection()
+
+ >>> a.how_to_update(['/my/strange/htdocs/where/i/installed/x'])
+ * One or more files have been config protected
+ * To complete your install, you need to run the following command(s):
+ *
+ * CONFIG_PROTECT="/my/strange/htdocs/where/i/installed/x" etc-update
+ *
+ >>> a.how_to_update(['/a/','/c/'])
+ * One or more files have been config protected
+ * To complete your install, you need to run the following command(s):
+ *
+ * CONFIG_PROTECT="/a/" etc-update
+ * CONFIG_PROTECT="/c/" etc-update
+ *
+ >>> a.how_to_update(['/a//test3','/a//test3/abc', '/c/'])
+ * One or more files have been config protected
+ * To complete your install, you need to run the following command(s):
+ *
+ * CONFIG_PROTECT="/a//test3" etc-update
+ * CONFIG_PROTECT="/c/" etc-update
+ *
+
+ Add a virtual config protected directory:
+
+ >>> a.config_protect += ' /my/strange/htdocs/'
+ >>> a.how_to_update(['/my/strange/htdocs/where/i/installed/x'])
+ * One or more files have been config protected
+ * To complete your install, you need to run the following command(s):
+ *
+ * etc-update
+ '''
+ my_command = self.update_command
+
+ directories = []
+
+ for i in dirs:
+ present = False
+ if directories:
+ for j in directories:
+ if (i == j[:len(i)] or
+ j == i[:len(j)]):
+ present = True
+ break
+ if not present:
+ directories.append(i)
+
+ my_command_list = ''
+
+ for i in directories:
+ if not self.dirisconfigprotected(i):
+ my_command_list += 'CONFIG_PROTECT="' + i + '" ' + my_command + '\n'
+
+ if not my_command_list:
+ my_command_list = my_command
+
+ OUT.warn('One or more files have been config protected\nTo comple'
+ 'te your install, you need to run the following command(s):\n\n'
+ + my_command_list)
+
+if __name__ == '__main__':
+ import doctest, sys
+ doctest.testmod(sys.modules[__name__])
diff --git a/WebappConfig/sandbox.py b/WebappConfig/sandbox.py
new file mode 100644
index 0000000..f010bd0
--- /dev/null
+++ b/WebappConfig/sandbox.py
@@ -0,0 +1,114 @@
+#!/usr/bin/python -O
+#
+# /usr/sbin/webapp-config
+# Python script for managing the deployment of web-based
+# applications
+#
+# Originally written for the Gentoo Linux distribution
+#
+# Copyright (c) 1999-2006 Gentoo Foundation
+# Released under v2 of the GNU GPL
+#
+# Author(s) Stuart Herbert <stuart@gentoo.org>
+# Renat Lumpau <rl03@gentoo.org>
+# Gunnar Wrobel <php@gunnarwrobel.de>
+#
+# ========================================================================
+'''
+Sandbox operations
+'''
+
+__version__ = "$Id: permissions.py 129 2005-11-06 12:46:31Z wrobel $"
+
+# ========================================================================
+# Dependencies
+# ------------------------------------------------------------------------
+
+import os, string, time
+
+from WebappConfig.wrapper import config_libdir
+
+from WebappConfig.debug import OUT
+
+class Sandbox:
+ '''
+ Basic class for handling sandbox stuff
+ Concept stolen from app-shells/sandboxshell
+ '''
+
+ def __init__(self, config):
+
+ self.config = config
+ self.__path = [config_libdir + '/libsandbox.so',
+ '/usr/lib/libsandbox.so', '/lib/libsandbox.so']
+ self.__export = {}
+ self.__write = ['g_installdir',
+ 'g_htdocsdir',
+ 'g_cgibindir',
+ 'vhost_root']
+ self.__read = '/'
+
+ self.__syswrite = ':/dev/tty:/dev/pts:/dev/null:/tmp'
+
+ self.sandbox = ''
+
+ self.log = '/tmp/w-c.sandbox-' \
+ + time.strftime("%Y-%m-%d-%H.%M.%S",time.gmtime())\
+ + '.log'
+ self.debug_log = self.log + '.debug'
+
+ def get_write(self):
+ '''Return write paths.'''
+ return string.join ( map ( self.get_config, self.__write ), ':' ) \
+ + self.__syswrite
+
+ def get_config(self, option):
+ ''' Return a config option.'''
+ return self.config.config.get('USER', option)
+
+ def start(self):
+ '''
+ Start sandbox. Return 1 if failed.
+ '''
+
+ OUT.debug('Initializing sandbox', 7)
+ for i in self.__path:
+ if os.access(i, os.R_OK):
+ self.sandbox = i
+ break
+
+ if not self.sandbox:
+ OUT.warn("Could not find a sandbox, disabling hooks")
+ return 1
+
+ try:
+ self.__ld = os.environ['LD_PRELOAD']
+ except KeyError, e:
+ self.__ld = ""
+
+ self.__export = {'LD_PRELOAD' : self.sandbox,
+ 'SANDBOX_WRITE' : self.get_write(),
+ 'SANDBOX_READ' : self.__read,
+ 'SANDBOX_LOG' : self.log,
+ 'SANDBOX_DEBUG_LOG' : self.debug_log,
+ 'SANDBOX_ON' : "1",
+ 'SANDBOX_ACTIVE' : "armedandready"}
+ self.run_vars()
+
+ def stop(self):
+ '''Stop sandbox'''
+
+ self.__export = {'LD_PRELOAD' : self.__ld,
+ 'SANDBOX_WRITE' : "",
+ 'SANDBOX_READ' : "",
+ 'SANDBOX_LOG' : "",
+ 'SANDBOX_DEBUG_LOG' : "",
+ 'SANDBOX_ON' : "0",
+ 'SANDBOX_ACTIVE' : ""}
+ self.run_vars()
+
+ def run_vars(self):
+
+ for i in self.__export.keys():
+ value = self.__export[i]
+ os.putenv(i, value)
diff --git a/WebappConfig/server.py b/WebappConfig/server.py
new file mode 100644
index 0000000..cfaf256
--- /dev/null
+++ b/WebappConfig/server.py
@@ -0,0 +1,344 @@
+#!/usr/bin/python -O
+#
+# /usr/sbin/webapp-config
+# Python script for managing the deployment of web-based
+# applications
+#
+# Originally written for the Gentoo Linux distribution
+#
+# Copyright (c) 1999-2006 Gentoo Technologies, Inc
+# Released under v2 of the GNU GPL
+#
+# Author(s) Stuart Herbert <stuart@gentoo.org>
+# Renat Lumpau <rl03@gentoo.org>
+# Gunnar Wrobel <php@gunnarwrobel.de>
+#
+# ========================================================================
+
+# ========================================================================
+# Dependencies
+# ------------------------------------------------------------------------
+
+import sys, os, os.path, re
+
+from WebappConfig.debug import OUT
+from WebappConfig.worker import WebappRemove, WebappAdd
+from WebappConfig.permissions import get_group, get_user
+
+from WebappConfig.wrapper import package_installed
+
+# ========================================================================
+# Server classes
+# ------------------------------------------------------------------------
+
+class Basic:
+
+ name = 'Basic Server'
+ desc = 'supports installation on all webservers'
+ dep = ''
+
+ def set_server_user(self):
+ self.vhost_server_uid = get_user(0)
+ self.vhost_server_gid = get_group(0)
+
+ def __init__(self,
+ directories,
+ permissions,
+ handler,
+ flags):
+
+ if self.dep and not self.supported():
+ OUT.die('Your configuration file sets the server type "' + self.name
+ + '"\nbut the corresponding package does not seem to be '
+ 'installed!\nPlease "emerge ' + self.dep + '" or correct '
+ 'your settings.')
+
+ try:
+ self.set_server_user()
+ except KeyError, e:
+ OUT.die('The user for the server type "' + self.name
+ + '" does not exist!')
+
+ self.__sourced = directories['source']
+ self.__destd = directories['destination']
+ self.__hostroot = directories['hostroot']
+ self.__vhostroot = directories['vhostroot']
+
+ # + server owned
+ permissions['file']['server-owned'][0] = self.vhost_server_uid
+ permissions['file']['server-owned'][1] = self.vhost_server_gid
+ permissions['dir']['server-owned'][0] = self.vhost_server_uid
+ permissions['dir']['server-owned'][1] = self.vhost_server_gid
+ # and config owned directories have server gid
+ permissions['dir']['config-owned'][1] = self.vhost_server_gid
+ # allows server and config owned
+ permissions['file']['config-server-owned'][1] = self.vhost_server_gid
+ permissions['dir']['config-server-owned'][1] = self.vhost_server_gid
+
+ self.__perm = permissions
+ self.__handler = handler
+ self.__flags = flags
+
+ self.__ws = handler['source']
+ self.__content = handler['content']
+ self.__protect = handler['protect']
+ self.__dotconfig = handler['dotconfig']
+ self.__ebuild = handler['ebuild']
+ self.__db = handler['db']
+
+ self.__v = flags['verbose']
+ self.__p = flags['pretend']
+
+ wd = WebappRemove(self.__content,
+ self.__v,
+ self.__p)
+
+ handler['removal'] = wd
+
+ self.__del = wd
+
+ # Set by the install function
+ self.__add = None
+
+
+ def upgrade(self, new_package, new_version):
+
+ # I have switched the order of upgrades
+ # we are now removing the olde app and then installing the new one
+ # I am not sure why it was the other way around before
+ # and this way seems more intuitive and also has the benefit
+ # of working -- rl03
+
+ # first remove the older app
+
+ OUT.info('Removing old version ' + self.__dotconfig.packagename())
+
+ self.clean()
+
+ # now install the new one
+ self.__content.set_version(new_version)
+ self.__content.set_package(new_package)
+ self.__db.set_version(new_version)
+ self.__db.set_package(new_package)
+
+ self.install(True)
+
+ def clean(self):
+
+ self.file_behind_flag = False
+
+ OUT.debug('Basic server clean', 7)
+
+ self.file_behind_flag |= self.__del.remove_files()
+
+ self.file_behind_flag |= self.__del.remove_dirs()
+
+ OUT.info('Any files or directories listed above must be removed b'
+ 'y hand')
+
+ # okay, let's finish off
+ #
+ # we don't need the contents file anymore
+
+ self.file_behind_flag |= not self.__content.kill()
+
+ # right - we need to run the hook scripts now
+ # if they fail, we don't actually care
+
+ # run the hooks
+
+ self.__ebuild.run_hooks('clean', self)
+
+ # do we need the dotconfig file?
+ #
+ # if the .webapp file is the only one in the dir, we believe
+ # that we can remove it
+
+ self.__dotconfig.kill()
+
+ # is the installation directory empty?
+
+ if not os.listdir(self.__destd):
+ if not self.__p:
+ os.rmdir(self.__destd)
+ else:
+ OUT.notice('--- ' + self.__destd)
+
+ # update the list of installs
+
+ self.__db.remove(self.__destd)
+
+ # did we leave anything behind?
+
+ if self.file_behind_flag:
+ OUT.warn('Remove whatever is listed above by hand')
+
+
+ def install(self, upgrade = False):
+
+ self.config_protected_dirs = []
+
+ OUT.debug('Basic server install', 7)
+
+ # The root of the virtual install location needs to exist
+
+ if not os.path.isdir(self.__destd) and not self.__p:
+
+ OUT.debug('Directory missing', 7)
+
+ dir = self.__destd
+ dirs = []
+
+ while dir != '/':
+ dirs.insert(0, dir)
+ dir = os.path.dirname(dir)
+
+ a = self.__perm['dir']['install-owned'][2]('0755')
+ OUT.debug('Strange')
+
+ # Create the directories
+ for i in dirs:
+ if not os.path.isdir(i):
+ os.mkdir(i)
+ os.chmod(i,
+ self.__perm['dir']['install-owned'][2]('0755'))
+ os.chown(i,
+ self.__perm['dir']['install-owned'][0],
+ self.__perm['dir']['install-owned'][1])
+
+ if self.__v:
+ OUT.info(' Creating installation directory: '
+ + i)
+
+ # Create the handler for installing
+
+ self.__flags['relative'] = True
+
+ wa = WebappAdd(self.__sourced,
+ self.__destd,
+ self.__perm,
+ self.__handler,
+ self.__flags)
+
+ self.__add = wa
+
+ OUT.info('Installing ' + self.__ws.package_name() + '...')
+
+ # we need to create the directories to place our files in
+ # and we need to copy in the files
+
+ OUT.info(' Creating required directories', 1)
+ OUT.info(' Linking in required files', 1)
+ OUT.info(' This can take several minutes for larger apps', 1)
+
+ self.__add.mkdirs()
+
+ self.config_protected_dirs += self.__add.config_protected_dirs
+
+ # Create the second handler for installing the root files
+
+ self.__flags['relative'] = False
+
+ wa = WebappAdd(self.__hostroot,
+ self.__vhostroot,
+ self.__perm,
+ self.__handler,
+ self.__flags)
+
+ self.__add = wa
+
+ self.__add.mkdirs()
+
+ self.config_protected_dirs += self.__add.config_protected_dirs
+
+ OUT.info(' Files and directories installed', 1)
+
+ self.__dotconfig.write(self.__ws.pn,
+ self.__ws.pvr,
+ self.__flags['host'],
+ self.__flags['orig'],
+ str(self.__perm['file']['config-owned'][0])
+ + ':' + str(self.__perm['file']['config-owned'][1]),)
+
+ self.__db.add(self.__destd,
+ self.__perm['file']['config-owned'][0],
+ self.__perm['file']['config-owned'][1])
+
+ # run the hooks
+
+ self.__ebuild.run_hooks('install', self)
+
+ # show the post-installation instructions
+
+ if not upgrade:
+ self.__ebuild.show_postinst(self)
+ else:
+ self.__ebuild.show_postupgrade(self)
+
+ # to finish, we need to tell the user if they need to run
+ # etc-update or not
+
+ if self.config_protected_dirs:
+
+ # work out whether this directory is part of the
+ # CONFIG_PROTECT list or not
+
+ self.__protect.how_to_update(self.config_protected_dirs)
+
+ self.__content.write()
+
+ # and we're done
+
+ OUT.info('Install completed - success', 1)
+
+ def supported(self):
+ if self.dep and package_installed(self.dep):
+ return True
+ return False
+
+class Apache(Basic):
+
+ name = 'Apache'
+ desc = 'supports installation on Apache 1 & 2'
+ dep = '>=net-www/apache-1.3'
+
+ def set_server_user(self):
+ self.vhost_server_uid = get_user('apache')
+ self.vhost_server_gid = get_group('apache')
+
+class Lighttpd(Basic):
+
+ name = 'Lighttpd'
+ desc = 'supports installation on lighttpd'
+ dep = 'www-servers/lighttpd'
+
+ def set_server_user(self):
+ self.vhost_server_uid = get_user('lighttpd')
+ self.vhost_server_gid = get_group('lighttpd')
+
+class Aolserver(Basic):
+
+ name = 'Aolserver'
+ desc = 'supports installation on Aolserver'
+ dep = 'www-servers/aolserver'
+
+ def set_server_user(self):
+ self.vhost_server_uid = get_user('aolserver')
+ self.vhost_server_gid = get_group('aolserver')
+
+class Cherokee(Basic):
+
+ name = 'Cherokee'
+ desc = 'supports installation on Cherokee'
+ dep = 'www-servers/cherokee'
+
+ def set_server_user(self):
+ self.vhost_server_uid = get_user('cherokee')
+ self.vhost_server_gid = get_group('cherokee')
+
+def listservers():
+
+ OUT.notice('\n'.join(['apache',
+ 'aolserver',
+ 'lighttpd',
+ 'cherokee']))
diff --git a/WebappConfig/tests/.svn.ignore b/WebappConfig/tests/.svn.ignore
new file mode 100644
index 0000000..0d20b64
--- /dev/null
+++ b/WebappConfig/tests/.svn.ignore
@@ -0,0 +1 @@
+*.pyc
diff --git a/WebappConfig/tests/dtest.py b/WebappConfig/tests/dtest.py
new file mode 100644
index 0000000..8ad3188
--- /dev/null
+++ b/WebappConfig/tests/dtest.py
@@ -0,0 +1,31 @@
+################################################################################
+# KOLAB LIBRARY - TESTING "CONDITION.PY"
+################################################################################
+# test_condition.py -- Testing condition.py
+# Copyright 2005 Gunnar Wrobel
+# Distributed under the terms of the GNU General Public License v2
+# $Id$
+
+import unittest, doctest, sys
+
+import WebappConfig.content
+import WebappConfig.db
+import WebappConfig.dotconfig
+import WebappConfig.ebuild
+import WebappConfig.filetype
+import WebappConfig.protect
+import WebappConfig.worker
+
+def test_suite():
+ return unittest.TestSuite((
+ doctest.DocTestSuite(WebappConfig.content),
+ doctest.DocTestSuite(WebappConfig.db),
+ doctest.DocTestSuite(WebappConfig.dotconfig),
+ doctest.DocTestSuite(WebappConfig.ebuild),
+ doctest.DocTestSuite(WebappConfig.filetype),
+ doctest.DocTestSuite(WebappConfig.protect),
+ doctest.DocTestSuite(WebappConfig.worker),
+ ))
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
diff --git a/WebappConfig/tests/testfiles/contents/.webapp-test-1.0 b/WebappConfig/tests/testfiles/contents/.webapp-test-1.0
new file mode 100644
index 0000000..f659e70
--- /dev/null
+++ b/WebappConfig/tests/testfiles/contents/.webapp-test-1.0
@@ -0,0 +1,12 @@
+dir 1 default-owned "lib" 1117009618 0
+dir 1 default-owned "util" 1117009618 0
+dir 1 config-owned "inc" 1117009618 0
+dir 0 default-owned "/var/www/localhost/error" 1124577740 0
+dir 0 default-owned "/var/www/localhost/icons" 1124577741 0
+dir 0 default-owned "/var/www/localhost/cgi-bin" 1124577741 0
+file 1 virtual "lib/prefs.php" 1124612215 ffae752dba7092cd2d1553d04a0f0045
+file 1 virtual "util/icon_browser.php" 1124612216 9ffb2ca9ccd2db656b97cd26a1b06010
+file 1 virtual "signup.php" 1124612220 dc838bc375b3d02dafc414f8e71a2aec
+sym 1 virtual "test" 1124612220 dc838bc375b3d02dafc414f8e71a2aec /I link / to a very / strange location
+file 1 config-owned "inc/prefs.php" 1124612215 ffae752dba7092cd2d1553d04a0f0045
+file 1 server-owned "data.php" 1117009618 0
diff --git a/WebappConfig/tests/testfiles/contents/.webapp-test-1.1 b/WebappConfig/tests/testfiles/contents/.webapp-test-1.1
new file mode 100644
index 0000000..abdf076
--- /dev/null
+++ b/WebappConfig/tests/testfiles/contents/.webapp-test-1.1
@@ -0,0 +1,12 @@
+dir 1 default-owned
+dir 1 nobody-owned "util" 1117009618 0
+dir 1 config-owned "inc" 1117009618 0
+dir 0 default-owned "/var/www/localhost/error" 1124577740 0
+dir 0 default-owned "/var/www/localhost/icons" 1124577741 0
+dir 0 default-owned "/var/www/localhost/cgi-bin" 1124577741 0
+garbage 1 virtual "lib/prefs.php" 1124612215 ffae752dba7092cd2d1553d04a0f0045
+file 1 virtual
+file 1 virtual "signup.php"
+sym 1 virtual "test" 1124612220 dc838bc375b3d02dafc414f8e71a2aec
+file 31 config-owned "inc/prefs.php" 1124612215 ffae752dba7092cd2d1553d04a0f0045
+file 1 server-owned "data.php" 1117009618 0
diff --git a/WebappConfig/tests/testfiles/contents/app/.webapp-test-1.0 b/WebappConfig/tests/testfiles/contents/app/.webapp-test-1.0
new file mode 100644
index 0000000..56c86d5
--- /dev/null
+++ b/WebappConfig/tests/testfiles/contents/app/.webapp-test-1.0
@@ -0,0 +1,9 @@
+file 1 config-owned "test2" 1130601441 d8e8fca2dc0f896fd7cb4cb0031ba249
+file 1 virtual "test6" 1130601441 d8e8fca2dc0f896fd7cb4cb0031ba249
+file 1 virtual "dir4" 1130601441 d8e8fca2dc0f896fd7cb4cb0031ba249
+file 1 virtual "test4" 0 d8e8fca2dc0f896fd7cb4cb0031ba249
+sym 1 virtual "test3" 1130601441 0 fail
+sym 1 virtual "dir3" 1130601441 d8e8fca2dc0f896fd7cb4cb0031ba249 /link
+dir 1 virtual "test7" 1130601441 d8e8fca2dc0f896fd7cb4cb0031ba249
+dir 1 default-owned "dir1" 1130602385 0
+dir 1 default-owned "dir2" 1130602385 0
diff --git a/WebappConfig/tests/testfiles/contents/app/dir1/.keep b/WebappConfig/tests/testfiles/contents/app/dir1/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/WebappConfig/tests/testfiles/contents/app/dir1/.keep
diff --git a/WebappConfig/tests/testfiles/contents/app/dir1/webapp_test b/WebappConfig/tests/testfiles/contents/app/dir1/webapp_test
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/WebappConfig/tests/testfiles/contents/app/dir1/webapp_test
diff --git a/WebappConfig/tests/testfiles/contents/app/dir2/webapp_test b/WebappConfig/tests/testfiles/contents/app/dir2/webapp_test
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/WebappConfig/tests/testfiles/contents/app/dir2/webapp_test
diff --git a/WebappConfig/tests/testfiles/contents/app/dir3/webapp_test b/WebappConfig/tests/testfiles/contents/app/dir3/webapp_test
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/WebappConfig/tests/testfiles/contents/app/dir3/webapp_test
diff --git a/WebappConfig/tests/testfiles/contents/app/dir4/webapp_test b/WebappConfig/tests/testfiles/contents/app/dir4/webapp_test
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/WebappConfig/tests/testfiles/contents/app/dir4/webapp_test
diff --git a/WebappConfig/tests/testfiles/contents/app/test1 b/WebappConfig/tests/testfiles/contents/app/test1
new file mode 100644
index 0000000..9daeafb
--- /dev/null
+++ b/WebappConfig/tests/testfiles/contents/app/test1
@@ -0,0 +1 @@
+test
diff --git a/WebappConfig/tests/testfiles/contents/app/test2 b/WebappConfig/tests/testfiles/contents/app/test2
new file mode 100644
index 0000000..9daeafb
--- /dev/null
+++ b/WebappConfig/tests/testfiles/contents/app/test2
@@ -0,0 +1 @@
+test
diff --git a/WebappConfig/tests/testfiles/contents/app/test3 b/WebappConfig/tests/testfiles/contents/app/test3
new file mode 120000
index 0000000..f079749
--- /dev/null
+++ b/WebappConfig/tests/testfiles/contents/app/test3
@@ -0,0 +1 @@
+test1 \ No newline at end of file
diff --git a/WebappConfig/tests/testfiles/contents/app/test4 b/WebappConfig/tests/testfiles/contents/app/test4
new file mode 100644
index 0000000..9daeafb
--- /dev/null
+++ b/WebappConfig/tests/testfiles/contents/app/test4
@@ -0,0 +1 @@
+test
diff --git a/WebappConfig/tests/testfiles/contents/app/test5 b/WebappConfig/tests/testfiles/contents/app/test5
new file mode 100644
index 0000000..9daeafb
--- /dev/null
+++ b/WebappConfig/tests/testfiles/contents/app/test5
@@ -0,0 +1 @@
+test
diff --git a/WebappConfig/tests/testfiles/contents/app/test7 b/WebappConfig/tests/testfiles/contents/app/test7
new file mode 100644
index 0000000..9daeafb
--- /dev/null
+++ b/WebappConfig/tests/testfiles/contents/app/test7
@@ -0,0 +1 @@
+test
diff --git a/WebappConfig/tests/testfiles/contents/app2/.webapp-test-1.0 b/WebappConfig/tests/testfiles/contents/app2/.webapp-test-1.0
new file mode 100644
index 0000000..0fd2827
--- /dev/null
+++ b/WebappConfig/tests/testfiles/contents/app2/.webapp-test-1.0
@@ -0,0 +1,5 @@
+file 1 virtual "test1" time-pretend md5-pretend
+file 1 virtual "test2" time-pretend md5-pretend
+file 1 virtual "dir2/test1" time-pretend md5-pretend
+sym 1 virtual "test3" time-pretend 0 link-pretend
+dir 1 default-owned "dir1" time-pretend 0
diff --git a/WebappConfig/tests/testfiles/contents/app2/dir1/webapp_test b/WebappConfig/tests/testfiles/contents/app2/dir1/webapp_test
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/WebappConfig/tests/testfiles/contents/app2/dir1/webapp_test
diff --git a/WebappConfig/tests/testfiles/contents/app2/dir2/test1 b/WebappConfig/tests/testfiles/contents/app2/dir2/test1
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/WebappConfig/tests/testfiles/contents/app2/dir2/test1
diff --git a/WebappConfig/tests/testfiles/contents/app2/dir2/webapp_test b/WebappConfig/tests/testfiles/contents/app2/dir2/webapp_test
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/WebappConfig/tests/testfiles/contents/app2/dir2/webapp_test
diff --git a/WebappConfig/tests/testfiles/contents/app2/test1 b/WebappConfig/tests/testfiles/contents/app2/test1
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/WebappConfig/tests/testfiles/contents/app2/test1
diff --git a/WebappConfig/tests/testfiles/contents/app2/test2 b/WebappConfig/tests/testfiles/contents/app2/test2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/WebappConfig/tests/testfiles/contents/app2/test2
diff --git a/WebappConfig/tests/testfiles/contents/app2/test3 b/WebappConfig/tests/testfiles/contents/app2/test3
new file mode 120000
index 0000000..f079749
--- /dev/null
+++ b/WebappConfig/tests/testfiles/contents/app2/test3
@@ -0,0 +1 @@
+test1 \ No newline at end of file
diff --git a/WebappConfig/tests/testfiles/htdocs/complain/.webapp b/WebappConfig/tests/testfiles/htdocs/complain/.webapp
new file mode 100644
index 0000000..5820041
--- /dev/null
+++ b/WebappConfig/tests/testfiles/htdocs/complain/.webapp
@@ -0,0 +1,26 @@
+# .webapp
+# config file for this copy of horde/3.0.4-r1
+#
+# automatically created by Gentoo's webapp-config
+# do NOT edit this file by hand
+
+WEB_PN="horde"
+WEB_PVR="3.0.4-r1"
+WEB_INSTALLEDBY="root"
+WEB_INSTALLEDDATE="2005-05-25 10:25:49"
+WEB_INSTALLEDFOR="root:apache"
+WEB_HOSTNAME="localhost"
+WEB_INSTALLDIR="/horde"
+# .webapp
+# config file for this copy of horde/3.0.5
+#
+# automatically created by Gentoo's webapp-config
+# do NOT edit this file by hand
+
+WEB_PN="horde"
+WEB_PVR="3.0.5"
+WEB_INSTALLEDBY="root"
+WEB_INSTALLEDDATE="2005-08-21 10:15:23"
+WEB_INSTALLEDFOR="root:root"
+WEB_HOSTNAME="localhost"
+WEB_INSTALLDIR="/horde"
diff --git a/WebappConfig/tests/testfiles/htdocs/complain/.webapp-cool-1.1.1 b/WebappConfig/tests/testfiles/htdocs/complain/.webapp-cool-1.1.1
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/WebappConfig/tests/testfiles/htdocs/complain/.webapp-cool-1.1.1
diff --git a/WebappConfig/tests/testfiles/htdocs/horde/.webapp b/WebappConfig/tests/testfiles/htdocs/horde/.webapp
new file mode 100644
index 0000000..5820041
--- /dev/null
+++ b/WebappConfig/tests/testfiles/htdocs/horde/.webapp
@@ -0,0 +1,26 @@
+# .webapp
+# config file for this copy of horde/3.0.4-r1
+#
+# automatically created by Gentoo's webapp-config
+# do NOT edit this file by hand
+
+WEB_PN="horde"
+WEB_PVR="3.0.4-r1"
+WEB_INSTALLEDBY="root"
+WEB_INSTALLEDDATE="2005-05-25 10:25:49"
+WEB_INSTALLEDFOR="root:apache"
+WEB_HOSTNAME="localhost"
+WEB_INSTALLDIR="/horde"
+# .webapp
+# config file for this copy of horde/3.0.5
+#
+# automatically created by Gentoo's webapp-config
+# do NOT edit this file by hand
+
+WEB_PN="horde"
+WEB_PVR="3.0.5"
+WEB_INSTALLEDBY="root"
+WEB_INSTALLEDDATE="2005-08-21 10:15:23"
+WEB_INSTALLEDFOR="root:root"
+WEB_HOSTNAME="localhost"
+WEB_INSTALLDIR="/horde"
diff --git a/WebappConfig/tests/testfiles/installtest/dir2 b/WebappConfig/tests/testfiles/installtest/dir2
new file mode 100644
index 0000000..180cf83
--- /dev/null
+++ b/WebappConfig/tests/testfiles/installtest/dir2
@@ -0,0 +1 @@
+test2
diff --git a/WebappConfig/tests/testfiles/installtest/test2 b/WebappConfig/tests/testfiles/installtest/test2
new file mode 100644
index 0000000..180cf83
--- /dev/null
+++ b/WebappConfig/tests/testfiles/installtest/test2
@@ -0,0 +1 @@
+test2
diff --git a/WebappConfig/tests/testfiles/installtest/test3 b/WebappConfig/tests/testfiles/installtest/test3
new file mode 100644
index 0000000..df6b0d2
--- /dev/null
+++ b/WebappConfig/tests/testfiles/installtest/test3
@@ -0,0 +1 @@
+test3
diff --git a/WebappConfig/tests/testfiles/protect/complex/._cfg0001_test b/WebappConfig/tests/testfiles/protect/complex/._cfg0001_test
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/WebappConfig/tests/testfiles/protect/complex/._cfg0001_test
diff --git a/WebappConfig/tests/testfiles/protect/complex/._cfg0033_test b/WebappConfig/tests/testfiles/protect/complex/._cfg0033_test
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/WebappConfig/tests/testfiles/protect/complex/._cfg0033_test
diff --git a/WebappConfig/tests/testfiles/protect/complex/._cfg0800_test b/WebappConfig/tests/testfiles/protect/complex/._cfg0800_test
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/WebappConfig/tests/testfiles/protect/complex/._cfg0800_test
diff --git a/WebappConfig/tests/testfiles/protect/empty/keep b/WebappConfig/tests/testfiles/protect/empty/keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/WebappConfig/tests/testfiles/protect/empty/keep
diff --git a/WebappConfig/tests/testfiles/protect/simple/._cfg0000_test b/WebappConfig/tests/testfiles/protect/simple/._cfg0000_test
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/WebappConfig/tests/testfiles/protect/simple/._cfg0000_test
diff --git a/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/config-files b/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/config-files
new file mode 100644
index 0000000..cd85853
--- /dev/null
+++ b/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/config-files
@@ -0,0 +1,2 @@
+test1
+test3
diff --git a/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/hooks/test b/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/hooks/test
new file mode 100755
index 0000000..65a582a
--- /dev/null
+++ b/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/hooks/test
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+echo "Called with" $1
diff --git a/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/htdocs/dir1/test1 b/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/htdocs/dir1/test1
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/htdocs/dir1/test1
diff --git a/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/htdocs/dir2/webapp_test b/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/htdocs/dir2/webapp_test
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/htdocs/dir2/webapp_test
diff --git a/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/htdocs/test1 b/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/htdocs/test1
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/htdocs/test1
diff --git a/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/htdocs/test2 b/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/htdocs/test2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/htdocs/test2
diff --git a/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/installed_by_webapp_eclass b/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/installed_by_webapp_eclass
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/installed_by_webapp_eclass
diff --git a/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/postinst-en.txt b/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/postinst-en.txt
new file mode 100644
index 0000000..b3b7155
--- /dev/null
+++ b/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/postinst-en.txt
@@ -0,0 +1,33 @@
+MY_HOSTROOTDIR: ${MY_HOSTROOTDIR}
+MY_HTDOCSDIR: ${MY_HTDOCSDIR}
+MY_CGIBINDIR: ${MY_CGIBINDIR}
+MY_INSTALLDIR: ${MY_INSTALLDIR}
+MY_ICONSDIR: ${MY_ICONSDIR}
+MY_SERVERCONFIGDIR: ${MY_SERVERCONFIGDIR}
+MY_ERRORSDIR: ${MY_ERRORSDIR}
+MY_SQLSCRIPTSDIR: ${MY_SQLSCRIPTSDIR}
+VHOST_ROOT: ${VHOST_ROOT}
+VHOST_HTDOCSDIR: ${VHOST_HTDOCSDIR}
+VHOST_CGIBINDIR: ${VHOST_CGIBINDIR}
+VHOST_CONFDIR: ${VHOST_CONFDIR}
+VHOST_ERRORSDIR: ${VHOST_ERRORSDIR}
+VHOST_ICONSDIR: ${VHOST_ICONSDIR}
+VHOST_HOSTNAME: ${VHOST_HOSTNAME}
+VHOST_SERVER: ${VHOST_SERVER}
+VHOST_APPDIR: ${VHOST_APPDIR}
+VHOST_CONFIG_UID: ${VHOST_CONFIG_UID}
+VHOST_CONFIG_GID: ${VHOST_CONFIG_GID}
+VHOST_SERVER_UID: ${VHOST_SERVER_UID}
+VHOST_SERVER_GID: ${VHOST_SERVER_GID}
+VHOST_DEFAULT_UID: ${VHOST_DEFAULT_UID}
+VHOST_DEFAULT_GID: ${VHOST_DEFAULT_GID}
+VHOST_PERMS_SERVEROWNED_DIR: ${VHOST_PERMS_SERVEROWNED_DIR}
+VHOST_PERMS_SERVEROWNED_FILE: ${VHOST_PERMS_SERVEROWNED_FILE}
+VHOST_PERMS_CONFIGOWNED_DIR: ${VHOST_PERMS_CONFIGOWNED_DIR}
+VHOST_PERMS_CONFIGOWNED_FILE: ${VHOST_PERMS_CONFIGOWNED_FILE}
+VHOST_PERMS_DEFAULTOWNED_DIR: ${VHOST_PERMS_DEFAULTOWNED_DIR}
+VHOST_PERMS_VIRTUALOWNED_FILE: ${VHOST_PERMS_VIRTUALOWNED_FILE}
+VHOST_PERMS_INSTALLDIR: ${VHOST_PERMS_INSTALLDIR}
+ROOT: ${ROOT}
+PN: ${PN}
+PVR: ${PVR}
diff --git a/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/server-owned-files b/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/server-owned-files
new file mode 100644
index 0000000..16f2b16
--- /dev/null
+++ b/WebappConfig/tests/testfiles/share-webapps/horde/3.0.5/server-owned-files
@@ -0,0 +1,2 @@
+test2
+test4
diff --git a/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/config-files b/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/config-files
new file mode 100644
index 0000000..4ee1b0a
--- /dev/null
+++ b/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/config-files
@@ -0,0 +1 @@
+htdocs/test3
diff --git a/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/htdocs/dir1/webapp_test b/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/htdocs/dir1/webapp_test
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/htdocs/dir1/webapp_test
diff --git a/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/htdocs/dir2/webapp_test b/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/htdocs/dir2/webapp_test
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/htdocs/dir2/webapp_test
diff --git a/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/htdocs/test1 b/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/htdocs/test1
new file mode 100644
index 0000000..9daeafb
--- /dev/null
+++ b/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/htdocs/test1
@@ -0,0 +1 @@
+test
diff --git a/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/htdocs/test2 b/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/htdocs/test2
new file mode 100644
index 0000000..9daeafb
--- /dev/null
+++ b/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/htdocs/test2
@@ -0,0 +1 @@
+test
diff --git a/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/htdocs/test3 b/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/htdocs/test3
new file mode 100644
index 0000000..9daeafb
--- /dev/null
+++ b/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/htdocs/test3
@@ -0,0 +1 @@
+test
diff --git a/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/htdocs/test4 b/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/htdocs/test4
new file mode 100644
index 0000000..9daeafb
--- /dev/null
+++ b/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/htdocs/test4
@@ -0,0 +1 @@
+test
diff --git a/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/installed_by_webapp_eclass b/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/installed_by_webapp_eclass
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/installed_by_webapp_eclass
diff --git a/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/server-owned-files b/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/server-owned-files
new file mode 100644
index 0000000..0395d10
--- /dev/null
+++ b/WebappConfig/tests/testfiles/share-webapps/installtest/1.0/server-owned-files
@@ -0,0 +1 @@
+htdocs/test4
diff --git a/WebappConfig/tests/testfiles/share-webapps/uninstalled/6.6.6/installed_by_webapp_eclass b/WebappConfig/tests/testfiles/share-webapps/uninstalled/6.6.6/installed_by_webapp_eclass
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/WebappConfig/tests/testfiles/share-webapps/uninstalled/6.6.6/installed_by_webapp_eclass
diff --git a/WebappConfig/tests/testfiles/webapps/gallery/1.4.4_p6/installs b/WebappConfig/tests/testfiles/webapps/gallery/1.4.4_p6/installs
new file mode 100644
index 0000000..a7dcf39
--- /dev/null
+++ b/WebappConfig/tests/testfiles/webapps/gallery/1.4.4_p6/installs
@@ -0,0 +1 @@
+1126090157 root root /var/www/localhost/htdocs/gallery
diff --git a/WebappConfig/tests/testfiles/webapps/gallery/2.0_rc2/installs b/WebappConfig/tests/testfiles/webapps/gallery/2.0_rc2/installs
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/WebappConfig/tests/testfiles/webapps/gallery/2.0_rc2/installs
diff --git a/WebappConfig/tests/testfiles/webapps/horde/3.0.5/installs b/WebappConfig/tests/testfiles/webapps/horde/3.0.5/installs
new file mode 100644
index 0000000..07f9e89
--- /dev/null
+++ b/WebappConfig/tests/testfiles/webapps/horde/3.0.5/installs
@@ -0,0 +1 @@
+1124612110 root root /var/www/localhost/htdocs/horde
diff --git a/WebappConfig/tests/testfiles/webapps/phpldapadmin/0.9.7_alpha4/installs b/WebappConfig/tests/testfiles/webapps/phpldapadmin/0.9.7_alpha4/installs
new file mode 100644
index 0000000..6ddf607
--- /dev/null
+++ b/WebappConfig/tests/testfiles/webapps/phpldapadmin/0.9.7_alpha4/installs
@@ -0,0 +1 @@
+1125000794 root root /var/www/localhost/htdocs/phpldapadmin
diff --git a/WebappConfig/version.py b/WebappConfig/version.py
new file mode 100644
index 0000000..db1f3db
--- /dev/null
+++ b/WebappConfig/version.py
@@ -0,0 +1,19 @@
+# /usr/sbin/webapp-config
+# Python script for managing the deployment of web-based
+# applications
+#
+# Originally written for the Gentoo Linux distribution
+#
+# Copyright (c) 1999-2006 Gentoo Foundation
+# Released under v2 of the GNU GPL
+#
+# Author(s) Stuart Herbert <stuart@gentoo.org>
+# Renat Lumpau <rl03@gentoo.org>
+# Gunnar Wrobel <php@gunnarwrobel.de>
+#
+# ========================================================================
+
+WCVERSION = '1.50.16'
+
+if __name__ == '__main__':
+ print WCVERSION
diff --git a/WebappConfig/worker.py b/WebappConfig/worker.py
new file mode 100644
index 0000000..b98a810
--- /dev/null
+++ b/WebappConfig/worker.py
@@ -0,0 +1,563 @@
+#!/usr/bin/python -O
+#
+# /usr/sbin/webapp-config
+# Python script for managing the deployment of web-based
+# applications
+#
+# Originally written for the Gentoo Linux distribution
+#
+# Copyright (c) 1999-2006 Gentoo Foundation
+# Released under v2 of the GNU GPL
+#
+# Author(s) Stuart Herbert <stuart@gentoo.org>
+# Renat Lumpau <rl03@gentoo.org>
+# Gunnar Wrobel <php@gunnarwrobel.de>
+#
+# ========================================================================
+''' This module provides the classes for actually adding or removing
+files of a virtual install location. '''
+
+__version__ = "$Id: worker.py 245 2006-01-13 16:57:29Z wrobel $"
+
+# ========================================================================
+# Dependencies
+# ------------------------------------------------------------------------
+
+import sys, os, os.path, shutil, stat, re
+
+from WebappConfig.debug import OUT
+import WebappConfig.wrapper as wrapper
+
+# ========================================================================
+# Helper functions
+# ------------------------------------------------------------------------
+
+def all(boolean):
+ ''' Replacement for reduce() '''
+ for i in boolean:
+ if not i:
+ return False
+ return True
+
+# ========================================================================
+# Worker class
+# ------------------------------------------------------------------------
+
+class WebappRemove:
+ '''
+ This is the handler for removal of web applications from their virtual
+ install locations.
+
+ For removal of files a content handler is sufficient:
+
+ >>> OUT.color_off()
+ >>> import os.path
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+ >>> from WebappConfig.content import Contents
+ >>> a = Contents(here + '/tests/testfiles/contents/app2',
+ ... package = 'test', version = '1.0', pretend = True)
+ >>> a.read()
+ >>> b = WebappRemove(a, True, True)
+
+ # Pretend to remove files:
+
+ # b.remove_files() #doctest: +ELLIPSIS
+
+ # Deleted the test since this will almost certainly fail because
+ # of the modification time.
+
+ Deleted test for removal of directories. They are always reported as 'not
+ empty' in case I am working in the subversion repository.
+ '''
+
+ def __init__(self,
+ content,
+ verbose,
+ pretend):
+
+ self.__content = content
+ self.__v = verbose
+ self.__p = pretend
+
+ def remove_dirs(self):
+ '''
+ It is time to remove the dirs that we installed originally.
+ '''
+
+ OUT.debug('Trying to remove directories', 6)
+
+ success = [self.remove(i) for i in self.__content.get_directories()]
+
+ # Tell the caller if anything was left behind
+
+ return all(success)
+
+ def remove_files(self):
+ '''
+ It is time to remove the files that we installed originally.
+ '''
+
+ OUT.debug('Trying to remove files', 6)
+
+ success = [self.remove(i) for i in self.__content.get_files()]
+
+ # Tell the caller if anything was left behind
+
+ return all(success)
+
+ def remove(self, entry):
+ '''
+ Decide whether to delete something - and then go ahead and do so
+
+ Just like portage, we only remove files that have not changed
+ from when we installed them. If the timestamp or checksum is
+ different, we leave the file in place.
+
+ Inputs
+
+ entry - file/dir/sym to remove
+ '''
+
+ OUT.debug('Trying to remove file', 6)
+
+ # okay, deal with the file | directory | symlink
+
+ removeable = self.__content.get_canremove(entry)
+
+ if not removeable:
+
+ # Remove directory or file.
+
+ # Report if we are only pretending
+ if self.__p:
+ OUT.info(' pretending to remove: ' + entry)
+
+ # try to remove the entry
+ try:
+ entry_type = self.__content.etype(entry)
+ if self.__content.etype(entry) == 'dir':
+ # its a directory -> rmdir
+ if not self.__p:
+ os.rmdir(entry)
+ else:
+ # its a file -> unlink
+ if not self.__p:
+ os.unlink(entry)
+ except:
+ # Report if there is a problem
+ OUT.notice('!!! '
+ + self.__content.epath(entry))
+ return
+
+ if self.__v and not self.__p:
+ # Report successful deletion
+
+ OUT.notice('<<< ' + entry_type + ' '
+ * (5 - len(entry_type))
+ + self.__content.epath(entry))
+
+ self.__content.delete(entry)
+
+ return True
+
+ else:
+
+ OUT.notice(removeable)
+
+ return False
+
+
+class WebappAdd:
+ '''
+ This is the class that handles the actual transfer of files from
+ the web application source directory to the virtual install location.
+
+ The setup of the class is rather complex since a lot of different
+ handlers are needed for the task.
+
+ >>> OUT.color_off()
+ >>> import os.path
+ >>> here = os.path.dirname(os.path.realpath(__file__))
+
+ The content handler points to the virtual install directory:
+
+ >>> from WebappConfig.content import Contents
+ >>> a = Contents(here + '/tests/testfiles/installtest', pretend = True)
+
+ Removal of files will be necessary while upgrading :
+
+ >>> b = WebappRemove(a, True, True)
+
+ The handler for protected files is simple:
+
+ >>> import WebappConfig.protect
+ >>> c = WebappConfig.protect.Protection()
+
+ And finally a fully initialized source is needed:
+
+ >>> from WebappConfig.db import WebappSource
+ >>> d = WebappSource(here + '/tests/testfiles/share-webapps',
+ ... 'installtest', '1.0')
+ >>> d.read()
+ >>> d.ignore = ['.svn']
+
+ >>> e = WebappAdd('htdocs',
+ ... here + '/tests/testfiles/installtest',
+ ... {'dir' : {
+ ... 'default-owned': ('root', 'root', '0644'),
+ ... },
+ ... 'file' : {
+ ... 'virtual' : ('root', 'root', '0644'),
+ ... 'server-owned' : ('apache', 'apache', '0660'),
+ ... 'config-owned' : ('nobody', 'nobody', '0600'),
+ ... }},
+ ... {'content': a,
+ ... 'removal': b,
+ ... 'protect': c,
+ ... 'source' : d},
+ ... {'relative': 1,
+ ... 'upgrade': False,
+ ... 'pretend': True,
+ ... 'verbose': False,
+ ... 'linktype': 'soft'})
+
+ Installing a standard file:
+
+ >>> e.mkfile('test1')
+ * pretending to add: sym 1 virtual "test1"
+ >>> e.mkfile('test4')
+ * pretending to add: file 1 server-owned "test4"
+
+ This location is already occupied. But since the file is not
+ known, it will be deleted:
+
+ >>> e.mkfile('test2') #doctest: +ELLIPSIS
+ * would have removed ".../tests/testfiles/installtest/test2" since it is in the way for the current install. It should not be present in that location!
+ * pretending to add: sym 1 virtual "test2"
+
+ This location is also occupied but it it is a config protected
+ file so it may not be removed:
+
+ >>> e.mkfile('test3') #doctest:
+ ^o^ hiding test3
+ * pretending to add: file 1 config-owned "test3"
+
+ >>> e.mkdir('dir1')
+ * pretending to add: dir 1 default-owned "dir1"
+
+ >>> e.mkdir('dir2') #doctest: +ELLIPSIS
+ * .../tests/testfiles/installtest/dir2 already exists, but is not a directory - removing
+ * pretending to add: dir 1 default-owned "dir2"
+
+ And finally everything combined:
+
+ >>> e.mkdirs('') #doctest: +ELLIPSIS
+ * Installing from .../tests/testfiles/share-webapps/installtest/1.0/htdocs/
+ * pretending to add: dir 1 default-owned "dir1"
+ * Installing from .../tests/testfiles/share-webapps/installtest/1.0/htdocs/dir1
+ * pretending to add: sym 1 virtual "dir1/webapp_test"
+ * .../tests/testfiles/installtest//dir2 already exists, but is not a directory - removing
+ * pretending to add: dir 1 default-owned "dir2"
+ * Installing from .../tests/testfiles/share-webapps/installtest/1.0/htdocs/dir2
+ * pretending to add: sym 1 virtual "dir2/webapp_test"
+ * pretending to add: sym 1 virtual "test1"
+ * would have removed ".../tests/testfiles/installtest//test2" since it is in the way for the current install. It should not be present in that location!
+ * pretending to add: sym 1 virtual "test2"
+ ^o^ hiding /test3
+ * pretending to add: file 1 config-owned "test3"
+ * pretending to add: file 1 server-owned "test4"
+
+ '''
+
+ def __init__(self,
+ source,
+ destination,
+ permissions,
+ handler,
+ flags):
+
+ self.__sourced = source
+ self.__destd = destination
+ self.__perm = permissions
+ self.__ws = handler['source']
+ self.__content = handler['content']
+ self.__remove = handler['removal']
+ self.__protect = handler['protect']
+ self.__link_type = flags['linktype']
+ self.__relative = flags['relative']
+ self.__u = flags['upgrade']
+ self.__v = flags['verbose']
+ self.__p = flags['pretend']
+
+ self.config_protected_dirs = []
+
+ def mkdirs(self, directory = ''):
+ '''
+ Create a set of directories
+
+ Inputs
+
+ directory - the directory within the source hierarchy
+ '''
+
+ sd = self.__sourced + '/' + directory
+ real_dir = re.compile('/+').sub('/',
+ self.__ws.appdir()
+ + '/' + self.__sourced
+ + '/' + directory)
+
+ OUT.debug('Creating directories', 6)
+
+ if not self.__ws.source_exists(sd):
+
+ OUT.warn(self.__ws.package_name()
+ + ' does not install any files from '
+ + real_dir + '; skipping')
+ return
+
+ OUT.info(' Installing from ' + real_dir)
+
+ for i in self.__ws.get_source_directories(sd):
+
+ OUT.debug('Handling directory', 7)
+
+ # create directory first
+ self.mkdir(directory + '/' + i)
+
+ # then recurse into the directory
+ self.mkdirs(directory + '/' + i)
+
+ for i in self.__ws.get_source_files(sd):
+
+ OUT.debug('Handling file', 7)
+
+ # handle the file
+ self.mkfile(directory + '/' + i)
+
+
+ def mkdir(self, directory):
+ '''
+ Create a directory with the correct ownership and permissions.
+
+ directory - name of the directory
+ '''
+ src_dir = self.__sourced + '/' + directory
+ dst_dir = self.__destd + '/' + directory
+
+ OUT.debug('Creating directory', 6)
+
+ # some special cases
+ #
+ # these should be triggered only if we are trying to install
+ # a webapp into a directory that already has files and dirs
+ # inside it
+
+ if os.path.exists(dst_dir) and not os.path.isdir(dst_dir):
+ # something already exists with the same name
+ #
+ # in theory, this should automatically remove symlinked
+ # directories
+
+ OUT.warn(' ' + dst_dir + ' already exists, but is not a di'
+ 'rectory - removing')
+ if not self.__p:
+ os.unlink(dst_dir)
+
+ dirtype = self.__ws.dirtype(src_dir)
+
+ OUT.debug('Checked directory type', 8)
+
+ (user, group, perm) = self.__perm['dir'][dirtype]
+
+ dsttype = 'dir'
+
+ if not os.path.isdir(dst_dir):
+
+ OUT.debug('Creating directory', 8)
+
+ if not self.__p:
+ os.makedirs(dst_dir, perm(0755))
+
+ os.chown(dst_dir,
+ user,
+ group)
+
+ self.__content.add(dsttype,
+ dirtype,
+ self.__destd,
+ directory,
+ self.__relative)
+
+ def mkfile(self, filename):
+ '''
+ This is what we are all about. No more games - lets take a file
+ from the master image of the web-based app, and make it available
+ inside the install directory.
+
+ filename - name of the file
+
+ '''
+
+ OUT.debug('Creating file', 6)
+
+ dst_name = self.__destd + '/' + filename
+ file_type = self.__ws.filetype(self.__sourced + '/' + filename)
+
+ OUT.debug('File type determined', 7)
+
+ # are we overwriting an existing file?
+
+ OUT.debug('Check for existing file', 7)
+
+ if os.path.exists(dst_name):
+
+ OUT.debug('File in the way!', 7)
+
+ my_canremove = True
+
+ # o-oh - we're going to be overwriting something that already
+ # exists
+
+ # If we are upgrading, check if the file can be removed
+ if self.__u:
+ my_canremove = self.__remove.remove(self.__destd, filename)
+ # Config protected file definitely cannot be removed
+ elif file_type[0:6] == 'config':
+ my_canremove = False
+
+ if not my_canremove:
+ # not able to remove the file
+ # or
+ # file is config-protected
+
+ dst_name = self.__protect.get_protectedname(self.__destd,
+ filename)
+ OUT.notice('^o^ hiding ' + filename)
+ self.config_protected_dirs.append(self.__destd + '/'
+ + os.path.dirname(filename))
+
+ OUT.debug('Hiding config protected file', 7)
+
+ else:
+
+ # it's a file we do not know about - so get rid
+ # of it anyway
+ #
+ # this behaviour here *is* by popular request
+ # personally, I'm not comfortable with it -- Stuart
+
+ if not self.__p:
+ if os.path.isdir(dst_name):
+ os.rmdir(dst_name)
+ else:
+ os.unlink(dst_name)
+ else:
+ OUT.info(' would have removed "' + dst_name + '" s'
+ 'ince it is in the way for the current instal'
+ 'l. It should not be present in that location'
+ '!')
+
+
+ # if we get here, we can get on with the business of making
+ # the file available
+
+ (user, group, perm) = self.__perm['file'][file_type]
+ my_contenttype = ''
+
+ src_name = self.__ws.appdir() + '/' + self.__sourced + '/' + filename
+
+ # Fix the paths
+ src_name = re.compile('/+').sub('/', src_name)
+ dst_name = re.compile('/+').sub('/', dst_name)
+
+ OUT.debug('Creating File', 7)
+
+ # this is our default file type
+ #
+ # we link in (soft and hard links are supported)
+ # if we're allowed to
+ #
+ # some applications (/me points at PHP scripts)
+ # won't run if symlinked in.
+ # so we now support copying files in too
+ #
+ # default behaviour is to hard link (if we can), and
+ # to copy if we cannot
+ #
+ # if the user wants symlinks, then the user has to
+ # use the new '--soft' option
+
+ if file_type == 'virtual' or os.path.islink(src_name):
+
+ if self.__link_type == 'soft':
+ try:
+
+ OUT.debug('Trying to softlink', 8)
+
+ if not self.__p:
+ os.symlink(src_name, dst_name)
+
+ my_contenttype = 'sym'
+
+ except Exception, e:
+
+ if self.__v:
+ OUT.warn('Failed to softlink (' + str(e) + ')')
+
+ elif os.path.islink(src_name):
+ try:
+
+ OUT.debug('Trying to copy symlink', 8)
+
+ if not self.__p:
+ os.symlink(os.readlink(src_name), dst_name)
+
+ my_contenttype = 'sym'
+
+ except Exception, e:
+
+ if self.__v:
+ OUT.warn('Failed copy symlink (' + str(e) + ')')
+
+ else:
+ try:
+
+ OUT.debug('Trying to hardlink', 8)
+
+ if not self.__p:
+ os.link(src_name, dst_name)
+
+ my_contenttype = 'file'
+
+ except Exception, e:
+
+ if self.__v:
+ OUT.warn('Failed to hardlink (' + str(e) + ')')
+
+ if not my_contenttype:
+ if not self.__p:
+ shutil.copy(src_name, dst_name)
+ my_contenttype = 'file'
+
+
+ if not self.__p and not os.path.islink(src_name):
+
+ old_perm = os.stat(src_name)[stat.ST_MODE] & 511
+
+ os.chown(dst_name,
+ user,
+ group)
+
+ os.chmod(dst_name,
+ perm(old_perm))
+
+ self.__content.add(my_contenttype,
+ file_type,
+ self.__destd,
+ filename,
+ self.__relative)
+
+
+if __name__ == '__main__':
+ import doctest, sys
+ doctest.testmod(sys.modules[__name__])
diff --git a/WebappConfig/wrapper.py b/WebappConfig/wrapper.py
new file mode 100644
index 0000000..44e4b90
--- /dev/null
+++ b/WebappConfig/wrapper.py
@@ -0,0 +1,110 @@
+#!/usr/bin/python -O
+#
+# /usr/sbin/webapp-config
+# Python script for managing the deployment of web-based
+# applications
+#
+# Originally written for the Gentoo Linux distribution
+#
+# Copyright (c) 1999-2006 Gentoo Foundation
+# Released under v2 of the GNU GPL
+#
+# Author(s) Stuart Herbert <stuart@gentoo.org>
+# Renat Lumpau <rl03@gentoo.org>
+# Gunnar Wrobel <php@gunnarwrobel.de>
+#
+# ========================================================================
+'''
+This helper module intends to provide a wrapper for some Gentoo
+specific features used by webapp-config. This might make it easier
+to use the tool on other distributions.
+
+Currently two parameters and one function are provided:
+
+ - conf_libdir [directory for libraries]
+ - config_protect [list of configuration protected directories]
+
+ - package_installed [indicates if the specified package is installed]
+'''
+
+__version__ = "$Id: wrapper.py 283 2006-04-20 22:53:04Z wrobel $"
+
+# ========================================================================
+# Dependencies
+# ------------------------------------------------------------------------
+
+import sys, portage, os, types
+
+from WebappConfig.debug import OUT
+
+from WebappConfig.debug import OUT
+from WebappConfig.version import WCVERSION
+
+# ========================================================================
+# Portage Wrapper
+# ------------------------------------------------------------------------
+
+# Variable for config protected files (used by protect.py)
+config_protect = portage.settings['CONFIG_PROTECT']
+
+# Try to derive the correct libdir location by first examining the portage
+# variable ABI then using it to determine the appropriate variable to read. For
+# example, if ABI == 'amd64' then read LIBDIR_amd64. This routine should work on
+# all arches as it sets '/usr/lib' as a fallback. See bugs #125032 and #125156.
+
+config_libdir = '/usr/lib'
+
+if 'ABI' in portage.settings.keys():
+ config_abi = portage.settings['ABI']
+ if 'LIBDIR_' + config_abi in portage.settings.keys():
+ config_libdir = '/usr/' + portage.settings['LIBDIR_' + config_abi]
+ else:
+ # This shouldn't happen but we want to know if it ever does
+ OUT.warn('Failed to determine libdir from portage.settings[\'LIBDIR_' + config_abi + '\']\n')
+
+protect_prefix = '._cfg'
+update_command = 'etc-update'
+
+# Link for bug reporting
+bugs_link = 'http://bugs.gentoo.org/'
+
+def get_root():
+ '''
+ This function returns the $ROOT variable
+ '''
+ return portage.root
+
+def package_installed(packagename):
+ '''
+ This function identifies installed packages.
+ Stolen from gentoolkit.
+ We are not using gentoolkit directly as it doesn't seem to support ${ROOT}
+ '''
+ if packagename in portage.settings.pprovideddict.keys():
+ return True
+ try:
+ t = portage.db[portage.root]["vartree"].dbapi.match(packagename)
+ # catch the "ambiguous package" Exception
+ except ValueError, e:
+ if type(e[0]) == types.ListType:
+ t = []
+ for cp in e[0]:
+ t += portage.db[portage.root]["vartree"].dbapi.match(cp)
+ else:
+ raise ValueError(e)
+ return t
+
+if __name__ == '__main__':
+ OUT.info('\nPORTAGE WRAPPER')
+ OUT.info('---------------\n')
+ if package_installed('=webapp-config-' + WCVERSION):
+ a = 'YES'
+ else:
+ a = 'NO'
+
+ OUT.info('package_installed("webapp-config-'
+ + WCVERSION + '") : ' + a + '\n')
+ OUT.info('config_protect : ' + config_protect)
+ OUT.info('protect_prefix : ' + protect_prefix)
+ OUT.info('update_command : ' + update_command)
+ OUT.info('bugs_link : ' + bugs_link)
diff --git a/config/webapp-config b/config/webapp-config
new file mode 100755
index 0000000..810ef89
--- /dev/null
+++ b/config/webapp-config
@@ -0,0 +1,229 @@
+# /etc/vhosts/webapp-config
+# Some default variables that are shared between the
+# webapp-config utility, and the webapp eclass
+#
+# Part of the Gentoo Linux distribution
+#
+# Copyright (c) 1999-2005 Gentoo Foundation
+# Released under v2 of the GNU GPL
+#
+# Author(s) Stuart Herbert <stuart@gentoo.org>
+# Renat Lumpau <rl03@gentoo.org>
+# Gunnar Wrobel <php@gunnarwrobel.de>
+#
+# ========================================================================
+
+# ========================================================================
+#
+# USER-EDITABLE SETTINGS
+#
+# Feel free to edit these settings to suit your local needs
+#
+# ========================================================================
+
+# vhost_root is the directory where virtual host websites are added
+# so, if your server is hosting (say)
+#
+# www.gentoo.org
+# bugs.gentoo.org
+#
+# then the htdocs directory for each of these would be
+#
+# /var/www/www.gentoo.org/htdocs
+# /var/www/bugs.gentoo.org/htdocs
+#
+# Change this setting *only* if you need your websites installed in
+# a different physical location
+#
+# If you prefer to use Gentoo's optional support for the /srv service
+# home file hierarchy, uncomment the second version of vhost_root
+# (this should have been done for you if you installed webapp-config
+# with the srvdir USE flag enabled)
+# If you want to have fine grained control over the location the web
+# applications get installed, you can use the third setting
+
+vhost_root="/var/www/${vhost_hostname}"
+#vhost_root="/srv/${vhost_hostname}/www"
+#vhost_root="/var/www/${vhost_subdomain_1}/${vhost_subdomain_2}/${vhost_subdomain_3}"
+
+# some web applications need to know what host they are serving up pages
+# for. this information is configured when the application is installed
+# by the webapp-config script
+#
+# changing this value *after* the application has been installed has
+# no effect!!
+#
+# you can override this setting by using the -h switch to webapp-config
+#
+# IMPORTANT: If you comment this setting and provide no explicit hostname
+# with the command line -h switch, webapp-config will try to determine
+# the fully qualified domain name by itself
+
+vhost_hostname="localhost"
+
+# what web server are you using?
+# your choices are:
+#
+# apache
+# aolserver
+# lighttpd
+# cherokee
+#
+# you can override this setting by using the -s switch to webapp-config
+
+vhost_server="apache"
+
+# which user should own config files?
+# the default is the user currently running webapp-config (which is
+# normally the root user). You may either use the numerical uid or the
+# user name. The internal default is "0" to accomodate for BSD style
+# systems.
+#
+# you can override this setting by using the -u switch to webapp-config
+
+#vhost_config_uid="root"
+
+# which group should own config files?
+# the default is the group of the user currently running webapp-config
+# (which is normally the root group). You may either use the numerical
+# gid or the group name. The internal default is "0" to accomodate for
+# BSD style systems.
+#
+# you can override this setting by using the -g switch to webapp-config
+
+#vhost_config_gid="root"
+
+# what type of shared directories should be created?
+# the default is 'default-owned', which means that each install of the app
+# gets a copy of the directory
+#
+# permitted values are: server-owned, config-owned, default-owned
+#
+# you can override this setting by using the --default-dirs switch to
+# webapp-config
+
+vhost_config_default_dirs="default-owned"
+
+# what type of shared files should be created?
+# the default is 'virtual', which means that each install of the app
+# does NOT get a unique copy of the files
+#
+# permitted values are: server-owned, config-owned, virtual
+#
+# you can override this setting by using the --virtual-files switch to
+# webapp-config
+
+vhost_config_virtual_files="virtual"
+
+# where should drop-in config files for webservers go?
+#
+# these files will be called <server>-<app>.conf. They are quite rare,
+# but are needed from time to time by the odd application or two
+
+vhost_config_dir="${vhost_root}/conf"
+
+# which user & group should own the files by default?
+#
+# the default is for files to be owned by the superuser root, so that
+# they cannot be tampered with by other users
+#
+# this setting affects the files and directories installed by the ebuild,
+# and it affects some directories created by webapp-config. It does not
+# affect any of the files installed by webapp-config at this time.
+
+vhost_default_uid="root"
+vhost_default_gid="root"
+
+# what type of links do you want to use?
+#
+# starting with webapp-config v1.10, the default behaviour is to attempt
+# to hardlink a file from /usr/share/webapps/* first. If the hardlink
+# fails (normally because /usr and /var | /srv are on different filesystems)
+# webapp-config will fall back to making a physical copy of the file
+# instead
+#
+# NOTE:
+# we have moved to hardlinks because not all web-based packages work
+# when their files are symlinked in
+#
+# please do not raise bugs about packages that do not work when
+# symlinked
+#
+# vhost_link_type="soft"
+
+# what are the names of your document directories?
+#
+# by default, your website lives in /var/www/<hostname>/htdocs. If you
+# run webapp-config with the --secure switch, your website instead lives
+# in /var/www/<hostname>/htdocs-secure.
+#
+# you can change the default names of 'htdocs' and 'htdocs-secure' by
+# editing these two variables
+
+vhost_htdocs_insecure="htdocs"
+vhost_htdocs_secure="htdocs-secure"
+
+# what permissions do you want the files to have?
+#
+# by default, webapp-config installs directories and files with these
+# permissions. You can change them here to comply with your local
+# security policies.
+# You need to specify octal values ("0nnn") or you can use the encoding
+# used by chmod ("[ugoa]{1,3}[+-=]{rwx]{1,3}" elements as a comma
+# seperated list)
+
+vhost_perms_serverowned_dir="0775"
+vhost_perms_serverowned_file="0664"
+
+vhost_perms_configowned_dir="0755"
+vhost_perms_configowned_file="0644"
+
+vhost_perms_defaultowned_dir="0755"
+vhost_perms_virtualowned_file="o-w"
+
+vhost_perms_installdir="0755"
+
+
+# ========================================================================
+# END OF USER-EDITABLE SETTINGS
+# ========================================================================
+
+# ========================================================================
+# EDIT THE VARIABLES BELOW THIS LINE AT YOUR OWN RISK
+#
+# These variables are used by the webapp.eclass component of Portage,
+# and by /usr/sbin/webapp-config.
+#
+# If you break your Gentoo installation by changing these settings, then
+# re-emerge the webapps-config package to restore the default values
+#
+# When webapp-config is upgraded, it may add to and/or change the value of
+# variables listed here. If you do not merge in these changes, you will
+# probably find that webapp-config will break in horrible ways.
+#
+# etc-update is NOT optional ;-)
+#
+# ========================================================================
+
+# which version of webapp-config is this file for?
+#
+# we have a problem when users upgrade webapp-config. It turns out that
+# they don't explicitly upgrade webapp-config ... instead, webapp-config
+# gets because it is a dependency for a web-based package.
+#
+# unfortunately, this means that any changes to this config file do not
+# get installed (through etc-update or equivalent) until after the web-
+# based package has been installed. We end up trying to install the
+# package using a partial install of webapp-config
+#
+# how can we work around this?
+#
+# the only way to work around this is to put some sort of version-control
+# marker in this config file. webapp-config and this file must agree on
+# what this marker is, otherwise webapp-config will refuse to work
+
+WA_CONF_VERSION="7"
+
+# ========================================================================
+# END OF CONFIG FILE
+# ========================================================================
diff --git a/doc/.svn.ignore b/doc/.svn.ignore
new file mode 100644
index 0000000..004ede6
--- /dev/null
+++ b/doc/.svn.ignore
@@ -0,0 +1,4 @@
+*.5
+*.8
+*.5.html
+*.8.html
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644
index 0000000..aaace8a
--- /dev/null
+++ b/doc/Makefile
@@ -0,0 +1,43 @@
+#
+# webapp-config/doc/Makefile
+# Simple Makefile to rebuild the documentation from the
+# docbook XML sources
+#
+# Part of the webapp-config package
+#
+# Copyright (c) 1999-2005 Gentoo Foundation
+# Released under v2 of the GNU GPL
+#
+# Author(s) Stuart Herbert <stuart@gentoo.org>
+# Renat Lumpau <rl03@gentoo.org>
+# Gunnar Wrobel <php@gunnarwrobel.de>
+#
+# ========================================================================
+
+MAN_PAGES = webapp-config.8 webapp-config.5 webapp.eclass.5
+HTML_PAGES = webapp-config.8.html webapp-config.5.html webapp.eclass.5.html
+
+TMPFILE=./webapp-config.man
+
+all: man html
+
+html: $(HTML_PAGES)
+
+man: $(MAN_PAGES)
+
+clean:
+ rm -f $(MAN_PAGES)
+ rm -f $(HTML_PAGES)
+
+%.html: %.xml
+ @echo HTML $@
+ @xmlto html-nochunks $<
+
+%: %.xml
+ @echo MAN $@
+ @xmlto man $<
+#
+# fix up the blank lines that docbook leaves behind
+#
+ @cat $@ | sed -e 's/$$/.fred/g;' | tr -d '\n' | sed -e 's/.fred.fred\./.fred./g;' | sed -e 's/.fred/\n/g;' > $(TMPFILE)
+ @mv $(TMPFILE) $@
diff --git a/doc/webapp-config.5.xml b/doc/webapp-config.5.xml
new file mode 100644
index 0000000..49de978
--- /dev/null
+++ b/doc/webapp-config.5.xml
@@ -0,0 +1,216 @@
+<?xml version='1.0'?>
+<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<article>
+ <articleinfo>
+ <title>webapp-config</title>
+
+ <authorgroup>
+ <author>
+ <firstname>Stuart</firstname>
+ <surname>Herbert</surname>
+ <affiliation>
+ <address><email>stuart@gentoo.org</email></address>
+ <address><email>stuart@gnqs.org</email></address>
+ </affiliation>
+ </author>
+ <author>
+ <firstname>Renat</firstname>
+ <surname>Lumpau</surname>
+ <affiliation>
+ <address><email>rl03@gentoo.org</email></address>
+ </affiliation>
+ </author>
+ <author>
+ <firstname>Gunnar</firstname>
+ <surname>Wrobel</surname>
+ <affiliation>
+ <address><email>php@gunnarwrobel.de</email></address>
+ </affiliation>
+ </author>
+ </authorgroup>
+
+ <copyright>
+ <year>2003-2005</year>
+ <holder>Stuart Herbert</holder>
+ <holder>Renat Lumpau</holder>
+ <holder>Gunnar Wrobel</holder>
+ </copyright>
+
+ </articleinfo>
+
+ <section>
+ <title>Reference</title>
+
+ <refentry>
+ <refentryinfo>
+ <title>webapp-config</title>
+ <date>November 2005</date>
+ <productname>Gentoo Linux</productname>
+ </refentryinfo>
+ <refmeta>
+ <refentrytitle>webapp-config</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </refmeta>
+ <refnamediv>
+ <refname>webapp-config</refname>
+ <refpurpose>Configuration file for the <citerefentry><refentrytitle>webapp-config</refentrytitle><manvolnum>8</manvolnum></citerefentry> tool</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <cmdsynopsis><command>/etc/vhosts/webapp-config</command></cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+ <para><filename>/etc/vhosts/webapp-config</filename> contains the default settings for the <citerefentry><refentrytitle>webapp-config</refentrytitle><manvolnum>8</manvolnum></citerefentry> installer tool.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Settings</title>
+ <variablelist>
+ <varlistentry>
+ <term>vhost_root</term>
+ <listitem>
+ <para>Directory which holds the <filename>htdocs</filename> directory for your website.</para>
+ <para>By default, all websites are <filename>/var/www/<replaceable>fqdn</replaceable></filename>, where <replaceable>fqdn</replaceable> is the full hostname of the website (e.g. www.gentoo.org). If you are putting your websites somewhere else, you must update vhost_root to suit.</para>
+ <para>You can use the value of <userinput>vhost_hostname</userinput> in your definition of <userinput>vhost_root</userinput>.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>vhost_hostname</term>
+ <listitem>
+ <para>Default value when <command>webapp-config</command>'s <option>-h</option> switch hasn't been used.</para>
+ <para>By default, this is set to the full hostname of your computer. If this hasn't been set correctly, then this is set to <userinput>localhost</userinput> instead.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>vhost_subdomain_{1,2,3...}</term>
+ <listitem>
+ <para>The value of <userinput>vhost_hostname</userinput>
+ is splitted at each dot and the resulting domain name
+ parts are stored in sequentially
+ numbered <userinput>vhost_subdomain_N</userinput>
+ variables (e.g. <userinput>www.test.org</userinput>
+ results
+ in <userinput>vhost_subdomain_1</userinput>=org, <userinput>vhost_subdomain_2</userinput>=test,
+ etc.).
+ </para>
+ <para>
+ You may not set these variables yourself since they
+ are generated internally
+ by <command>webapp-config</command>. But you can use
+ them within the configuration file. So if you want to
+ have fine grained control over the location the web
+ applications get installed, you can
+ set <userinput>vhost_root</userinput>="/var/www/${vhost_subdomain_1}/${vhost_subdomain_2}/${vhost_subdomain_3}"
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>vhost_server</term>
+ <listitem>
+ <para>Default value when <command>webapp-config</command>'s <option>-s</option> switch hasn't been used.</para>
+ <para>By default, this is set to <userinput>apache</userinput>, which is the webserver that most people use.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>vhost_config_uid</term>
+ <listitem>
+ <para>Default value when <command>webapp-config</command>'s <option>-u</option> switch hasn't been used.</para>
+ <para>By default, this is set to the username of the user who is running <command>webapp-config</command>. At the time of writing, <command>webapp-config</command> only works for the <userinput>root</userinput> user, because only the <userinput>root</userinput> user is allowed to change the ownership of files and directories on disk.</para>
+ <para>This can be <emphasis>either</emphasis> the name of a user or their numerical user id.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>vhost_config_gid</term>
+ <listitem>
+ <para>Default value when <command>webapp-config</command>'s <option>-g</option> switch hasn't been used.</para>
+ <para>By default, this is set to the primary group of the user who is running <command>webapp-config</command>.</para>
+ <para>This can be <emphasis>either</emphasis> the name of a group or their numerical group id.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>vhost_config_virtual_files</term>
+ <listitem>
+ <para>Default value when <command>webapp-config</command>'s <option>--virtual-files</option> switch hasn't been used.</para>
+ <para>By default, files which can be shared are hardlinked in. The <glossterm>virtual install</glossterm> does not get a local copy of the file, which normally prevents the web server or non-root users from editing the file.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>vhost_default_uid</term>
+ <listitem>
+ <para>Default user to own all files and directories that aren't server-owned or config-owned.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>vhost_default_gid</term>
+ <listitem>
+ <para>Default group to own all files and directories that aren't server-owned or config-owned.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>vhost_htdocs_insecure</term>
+ <term>vhost_htdocs_secure</term>
+ <listitem>
+ <para>Default values for the basename of the DocumentRoot.</para>
+ <para><command>webapp-config</command> installs into <filename>vhost_root/vhost_htdocs_insecure</filename> by default. If you use the <option>--secure</option> switch, <command>webapp-config</command> installs into <filename>vhost_root/vhost_htdocs_secure</filename> instead.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>vhost_perms_serverowned_dir</term>
+ <term>vhost_perms_serverowned_file</term>
+ <listitem>
+ <para>Default filesystem permissions for directories and files that are installed as 'server-owned'.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>vhost_perms_configowned_dir</term>
+ <term>vhost_perms_configowned_file</term>
+ <listitem>
+ <para>Default filesystem permissions for directories and files that are installed as 'config-owned'.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>vhost_perms_defaultowned_dir</term>
+ <listitem>
+ <para>Default filesystem permissions for directories that are installed as 'default-owned'. Note that it is not possible to install files that are 'default-owned'.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>vhost_perms_virtual_dir</term>
+ <term>vhost_perms_virtual_file</term>
+ <listitem>
+ <para>Default filesystem permissions for directories and files that are installed as 'virtual'.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>vhost_perms_installdir</term>
+ <listitem>
+ <para>Default filesystem permissions for the directory that <command>webapp-config</command> installs the package into.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para><citerefentry><refentrytitle>webapp.eclass</refentrytitle><manvolnum>5</manvolnum></citerefentry>, <citerefentry><refentrytitle>webapp-config</refentrytitle><manvolnum>8</manvolnum></citerefentry></para>
+ <para><command>webapp-config</command> is based on the design for an installer for web-based applications first defined in <ulink url="http://www.gentoo.org/proj/en/glep/glep-0011.html">GLEP #11</ulink> for the Gentoo Linux project.</para>
+ </refsect1>
+ </refentry>
+ </section>
+</article>
diff --git a/doc/webapp-config.8.xml b/doc/webapp-config.8.xml
new file mode 100644
index 0000000..e51e172
--- /dev/null
+++ b/doc/webapp-config.8.xml
@@ -0,0 +1,573 @@
+<?xml version='1.0'?>
+<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<article>
+ <articleinfo>
+ <title>webapp-config</title>
+
+ <authorgroup>
+ <author>
+ <firstname>Stuart</firstname>
+ <surname>Herbert</surname>
+ <affiliation>
+ <address><email>stuart@gentoo.org</email></address>
+ <address><email>stuart@gnqs.org</email></address>
+ </affiliation>
+ </author>
+ <author>
+ <firstname>Renat</firstname>
+ <surname>Lumpau</surname>
+ <affiliation>
+ <address><email>rl03@gentoo.org</email></address>
+ </affiliation>
+ </author>
+ <author>
+ <firstname>Gunnar</firstname>
+ <surname>Wrobel</surname>
+ <affiliation>
+ <address><email>php@gunnarwrobel.de</email></address>
+ </affiliation>
+ </author>
+ </authorgroup>
+
+ <copyright>
+ <year>2003-2005</year>
+ <holder>Stuart Herbert</holder>
+ <holder>Renat Lumpau</holder>
+ <holder>Gunnar Wrobel</holder>
+ </copyright>
+ </articleinfo>
+
+ <section>
+ <title>Reference</title>
+
+ <refentry>
+ <refentryinfo>
+ <title>webapp-config</title>
+ <date>November 2005</date>
+ <productname>Gentoo Linux</productname>
+ </refentryinfo>
+ <refmeta>
+ <refentrytitle>webapp-config</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+ <refnamediv>
+ <refname>webapp-config</refname>
+ <refpurpose>manage installs of web-based applications for virtual hosting</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>webapp-config</command>
+ <arg choice="opt">
+ <option>--bug-report</option>
+ </arg>
+ <arg choice="plain">
+ <option>-I</option>
+ </arg>
+ <arg choice="opt">
+ <option>-dghusDE</option>
+ <option>--soft</option>
+ <option>--secure</option>
+ </arg>
+ <arg choice="req">
+ <replaceable>app-name</replaceable>
+ </arg>
+ <arg choice="req">
+ <replaceable>app-version</replaceable>
+ </arg>
+ </cmdsynopsis>
+
+ <cmdsynopsis>
+ <command>webapp-config</command>
+ <arg choice="opt">
+ <option>--bug-report</option>
+ </arg>
+ <arg choice="plain">
+ <option>-U</option>
+ </arg>
+ <arg choice="opt">
+ <option>-dghusDE</option>
+ </arg>
+ <arg choice="req">
+ <replaceable>app-name</replaceable>
+ </arg>
+ <arg choice="req">
+ <replaceable>app-version</replaceable>
+ </arg>
+ </cmdsynopsis>
+
+ <cmdsynopsis>
+ <command>webapp-config</command>
+ <arg choice="opt">
+ <option>--bug-report</option>
+ </arg>
+ <arg choice="plain">
+ <option>-C</option>
+ </arg>
+ <arg choice="req">
+ <option>-d</option>
+ <replaceable>directory</replaceable>
+ </arg>
+ </cmdsynopsis>
+
+ <cmdsynopsis>
+ <command>webapp-config</command>
+ <arg choice="plain">
+ <option>--list-installs</option>
+ </arg>
+ <group choice="opt">
+ <arg>
+ <replaceable>app-name</replaceable>
+ </arg>
+ </group>
+ <group choice="opt">
+ <arg>
+ <replaceable>app-version</replaceable>
+ </arg>
+ </group>
+ </cmdsynopsis>
+
+ <cmdsynopsis>
+ <command>webapp-config</command>
+ <arg choice="plain">
+ <option>--list-unused-installs</option>
+ </arg>
+ <group choice="opt">
+ <arg>
+ <replaceable>app-name</replaceable>
+ </arg>
+ </group>
+ <group choice="opt">
+ <arg>
+ <replaceable>app-version</replaceable>
+ </arg>
+ </group>
+ </cmdsynopsis>
+
+ <cmdsynopsis>
+ <command>webapp-config</command>
+ <arg choice="plain">
+ <option>--show-installed</option>
+ </arg>
+ <group choice="opt">
+ <arg>
+ <option>-d</option>
+ <replaceable>directory</replaceable>
+ </arg>
+ </group>
+ </cmdsynopsis>
+
+ <cmdsynopsis>
+ <command>webapp-config</command>
+ <arg choice="plain">
+ <option>--show-postinst</option>
+ </arg>
+ <arg choice="plain">
+ <replaceable>app-name</replaceable>
+ </arg>
+ <arg choice="plain">
+ <replaceable>app-version</replaceable>
+ </arg>
+ </cmdsynopsis>
+
+ <cmdsynopsis>
+ <command>webapp-config</command>
+ <group choice="plain">
+ <arg>--list-servers</arg>
+ <arg>-v</arg>
+ <arg>--version</arg>
+ <arg>-h</arg>
+ <arg>--help</arg>
+ </group>
+ </cmdsynopsis>
+
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+ <para><command>webapp-config</command> is a powerful tool that allows you to install, upgrade, and remove web-based applications in a virtual-hosting environment.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Web-Based Applications And Virtual Hosts</title>
+ <para><command>webapp-config</command> is aimed at providing the package management functionality that you need if you are running multiple web sites off of the same computer (<glossterm>virtual hosting</glossterm>).</para>
+ <refsect2>
+ <title>Two-Stage Install</title>
+ <para>Package managers such as <command>rpm</command> and <command>emerge</command> are designed to install one copy of a package, and to install it onto one fixed location. This conflicts with the needs of a virtual hosting environment, where you need to be able to install a package in multiple places, so that it can be part of more than just the one website. But package managers are essential for maintaining a computer over time - so how can we have both?</para>
+ <para>The answer is a two-stage install. The traditional package manager installs a <glossterm>master copy</glossterm> into <filename>/usr/share/webapps/</filename>. This <glossterm>master copy</glossterm> isn't fit to run - but it is ready to then be used by <command>webapp-config</command> to install the package multiple times in multiple places.</para>
+ </refsect2>
+
+ <refsect2>
+ <title>Multiple Installations Of The Same Package</title>
+ <para><command>webapp-config</command> allows you to install multiple copies of the same package on the same computer at the same time. You choose which directory to install each separate copy into.</para>
+ <para>We call these multiple installations "<glossterm>virtual copies</glossterm>".</para>
+ <para>You can also have different versions of the same package installed at the same time. This allows you to gradually roll out a new version of a package across your sites; you are not forced to upgrade every single website at once.</para>
+ <para><command>webapp-config</command> minimises the number of duplicated files to the absolute minimum possible, to keep disk space usage low. The majority of files are hard linked to the master copy; only configuration files, and any files that the package needs to write to, are copied into the virtual copy.</para>
+ </refsect2>
+
+ <refsect2>
+ <title>File Ownership And Permissions</title>
+ <para>If you are used to installing web-based applications by hand, you'll appreciate that it can be a pain to get every file owned by the correct user, and with the correct permissions. Some files need to be owned by the user that the webserver runs as. Others need to be owned by specific shell accounts, so that those users can login and edit the configuration files. If your Linux distribution offers you a choice of web servers - each running under a different user - even the installers can struggle to get it right.</para>
+ <para>With <command>webapp-config</command>, you tell the installer which web server you are going to be using, and which shell account needs to be able to edit the configuration files. <command>webapp-config</command> then installs your files with the correct ownership and permissions.</para>
+ </refsect2>
+
+ <refsect2>
+ <title>Protected Configuration Files</title>
+ <para><command>webapp-config</command> automatically ensures that your configuration files are never overwritten during an upgrade - even if you have not edited the files at all. Additionally, <command>webapp-config</command> will never overwrite any file that it did not install, or that has been changed since it was installed by <command>webapp-config</command>. <command>webapp-config</command> uses md5 checksums to determine whether a file has been changed or not. In the case of symbolic links, <command>webapp-config</command> will not replace a symlink that points to a different file.</para>
+ <para>When an upgrade does attempt to overwrite a protected file, <command>webapp-config</command> creates a ._cfg file with the new file inside. You can use <command>etc-update</command> to complete the install, just as you would with the regular <command>emerge</command>.</para>
+ </refsect2>
+
+ <refsect2>
+ <title>Hard Linking vs Soft Linking</title>
+ <para>A <glossterm>virtual copy</glossterm> is built mostly by creating hard links to files under <filename>/usr/share/webapps</filename>. If a hard link cannot be created, the file is copied from <filename>/usr/share/webapps</filename> instead.</para>
+ <para>Hard links can only be created to files on the same filesystem. If you keep <filename>/usr/share/webapps</filename> and <filename>/var/www</filename> on different filesystems, <command>webapp-config</command> cannot use hard links, and will be forced to copy the files instead.</para>
+ <para>There are two ways to get around the hard link problem.</para>
+ <para>The easiest way is to make <filename>/usr/share/webapps</filename> a symlink to a directory under <filename>/var/www</filename>. For most people, this will ensure that everything is on the same filesystem.</para>
+ <para>However, if you keep the websites you host on separate filesystems (like I do), then <command>webapp-config</command> is never going to be able to hard-link files for you.</para>
+ <para>You can choose to use the <option>--soft</option> command-line switch instead. This switch tells <command>webapp-config</command> to create symbolic links instead of hard links. Symbolic links work across filesystems.</para>
+ <para>The problem with using symbolic links is that some packages do not work when the virtual copy is made from symbolic links. Many users - and system administrators alas - have also complained that they find directories full of symbolic links confusing. For these reasons, symbolic links are not used by default in <command>webapp-config</command> any more.</para>
+ </refsect2>
+
+ <refsect2>
+ <title>Virtual File Voodoo</title>
+ <para>By default, the <glossterm>master copy</glossterm> contains the metadata that decides which files get linked into a <glossterm>virtual copy</glossterm> and which files do not. Files are either owned by the web server (server-owned), are configuration files (config-owned), or are linked in (virtual). Directories can be server-ownedor config-owned, but most of the time they need to be just plain directories (default-owned) created inside the installation directory (set with the <option>-d</option> switch). <command>webapp-config</command> provides a number of switches which allows you to override the <glossterm>master copy</glossterm>'s metadata - if you ever find that you need to.</para>
+ <para>The <option>--default-dirs</option> and <option>--virtual-files</option> switches allow you to decide what <command>webapp-config</command> will do if (respectively) a directory or a file is marked as being default or virtual. You can tell <command>webapp-config</command> to make the directory or file any of the other choices - server-owned or config-owned - instead.</para>
+ </refsect2>
+ <refsect2>
+ <title>${ROOT}</title>
+ <para>This version of webapp-config is intended to fully support ${ROOT}. If you are unsure what that means, consult <citerefentry><refentrytitle>emerge</refentrytitle><manvolnum>1</manvolnum></citerefentry></para>
+ </refsect2>
+ </refsect1>
+
+ <refsect1>
+ <title>Actions</title>
+ <variablelist>
+ <varlistentry>
+ <term><option>-I</option></term>
+ <term><option>--install</option></term>
+ <listitem>
+ <para>Activate <emphasis>install mode</emphasis>.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-U</option></term>
+ <term><option>--upgrade</option></term>
+ <listitem>
+ <para>Activate <emphasis>upgrade mode</emphasis>.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-C</option></term>
+ <term><option>--clean</option></term>
+ <listitem>
+ <para>Activate <emphasis>remove mode</emphasis>.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--list-installs</option> <replaceable>app-name</replaceable> <replaceable>app-version</replaceable></term>
+ <term><option>--li</option> <replaceable>app-name</replaceable> <replaceable>app-version</replaceable></term>
+ <listitem>
+ <para>Outputs a list of all the virtual copies of the <replaceable>app-name</replaceable>-<replaceable>app-version</replaceable> package.</para>
+ <para>If you omit <replaceable>app-name</replaceable> or <replaceable>app-version</replaceable> webapp-config will display all available packages/versions.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--list-unused-installs</option> <replaceable>app-name</replaceable> <replaceable>app-version</replaceable></term>
+ <term><option>--lui</option> <replaceable>app-name</replaceable> <replaceable>app-version</replaceable></term>
+ <listitem>
+ <para>Outputs a list of all the master copies of the <replaceable>app-name</replaceable>-<replaceable>app-version</replaceable> package that have not been installed using <command>webapp-config</command> <option>-I</option>.</para>
+ <para>Both <replaceable>app-name</replaceable> or <replaceable>app-version</replaceable> can be <emphasis>*</emphasis> to search for multiple packages or versions.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--show-installed</option></term>
+ <term><option>--si</option></term>
+ <listitem>
+ <para>Outputs the app-name and app-version of the application installed in <replaceable>directory</replaceable>.</para>
+ <para>Use the <option>-d</option> switch to tell <command>webapp-config</command> which <replaceable>directory</replaceable> to look in. <replaceable>directory</replaceable> is a directory under the htdocs dir.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--show-postinst</option> <replaceable>app-name</replaceable> <replaceable>app-version</replaceable></term>
+ <term><option>--spi</option> <replaceable>app-name</replaceable> <replaceable>app-version</replaceable></term>
+ <listitem>
+ <para>Displays the post-installation instructions of the <replaceable>app-name</replaceable>-<replaceable>app-version</replaceable> package. Very handy if you didn't see them displayed when the package was installed.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--show-postupgrade</option> <replaceable>app-name</replaceable> <replaceable>app-version</replaceable></term>
+ <term><option>--spu</option> <replaceable>app-name</replaceable> <replaceable>app-version</replaceable></term>
+ <listitem>
+ <para>Displays the post-upgrade instructions of the <replaceable>app-name</replaceable>-<replaceable>app-version</replaceable> package. Very handy if you didn't see them displayed when the package was upgraded.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--list-servers</option></term>
+ <term><option>--ls</option></term>
+ <listitem>
+ <para>Outputs a list of the web servers that <command>webapp-config</command> currently supports.</para>
+ <para>Use the <option>-s</option> <replaceable>server</replaceable> switch to change which web-server an install or upgrade should use.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-?</option></term>
+ <term><option>--help</option></term>
+ <listitem>
+ <para>Provide a list of supported switches. Also lists all the default values for each switch.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-v</option></term>
+ <term><option>--version</option></term>
+ <listitem>
+ <para>Displays the current version number of <command>webapp-config</command></para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Options</title>
+ <para>List of the remaining switches that <command>webapp-config</command> accepts. To see the default values that <command>webapp-config</command> will use when a switch is omitted, use <userinput>webapp-config --help</userinput>.</para>
+ <variablelist>
+ <varlistentry>
+ <term><replaceable>app-name</replaceable> <replaceable>app-version</replaceable></term>
+ <listitem>
+ <para>Together, these two parameters tell <command>webapp-config</command> which package to install (<option>-I</option> mode), upgrade to (<option>-U</option> mode), or to search for (<option>--list-installs</option> mode).</para>
+ <para>They must be the last two parameters passed to <command>webapp-config</command>.</para>
+ <para>These parameters are not optional.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--bug-report</option></term>
+ <term><option>--pretend</option></term>
+ <term><option>-p</option></term>
+ <listitem>
+ <para>Provide output to include inside a bug-report.</para>
+ <para>Use this switch if you're having problems with the install (<option>-I</option> mode), upgrade (<option>-U</option> mode), or clean (<option>-C</option> mode) operations. Add this switch to the command-line that's not working, and make sure you paste the output into your bug report.</para>
+ <para>If you need to use this switch, make sure it's the first switch you use to call <command>webapp-config</command>.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-s</option> <replaceable>server</replaceable></term>
+ <term><option>--server</option> <replaceable>server</replaceable></term>
+ <listitem>
+ <para>Set which web-server to install (<option>-I</option> mode) or upgrade (<option>-U</option> mode) for.</para>
+ <para><command>webapp-config</command> needs to know which web server you are going to use to access your virtual copy. If you don't provide the correct switch, your virtual copy may not work correctly.</para>
+ <para>Use <userinput>webapp-config --list-servers</userinput> to see a list of valid <replaceable>server</replaceable> settings.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-u</option> <replaceable>user</replaceable></term>
+ <term><option>--user</option> <replaceable>user</replaceable></term>
+ <listitem>
+ <para>Set which user will own any installed configuration files.</para>
+ <para>When <command>webapp-apache</command> creates a <glossterm>virtual copy</glossterm> (<option>-I</option> mode), the <glossterm>virtual copy</glossterm> creates <emphasis>local</emphasis> copies of any configuration files that the package needs to use. By using the <option>-u</option> switch, you can specify which <replaceable>user</replaceable> owns these configuration files.</para>
+ <para>If you give shell accounts out to the users who host websites on your computer, the <option>-u</option> allows you to give ownership of the configuration file (and therefore write permission) to the shell account associated with the website.</para>
+ <para><replaceable>user</replaceable> can be a username or a numerical user id.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-g</option> <replaceable>group</replaceable></term>
+ <term><option>--group</option> <replaceable>group</replaceable></term>
+ <listitem>
+ <para>Set which group will own any installed configuration files.</para>
+ <para>When <command>webapp-apache</command> creates a <glossterm>virtual copy</glossterm> (<option>-I</option> mode), the <glossterm>virtual copy</glossterm> creates <emphasis>local</emphasis> copies of any configuration files that the package needs to use. By using the <option>-g</option> switch, you can specify which <replaceable>group</replaceable> owns these configuration files.</para>
+ <para>If you give shell accounts out to groups of users who host websites on your computer, the <option>-g</option> allows you to give ownership of the configuration file (and therefore write permission) to the group associated with the website.</para>
+ <para><replaceable>group</replaceable> can be a group name or a numerical group id.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-d</option> <replaceable>directory</replaceable></term>
+ <term><option>--dir</option> <replaceable>directory</replaceable></term>
+ <listitem>
+ <para>Specify where to create the virtual copy.</para>
+ <para>The <command>webapp-config</command> tool allows you to create a virtual copy anywhere you want. You are no longer limited to installing a web-based app in /home/httpd/htdocs/&lt;package-name&gt;/! Simply use the <option>-d</option> switch to tell <command>webapp-config</command> where you want to create your virtual copy.</para>
+ <para><replaceable>directory</replaceable> is a directory under your htdocs dir. If you do not set the <replaceable>hostname</replaceable> correctly (by using the <option>-h</option> switch), <command>webapp-config</command> will look under the wrong htdocs directory!</para>
+ <para>This option is required by the <option>-C</option> switch.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-h</option> <replaceable>host</replaceable></term>
+ <term><option>--host</option> <replaceable>host</replaceable></term>
+ <listitem>
+ <para>Specify the <glossterm>fully-qualified domain name</glossterm> of the virtual host.</para>
+ <para>Some web-based applications - whether through genuine need or bad design - need to know the hostname of the website that they are part of.</para>
+ <para>Some web-based applications need to install files (such as cgi scripts) that do not belong under the htdocs directory. To make sure that these files go in the right place, you need to use the <option>-h</option> switch to tell <command>webapp-config</command> the hostname of the website.</para>
+ <para><replaceable>host</replaceable> must be a <glossterm>fully-qualified domain name</glossterm>.</para>
+ <para>If you do not use the <option>-h</option> switch, your virtual copy may not work correctly.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-D</option> <replaceable>KEY=VALUE</replaceable></term>
+ <term><option>--define</option> <replaceable>KEY=VALUE</replaceable></term>
+ <listitem>
+ <para>Define a configuration variable for <command>webapp-config</command>.</para>
+ <para>
+ Allows to name a <replaceable>KEY=VALUE</replaceable>
+ pair that will be imported into the configuration
+ variables of webapp-config. This allows you to provide
+ customized variables which can be used in the
+ configuration file. This can also be used to
+ temporarily overwrite variables from the configuration
+ file.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-E</option> <replaceable>variable name</replaceable></term>
+ <term><option>--envvar</option> <replaceable>variable name</replaceable></term>
+ <listitem>
+ <para>Define an environment variable that will be picked up by <command>webapp-config</command>.</para>
+ <para>
+ Allows to name single environment variable that will
+ be imported by webapp-config. This allows you to
+ provide customized variables which can be used in the
+ configuration file. This can also be used to
+ temporarily overwrite variables from the configuration
+ file.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--envall</option></term>
+ <listitem>
+ <para>Imports all environment variables into the configuration process of <command>webapp-config</command>.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-V</option></term>
+ <term><option>--verbose</option></term>
+ <listitem>
+ <para>Use this option to increase the amount of output from the <option>--list-installs</option> switch.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--soft</option></term>
+ <listitem>
+ <para>Use this option to create the virtual copy using symbolic links instead of hard links.</para>
+ <para>You may find this option useful if <filename>/usr/share/webapps</filename> is on a different filesystem to your htdocs directories. However, it has been discovered that some packages do not work with this option, which is why it is no longer the default behaviour. You are always better off making <filename>/usr/share/webapps</filename> a symlink to a directory on the same filesystem as your htdocs directories.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--secure</option></term>
+ <listitem>
+ <para>Use this option to install into the <filename>htdocs-secure</filename> directory rather than into the <filename>htdocs</filename> directory.</para>
+ <para>This option is useful if you keep separate directories for your http: and https: sites.</para>
+ <para>You can change 'htdocs-secure' by editing the config file <filename>/etc/vhosts/webapp-config</filename>.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--default-dirs</option> <replaceable>type</replaceable></term>
+ <term><option>--vd</option> <replaceable>type</replaceable></term>
+ <term><option>--virtual-files</option> <replaceable>type</replaceable></term>
+ <term><option>--vf</option> <replaceable>type</replaceable></term>
+ <listitem>
+ <para><replaceable>type</replaceable> must be one of:</para>
+ <variablelist>
+ <varlistentry>
+ <term>server-owned</term>
+ <listitem>
+ <para>Directories or files are owned by the user that the web server runs as. Use the <option>-s</option> switch to specify which web server to use.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>config-owned</term>
+ <listitem>
+ <para>Directories or files are owned by the user and group specified with the <option>-u</option> and <option>-g</option> switches.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>virtual</term>
+ <listitem>
+ <para>Directories or files are shared; no local copy is created.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Examples</title>
+ <para>All of these examples are aimed at Gentoo Linux. If you are using <command>webapp-config</command> on a different Linux distribution, they may not work out of the box for you.</para>
+ <refsect2>
+ <title>Installing applications</title>
+ <para>To install a copy of phpmyadmin-2.5.6, so that it is available from http://www.example.com/databases/admin/, you would do this:</para>
+ <para><userinput>webapp-config -I -h www.example.com -d /databases/admin/ phpmyadmin 2.5.6</userinput></para>
+ <para>To make sure that the shell account 'dbadmin' could edit the configuration files of phpmyadmin, you'd add the <option>-u</option> switch like this:</para>
+ <para><userinput>webapp-config -I -h www.example.com -d /databases/admin -u dbadmin phpmyadmin 2.5.6</userinput></para>
+ </refsect2>
+ <refsect2>
+ <title>Upgrading applications</title>
+ <para>To upgrade the copy of phpmyadmin-2.5.6 to version 2.5.7, you would do this:</para>
+ <para><userinput>webapp-config -U -d /databases/admin/ phpmyadmin 2.5.7</userinput></para>
+ <para>To upgrade <emphasis>all</emphasis> the virtual copies of phpmyadmin-2.5.6, you would do this:</para>
+ <para>
+ <userinput>for x in `webapp-config --li phpmyadmin 2.5.6`;do . ${x}/.webapp &amp;&amp; webapp-config -U -h ${WEB_HOSTNAME} -d ${WEB_INSTALLDIR} phpmyadmin 2.5.7; done</userinput>
+ </para>
+ </refsect2>
+ <refsect2>
+ <title>Removing applications</title>
+ <para>To remove the copy of phpmyadmin-2.5.7, you would do this:</para>
+ <para><userinput>webapp-config -C -h www.example.com -d /databases/admin/</userinput></para>
+ <para>To remove <emphasis>all</emphasis> the virtual copies of phpmyadmin-2.5.7, you would do this:</para>
+ <para>
+ <userinput>for x in `webapp-config --li phpmyadmin 2.5.7`;do . ${x}/.webapp &amp;&amp; webapp-config -C -h ${WEB_HOSTNAME} -d ${WEB_INSTALLDIR}; done</userinput>
+ </para>
+ </refsect2>
+ </refsect1>
+
+ <refsect1>
+ <title>Files</title>
+ <variablelist>
+ <varlistentry>
+ <term><filename>/etc/vhosts/webapp-config</filename></term>
+ <listitem>
+ <para>Configuration file, holding the defaults for <command>webapp-config</command></para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><filename>/var/db/webapps</filename></term>
+ <listitem>
+ <para>This directory tree holds information about the location of each virtual copy on the computer.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para><citerefentry><refentrytitle>webapp.eclass</refentrytitle><manvolnum>5</manvolnum></citerefentry>, <citerefentry><refentrytitle>webapp-config</refentrytitle><manvolnum>5</manvolnum></citerefentry>, <citerefentry><refentrytitle>emerge</refentrytitle><manvolnum>1</manvolnum></citerefentry></para>
+ <para><command>webapp-config</command> is based on the design for an installer for web-based application installers first defined in <ulink url="http://www.gentoo.org/proj/en/glep/glep-0011.html">GLEP #11</ulink> for the Gentoo Linux project.</para>
+ </refsect1>
+ </refentry>
+ </section>
+</article>
diff --git a/doc/webapp-eclass.xml b/doc/webapp-eclass.xml
new file mode 100644
index 0000000..569b353
--- /dev/null
+++ b/doc/webapp-eclass.xml
@@ -0,0 +1,567 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE guide SYSTEM "/dtd/guide.dtd">
+<!-- $Header: /var/cvsroot/gentoo/xml/htdocs/proj/en/webapps/webapp-eclass.xml,v 1.3 2006/01/19 16:43:24 rl03 Exp $ -->
+
+<guide link="/proj/en/webapps/webapp-eclass.xml" lang="en">
+<title>webapp.eclass Documentation</title>
+
+<author title="Author">
+ <mail link="rl03@gentoo.org">Renat Lumpau</mail>
+</author>
+
+<abstract>
+The goal of this guide is to teach the reader how to create and maintain ebuilds
+for web applications. It presents an overview of the webapp.eclass and then
+discusses three ebuilds of increasing complexity and functionality.
+</abstract>
+
+<!-- The content of this document is licensed under the CC-BY-SA license -->
+<!-- See http://creativecommons.org/licenses/by-sa/2.5 -->
+<license/>
+
+<version>1.0</version>
+<date>2006-01-17</date>
+
+<chapter>
+<title>Introduction</title>
+<section>
+<title>Background</title>
+<body>
+
+<p>
+<c>webapp.eclass</c> and <c>webapp-config</c> provide a standardized way to
+maintain web applications on Gentoo. Server administrators can use the
+<c>webapp-config</c> utility to install, upgrade, and remove software.
+<c>webapp-config</c> relies on a package manager such as Portage to prepare
+the application for installation into multiple virtual hosts. On Gentoo, this
+is done by an ebuild that uses <c>webapp.eclass</c>.
+</p>
+
+<p>
+The goal of this guide is to teach the reader how to create and maintain
+ebuilds for web applications. It presents an overview of the
+<c>webapp.eclass</c> and then discusses three ebuilds of increasing complexity
+and functionality. Using the <c>webapp-config</c> utility is beyond the scope
+of this guide and is documented in <c>man 8 webapp-config</c>.
+</p>
+
+</body>
+</section>
+<section>
+<title>Prerequisites</title>
+<body>
+
+<p>
+This guide assumes a basic familiarity with Portage and the ebuild format. Both
+are well-documented; the reader is encouraged to consult the <uri
+link="/proj/en/devrel/handbook/handbook.xml">official ebuild HOWTO</uri> and
+the unofficial <uri
+link="http://dev.gentoo.org/~plasmaroo/devmanual/">devmanual</uri>.
+</p>
+
+<p>
+<c>webapp-config</c> is under active development. Be sure to install the latest
+version; as of the time of this writing, it is 1.50.7. You can follow the
+development process on our <uri
+link="http://www.vhost-tools.org/">website</uri>.
+</p>
+
+</body>
+</section>
+<section>
+<title>A standardized approach to installing web applications</title>
+<body>
+
+<p>
+Gentoo has developed a standardized way of handling web applications. It is
+outlined in <uri link="/proj/en/glep/glep-0011.html">GLEP 11</uri> and
+discussed in detail in <c>man 5 webapp.eclass</c>. The reader is urged to
+familiarize himself with these documents before proceeding. The manpage also
+outlines the filesystem locations into which the eclass and
+<c>webapp-config</c> install files; advanced users and developers should take
+note.
+</p>
+
+<p>
+The <c>webapp.eclass</c> is located in the usual place in the Portage tree. By
+default, it will be found in <path>/usr/portage/eclass/webapp.eclass</path>. By
+definition, the source code is the ultimate documentation and should be
+consulted whenever something does not perform as expected or further
+clarification is required.
+</p>
+</body>
+</section>
+</chapter>
+
+<chapter>
+<title>Writing Ebuilds</title>
+<section>
+<title>Beginner: www-apps/gallery-2.0.2</title>
+<body>
+
+<p>
+Many web applications require no compilation and are installed by copying their
+files to a directory to be served by a httpd server such as Apache. The
+<c>webapp.eclass</c> simplifies this task by preparing the necessary set of
+directories.
+</p>
+
+<p>
+Let's take a look at a simple ebuild for <uri
+link="http://sources.gentoo.org/viewcvs.py/gentoo-x86/www-apps/gallery">www-apps/gallery-2.0.2</uri>.
+To use any of the functions in the eclass, the ebuild must first inherit it:
+</p>
+
+<pre caption="Inheriting the eclass">
+inherit webapp.eclass
+</pre>
+
+<p>
+The ebuild then sets several standard variables, such as <c>DESCRIPTION</c>,
+<c>IUSE</c>, <c>RDEPEND</c>, and <c>S</c>. The first important note is that
+ebuilds that use the <c>webapp.eclass</c> do not typically set the <c>SLOT</c>
+variable (the rationale for this is described in the manpage). <uri
+link="#doc_chap2_sect3">Section 2.3</uri> will explain how <c>SLOT</c> can be
+set when it is truly needed, but for now we will let the eclass handle it.
+</p>
+
+<note>
+Unless explicitly overridden, the eclass sets <c>SLOT="${PVR}"</c>. One
+important downside of this behavior is that security revision bumps are no
+longer possible. This is unfortunate and will be changed soon.
+</note>
+
+<p>
+<path>www-apps/gallery-2.0.2</path> does not require patching or compiling, so
+the ebuild does not call <c>src_unpack</c> or <c>src_compile</c>; installation
+is handled in <c>src_install</c>. The first thing <c>src_install</c> does is
+call a special helper function that sets up the required directory structure:
+</p>
+
+<pre caption="Setting up src_install">
+src_install() {
+ webapp_src_preinst
+</pre>
+
+<p>
+This function is required of all ebuilds that use the <c>webapp.eclass</c> and
+override the default <c>src_install</c>.
+</p>
+
+<p>
+Having set up the environment, the ebuild installs the web application:
+</p>
+
+<pre caption="Installing">
+cp -R * ${D}/${MY_HTDOCSDIR}
+</pre>
+
+<p>
+<c>${MY_HTDOCSDIR}</c> is one of the variables exported by
+<c>webapp_src_preinst</c>. Files placed there will be copied into the right
+directory under the webserver's document root by <c>webapp-config</c>. For a
+full list of variables exported by the eclass, please see the manpage.
+</p>
+
+<p>
+Next, the ebuild marks a file in <c>${FILESDIR}</c> as a file containing
+instructions to be displayed by <c>webapp-config</c> after the application has
+been installed:
+</p>
+
+<pre caption="Post-install instructions">
+webapp_postinst_txt en ${FILESDIR}/postinstall-en2.txt
+</pre>
+
+<note>
+A careful reader will observe that it is customary for other ebuilds to display
+instructions to the user in <c>pkg_postinst</c>. Ebuilds that inherit
+<c>webapp.eclass</c> may still do so, but the ebuild author should understand
+the important difference in usage. More often than not, post-install
+instructions include information specific to a virtual host, such as locations
+of a particular configuration file or a URL to access the web application
+remotely. This information is not available to Portage and cannot be included
+in <c>pkg_postinst</c>. Instead, it should be included in a post-install file
+and installed using <c>webapp_postinstall_txt</c>.
+</note>
+
+<p>
+Let's examine a typical post-install file:
+</p>
+
+<pre caption="Post-install file">
+ 0. Create a new MySQL database:
+ mysqladmin create geeklog
+
+ 1. Edit ${VHOST_ROOT}/${PN}-${PVR}/config.php and set database settings.
+
+ 2. Login on
+ http://${VHOST_HOSTNAME}/${VHOST_APPDIR}/admin/install/install.php
+ and follow the directions.
+
+ 3. Don't forget to delete the admin/install directory when you're done!
+</pre>
+
+<p>
+Post-install instruction files are plain text files. They can contain
+bash-style variables (e.g., <c>${PN}</c>) that will be expanded at display
+time. The <c>webapp-config</c> utility responsible for displaying the file
+exports a number of variables that allow for the customization of instructions.
+Consult the manpage for a list of available variables.
+</p>
+
+<p>
+Finally, the ebuild calls another mandatory helper function:
+</p>
+
+<pre caption="webapp_src_install">
+webapp_src_install
+</pre>
+
+<p>
+<c>webapp_src_install</c> is responsible for setting the right permissions on
+installed files.
+</p>
+
+<p>
+There are several best practices for writing simple ebuilds:
+</p>
+
+<ul>
+ <li>
+ Don't copy unneeded files. Some packages include <path>.svn</path> or
+ <path>CVS</path> directories or <path>Makefiles</path>; those should be
+ removed prior to installation. Note that the <path>LICENSE</path> file is
+ sometimes needed by the application and should not be removed.
+ </li>
+ <li>
+ Ensure that consecutive calls to all ebuild functions succeed. For
+ instance, don't remove files outside of <c>src_unpack</c>. If you must,
+ remove files from <c>${D}</c> rather than <c>${S}</c>.
+ </li>
+ <li>
+ For portability purposes, don't use GNU-only extensions such as <c>cp
+ -a</c>. They will break on non-GNU userlands such as Gentoo/FreeBSD.
+ </li>
+</ul>
+
+</body>
+</section>
+<section>
+<title>Intermediate: www-apps/tikiwiki-1.9.2</title>
+<body>
+
+<p>
+Many web applications have a more complicated installation procedure. Let's
+look at how <uri
+link="http://sources.gentoo.org/viewcvs.py/gentoo-x86/www-apps/tikiwiki">www-apps/tikiwiki-1.9.2</uri>
+handles one such package. After inheriting the eclass, the ebuild specifies a
+number of required and optional dependencies.
+</p>
+
+<pre caption="Specifying Dependencies">
+RDEPEND="virtual/php
+ mysql? ( >=dev-db/mysql-4 )
+ postgres? ( dev-db/postgresql )
+ graphviz? ( media-gfx/graphviz )
+"
+</pre>
+
+<p>
+Many web-applications written in PHP require that the PHP binary have certain
+capabilities built-in; common requirements include support for sessions and a
+specific database engine. This is typically accomplished by using the
+<c>depend.php</c> eclass; unfortunately, the author is not aware of any existing
+documentation for that eclass. The reader is referred to the eclass itself for
+further information and encouraged to consult relevant ebuilds in Portage.
+</p>
+
+<warn>
+If the package requires specific Perl modules, all dependencies must have
+ebuilds available. Relying on CPAN is not acceptable.
+</warn>
+
+<p>
+A common mistake with specifying dependencies for web applications is to
+unconditionally <c>RDEPEND</c> on a database engine such as MySQL or
+PostgreSQL. Many, if not all, web applications are able to connect to a remote
+database server. Thus, a local database should not be a requirement; the right
+syntax for dealing with this is:
+</p>
+
+<pre caption="Database Dependencies">
+mysql? ( >=dev-db/mysql-4 )
+</pre>
+
+<note>
+It is currently not possible to depend on a generic webserver. Instead, you
+must explicitly specify the webserver (e.g., <path>net-www/apache</path>). This
+is inconvenient and has been reported in <uri
+link="http://bugs.gentoo.org/show_bug.cgi?id=11007">bug #11007</uri>; we hope
+that this issue will be resolved soon.
+</note>
+
+<p>
+<path>www-apps/tikiwiki</path> does not use <c>depend.php</c> and instead uses
+<c>pkg_setup</c> to print a warning:
+</p>
+
+<pre caption="pkg_setup()">
+pkg_setup () {
+ webapp_pkg_setup
+ einfo "Make sure your PHP is compiled with mysql or postgres support"
+ einfo "If you need PDF generation, make sure your PHP is emerged with xml2"
+}
+</pre>
+
+<p>
+Note the use of a mandatory helper function <c>webapp_pkg_setup</c>.
+</p>
+
+<p>
+Many web applications require write access to certain files. The eclass
+provides a helper function that marks a file as server-owned; at install time,
+<c>webapp-config</c> will ensure that it has the right owner and permissions:
+</p>
+
+<pre caption="webapp_serverowned">
+webapp_serverowned ${MY_HTDOCSDIR}/tiki-install.php
+</pre>
+
+<p>
+<c>webapp_serverowned</c> takes an optional <c>-R</c> flag to recurse into
+subdirectories. This flag has been added recently and not many ebuilds take
+advantage of it. Please consult the manpage for more information.
+</p>
+
+<p>
+Practically all web applications use configuration files to store settings.
+Such files should not be placed into <path>/etc</path> (figuring out the
+rationale is left as an exercise for the reader). To avoid losing important
+configuration information, the eclass provides another helper function that
+will instruct <c>webapp-config</c> not to overwrite an existing file. The
+administrator can use familiar tools such as <c>etc-update</c> or
+<c>dispatch-conf</c> to manage such files.
+</p>
+
+<pre caption="webapp_configfile">
+webapp_configfile ${MY_HTDOCSDIR}/config.php
+</pre>
+
+<p>
+Note that it is currently not possible to mark a file as both server-owned and
+config-protected. This has been fixed in an upcoming version of
+<c>webapp-config</c>. For now, there is a workaround described in <uri
+link="#doc_chap2_sect3">Section 2.3</uri>.
+</p>
+
+<p>
+To ease upgrades of web applications, the eclass provides a helper function
+that displays instructions when an existing installation is being upgraded:
+</p>
+
+<pre caption="Post-upgrade file">
+webapp_postupgrade_txt en ${FILESDIR}/postupgrade-en.txt
+</pre>
+
+<p>
+Even though no ebuilds in Portage currently take advantage this functionality,
+the reader is encouraged to use it in his ebuilds.
+</p>
+
+<p>
+The ebuild calls the familiar helper function to complete the installation.
+Note an important consequence of using <c>webapp_src_install</c> to set the
+correct file permissions: any manual adjustments to file permissions and
+ownership via <c>fowners</c> and <c>fperms</c> must occur <e>after</e>
+<c>webapp_src_install</c> is called.
+</p>
+
+<pre caption="Adjusting permissions">
+webapp_src_install
+fperms 0644 /etc/zm.conf
+</pre>
+
+<p>
+The tikiwiki ebuild displays some brief notes using <c>pkg_postinst</c>. Note
+the use of another helper function:
+</p>
+
+<pre caption="pkg_postinst">
+pkg_postinst() {
+ einfo "To setup a MySQL database, run:"
+ einfo "\"emerge --config =${PF}\""
+ einfo "If you are using PostgreSQL, consult your documentation"
+ webapp_pkg_postinst
+}
+</pre>
+
+<p>
+Strictly speaking, <c>webapp_pkg_postinst</c> is not mandatory. It is
+responsible for automatically calling <c>webapp-config</c> when the
+<c>vhosts</c> USE flag is unset. In rare instances it may be desirable to
+override this behavior; please consult <path>www-apps/otrs</path> for an
+example.
+</p>
+
+</body>
+</section>
+<section>
+<title>Advanced: www-apps/moinmoin-1.5.0</title>
+<body>
+
+<p>
+This section presents an overview of more advanced installation tasks provided
+by the <c>webapp.eclass</c>. An example of this functionality is <uri
+link="http://sources.gentoo.org/viewcvs.py/gentoo-x86/www-apps/moinmoin">www-apps/moinmoin-1.5.0</uri>.
+</p>
+
+<p>
+<c>moinmoin</c> is a wiki engine written in Python that uses <c>distutils</c>
+to install itself. Thus, it requires <c>SLOT="0"</c> to avoid collisions.
+Ebuilds that inherit <c>webapp.eclass</c> must set
+<c>WEBAPP_MANUAL_SLOT="yes"</c> to override the default <c>SLOT</c>ting
+behavior:
+</p>
+
+<pre caption="Setting SLOT">
+SLOT="0"
+WEBAPP_MANUAL_SLOT="yes"
+</pre>
+
+<note>
+The <c>yes</c> in <c>WEBAPP_MANUAL_SLOT="yes"</c> is case-sensitive.
+</note>
+
+<p>
+<c>moinmoin</c> installs files that should not be served by the webserver. The
+ebuild places such files in <c>${MY_HOSTROOTDIR}/${PF}</c>.
+</p>
+
+<pre caption="Installing into dodir ${MY_HOSTROOTDIR}/${PF}">
+dodir ${MY_HOSTROOTDIR}/${PF}
+cp -r data underlay config/wikiconfig.py ${D}/${MY_HOSTROOTDIR}/${PF}
+</pre>
+
+<p>
+A best practice for installing any application via Portage is to ensure it
+operates out of the proverbial box with sensible defaults. The eclass
+implements a helper function to aid with configuring the application after it
+has been installed by <c>webapp-config</c>.
+</p>
+
+<pre caption="webapp_hook_script">
+webapp_hook_script ${FILESDIR}/reconfig-2
+</pre>
+
+<p>
+The reconfig hook is a script, typically written in bash, that is invoked by
+<c>webapp-config</c> after installation and before removal.
+</p>
+
+<pre caption="Reconfig hook">
+#!/bin/bash
+
+die() {
+ echo "#####"
+ echo $1
+ echo "#####"
+ exit 1
+}
+
+if [ $1 = "install" ]; then
+ sed -e "s|/path/to/wikiconfig|${VHOST_ROOT}/${PN}-${PVR}|g" \
+ -i ${MY_INSTALLDIR}/moin.cgi || die "sed failed"
+ sed -e "s|\./data/|${VHOST_ROOT}/${PN}-${PVR}/data|
+ s|\./underlay/|${VHOST_ROOT}/${PN}-${PVR}/underlay|" \
+ -i ${VHOST_ROOT}/${PN}-${PVR}/wikiconfig.py || die "sed failed"
+
+elif [ $1 = "clean" ]; then
+ echo $1
+fi
+</pre>
+
+<p>
+The reconfig hook can use the same variables as the postinstall file. It is
+typically used to edit configuration files to set file locations that may
+differ from the default values. As of <c>webapp-config-1.50</c>, reconfig hooks
+are run in a sandbox to minimize risk; please consult the manpage for more
+details.
+</p>
+
+<p>
+There are several best practices for using reconfig hooks:
+</p>
+
+<ul>
+ <li>Die with an error message if the script failed</li>
+ <li>
+ Be careful about removing files when called with <c>clean</c>. Don't remove
+ configuration files or any other files that may have been locally
+ modified.
+ </li>
+ <li>
+ To mark a file as both config-protected and server-owned, use
+ <c>webapp_configfile</c> from the ebuild and use the reconfig hook to mark
+ it as server-owned:
+ </li>
+</ul>
+
+<pre caption="Using the reconfig hook to mark files as server-owned">
+chown ${VHOST_SERVER_UID}:${VHOST_SERVER_GID} ${MY_INSTALLDIR}/Settings.php
+</pre>
+
+<p>
+The eclass provides several other functions that are rarely used. The most
+notable is <c>webapp_sqlscript</c>, which marks a file as an SQL create script.
+In its current state, this function is only moderately useful.
+</p>
+
+</body>
+</section>
+</chapter>
+
+<chapter>
+<title>Additional Best Practices</title>
+<section>
+<title>Apache</title>
+<body>
+
+<ul>
+ <li>
+ Be careful when installing apache configuration files. If you place a
+ misconfigured Apache configuration file into
+ <path>/etc/apache{2}/vhosts.d</path>, the server will fail on next restart.
+ Is is better to either install the file elsewhere and let the administrator
+ customize it, or comment out the contents to avoid failure.
+ </li>
+</ul>
+
+</body>
+</section>
+</chapter>
+
+<chapter>
+<title>Next Steps</title>
+<section>
+<title>Obtaining More Information</title>
+<body>
+
+<p>
+Additional examples can be found in the <path>www-apps</path> category of the
+Portage tree. Developers and users should also consult the <uri
+link="http://svn.gnqs.org/projects/gentoo-webapps-overlay">web-apps
+overlay</uri> for even more examples and software packages.
+</p>
+
+<p>
+If you have a question that is not answered in this document, please feel free
+to contact the <uri
+link="http://svn.gnqs.org/projects/gentoo-webapps-overlay/wiki/Contributors">web-apps
+herd</uri> or ask on <uri link="irc://freenode/gentoo-web">IRC</uri>.
+</p>
+
+</body>
+</section>
+</chapter>
+</guide>
diff --git a/doc/webapp.eclass.5.xml b/doc/webapp.eclass.5.xml
new file mode 100644
index 0000000..e9bd4ff
--- /dev/null
+++ b/doc/webapp.eclass.5.xml
@@ -0,0 +1,444 @@
+<?xml version='1.0'?>
+<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<article>
+ <articleinfo>
+ <title>webapp.eclass</title>
+
+ <authorgroup>
+ <author>
+ <firstname>Stuart</firstname>
+ <surname>Herbert</surname>
+ <affiliation>
+ <address><email>stuart@gentoo.org</email></address>
+ <address><email>stuart@gnqs.org</email></address>
+ </affiliation>
+ </author>
+ <author>
+ <firstname>Renat</firstname>
+ <surname>Lumpau</surname>
+ <affiliation>
+ <address><email>rl03@gentoo.org</email></address>
+ </affiliation>
+ </author>
+ <author>
+ <firstname>Gunnar</firstname>
+ <surname>Wrobel</surname>
+ <affiliation>
+ <address><email>wrobel@gentoo.org</email></address>
+ </affiliation>
+ </author>
+ </authorgroup>
+
+ <copyright>
+ <year>2003-2006</year>
+ <holder>Stuart Herbert</holder>
+ <holder>Renat Lumpau</holder>
+ <holder>Gunnar Wrobel</holder>
+ </copyright>
+ </articleinfo>
+
+ <section>
+ <title>Reference</title>
+
+ <refentry>
+ <refentryinfo>
+ <title>webapp.eclass</title>
+ <date>January 2006</date>
+ <productname>Gentoo Linux</productname>
+ </refentryinfo>
+ <refmeta>
+ <refentrytitle>webapp.eclass</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </refmeta>
+ <refnamediv>
+ <refname>webapp.eclass</refname>
+ <refpurpose>standardised approach to writing ebuilds for web-based applications</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>inherit webapp</command>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+ <para>If you are writing an ebuild for a web-based application, your ebuild should use the functions, variables, and techniques documented in this man page.</para>
+ <para>The main reason to use the webapp eclass is that it provides a solution to a number of problems that plagued older approaches:</para>
+ <refsect2>
+ <title>Support For Virtual Hosts</title>
+ <para>Many web servers today have to be able to host more than one website at a time. This is normally done through <glossterm>name-based virtual hosting</glossterm>. See the Apache documentation for more details about how this works.</para>
+ <para>Traditional package installers only install a single copy of a package. Many webservers need to make the same package available as part of different virtual hosts. The administrator normally ends up installing and upgrading each of these packages by hand. This makes for more work for the administrator.</para>
+ <para><userinput>webapp.eclass</userinput> overcomes this problem by installing packages, and associated metadata, in a format and location understood by <citerefentry><refentrytitle>webapp-config</refentrytitle><manvolnum>8</manvolnum></citerefentry>. The administrator then uses <command>webapp-config</command> to install and upgrade however many copies of the package as he needs.</para>
+ </refsect2>
+ <refsect2>
+ <title>Installing Into The Correct Directory</title>
+ <para>Virtual hosts are normally stored on disk in their own directories. The location of these directories can vary from computer to computer. It is not unusual for each administrator to have his own preferred location for putting virtual hosts.</para>
+ <para>Traditional packagers always install a single copy of the package, normally into <filename>/home/httpd/htdocs/&lt;package-name&gt;/</filename>. If the administrator changes the DocumentRoot of their preferred web server to point to a different directory, traditional packagers are unable to detect this and adjust accordingly. They also are unable to detect any attempt to install a package into a virtual host.</para>
+ <para><userinput>webapp.eclass</userinput> overcomes this problem because it makes no attempt to install packages into the webserver's DocumentRoot. Instead, it installs the files of the package into the <filename>/usr/share/webapps</filename> hierarchy - which is never directly used by any webserver. The administrator then uses <command>webapp-config</command> to complete the installation to the directory of his choice.</para>
+ </refsect2>
+ <refsect2>
+ <title>Installing With The Correct File Ownerships</title>
+ <para>Web-based applications are made up of files and directories that need to be owned by more than one user. Any files or directories that the web server needs to write to - these need to be owned by whatever user the web server runs as. Configuration files need to be owned by the non-root user who owns the website. The rest of the files and directories need to be owned by root, to ensure that they cannot be tampered with by other users.</para>
+ <para>Traditional packagers have no way to understand which user the web server runs as, or which user needs to own the configuration files. Some packages assume that the user is running the Apache web server, and hard-code the information into their installation scripts. Unfortunately, any packages that do this will not work if the user chooses to use an alternative web server.</para>
+ <para><userinput>webapp.eclass</userinput> overcomes this problem because it provides a number of functions which the ebuild can use to indicate who needs to own what. When the administrator completes the install using <command>webapp-config</command>, he can tell <command>webapp-config</command> which web server the package will be running under, and which user needs to own the configuration files.</para>
+ </refsect2>
+ <refsect2>
+ <title>Apache Modules vs CGI Scripts</title>
+ <para>Most web-based applications are written in scripting languages such as PHP, Python, or Perl. Most scripting languages are available either as Apache modules and as stand-alone CGI scripting engines. Sometimes, it makes more sense to install scripting languages as CGI scripting engines. Although CGI engines run much slower than Apache modules, the advantage is that it allows a single computer to run multiple versions of the same language - something that sometimes is necessary.</para>
+ <para>It's also worth pointing out that the administrator has a choice of web servers - and not all scripting languages run as compiled-in modules in all web servers.</para>
+ <para>It is difficult-to-impossible for a traditional package manager to provide a single package that can cope with all of the possible permutations.</para>
+ <para><userinput>webapp.eclass</userinput> provides a function for ebuilds to indicate which script files need to be run by which scripting engine. <command>webapp-config</command> can, if necessary, modify these script files to use the correct CGI engine when installing into a virtual host.</para>
+ </refsect2>
+ <refsect2>
+ <title>Overwriting Configuration Files</title>
+ <para>When packages are upgraded, new configuration files are installed. Different package managers handle this in different ways. Portage can prevent files being overwritten on a per-directory basis. Web-based applications normally have configuration files installed in the same directories as the scripts themselves. Packages for Portage, therefore, normally end up overwriting the configuration file because the alternative is having to wade through pages of <command>etc-update</command> output.</para>
+ <para><userinput>webapp.eclass</userinput> avoids this problem by allowing ebuilds to indicate which files are configuration files. <command>webapp-config</command> uses this information to ensure that configuration files are not overwritten. The administrator can then use <command>etc-update</command> as normal to complete the upgrade.</para>
+ </refsect2>
+ <refsect2>
+ <title>Handling Web Server Configuration Files</title>
+ <para>There are some web-based applications that will not work without installing configuration files for the webserver. Deciding which webserver to install configuration files for, and where to install these configuration files, is problematic at best.</para>
+ <para><userinput>webapp.eclass</userinput> addresses this in two ways. Firstly, ebuilds are encouraged to supply configuration files for <emphasis>all</emphasis> supported webservers. <command>webapp-config</command> will then install the correct configuration files for the selected webserver when the virtual copy of the application is installed.</para>
+ </refsect2>
+ <refsect2>
+ <title>Handling SQL Databases</title>
+ <para>Many web-based applications store their data in sql-based databases. Installing these databases is enough of a pain; upgrading them is something that's often left to the administrator to struggle through.</para>
+ <para><userinput>webapp.eclass</userinput> does not provide a full solution today to this problem. Ebuilds can indicate which files are SQL scripts, and which database engine the scripts are for. A future version of <userinput>webapp.eclass</userinput> will (hopefully) provide a tool to automatically generate an upgrade script to help the administrator.</para>
+ </refsect2>
+ </refsect1>
+
+ <refsect1>
+ <title>Installation Directories</title>
+ <para><glossterm>webapp.eclass</glossterm> creates an intermediate install of a package, so that the computer's administrator can then perform multiple installations using <citerefentry><refentrytitle>webapp-config</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
+ <para>The intermediate install (known as the <glossterm>master copy</glossterm>) is never used by, or accessed via, the web server.</para>
+ <para>The intermediate install goes into the <filename>/usr/share/webapps</filename> directory structure:</para>
+ <screen>
+/usr/share/webapps
+|- &lt;package name&gt;
+ |- &lt;package version&gt;
+ |- hostroot ${MY_HOSTROOTDIR}
+ |- htdocs ${MY_HTDOCSDIR}
+ |- cgi-bin ${MY_CGIBINDIR}
+ |- conf ${MY_SERVERCONFIGDIR}
+ |- errors ${MY_ERRORSDIR}
+ |- icons ${MY_ICONSDIR}
+ </screen>
+ <variablelist>
+ <varlistentry>
+ <term>MY_HOSTROOTDIR</term>
+ <listitem>
+ <para>Any files or directories that would normally go into <filename>/var/www/localhost</filename> should be installed into this directory.</para>
+ <para>This directory is for files that you do <emphasis>not</emphasis> want served up under the DocumentRoot. An example of suitable files would be password databases.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>MY_HTDOCSDIR</term>
+ <listitem>
+ <para>Any files or directories that would normally go into <filename>/var/www/localhost/htdocs/&lt;package-name&gt;/</filename> should be installed into this directory.</para>
+ <para>This directory is for the main files of the package. Don't put all the files in a subdirectory called &lt;package-name&gt;.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>MY_CGIBINDIR</term>
+ <listitem>
+ <para>Any files or directories that would normally go into <filename>/var/www/localhost/cgi-bin/</filename> should be installed into this directory.</para>
+ <para>At runtime (when the package is running), this directory will be read-only. You should keep this in mind when deciding which files, scripts, and executables belong in here.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>MY_SERVERCONFIGDIR</term>
+ <listitem>
+ <para>This directory contains the per-vhost config files that the web server will use.</para>
+ <para>You may have to manually configure your webserver to actually use any configuration files installed in this directory.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>MY_ERRORSDIR</term>
+ <listitem>
+ <para>Any files or directories that would normally go into <filename>/var/www/localhost/errors/</filename> should be installed into this directory.</para>
+ <para>When the webserver encounters an error, such as 403: Forbidden or 404: File not found, the default behaviour of the webserver is to look for a page in the MY_ERRORSDIR directory.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>MY_ICONSDIR</term>
+ <listitem>
+ <para>Any files or directories that would normally to into <filename>/var/www/localhost/icons/</filename> should be installed into this directory.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Functions</title>
+ <refsect2>
+ <title>Functions for pkg_setup()</title>
+ <variablelist>
+ <varlistentry>
+ <term><command>webapp_pkg_setup</command></term>
+ <listitem>
+ <para>If your ebuild has a <userinput>pkg_setup</userinput>, make sure that the first thing it does is to call the <userinput>webapp_pkg_setup</userinput> function.</para>
+ <para>This function performs a lot of sanity checks.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+ <refsect2>
+ <title>Functions for src_install()</title>
+ <para>The first line of your <userinput>src_install()</userinput> function must be</para>
+ <screen>
+ webapp_src_preinst
+ </screen>
+ <para><userinput>webapp_src_preinst()</userinput> prepares ${D}, so that you can install your files into directories as normal. Then, use these functions <emphasis>after</emphasis> you have installed your files, to add metadata that <command>webapp-config</command> will use later.</para>
+ <para>Unless explicitly stated in the description of the function, you should assume that these functions do <emphasis>not</emphasis> copy any files into ${D} for you.</para>
+ <variablelist>
+ <varlistentry>
+ <term><command>webapp_configfile</command> <replaceable>file</replaceable> <replaceable>[file ...]</replaceable></term>
+ <listitem>
+ <para>Use this function to indicate that a particular file is an editable configuration file. <command>webapp-config</command> will install configuration files so that they can be edited by non-root users.</para>
+ <para><replaceable>file</replaceable> is a config-file. The file should be in ${D}. <replaceable>file</replaceable> is normally under ${MY_HTDOCSDIR}.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>webapp_hook_script</command> <replaceable>file</replaceable></term>
+ <listitem>
+ <para>Use this function to install a script that <command>webapp-config</command> will run immediately after the creation of a <glossterm>virtual copy</glossterm> and immediately before the removal of a <glossterm>virtual copy</glossterm>.</para>
+ <para><replaceable>file</replaceable> is an executable script. The script must accept one parameter; either <userinput>file install</userinput>, or <userinput>file clean</userinput>. When called as <userinput>file install</userinput>, the script is running after the creation of a <glossterm>virtual copy</glossterm>. And when called as <userinput>file clean</userinput>, the script is running immediately before the removal of a <glossterm>virtual copy</glossterm>.</para>
+ <para>Think of hook scripts as a way to perform custom actions that <command>webapp-config</command> itself doesn't give you a way to do. Because any files created by hook scripts aren't added to the contents list, it is essential that hook scripts also clean up after themselves - because <command>webapp-config</command> cannot do it for you.</para>
+ <para>Hook scripts are now run inside a sandbox. Write access is limited to <userinput>VHOST_HTDOCSDIR</userinput>, <userinput>MY_INSTALLDIR</userinput>, <userinput>VHOST_ROOT</userinput> and <userinput>VHOST_CGIBINDIR</userinput>.</para>
+ <para>Hook scripts can rely on a number of environment variables being set. They are listed in the HOOK SCRIPT VARS section below.</para>
+ <para>Please note that hook scripts <emphasis>must</emphasis> work correctly with <userinput>${ROOT}</userinput> set!</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>webapp_postinst_txt</command> <replaceable>language</replaceable> <replaceable>file</replaceable></term>
+ <listitem>
+ <para>Use this function to install a plain text file that contains any post-installation instructions that the administrator needs to read about. <command>webapp-config</command> will show these instructions to the administrator after he has run <command>webapp-config</command> <option>-I</option>.</para>
+ <para>The instructions can contain shell variables. They will be expanded by <command>webapp-config</command> before being shown to the administrator.</para>
+ <para>The instructions can make use of the environment variables that are listed in the HOOK SCRIPT VARS section below.</para>
+ <para><replaceable>language</replaceable> is for future compatibility. For now, always use <userinput>en</userinput>.</para>
+ <para><replaceable>file</replaceable> is a text file in ${S}. This function will install this file for you into ${D}.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>webapp_postupgrade_txt</command> <replaceable>language</replaceable> <replaceable>file</replaceable></term>
+ <listitem>
+ <para>Use this function to install a plain text file that contains any post-uprade instructions that the administrator needs to read about. <command>webapp-config</command> will show these instructions to the administrator after he has run <command>webapp-config</command> <option>-U</option>.</para>
+ <para>The instructions can contain shell variables. They will be expanded by <command>webapp-config</command> before being shown to the administrator.</para>
+ <para>The instructions can make use of the environment variables that are listed in the HOOK SCRIPT VARS section below.</para>
+ <para><replaceable>language</replaceable> is for future compatibility. For now, always use <userinput>en</userinput>.</para>
+ <para><replaceable>file</replaceable> is a text file in ${S}. This function will install this file for you into ${D}.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>webapp_serverowned</command> <replaceable>[-R]</replaceable> <replaceable>file</replaceable> <replaceable>[file ...]</replaceable></term>
+ <listitem>
+ <para>Use this function to mark a file that needs to be owned by whichever user the web server runs as. <command>webapp-config</command> will ensure that the file is owned by the correct user when the time comes.</para>
+ <para><replaceable>file</replaceable> is a file under ${D}.</para>
+ <para>Use the optional <replaceable>-R</replaceable> flag to recurse into subdirectories.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>webapp_server_configfile</command> <replaceable>server</replaceable> <replaceable>file</replaceable> <replaceable>newfile</replaceable></term>
+ <listitem>
+ <para>Use this function to install a webserver configuration file. The configuration file is installed into ${MY_SERVERCONFIGDIR}, and <command>webapp-config</command> will install this file during install (-I mode).</para>
+ <para><replaceable>server</replaceable> is one of: apache, lighttpd, aolserver.</para>
+ <para><replaceable>file</replaceable> is a webserver configuration file in ${S}. This should be a configuration file tested with <replaceable>server</replaceable>.</para>
+ <para><replaceable>newfile</replaceable> is the new name for <replaceable>file</replaceable>. This parameter is optional; if not supplied, <replaceable>newfile</replaceable> is set to <userinput>`basename <replaceable>file</replaceable>`</userinput>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>webapp_sqlscript</command> <replaceable>sql-engine</replaceable> <replaceable>file</replaceable></term>
+ <listitem>
+ <para>Use this function to install an SQL script. The script is installed into ${MY_SQLSCRIPTSDIR}, but is <emphasis>not</emphasis> installed into any database server at this time.</para>
+ <para><replaceable>sql-engine</replaceable> is one of: mysql, postgresql.</para>
+ <para><replaceable>file</replaceable> is an SQL script in ${S}. This should be a script to create the tables from scratch. <replaceable>file</replaceable> will be installed into ${D} for you by this function.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><command>webapp_src_install</command></term>
+ <listitem>
+ <para>Call this function at the end of your <userinput>src_install</userinput> function.</para>
+ <para>To see what this function does, read the source code.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+ <refsect2>
+ <title>Functions for pkg_postinst()</title>
+ <variablelist>
+ <varlistentry>
+ <term><command>webapp_pkg_postinst</command></term>
+ <listitem>
+ <para>If your ebuild has a <userinput>pkg_postinst</userinput> function, make sure the last thing it does is to call <userinput>webapp_pkg_postinst</userinput>.</para>
+ <para>This function automatically calls <command>webapp-config</command> if the <emphasis>vhosts</emphasis> flag is NOT present.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+ </refsect1>
+
+ <refsect1>
+ <title>Hook Script Vars</title>
+ <para>These environment variables will always be present whenever <command>webapp-config</command> runs a hook script, or whenever post-installation instructions are shown.</para>
+ <variablelist>
+ <varlistentry>
+ <term>MY_HOSTROOTDIR</term>
+ <term>MY_HTDOCSDIR</term>
+ <term>MY_CGIBINDIR</term>
+ <term>MY_ERRORSDIR</term>
+ <term>MY_ICONSDIR</term>
+ <term>MY_SERVERCONFIGDIR</term>
+ <term>MY_SQLSCRIPTSDIR</term>
+ <listitem>
+ <para>See the Install Directories section above.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>ROOT</term>
+ <listitem>
+ <para>The ${ROOT} variable as set by Portage. Please note that you do not need to explicitly add the ${ROOT} prefix - webapp-config will do that automatically.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>MY_INSTALLDIR</term>
+ <listitem>
+ <para>The full path to the directory that the virtual copy has been installed into.</para>
+ <para>If you are not using virtual hosts, this defaults to <filename>/var/www/localhost/htdocs/${PN}/</filename>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>VHOST_SERVER</term>
+ <listitem>
+ <para>The name of the webserver that we are installing for. Set with the <option>-s</option> option to <command>webapp-config</command>.</para>
+ <para>At the moment, <command>webapp-config</command> only supports the <command>apache-basic</command> web server, so this isn't a lot of use.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>VHOST_ROOT</term>
+ <listitem>
+ <para>The full path to the Document Root's parent directory.</para>
+ <para>If you are not using virtual hosts, this defaults to <filename>/var/www/localhost/</filename>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>VHOST_HOSTNAME</term>
+ <listitem>
+ <para>The hostname that this application will be accessed via.</para>
+ <para>If you are not using virtual hosts, this defaults to <filename>localhost</filename>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>VHOST_HTDOCSDIR</term>
+ <listitem>
+ <para>The full path to the Document Root of the website that the virtual copy is being installed into.</para>
+ <para>If you are not using virtual hosts, this defaults to <filename>/var/www/localhost/htdocs/</filename>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>VHOST_APPDIR</term>
+ <listitem>
+ <para>The directory under VHOST_HTDOCSDIR where the virtual copy is being installed into. If you want the full path, use $MY_INSTALLDIR.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>VHOST_CGIBINDIR</term>
+ <listitem>
+ <para>The directory under VHOST_HTDOCSDIR which cgi-bin files installed into.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>VHOST_CONFDIR</term>
+ <listitem>
+ <para>The directory under VHOST_HTDOCSDIR which server configuration files are installed into.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>VHOST_ERRORSDIR</term>
+ <listitem>
+ <para>The directory under VHOST_HTDOCSDIR which custom error files are installed into.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>VHOST_ICONSDIR</term>
+ <listitem>
+ <para>The directory under VHOST_HTDOCSDIR which custom server icons are installed into.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>VHOST_CONFIG_UID</term>
+ <listitem>
+ <para>The name of the user who owns all configuration files.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>VHOST_CONFIG_GID</term>
+ <listitem>
+ <para>The name of the user who owns all configuration files.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>VHOST_SERVER_UID</term>
+ <listitem>
+ <para>The name of the user who will own all server-owned files and directories.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>VHOST_SERVER_GID</term>
+ <listitem>
+ <para>The name of the group who owns all server-owned files and directories.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>VHOST_DEFAULT_UID</term>
+ <listitem>
+ <para>The name of the user who owns all the remaining files and directories.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>VHOST_DEFAULT_GID</term>
+ <listitem>
+ <para>The name of the group who owns all the remaining files and directories.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>VHOST_PERMS_SERVEROWNED_DIR</term>
+ <term>VHOST_PERMS_SERVEROWNED_FILE</term>
+ <term>VHOST_PERMS_CONFIGOWNED_DIR</term>
+ <term>VHOST_PERMS_CONFIGOWNED_FILE</term>
+ <term>VHOST_PERMS_DEFAULTOWNED_DIR</term>
+ <term>VHOST_PERMS_VIRTUALOWNED_DIR</term>
+ <term>VHOST_PERMS_VIRTUALOWNED_FILE</term>
+ <term>VHOST_PERMS_INSTALLDIR</term>
+ <listitem>
+ <para>See <citerefentry><refentrytitle>webapp-config</refentrytitle><manvolnum>5</manvolnum></citerefentry> for details.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Examples</title>
+ <para>See <filename>/usr/share/doc/webapp-config*/</filename> for example ebuilds.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Files</title>
+ <variablelist>
+ <varlistentry>
+ <term><filename>/etc/vhosts/webapp-config</filename></term>
+ <listitem>
+ <para>Configuration file, holding the defaults for <command>webapp-config</command> and the <userinput>webapp.eclass</userinput> eclass.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para><citerefentry><refentrytitle>webapp-config</refentrytitle><manvolnum>5</manvolnum></citerefentry>, <citerefentry><refentrytitle>webapp-config</refentrytitle><manvolnum>8</manvolnum></citerefentry>, <citerefentry><refentrytitle>emerge</refentrytitle><manvolnum>1</manvolnum></citerefentry></para>
+ <para>The <userinput>webapp.eclass</userinput> is part of the <command>webapp-config</command> package. <command>webapp-config</command> is based on the design for an installer for web-based applications first discussed in <ulink url="http://www.gentoo.org/proj/en/glep/glep-0011.html">GLEP #11</ulink> for the Gentoo Linux project.</para>
+ </refsect1>
+ </refentry>
+ </section>
+</article>
diff --git a/ebuild/webapp-config-1.50.16.ebuild b/ebuild/webapp-config-1.50.16.ebuild
new file mode 100644
index 0000000..4185ee2
--- /dev/null
+++ b/ebuild/webapp-config-1.50.16.ebuild
@@ -0,0 +1,58 @@
+# Copyright 1999-2006 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+# $Header: /var/cvsroot/gentoo-x86/app-admin/webapp-config/webapp-config-1.50.14.ebuild,v 1.2 2006/04/22 21:41:52 flameeyes Exp $
+
+inherit eutils distutils
+
+DESCRIPTION="Gentoo's installer for web-based applications"
+HOMEPAGE="http://www.gentoo.org/"
+SRC_URI="http://dev.gentoo.org/~wrobel/webapp-config/${PF}.tar.gz"
+
+LICENSE="GPL-2"
+SLOT="0"
+KEYWORDS="~alpha ~amd64 ~arm ~hppa ~ia64 ~m68k ~mips ~ppc ~ppc64 ~s390 ~sh ~sparc ~x86 ~x86-fbsd"
+IUSE=""
+S=${WORKDIR}/${PF}
+
+DEPEND=""
+
+src_install() {
+
+ # According to this discussion:
+ # http://mail.python.org/pipermail/distutils-sig/2004-February/003713.html
+ # distutils does not provide for specifying two different script install
+ # locations. Since we only install one script here the following should
+ # be ok
+ distutils_src_install --install-scripts="/usr/sbin"
+
+ dodir /etc/vhosts
+ cp config/webapp-config ${D}/etc/vhosts/
+ keepdir /usr/share/webapps
+ keepdir /var/db/webapps
+ dodoc examples/phpmyadmin-2.5.4-r1.ebuild AUTHORS.txt CHANGES.txt examples/postinstall-en.txt
+ doman doc/webapp-config.5 doc/webapp-config.8 doc/webapp.eclass.5
+ dohtml doc/webapp-config.5.html doc/webapp-config.8.html doc/webapp.eclass.5.html
+}
+
+src_test() {
+ cd ${S}
+ distutils_python_version
+ if [[ $PYVER_MAJOR > 1 ]] && [[ $PYVER_MINOR > 3 ]] ; then
+ einfo "Running webapp-config doctests..."
+ if ! PYTHONPATH="." ${python} WebappConfig/tests/dtest.py; then
+ eerror "DocTests failed - please submit a bug report"
+ die "DocTesting failed!"
+ fi
+ else
+ einfo "Python version below 2.4! Disabling tests."
+ fi
+}
+
+pkg_postinst() {
+ echo
+ einfo "Now that you have upgraded webapp-config, you **must** update your"
+ einfo "config files in /etc/vhosts/webapp-config before you emerge any"
+ einfo "packages that use webapp-config."
+ echo
+ epause 5
+}
diff --git a/eclass/webapp.eclass b/eclass/webapp.eclass
new file mode 100644
index 0000000..e1d38d9
--- /dev/null
+++ b/eclass/webapp.eclass
@@ -0,0 +1,565 @@
+# Copyright 1999-2006 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+# $Header: /var/cvsroot/gentoo-x86/eclass/webapp.eclass,v 1.43 2006/07/10 00:52:34 rl03 Exp $
+#
+# eclass/webapp.eclass
+# Eclass for installing applications to run under a web server
+#
+# Part of the implementation of GLEP #11
+#
+# Author(s) Stuart Herbert <stuart@gentoo.org>
+# Renat Lumpau <rl03@gentoo.org>
+# Gunnar Wrobel <wrobel@gentoo.org>
+#
+# ------------------------------------------------------------------------
+#
+# The master copy of this eclass is held in our subversion repository.
+# http://svn.gnqs.org/projects/vhost-tools/browser/
+#
+# If you make changes to this file and don't tell us, chances are that
+# your changes will be overwritten the next time we release a new version
+# of webapp-config.
+#
+# ------------------------------------------------------------------------
+
+SLOT="${PVR}"
+IUSE="vhosts"
+DEPEND="app-admin/webapp-config"
+RDEPEND="${DEPEND}"
+
+EXPORT_FUNCTIONS pkg_postinst pkg_setup src_install pkg_prerm
+
+INSTALL_DIR="/${PN}"
+IS_UPGRADE=0
+IS_REPLACE=0
+
+INSTALL_CHECK_FILE="installed_by_webapp_eclass"
+
+ETC_CONFIG="${ROOT}/etc/vhosts/webapp-config"
+WEBAPP_CONFIG="${ROOT}/usr/sbin/webapp-config"
+
+# ------------------------------------------------------------------------
+# INTERNAL FUNCTION - USED BY THIS ECLASS ONLY
+#
+# Load the config file /etc/vhosts/webapp-config
+#
+# Supports both the old bash version, and the new python version
+#
+# ------------------------------------------------------------------------
+
+function webapp_read_config ()
+{
+ if has_version '>=app-admin/webapp-config-1.50'; then
+ ENVVAR=$(${WEBAPP_CONFIG} --query ${PN} ${PVR}) || die "Could not read settings from webapp-config!"
+ eval ${ENVVAR}
+ else
+ . ${ETC_CONFIG} || die "Unable to read ${ETC_CONFIG}"
+ fi
+}
+
+# ------------------------------------------------------------------------
+# INTERNAL FUNCTION - USED BY THIS ECLASS ONLY
+#
+# Check whether a specified file exists within the image/ directory
+# or not.
+#
+# @param $1 - file to look for
+# @param $2 - prefix directory to use
+# @return 0 on success, never returns on an error
+# ------------------------------------------------------------------------
+
+function webapp_checkfileexists ()
+{
+ local my_prefix
+
+ [ -n "${2}" ] && my_prefix="${2}/" || my_prefix=
+
+ if [ ! -e "${my_prefix}${1}" ]; then
+ msg="ebuild fault: file '${1}' not found"
+ eerror "$msg"
+ eerror "Please report this as a bug at http://bugs.gentoo.org/"
+ die "$msg"
+ fi
+}
+
+# ------------------------------------------------------------------------
+# INTERNAL FUNCTION - USED BY THIS ECLASS ONLY
+# ------------------------------------------------------------------------
+
+function webapp_check_installedat
+{
+ local my_output
+
+ ${WEBAPP_CONFIG} --show-installed -h localhost -d "${INSTALL_DIR}" 2> /dev/null
+}
+
+# ------------------------------------------------------------------------
+# INTERNAL FUNCTION - USED BY THIS ECLASS ONLY
+#
+# ------------------------------------------------------------------------
+
+function webapp_strip_appdir ()
+{
+ local my_stripped="${1}"
+ echo "${1}" | sed -e "s|${MY_APPDIR}/||g;"
+}
+
+function webapp_strip_d ()
+{
+ echo "${1}" | sed -e "s|${D}||g;"
+}
+
+function webapp_strip_cwd ()
+{
+ local my_stripped="${1}"
+ echo "${1}" | sed -e 's|/./|/|g;'
+}
+
+# ------------------------------------------------------------------------
+# EXPORTED FUNCTION - FOR USE IN EBUILDS
+#
+# Identify a config file for a web-based application.
+#
+# @param $1 - config file
+# ------------------------------------------------------------------------
+
+function webapp_configfile ()
+{
+ local m=""
+ for m in "$@" ; do
+ webapp_checkfileexists "${m}" "${D}"
+
+ local MY_FILE="$(webapp_strip_appdir "${m}")"
+ MY_FILE="$(webapp_strip_cwd "${MY_FILE}")"
+
+ elog "(config) ${MY_FILE}"
+ echo "${MY_FILE}" >> ${D}/${WA_CONFIGLIST}
+ done
+}
+
+# ------------------------------------------------------------------------
+# EXPORTED FUNCTION - FOR USE IN EBUILDS
+#
+# Install a script that will run after a virtual copy is created, and
+# before a virtual copy has been removed
+#
+# @param $1 - the script to run
+# ------------------------------------------------------------------------
+
+function webapp_hook_script ()
+{
+ webapp_checkfileexists "${1}"
+
+ elog "(hook) ${1}"
+ cp "${1}" "${D}/${MY_HOOKSCRIPTSDIR}/$(basename "${1}")" || die "Unable to install ${1} into ${D}/${MY_HOOKSCRIPTSDIR}/"
+ chmod 555 "${D}/${MY_HOOKSCRIPTSDIR}/$(basename "${1}")"
+}
+
+# ------------------------------------------------------------------------
+# EXPORTED FUNCTION - FOR USE IN EBUILDS
+#
+# Install a text file containing post-installation instructions.
+#
+# @param $1 - language code (use 'en' for now)
+# @param $2 - the file to install
+# ------------------------------------------------------------------------
+
+function webapp_postinst_txt ()
+{
+ webapp_checkfileexists "${2}"
+
+ elog "(info) ${2} (lang: ${1})"
+ cp "${2}" "${D}/${MY_APPDIR}/postinst-${1}.txt"
+}
+
+# ------------------------------------------------------------------------
+# EXPORTED FUNCTION - FOR USE IN EBUILDS
+#
+# Install a text file containing post-upgrade instructions.
+#
+# @param $1 - language code (use 'en' for now)
+# @param $2 - the file to install
+# ------------------------------------------------------------------------
+
+function webapp_postupgrade_txt ()
+{
+ webapp_checkfileexists "${2}"
+
+ elog "(info) ${2} (lang: ${1})"
+ cp "${2}" "${D}/${MY_APPDIR}/postupgrade-${1}.txt"
+}
+
+# ------------------------------------------------------------------------
+# EXPORTED FUNCTION - FOR USE IN EBUILDS
+#
+# Identify a file which must be owned by the webserver's user:group
+# settings.
+#
+# The ownership of the file is NOT set until the application is installed
+# using the webapp-config tool.
+#
+# @param $1 - file to be owned by the webserver user:group combo
+#
+# ------------------------------------------------------------------------
+
+function webapp_serverowned ()
+{
+ local a=""
+ local m=""
+ if [ "${1}" = "-R" ]; then
+ shift
+ for m in "$@" ; do
+ for a in $(find ${D}/${m}); do
+ a=${a/${D}\/\///}
+ webapp_checkfileexists "${a}" "$D"
+ local MY_FILE="$(webapp_strip_appdir "${a}")"
+ MY_FILE="$(webapp_strip_cwd "${MY_FILE}")"
+
+ elog "(server owned) ${MY_FILE}"
+ echo "${MY_FILE}" >> "${D}/${WA_SOLIST}"
+ done
+ done
+ else
+ for m in "$@" ; do
+ webapp_checkfileexists "${m}" "$D"
+ local MY_FILE="$(webapp_strip_appdir "${m}")"
+ MY_FILE="$(webapp_strip_cwd "${MY_FILE}")"
+
+ elog "(server owned) ${MY_FILE}"
+ echo "${MY_FILE}" >> "${D}/${WA_SOLIST}"
+ done
+ fi
+}
+
+# ------------------------------------------------------------------------
+# EXPORTED FUNCTION - FOR USE IN EBUILDS
+#
+# @param $1 - the webserver to install the config file for
+# (one of apache1, apache2, cherokee)
+# @param $2 - the config file to install
+# @param $3 - new name for the config file (default is `basename $2`)
+# this is an optional parameter
+#
+# NOTE:
+# this function will automagically prepend $1 to the front of your
+# config file's name
+# ------------------------------------------------------------------------
+
+function webapp_server_configfile ()
+{
+ webapp_checkfileexists "${2}"
+
+ # sort out what the name will be of the config file
+
+ local my_file
+
+ if [ -z "${3}" ]; then
+ my_file="${1}-$(basename "${2}")"
+ else
+ my_file="${1}-${3}"
+ fi
+
+ # warning:
+ #
+ # do NOT change the naming convention used here without changing all
+ # the other scripts that also rely upon these names
+
+ elog "(${1}) config file '${my_file}'"
+ cp "${2}" "${D}/${MY_SERVERCONFIGDIR}/${my_file}"
+}
+
+# ------------------------------------------------------------------------
+# EXPORTED FUNCTION - FOR USE IN EBUILDS
+#
+# @param $1 - the db engine that the script is for
+# (one of: mysql|postgres)
+# @param $2 - the sql script to be installed
+# @param $3 - the older version of the app that this db script
+# will upgrade from
+# (do not pass this option if your SQL script only creates
+# a new db from scratch)
+# ------------------------------------------------------------------------
+
+function webapp_sqlscript ()
+{
+ webapp_checkfileexists "${2}"
+
+ # create the directory where this script will go
+ #
+ # scripts for specific database engines go into their own subdirectory
+ # just to keep things readable on the filesystem
+
+ if [ ! -d "${D}/${MY_SQLSCRIPTSDIR}/${1}" ]; then
+ mkdir -p "${D}/${MY_SQLSCRIPTSDIR}/${1}" || die "unable to create directory ${D}/${MY_SQLSCRIPTSDIR}/${1}"
+ fi
+
+ # warning:
+ #
+ # do NOT change the naming convention used here without changing all
+ # the other scripts that also rely upon these names
+
+ # are we dealing with an 'upgrade'-type script?
+ if [ -n "${3}" ]; then
+ # yes we are
+ elog "(${1}) upgrade script from ${PN}-${PVR} to ${3}"
+ cp "${2}" "${D}${MY_SQLSCRIPTSDIR}/${1}/${3}_to_${PVR}.sql"
+ chmod 600 "${D}${MY_SQLSCRIPTSDIR}/${1}/${3}_to_${PVR}.sql"
+ else
+ # no, we are not
+ elog "(${1}) create script for ${PN}-${PVR}"
+ cp "${2}" "${D}/${MY_SQLSCRIPTSDIR}/${1}/${PVR}_create.sql"
+ chmod 600 "${D}/${MY_SQLSCRIPTSDIR}/${1}/${PVR}_create.sql"
+ fi
+}
+
+# ------------------------------------------------------------------------
+# EXPORTED FUNCTION - call from inside your ebuild's src_install AFTER
+# everything else has run
+#
+# For now, we just make sure that root owns everything, and that there
+# are no setuid files.
+# ------------------------------------------------------------------------
+
+function webapp_src_install ()
+{
+ chown -R "${VHOST_DEFAULT_UID}:${VHOST_DEFAULT_GID}" "${D}/"
+ chmod -R u-s "${D}/"
+ chmod -R g-s "${D}/"
+
+ keepdir "${MY_PERSISTDIR}"
+ fowners "root:0" "${MY_PERSISTDIR}"
+ fperms 755 "${MY_PERSISTDIR}"
+
+ # to test whether or not the ebuild has correctly called this function
+ # we add an empty file to the filesystem
+ #
+ # we used to just set a variable in the shell script, but we can
+ # no longer rely on Portage calling both webapp_src_install() and
+ # webapp_pkg_postinst() within the same shell process
+
+ touch "${D}/${MY_APPDIR}/${INSTALL_CHECK_FILE}"
+}
+
+# ------------------------------------------------------------------------
+# EXPORTED FUNCTION - call from inside your ebuild's pkg_config AFTER
+# everything else has run
+#
+# If 'vhosts' USE flag is not set, auto-install this app
+#
+# ------------------------------------------------------------------------
+
+function webapp_pkg_setup ()
+{
+ # add sanity checks here
+
+ if [ "${SLOT}+" != "${PVR}+" ]; then
+ # special case - some ebuilds *do* need to overwride the SLOT
+ if [ "${WEBAPP_MANUAL_SLOT}" != "yes" ]; then
+ die "ebuild sets SLOT, overrides webapp.eclass"
+ else
+ ewarn
+ ewarn "This ebuild overrides the default SLOT behaviour for webapps"
+ ewarn "If this package installs files into the htdocs dir, this is"
+ ewarn "probably a bug in the ebuild."
+ ewarn
+ fi
+ fi
+
+ # pull in the shared configuration file
+
+ G_HOSTNAME="localhost"
+ webapp_read_config
+
+ # are we installing a webapp-config solution over the top of a
+ # non-webapp-config solution?
+
+ if ! use vhosts ; then
+ local my_dir="${ROOT}${VHOST_ROOT}/${MY_HTDOCSBASE}/${PN}"
+ local my_output
+
+ if [ -d "${my_dir}" ] ; then
+ my_output="$(webapp_check_installedat)"
+
+ if [ "$?" != "0" ]; then
+ # okay, whatever is there, it isn't webapp-config-compatible
+ ewarn "You already have something installed in ${my_dir}"
+ ewarn
+ ewarn "Whatever is in ${my_dir}, it's not"
+ ewarn "compatible with webapp-config."
+ ewarn
+ ewarn "This ebuild may be overwriting important files."
+ ewarn
+ elif [ "$(echo ${my_output} | awk '{ print $1 }')" != "${PN}" ]; then
+ eerror "${my_dir} contains ${my_output}"
+ eerror "I cannot upgrade that"
+ die "Cannot upgrade contents of ${my_dir}"
+ fi
+ fi
+ fi
+}
+
+function webapp_getinstalltype ()
+{
+ # or are we upgrading?
+
+ if ! use vhosts ; then
+ # we only run webapp-config if vhosts USE flag is not set
+
+ local my_output
+
+ my_output="$(webapp_check_installedat)"
+
+ if [ "${?}" = "0" ] ; then
+ # something is already installed there
+ #
+ # make sure it isn't the same version
+
+ local my_pn="$(echo ${my_output} | awk '{ print $1 }')"
+ local my_pvr="$(echo ${my_output} | awk '{ print $2 }')"
+
+ REMOVE_PKG="${my_pn}-${my_pvr}"
+
+ if [ "${my_pn}" == "${PN}" ]; then
+ if [ "${my_pvr}" != "${PVR}" ]; then
+ elog "This is an upgrade"
+ IS_UPGRADE=1
+ else
+ elog "This is a re-installation"
+ IS_REPLACE=1
+ fi
+ else
+ elog "${my_output} is installed there"
+ fi
+ else
+ elog "This is an installation"
+ fi
+ fi
+}
+
+function webapp_src_preinst ()
+{
+ # create the directories that we need
+
+ dodir "${MY_HTDOCSDIR}"
+ dodir "${MY_HOSTROOTDIR}"
+ dodir "${MY_CGIBINDIR}"
+ dodir "${MY_ICONSDIR}"
+ dodir "${MY_ERRORSDIR}"
+ dodir "${MY_SQLSCRIPTSDIR}"
+ dodir "${MY_HOOKSCRIPTSDIR}"
+ dodir "${MY_SERVERCONFIGDIR}"
+}
+
+function webapp_pkg_postinst ()
+{
+ webapp_read_config
+
+ # sanity checks, to catch bugs in the ebuild
+
+ if [ ! -f "${ROOT}${MY_APPDIR}/${INSTALL_CHECK_FILE}" ]; then
+ eerror
+ eerror "This ebuild did not call webapp_src_install() at the end"
+ eerror "of the src_install() function"
+ eerror
+ eerror "Please log a bug on http://bugs.gentoo.org"
+ eerror
+ eerror "You should use emerge -C to remove this package, as the"
+ eerror "installation is incomplete"
+ eerror
+ die "Ebuild did not call webapp_src_install() - report to http://bugs.gentoo.org"
+ fi
+
+ # if 'vhosts' is not set in your USE flags, we install a copy of
+ # this application in ${ROOT}/var/www/localhost/htdocs/${PN}/ for you
+
+ if ! use vhosts ; then
+ echo
+ elog "vhosts USE flag not set - auto-installing using webapp-config"
+
+ webapp_getinstalltype
+
+ G_HOSTNAME="localhost"
+ local my_mode=-I
+ webapp_read_config
+
+ if [ "${IS_REPLACE}" = "1" ]; then
+ elog "${PN}-${PVR} is already installed - replacing"
+ my_mode=-I
+ elif [ "${IS_UPGRADE}" = "1" ]; then
+ elog "${REMOVE_PKG} is already installed - upgrading"
+ my_mode=-U
+ else
+ elog "${PN}-${PVR} is not installed - using install mode"
+ fi
+
+ my_cmd="${WEBAPP_CONFIG} ${my_mode} -h localhost -u root -d ${INSTALL_DIR} ${PN} ${PVR}"
+ elog "Running ${my_cmd}"
+ ${my_cmd}
+
+ # remove the old version
+ #
+ # why do we do this? well ...
+ #
+ # normally, emerge -u installs a new version and then removes the
+ # old version. however, if the new version goes into a different
+ # slot to the old version, then the old version gets left behind
+ #
+ # if USE=-vhosts, then we want to remove the old version, because
+ # the user is relying on portage to do the magical thing for it
+
+ if [ "${IS_UPGRADE}" = "1" ] ; then
+ elog "Removing old version ${REMOVE_PKG}"
+
+ emerge -C "${REMOVE_PKG}"
+ fi
+ else
+ # vhosts flag is on
+ #
+ # let's tell the administrator what to do next
+
+ elog
+ elog "The 'vhosts' USE flag is switched ON"
+ elog "This means that Portage will not automatically run webapp-config to"
+ elog "complete the installation."
+ elog
+ elog "To install ${PN}-${PVR} into a virtual host, run the following command:"
+ elog
+ elog " webapp-config -I -h <host> -d ${PN} ${PN} ${PVR}"
+ elog
+ elog "For more details, see the webapp-config(8) man page"
+ fi
+
+ return 0
+}
+
+function webapp_pkg_prerm ()
+{
+ # remove any virtual installs that there are
+
+ local my_output
+ local x
+
+ my_output="$(${WEBAPP_CONFIG} --list-installs ${PN} ${PVR})"
+
+ if [ "${?}" != "0" ]; then
+ return
+ fi
+
+ for x in ${my_output} ; do
+ [ -f ${x}/.webapp ] && . ${x}/.webapp || ewarn "Cannot find file ${x}/.webapp"
+
+ if [ -z "${WEB_HOSTNAME}" -o -z "${WEB_INSTALLDIR}" ]; then
+ ewarn "Don't forget to use webapp-config to remove the copy of"
+ ewarn "${PN}-${PVR} installed in"
+ ewarn
+ ewarn " ${x}"
+ ewarn
+ else
+ # we have enough information to remove the virtual copy ourself
+
+ ${WEBAPP_CONFIG} -C -h ${WEB_HOSTNAME} -d ${WEB_INSTALLDIR}
+
+ # if the removal fails - we carry on anyway!
+ fi
+ done
+}
diff --git a/examples/phpmyadmin-2.5.4-r1.ebuild b/examples/phpmyadmin-2.5.4-r1.ebuild
new file mode 100644
index 0000000..d84d7ad
--- /dev/null
+++ b/examples/phpmyadmin-2.5.4-r1.ebuild
@@ -0,0 +1,93 @@
+# Copyright 1999-2003 Gentoo Technologies, Inc.
+# Distributed under the terms of the GNU General Public License v2
+# $Header: /home/cvsroot/gentoo-x86/dev-db/phpmyadmin/phpmyadmin-2.5.4.ebuild,v 1.6 2003/12/15 20:03:27 stuart Exp $
+
+inherit webapp
+
+MY_P=phpMyAdmin-${PV/_p/-pl}
+DESCRIPTION="Web-based administration for MySQL database in PHP"
+HOMEPAGE="http://phpmyadmin.sourceforge.net/"
+SRC_URI="mirror://sourceforge/${PN}/${MY_P}-php.tar.bz2"
+RESTRICT="nomirror"
+LICENSE="GPL-2"
+KEYWORDS="alpha arm ppc hppa mips sparc x86 amd64"
+DEPEND=">=net-www/apache-1.3
+ >=dev-db/mysql-3.21 <dev-db/mysql-5.0
+ >=dev-php/mod_php-3.0.8
+ sys-apps/findutils"
+S=${WORKDIR}/${MY_P}
+
+src_unpack() {
+ unpack ${A}
+ epatch ${FILESDIR}/config.inc.php-${PV}.patch
+
+ # Remove .cvs* files and CVS directories
+ find ${S} -name .cvs\* -or \( -type d -name CVS -prune \) | xargs rm -rf
+}
+
+src_compile() {
+ einfo "Setting random user/password details for the controluser"
+
+ local pmapass="${RANDOM}${RANDOM}${RANDOM}${RANDOM}"
+ mv config.inc.php ${T}/config.inc.php
+ sed -e "s/@pmapass@/${pmapass}/g" \
+ ${T}/config.inc.php > config.inc.php
+ sed -e "s/@pmapass@/${pmapass}/g" \
+ ${FILESDIR}/mysql-setup.sql.in-${PV} > ${T}/mysql-setup.sql
+}
+
+src_install() {
+ webapp_src_preinst
+
+ local docs="ANNOUNCE.txt CREDITS Documentation.txt RELEASE-DATE-${PV} TODO ChangeLog LICENSE README"
+
+ # install the SQL scripts available to us
+ #
+ # unfortunately, we do not have scripts to upgrade from older versions
+ # these are things we need to add at a later date
+
+ webapp_sqlscript mysql ${T}/mysql-setup.sql
+
+ # handle documentation files
+ #
+ # NOTE that doc files go into /usr/share/doc as normal; they do NOT
+ # get installed per vhost!
+
+ dodoc ${docs}
+ for doc in ${docs} INSTALL; do
+ rm -f ${doc}
+ done
+
+ # Copy the app's main files
+
+ einfo "Installing main files"
+ cp -r . ${D}${MY_HTDOCSDIR}
+
+ # Identify the configuration files that this app uses
+
+ webapp_configfile ${MY_HTDOCSDIR}/config.inc.php
+
+ # Identify any script files that need #! headers adding to run under
+ # a CGI script (such as PHP/CGI)
+ #
+ # for phpmyadmin, we *assume* that all .php files that don't end in
+ # .inc.php need to have CGI/BIN support added
+
+ for x in `find . -name '*.php' -print | grep -v 'inc.php'` ; do
+ webapp_runbycgibin php ${MY_HTDOCSDIR}/$x
+ done
+
+ # there are no files which need to be owned by the web server
+
+ webapp_serverowned ${MY_HTDOCSDIR}
+
+ # add the post-installation instructions
+
+ webapp_postinst_txt en ${FILESDIR}/postinstall-en.txt
+
+ # all done
+ #
+ # now we let the eclass strut its stuff ;-)
+
+ webapp_src_install
+}
diff --git a/examples/phpmyadmin-2.5.4-r2.ebuild b/examples/phpmyadmin-2.5.4-r2.ebuild
new file mode 100644
index 0000000..88beeb1
--- /dev/null
+++ b/examples/phpmyadmin-2.5.4-r2.ebuild
@@ -0,0 +1,92 @@
+# Copyright 1999-2003 Gentoo Technologies, Inc.
+# Distributed under the terms of the GNU General Public License v2
+# $Header: /home/cvsroot/gentoo-x86/dev-db/phpmyadmin/phpmyadmin-2.5.4.ebuild,v 1.6 2003/12/15 20:03:27 stuart Exp $
+
+inherit eutils
+inherit webapp
+
+MY_P=phpMyAdmin-${PV/_p/-pl}
+DESCRIPTION="Web-based administration for MySQL database in PHP"
+HOMEPAGE="http://phpmyadmin.sourceforge.net/"
+SRC_URI="mirror://sourceforge/${PN}/${MY_P}-php.tar.bz2"
+RESTRICT="nomirror"
+LICENSE="GPL-2"
+KEYWORDS="alpha arm ppc hppa mips sparc x86 amd64"
+DEPEND=">=net-www/apache-1.3
+ >=dev-db/mysql-3.21 <dev-db/mysql-5.0
+ >=dev-php/mod_php-3.0.8
+ sys-apps/findutils"
+S=${WORKDIR}/${MY_P}
+
+src_unpack() {
+ unpack ${A}
+ epatch ${FILESDIR}/config.inc.php-${PV}.patch
+
+ # Remove .cvs* files and CVS directories
+ find ${S} -name .cvs\* -or \( -type d -name CVS -prune \) | xargs rm -rf
+}
+
+src_compile() {
+ einfo "Setting random user/password details for the controluser"
+
+ local pmapass="${RANDOM}${RANDOM}${RANDOM}${RANDOM}"
+ mv config.inc.php ${T}/config.inc.php
+ sed -e "s/@pmapass@/${pmapass}/g" \
+ ${T}/config.inc.php > config.inc.php
+ sed -e "s/@pmapass@/${pmapass}/g" \
+ ${FILESDIR}/mysql-setup.sql.in-${PV} > ${T}/mysql-setup.sql
+}
+
+src_install() {
+ webapp_src_preinst
+
+ local docs="ANNOUNCE.txt CREDITS Documentation.txt RELEASE-DATE-${PV} TODO ChangeLog LICENSE README"
+
+ # install the SQL scripts available to us
+ #
+ # unfortunately, we do not have scripts to upgrade from older versions
+ # these are things we need to add at a later date
+
+ webapp_sqlscript mysql ${T}/mysql-setup.sql
+
+ # handle documentation files
+ #
+ # NOTE that doc files go into /usr/share/doc as normal; they do NOT
+ # get installed per vhost!
+
+ dodoc ${docs}
+ for doc in ${docs} INSTALL; do
+ rm -f ${doc}
+ done
+
+ # Copy the app's main files
+
+ einfo "Installing main files"
+ cp -r . ${D}${MY_HTDOCSDIR}
+
+ # Identify the configuration files that this app uses
+
+ webapp_configfile ${MY_HTDOCSDIR}/config.inc.php
+
+ # Identify any script files that need #! headers adding to run under
+ # a CGI script (such as PHP/CGI)
+ #
+ # for phpmyadmin, we *assume* that all .php files that don't end in
+ # .inc.php need to have CGI/BIN support added
+
+ for x in `find . -name '*.php' -print | grep -v 'inc.php'` ; do
+ webapp_runbycgibin php ${MY_HTDOCSDIR}/$x
+ done
+
+ # there are no files which need to be owned by the web server
+
+ # add the post-installation instructions
+
+ webapp_postinst_txt en ${FILESDIR}/postinstall-en.txt
+
+ # all done
+ #
+ # now we let the eclass strut its stuff ;-)
+
+ webapp_src_install
+}
diff --git a/examples/postinstall-en.txt b/examples/postinstall-en.txt
new file mode 100644
index 0000000..4bd19f0
--- /dev/null
+++ b/examples/postinstall-en.txt
@@ -0,0 +1,13 @@
+To complete installation, you must
+
+1. Update MySQL's grant tables and the pmadb database:
+ mysql -u root -p < ${MY_SQLSCRIPTSDIR}/mysql/${PVR}_create.sql
+2. Reload MySQL:
+ /etc/init.d/mysql restart
+
+If you are upgrading from an earlier version and are using phpMyAdmin's
+features for master/foreign tables be sure to read
+ http://localhost/phpmyadmin/Documentation.html#col_com
+You will need to perform the ALTER TABLE step yourself.
+
+Finally, point your browser to http://localhost/phpmyadmin/.
diff --git a/sbin/webapp-cleaner b/sbin/webapp-cleaner
new file mode 100755
index 0000000..d7ded2c
--- /dev/null
+++ b/sbin/webapp-cleaner
@@ -0,0 +1,144 @@
+#!/bin/bash
+# Distributed under the terms of the GNU General Public License v2
+# $Header: $
+
+PN=
+
+ACTION=
+PRETEND=
+
+CMD="emerge -Cav"
+WEBAPP_DIR="/usr/share/webapps"
+WEBAPP_CONFIG=
+
+[[ -z ${RC_GOT_FUNCTIONS} ]] && source /sbin/functions.sh
+
+function help() {
+ echo "Remove obsolete and unused versions of web applications"
+ echo
+ echo "Usage:"
+ echo " $0 [options] [action] app_name"
+ echo
+ echo "Options:"
+ echo " -p, --pretend"
+ echo " Instead of cleaning, simply show the commands to be executed"
+ echo
+ echo "Actions:"
+ echo " -P, --prune"
+ echo " Removes all but the latest version of a package. Similar to"
+ echo " emerge --prune"
+ echo
+ echo " -C, --clean-unused"
+ echo " Removes all versions of a web application that have not been"
+ echo " installed into a virtual host"
+ echo
+ echo " -h, --help"
+ echo " Displays this help message"
+ echo
+ echo "If multiple actions are given, only the action specified last will"
+ echo "be executed"
+}
+
+function sanity_checks() {
+ WEBAPP_CONFIG=$(which webapp-config)
+ if [ "${WEBAPP_CONFIG}x" == "x" ]; then
+ eerror "webapp-config not found"
+ exit 1
+ fi
+
+ if [[ -z "${ACTION}" ]]; then
+ eerror "Please specify a valid action"
+ exit 1
+ fi
+
+ if [[ -z ${PN} && ${ACTION} != "help" ]]; then
+ eerror "Please specify a web application to be cleaned"
+ exit 1
+ fi
+
+ if [[ ! -d "${WEBAPP_DIR}/${PN}" ]]; then
+ eerror "${PN} not found"
+ exit 1
+ fi
+
+}
+
+
+function prune() {
+
+ if [[ $(ls -1 "${WEBAPP_DIR}/${PN}" | wc -l) == "1" ]]; then
+ einfo "Nothing to clean"
+ return
+ else
+ local BEST_VERSION=$(ls ${WEBAPP_DIR}/${PN} | sort -nr | head -n 1)
+ for x in $(ls ${WEBAPP_DIR}/${PN}); do
+ if [[ ${BEST_VERSION} != ${x} ]]; then
+ CMD="${CMD} =${PN}-${x}"
+ fi
+ done
+
+ einfo "Multiple versions of ${PN} detected."
+ if [[ -z ${PRETEND} ]]; then
+ einfo "Running ${CMD}"
+ ${CMD}
+ else
+ einfo "To prune, run the following command:"
+ einfo "${CMD}"
+ fi
+ fi
+}
+
+function clean_unused() {
+ local output=$(${WEBAPP_CONFIG} --lui ${PN})
+
+ if [[ -z ${output} ]] ; then
+ einfo "Nothing to clean"
+ return
+ else
+ CMD="${CMD} =${output}"
+ einfo "Unused versions of ${PN} detected."
+ if [[ -z ${PRETEND} ]]; then
+ einfo "Running ${CMD}"
+ ${CMD}
+ else
+ einfo "To clean, run the following command:"
+ einfo "${CMD}"
+ fi
+ fi
+}
+
+function process_opts() {
+
+ if [[ -z $1 ]]; then
+ help
+ exit 0
+ fi
+
+ while [ "$1+" != "+" ]; do
+ case "$1" in
+ -p|--pretend)
+ PRETEND="yes"
+ ;;
+ -P|--prune)
+ ACTION="prune"
+ ;;
+ -C|--clean-unused)
+ ACTION="clean_unused"
+ ;;
+ -h|--help)
+ ACTION="help"
+ ;;
+ *)
+ PN="$1"
+ ;;
+ esac
+
+ shift
+ done
+}
+
+process_opts $@
+
+sanity_checks
+
+${ACTION}
diff --git a/sbin/webapp-config b/sbin/webapp-config
new file mode 100755
index 0000000..7d30ba6
--- /dev/null
+++ b/sbin/webapp-config
@@ -0,0 +1,44 @@
+#!python
+#
+# /usr/sbin/webapp-config
+# Python script for managing the deployment of web-based
+# applications
+#
+# Originally written for the Gentoo Linux distribution
+#
+# Copyright (c) 1999-2005 Gentoo Foundation
+# Released under v2 of the GNU GPL
+#
+# Author(s) Stuart Herbert <stuart@gentoo.org>
+# Renat Lumpau <rl03@gentoo.org>
+# Gunnar Wrobel <php@gunnarwrobel.de>
+#
+# ========================================================================
+''' webapp-config is a powerful tool that allows you to install,
+upgrade, and remove web-based applications in a virtual-hosting
+environment. '''
+
+__version__ = "$Id: webapp-config 126 2005-11-05 23:26:48Z wrobel $"
+
+# ========================================================================
+# Dependencies
+# ------------------------------------------------------------------------
+
+from WebappConfig.config import Config
+
+def main():
+ '''
+ Main program call.
+ '''
+ # Get the configuration
+
+ config = Config()
+
+ config.parseparams()
+
+ # Handle the work
+
+ config.run()
+
+if __name__ == "__main__":
+ main()
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..65ab502
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+
+import sys
+
+from distutils.core import setup
+
+# this affects the names of all the directories we do stuff with
+sys.path.insert(0, './')
+from WebappConfig.version import WCVERSION
+
+
+setup(name = 'webapp-config',
+ version = WCVERSION,
+ description = 'Python script for managing the deployment of web-based applications',
+ author = 'Stuart Herbert, Renat Lumpau, Gunnar Wrobel',
+ author_email = 'stuart@gentoo.org',
+ url = 'http://svn.gnqs.org/projects/vhost-tools',
+ packages = ['WebappConfig'],
+ scripts = ['sbin/webapp-config', 'sbin/webapp-cleaner'],
+ license = 'GPL',
+ )