If you remember, so far I had set up a server running Lighttpd as its web server and a plethora of spam hardened backend services. I then reviewed the PHP based CMS solutions and found them all far too toy for serious usage. This got me thinking about crazy alternatives ...
1. Running Plone & Zope on a low-end VPS
At this stage I began to think something unthinkable - what if I tried something real heavy like Plone? Plone was designed for server farms rather than a low end VPS. PHP is designed as a lightweight web services programming language which runs snappily on modest hardware (especially with XCache), but Plone runs on Zope which is heavy lifting stuff indeed. I had the disc space definitely on my VPS: 20Gb was more than enough, but it's the RAM usage which starts at 100Mb just for one instance. Plone will happily chew half a Gigabyte in RAM and send my little VPS into swap hell - or even actual proper memory exhaustion.
But I wanted to see if it's possible, and here are my tips. Firstly, you need to upgrade to Ubuntu Intrepid as you'll need the latest packages. And you definitely don't want to attempt this on less than 256Mb RAM and 32 bit code only (Python chomps lots more RAM on 64 bit). Also, don't even think of doing this if your site serves much content to logged in users - you'll be relying on aggressive caching of anonymous-only pages to run in any way quickly and hoping that Zope rarely if ever actually gets called. Lastly, make sure that swap is turned on and working - somehow it had disabled itself on my Xen installation thus requiring an addition to /etc/fstab.
8. Install Plone
Depending upon your technical knowhow, you may wish to install the absolutely bleeding edge Plone by pinning your Ubuntu/Debian package repositories such that new packages are available but won't override current ones. For jaunty on intrepid, /etc/apt/preferences looks like:
Package: * Pin: release a=jaunty Pin-Priority: 400
If you can wait for Jaunty, then you probably should - however, Plone v3.1.7 fixes a few important bugs over v3.1.1 and varnish v2 is vast improvement over varnish v1.
Thankfully, Ubuntu Intrepid thanks to its Debian source already has Plone 3.1 in its repositories, so simply do apt-get install plone3-site. This will leave you with a default Zope instance running on port 8081. To activate cache support, install CacheFu by doing sudo dzhandle list-instances and then dzhandle -z <zope_version> add-product <instance> CacheSetup which probably will be sudo dzhandle -z 2.10 add-product plone-site CacheSetup. Next, edit /etc/zope2.10/plone-site/zope.conf to use port 80 instead of 8081 so you get a "bare Zope". Shutdown lighttpd, and restart Zope.
Visiting your site should yield a Zope configuration page. It's already secure via the admin password you gave during installation. Now select "Plone Site" from the drop down box and add as many sites as your VPS hosts using some sensible name. There should be a thing called virtual_hosting which will dispatch URLs to specific Plone sites - as you're running a "bare" Zope, choose Mappings and enter something like this:
*.freeinggrowth.org /freeinggrowth.org *.neocapitalism.org /neocapitalism.org
This assumes that you named your Plone sites as /<domain>. Now when you visit your domains, bam! there's your blank Plone instance. You'll be able to log in as admin straight away - bear in mind the sluggishness here is from caches being empty, but things will speed up.
To test the performance of your sites under load, try openload from the Ubuntu repositories. Simply do openload <url> <concurrency> and press Enter to stop. For nedprod.com, a fairly standard Apache + PHP based site this is:
Clients: 1 Total TPS: 7.87 Avg. Response time: 0.101 sec. Max Response time: 0.107 sec Clients: 10 Total TPS: 30.04 Avg. Response time: 0.274 sec. Max Response time: 2.405 sec Clients: 50 Total TPS: 28.42 Avg. Response time: 0.945 sec. Max Response time: 3.084 sec Clients: 100 Total TPS: 29.09 Avg. Response time: 2.213 sec. Max Response time: 5.062 sec
As you can see, nedprod.com struggles after about fifty and would die if it got slashdotted or something (I didn't dare let the 100 clients test run till stabilisation lest I upset my hosting provider). Now compare Plone running on a bare Zope:
Clients: 1 Total TPS: 8.52 Avg. Response time: 0.109 sec. Max Response time: 0.266 sec Clients: 10 Total TPS: 8.06 Avg. Response time: 0.947 sec. Max Response time: 1.803 sec Clients: 50 Total TPS: 8.48 Avg. Response time: 4.296 sec. Max Response time: 6.503 sec Clients: 100 Total TPS: 8.53 Avg. Response time: 8.169 sec. Max Response time: 11.788 sec
One difference is that this is running from localhost whereas nedprod.com was from remote, nevertheless it seems that Zope does a surprisingly good job of optimising itself on a low end VPS. Admittedly, a twelve second wait is excessive but we are talking a hundred concurrent clients, and moreover, this is a stable reading in that this is as bad as it gets. A bare Zope might even handle a mild slashdotting!
However, the truth is in fact being hidden. As installed from Debian, Zope is already implementing a series of caches and you're already seeing Zope go as fast as Zope can. Maybe if you're some sort of Zope expert you could improve these results, but IMHO they're good enough to avoid wasting time on doing more. I do know that a completely uncached Zope does about half a transaction per second, so this is sixteen times faster!
You may wish to decrease the memory given over to caching - edit /etc/zope2.10/plone-site/zope.conf adding to this section:
<zodb_db main>
# Main FileStorage database
<filestorage>
path $INSTANCE/var/Data.fs
</filestorage>
mount-point /
cache-size 1000
</zodb_db>
This should decrease the default caching of the Zope Object Database (ZOBD) from the default 5000 to 1000.
9. Make Plone scale on a low end VPS very well indeed!
Ok, time to improve things. First things first, you want a thing called CacheFu installed which I gave instructions for above (be very grateful, because no where is it documented properly). What we want is for CacheFu to do all the generation of the cache control magic (i.e. telling the cache when to purge a changed page etc) and for that to interact with a front reverse web proxy which is basically a store of static content i.e. the output from previous runs of Zope/Plone. Therefore, anonymous users especially will only ever hammer your proxy and not your Zope which should enable your server to easily handle a slashdotting.
What comes next is tricky and very poorly documented. It took me weeks to figure all this out. If you don't want to use a reverse proxy, at least do the following (do NOT do this if you will be using a reverse proxy): Plone doesn't return a Last-updated HTTP header for HTML output (it does for images etc) which forces everything using your site to pull the latest & greatest version. This obviously hammers your server, so in your Zope config page go to /<site>/portal_skins/custom/global_cache_settings and ask to customise. Now replace what's there with this:
<metal:cacheheaders define-macro="cacheheaders"> <metal:block tal:define="dummy python:request.RESPONSE.setHeader('Content-Type', 'text/html;;charset=%s' % charset)" /> <metal:block tal:define="dummy python:request.RESPONSE.setHeader('Content-Language', lang)" /> <metal:block tal:define="dummy python:request.RESPONSE.setHeader('Vary', 'Accept-Language,Accept-Encoding,User-Agent,Cookie')" /> <metal:block tal:define="dummy python:request.RESPONSE.setHeader('Last-Modified', here.modified().toZone('GMT').rfc822())" /> <metal:block tal:content="structure python:here.enableHTTPCompression(request=request, debug=0)" /> </metal:cacheheaders>
That on its own will greatly reduce server load when the page hasn't been updated. However, a much improved way is to use HTTP 1.1 etags to denote "your" content such that the browser will cooperate with the cache both in the forward & backwards direction i.e. Zope will explicitly tell your reverse proxy to purge pages which have changed, and your browser will pass a special etag which the proxy can use to return cached content specialised just for you (e.g. with your name). This obviously enough helps even logged in users to run much faster (again, you wouldn't want too many as each page still needs customising for each).
This all sounds complex to configure. CacheFu makes it very easy but how to install it is very poorly documented indeed. Firstly, you need a thing called 'varnish' which is a caching proxy which will make your server more than capable of handling anything both slashdot and digg added together could throw at it (you'll easily max out your network well before your CPU even on a Gigabit port). v1.1 is inside Ubuntu Intrepid repositories, but it keeps stalling randomly so I suggest you pin & install the v2.02 deb's from Ubuntu Jaunty. Readjust Zope to run on server:port localhost:8081, and modify /etc/default/varnish as follows:
DAEMON_OPTS="-a localhost:8080 \ -T localhost:8079 \ -b localhost:8081 \ -u varnish -g varnish \ -t 300 \ -s file,/var/lib/varnish/$INSTANCE/varnish_storage.bin,128Mb"
This configures varnish to refresh its cache every five minutes and to hold up to 128Mb in that cache. Ports 8079, 8080 and 8081 are on localhost and therefore aren't exposed to the public (even if they were, the Shorewall config from above should keep them out). Restart zope and varnish: now zope is available on port 8081 and zope-through-varnish is on port 8080.
Next thing you'll need is lighttpd again to act as the front-facing layer. You can put varnish directly onto port 80, but I wanted HTTPS support and varnish can't do that so I basically configured lighttpd to act as the HTTPS delegator:
# Setup password protect auth.backend = "htdigest" auth.backend.htdigest.userfile = "/etc/lighttpd/lighttpd-htdigest.user" # Enable SSL $SERVER["socket"] == ":443" { ssl.engine = "enable" ssl.pemfile = "/etc/lighttpd/ssl/vps.nedprod.com/server.pem" #ssl.ca-file = "/etc/lighttpd/theos.in/CA_issuing.crt" # Make stuff auth protected $HTTP["host"] != "vps.nedprod.com" { auth.require = ( "/" => ( "method" => "digest", "realm" => "admin", "require" => "user=ned" ) ) } # Rewrite for Zope's VirtualHostMonster $HTTP["host"] =~ "^(www\.|)([^.]+\.org)$" { url.rewrite-once = ( # "^/misc/.*" => "$0", "^/(.*)$" => "/VirtualHostBase/https/www.%2:443/%2/VirtualHostRoot/$1" ) } #proxy.server = ( "/VirtualHostBase/" => # ( ( "host" => "127.0.0.1" , "port" => 8081 ) ) ) proxy.server = ( "/" => ( ( "host" => "127.0.0.1" , "port" => 8081 ) ) ) } $SERVER["socket"] == ":80" { $HTTP["host"] == "vps.nedprod.com" { url.redirect = ( "^/(.*)$" => "https://vps.nedprod.com/$1" ) } else $HTTP["host"] != "vps.nedprod.com" { # Rewrite for Zope's VirtualHostMonster $HTTP["host"] =~ "^(www\.|)([^.]+\.org)$" { url.rewrite-once = ( # "^/misc/.*" => "$0", "^/(.*)$" => "/VirtualHostBase/http/www.%2:80/%2/VirtualHostRoot/$1" ) # And now through varnish proxy.server = ( "/VirtualHostBase/" => ( ( "host" => "127.0.0.1" , "port" => 8080 ) ) ) } } }
All that VirtualHostBase stuff is there to tell Zope when it's supposed to be using HTTPS or just normal HTTP - and getting its config right took ages. I auth-digested all HTTPS access to keep it an admin-only function - as you can see, HTTPS doesn't go through varnish because I wanted only fresh content to appear and that makes a DDoS attack very easy.
After all that, it's time to activate CacheFu. Login as admin to your Plone site and go to Site Setup->Add/Remove Products and enable CacheSetup 1.2. Enter its config page, choose "Purge with VHM URLs (squid/varnish behind apache, VHM virtual hosting", enter all the addresses for your website including their port numbers, enter http://localhost:8080 for your proxy cache and I'd personally enable gzip compression too.
Now watch:
Clients: 1 Total TPS: 1074.10 Avg. Response time: 0.001 sec. Max Response time: 0.034 sec Clients: 10 Total TPS: 873.05 Avg. Response time: 0.010 sec. Max Response time: 0.395 sec Clients: 50 Total TPS: 1167.46 Avg. Response time: 0.040 sec. Max Response time: 0.252 sec Clients: 100 Total TPS: 1072.87 Avg. Response time: 0.079 sec. Max Response time: 0.337 sec
Rather somewhat better eh? I couldn't go past 100 simultaneous because my VPS provider's automatic anti-DDoS defences kick in. Slashdot sees around 600-1000 concurrent, so theoretically your server is now slashdottable!
As for memory usage:
top - 13:11:06 up 1 day, 22:58, 1 user, load average: 0.08, 0.02, 0.01 Tasks: 61 total, 2 running, 59 sleeping, 0 stopped, 0 zombie Cpu(s): 0.0%us, 0.0%sy, 0.0%ni, 99.7%id, 0.0%wa, 0.0%hi, 0.0%si, 0.3%st Mem: 262144k total, 258604k used, 3540k free, 11068k buffers Swap: 262136k total, 40944k used, 221192k free, 26260k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 16784 zope 15 0 199m 135m 4540 S 0.0 53.0 5:22.30 python2.4 10449 spamd 15 0 34896 27m 2564 S 0.0 10.6 0:06.57 spamd 10447 root 15 0 30680 14m 2524 S 0.0 5.8 0:00.85 spamd 3413 varnish 15 0 231m 10m 9412 S 0.0 4.2 0:01.42 varnishd 3465 root 15 0 10488 7532 7508 S 0.0 2.9 0:00.08 varnishlog 2898 postgrey 15 0 11408 5400 2404 S 0.0 2.1 0:00.11 postgrey 3534 www-data 15 0 9836 4204 1312 S 0.0 1.6 0:02.05 lighttpd 2856 mysql 15 0 56412 3196 2240 S 0.0 1.2 0:00.47 mysqld 23394 ned 16 0 3428 1892 1272 S 0.0 0.7 0:00.00 bash 4013 postfix 15 0 6056 1804 1588 S 0.0 0.7 0:00.06 tlsmgr 3246 root 15 0 5672 1664 1312 S 0.0 0.6 0:00.07 master 23250 postfix 15 0 5688 1664 1328 S 0.0 0.6 0:00.00 pickup 3275 postfix 16 0 5732 1508 1380 S 0.0 0.6 0:00.00 qmgr 2971 root 16 0 4496 1428 952 S 0.0 0.5 0:00.00 authdaemond 2968 root 15 0 4496 1424 952 S 0.0 0.5 0:00.00 authdaemond 2970 root 15 0 4496 1424 952 S 0.0 0.5 0:00.00 authdaemond 16783 root 18 0 7960 1400 812 S 0.0 0.5 0:00.00 python2.4 2969 root 15 0 4496 1324 952 S 0.0 0.5 0:00.00 authdaemond 2972 root 15 0 4496 1264 940 S 0.0 0.5 0:00.01 authdaemond 23405 virtual 16 0 2916 1156 1020 S 0.0 0.4 0:00.00 gam_server 23399 ned 15 0 2352 1116 888 R 0.0 0.4 0:00.01 top 23393 root 15 0 2456 1084 824 R 0.0 0.4 0:00.07 dropbear 2921 root 18 0 4444 856 804 S 0.0 0.3 0:00.01 authdaemond 3512 root 16 0 2136 792 724 S 0.0 0.3 0:00.02 cron 3412 root 15 0 10740 684 584 S 0.0 0.3 0:00.00 varnishd 2723 syslog 15 0 1952 668 548 S 0.0 0.3 0:00.35 syslogd
As you can see, roughly what is being used in swap is being used for caching & buffers which basically means that I am my 256Mb RAM limit. You wouldn't want to run anything else on this server.
10. Getting Plone into something useable
Plone suffers from the reputation of having a steep learning curve. I have to agree - its documentation assumes that the reader has a certain "Plone mentality" which I really think means "Zope mentality". If you understand that mentality, then things that you need to twiddle live in obvious places and customising things happen in an obvious fashion. If however you don't yet understand that mentality, things are somewhat more tricky as little of it is intuitive nor obvious. If I were you, I would start by very carefully following the tutorials at http://www.insmallsteps.com/lessons which have saved me a few times, in particular with regard to how to customise your Plone site's logo and such.
First things first, make sure you check /var/log/zope2.10/plone-site/event.log every now and then for errors. Sometimes Zope will be internally throwing an error which Plone doesn't report, so when for example you install some add-on and it just doesn't appear, check that log before all else.
Secondly, remember that Zope uses a transactional database whose operations can be rolled back i.e. you can undo whatever you have done. If you screw things up real bad - and trust me, you will - just enter the Zope Management Interface (ZMI) and go to the Undo tab. I have successfully rolled page several pages of operations though they need to be done in sequence or it complains. Don't forget to pack your ZODB after you do lots of this to free up space and reduce memory usage.
Thirdly, installing add-ons for Plone is an absolute c*nt - I haven't seen such a badly implemented half-arsed plugin installation system anywhere else in years. Some "products" (as add-ons are called in Zope and Plone) will go into the Plone extensions directory (/var/lib/zope2.10/instance/plone-site/Products) whereupon after a Zope restart they will appear in the Add/Remove Products Plone page - these are the easy ones.
Others come packaged as Python eggs. The proper way is to install using python's "easy_install", working around any errors and fatal errors which appear due to bad dependencies and/or conflicts with Ubuntu provided libraries (try installing a slightly older version if a dependency package installation fails). If all goes well, then you must add a file called /etc/zope2.10/plone-site/package-includes/<Zope.name>-configure.zcml with the single XML command '<include package="<Zope.name>" />'. What should <Zope.name> be? Well, it's the Zope namespace name of the package and who knows because they are fairly arbitrary. I open up the egg file in a zip archive reader and poke around its contents until I find it.
Sometimes installing the egg and linking to it via package-includes doesn't work - the addon may work most of the time but sometimes fail with random errors. You now need the next method of manually extracting from the source tarball. There is yet another method again which uses a system called "buildout" which isn't available when you install from Debian/Ubuntu repositories because as far as I can tell, it needs the Zope build system. Unfortunately, you will find that quite a few add-ons use buildout exclusively, in which case you will simply have to delve into the source tarball, dig out the requisite folders and stick them either into the Plone products directory, the Python site-packages extension directory or best of all, sometimes a combination of both. The same goes for eggs - sometimes they just won't install and/or conflict so badly with Ubuntu repository versions that it is simply easier to copy the Products/<whatever> folder into the Plone products directory, whereupon it tends to just work.
Let me be most clear: installation of Plone & Zope add-ons is appalling and it's very easy to kill a currently working installation in a way which takes hours to undo (it's one of the big planned new improvements next Plone version). That said, once you've done it then it's gone forever and you never need worry again. I would absolutely love a formal, "proper" way of installing addons like every other half decent CMS uses - maybe even something like Firefox with an integrated search, version check & install system.
One problem that you will almost certainly meet is the dreaded "ExtractionError: Can't extract file(s) to egg cache" as reported here and here. I twiddled with this for hours and my final solution was neither of what those two articles proposed: I created a single .python-eggs cache directory with world writeable and symlinked all the random locations Zope would sometimes choose (e.g. my home directory) to that one location. I gave up on trying to modify the PYTHON_EGG_CACHE environment variable because Debian/Ubuntu's package setup makes this very tough indeed.
Another piece of advice: find out how to turn on short name editing (in the site setup, plus you need to enable it in your admin user profile) which lets you set intelligent URLs for content. Turn on UID linking in Kupu so you can change short names at will without breaking links (don't worry, links still appear with the right short name as it gets munged server side).
Just the advice so far should save you days of unnecessary labour and head scratching - once again, documentation for all this so far does exist but not in one place. What's next? Well, you definitely need some third party packages (available from http://plone.org/products) and once again, after much trial & error I have settled on the following as a mandatory minimum (all are Plone 3.x products):
-
Scrawl v1.1 [easy to install: copy
to Products directory]
We like Scrawl a lot, though it should be in the core already - all it does is to add a display of the content of news item. Via Collections, it is easy in Plone to specify "all data of type 'news post' matching the following criteria X, Y and Z" and the collection will display the entries. However, if you want a blog-type display, just seeing the title & summaries of the blog posts isn't much use - here is where Scrawl simply adds the displaying of the Collection items content, no more, no less.
-
qPloneCaptchas v1.3.3 [easy to
install: copy to Products directory]
Captchas are pretty useless in general, but not having them simply invites spam. There are several captcha add-ons for Plone but this one integrates with other Quintagroup products transparently which saves work. Make sure you add a captcha to the "Contact" form for your site (see below in customising your site).
-
qPloneComments v3.1.1 [easy to
install: copy to Products directory]
If you'd like anonymous or otherwise commenting upon/discussion of blog entries or pages, then this is the best I've seen. Handily automatically uses qPloneCaptchas if installed.
-
Collage v1.2.1 [easy to install:
copy Products/Collage inside zip file to Products directory]
Most CMSs have a portal layout whereby there is a top bit, a left column, content and usually a right column. While straightforward, this layout is boring and inflexible for certain content types - one of the big reasons I stayed with hand-edited HTML on nedprod for so long. Collage has two particularly useful functions: firstly, it lets you break up your big content box into columns and rows. Secondly, it lets you very easily incorporate other Plone content - think of it a bit like a series of iframes whereby you can create a series of unpublished mini-bits of content and yank them all together into a Collage. This lets you make your content box much more dynamic because you can assemble a variation of lots of types of content presentation which normally speaking would require separate pages in your CMS.
11. Customising Plone
First things first, do NOT install third party themes!!! If you do then some of them will utter screw up /portal_css and you'll have to hand hack them all back in from a virgin installation. Furthermore, many of the third party themes don't work properly with printing or disabled person accessibility so believe it or not it's best & easiest to always use the default Plone theme and customise that via CSS override.
The basics of overriding Plone's themes is covered amply at http://www.insmallsteps.com/lessons/lesson-customizing-design - simply muck around with your own ploneCustom.css using the Firebug addon to Firefox and you'll do just fine. Remember to turn on debug mode in /portal_css when testing CSS changes. To change the colour scheme or logo graphic, simply customise the styles property sheet - this saves your overriding everything.
For content changes, it depends on what you're wanting to change. A Plone3 page is made up of a series of viewlets with the most important being the top page header - you can view these but not edit them via /@@manage-viewlets. The left and right columns are composed of portlets which can be managed in admin edit mode - remember that a Static Text Portlet is extremely powerful indeed, making things like inserting a 160x600 iframe advert extremely easy. To modify a viewlet you'll need to customise that viewlet's source - this is actually extremely easy, simply go to /portal_view_customizations and look around the bottom of the page for things like plone.top.logo which lets you replace the graphical logo with something else.
One tricky thing is how to add a captcha to anywhere where an anonymous user might have access. Once again, a total lack of documentation on the web, so here's how:
Firstly, customise /portal_skins/plone-templates/contact-info and just before the line "<div class="formControls">" enter:
<div class="field" tal:define="error errors/captcha| nothing;" tal:attributes="class python:test(error, 'field error', 'field')"> <label i18n:translate="label_captcha_help">Verification Code</label> <div class="formHelp" i18n:translate="help_plone_captcha"> This helps us prevent automated spamming. </div> <div tal:content="error">Validation error output</div> <div metal:use-macro="here/captcha_widget/macros/captcha" /> </div>
After saving, hit the Validation tab and for default validators enter "validate_site_feedback, captcha_validator" where there used to be just "validate_site_feedback" before. This displays & validates the captcha for both anonymous and logged in users - you may wish to wrap it with a " <tal:feedbackForm condition="isAnon">" condition so it only appears when necessary.
You might like the default Plone skin to gain roundy corners on CSS3 capable browsers which I think makes it look much better. Customise /portal_skins/custom/ploneCustom.css as follows:
/* * This is the file where you put your CSS changes. * You should preferably use this and override the * relevant properties you want to change here instead * of customizing plone.css to survive upgrades. Writing * your own plone.css only makes sense for very heavy * customizations. Useful variables from Plone are * documented at the bottom of this file. */ /* <dtml-with base_properties> (do not remove this :) */ /* <dtml-call "REQUEST.set('portal_url', portal_url())"> (not this either :) */ /* Specialise font selection for Cleartype display: serif = Constantia sans-serif = Corbel monospace = Consolas */ pre, code, tt, textarea { font-family: Consolas, monospace; } body, h1, h2, h3, h4, h5, h6, input { font-family: Corbel, sans-serif; } body { font-size: 75%; } .documentContent { font-family: Constantia, serif; font-size: 110% !important; } .documentContent textarea { font-size: 100% !important; } #neocapitalismlogo { color: &dtml-fontColor;; font-size: 400%; font-family: "Times New Roman", Times, serif; font-weight: bold; margin-top: 16px; margin-left: 16px; } #portal-logo { text-decoration: none; } #neocapitalismsublogo { color: &dtml-fontColor;; font-size: 200%; margin-top: 8px; margin-left: 32px; margin-bottom: 16px; } .bigtext { font-size: x-large; } .smalltext { font-size: x-small; } body { } #portal-siteactions li a { border-radius: 9px; -moz-border-radius: 9px; -webkit-border-radius: 9px; } #portal-sitemap a { border: none; padding: 5px; } #portal-sitemap a:hover { padding: 4px; } #portal-globalnav li a { border-top-left-radius: 9px; border-top-right-radius: 9px; -moz-border-radius-topleft: 9px; -moz-border-radius-topright: 9px; -webkit-border-top-left-radius: 9px; -webkit-border-top-right-radius: 9px; } #portal-personaltools { border: &dtml-borderWidth; &dtml-borderStyle; &dtml-globalBorderColor;; border-top-left-radius: 9px; border-top-right-radius: 9px; -moz-border-radius-topleft: 9px; -moz-border-radius-topright: 9px; -webkit-border-top-left-radius: 9px; -webkit-border-top-right-radius: 9px; } div#portal-breadcrumbs { border: &dtml-borderWidth; &dtml-borderStyle; &dtml-globalBorderColor;; border-top-left-radius: 9px; border-top-right-radius: 9px; -moz-border-radius-topleft: 9px; -moz-border-radius-topright: 9px; -webkit-border-top-left-radius: 9px; -webkit-border-top-right-radius: 9px; } div#portal-top div#portal-breadcrumbs { border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; -moz-border-radius-bottomleft: 9px; -moz-border-radius-bottomright: 9px; -webkit-border-bottom-left-radius: 9px; -webkit-border-bottom-right-radius: 9px; border-top-left-radius: 0px; border-top-right-radius: 0px; -moz-border-radius-topleft: 0px; -moz-border-radius-topright: 0px; -webkit-border-top-left-radius: 0px; -webkit-border-top-right-radius: 0px; } .portlet { border: &dtml-borderWidth; &dtml-borderStyle; &dtml-globalBorderColor;; border-radius: 9px; -moz-border-radius: 9px; -webkit-border-radius: 9px; } .portletHeader { border: none; border-bottom: &dtml-borderWidth; &dtml-borderStyle; &dtml-globalBorderColor;; border-top-left-radius: 9px; border-top-right-radius: 9px; -moz-border-radius-topleft: 9px; -moz-border-radius-topright: 9px; -webkit-border-top-left-radius: 9px; -webkit-border-top-right-radius: 9px; } .portletItem { border: none; } .portletFooter { border: none; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; -moz-border-radius-bottomleft: 9px; -moz-border-radius-bottomright: 9px; -webkit-border-bottom-left-radius: 9px; -webkit-border-bottom-right-radius: 9px; } .managePortletsLink a, .managePortletsAboveLink a, .managePortletsBelowLink a { border-radius: 9px; -moz-border-radius: 9px; -webkit-border-radius: 9px; } .portletCalendar { border-radius: 9px; -moz-border-radius: 9px; -webkit-border-radius: 9px; } .documentContent { text-align: justify; border: &dtml-borderWidth; &dtml-borderStyle; &dtml-globalBorderColor;; border-bottom-left-radius: 9px; border-bottom-right-radius: 9px; -moz-border-radius-bottomleft: 9px; -moz-border-radius-bottomright: 9px; -webkit-border-bottom-left-radius: 9px; -webkit-border-bottom-right-radius: 9px; } #collage .collage-row { text-align: justify; } pre, input, fieldset, div.error, dl.portalMessage { border-radius: 9px; -moz-border-radius: 9px; -webkit-border-radius: 9px; } dl.portalMessage dt { border-top-left-radius: 7px; border-bottom-right-radius: 7px; -moz-border-radius-topleft: 7px; -moz-border-radius-bottomright: 7px; -webkit-border-top-left-radius: 7px; -webkit-border-bottom-right-radius: 7px; } legend { background-color: transparent; } .image-caption { text-align: center; font-size: smaller; font-weight: bold; } .fieldRequired { color: transparent; } .documentActions li { background-color: white; } #portal-footer { border: &dtml-borderWidth; &dtml-borderStyle; &dtml-globalBorderColor;; border-radius: 9px; -moz-border-radius: 9px; -webkit-border-radius: 9px; } #portal-colophon ul li { border-radius: 9px; -moz-border-radius: 9px; -webkit-border-radius: 9px; } .LSBox { border-radius: 9px; -moz-border-radius: 9px; -webkit-border-radius: 9px; } .livesearchContainer { border-radius: 9px; -moz-border-radius: 9px; -webkit-border-radius: 9px; } .LSIEFix { border-radius: 9px; -moz-border-radius: 9px; -webkit-border-radius: 9px; border-top-left-radius: 0px; -moz-border-radius-topleft: 0px; -webkit-border-top-left-radius: 0px; box-shadow: 4px 4px 4px #333; -moz-box-shadow: 4px 4px 4px #333; -webkit-box-shadow: 4px 4px 4px #333; } #livesearchLegend { background: White; border-top-left-radius: 9px; border-top-right-radius: 9px; -moz-border-radius-topleft: 9px; -moz-border-radius-topright: 9px; -webkit-border-top-left-radius: 9px; -webkit-border-top-right-radius: 9px; box-shadow: 4px 4px 4px #333; -moz-box-shadow: 4px 4px 4px #333; -webkit-box-shadow: 4px 4px 4px #333; } #searchGadget { border-radius: 9px; -moz-border-radius: 9px; -webkit-border-radius: 9px; } input.searchButton { border-radius: 9px; -moz-border-radius: 9px; -webkit-border-radius: 9px; } .contentViews { border: none; } .contentViews li a { border-top-left-radius: 9px; border-top-right-radius: 9px; -moz-border-radius-topleft: 9px; -moz-border-radius-topright: 9px; -webkit-border-top-left-radius: 9px; -webkit-border-top-right-radius: 9px; } .contentActions { border: &dtml-borderWidth; &dtml-borderStyle; &dtml-contentViewBorderColor;; border-bottom: none; border-top-left-radius: 9px; border-top-right-radius: 9px; -moz-border-radius-topleft: 9px; -moz-border-radius-topright: 9px; -webkit-border-top-left-radius: 9px; -webkit-border-top-right-radius: 9px; } dl.portalMessage_info { border-radius: 9px; -moz-border-radius: 9px; -webkit-border-radius: 9px; } #portal-globalnav-footer li { } dd.portletItemSingle { margin-left: 0; } /***********************************************************************-*-c-*- First, docutils' html4css stylesheet, inlined here for easy reference For easier upgrades, you may want to add your changes as overrides at the end *****************************************************************************/ /* :Author: David Goodger :Contact: [email protected] :Date: $Date: 2006-02-02 19:09:16 -0600 (Thu, 02 Feb 2006) $ :Revision: $Revision: 4332 $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to customize this style sheet. */ /* used to remove borders from tables and images */ .borderless, table.borderless td, table.borderless th { border: 0 } table.borderless td, table.borderless th { /* Override padding for "table.docutils td" with "! important". The right padding separates the table cells. */ padding: 0 0.5em 0 0 ! important } .first { /* Override more specific margin styles with "! important". */ margin-top: 0 ! important } .last, .with-subtitle { margin-bottom: 0 ! important } .hidden { display: none } a.toc-backref { text-decoration: none ; color: black } blockquote.epigraph { margin: 2em 5em ; } dl.docutils dd { margin-bottom: 0.5em } /* Uncomment (and remove this text!) to get bold-faced definition list terms dl.docutils dt { font-weight: bold } */ div.abstract { margin: 2em 5em } div.abstract p.topic-title { font-weight: bold ; text-align: center } div.admonition, div.attention, div.caution, div.danger, div.error, div.hint, div.important, div.note, div.tip, div.warning { margin: 2em ; border: medium outset ; padding: 1em } div.admonition p.admonition-title, div.hint p.admonition-title, div.important p.admonition-title, div.note p.admonition-title, div.tip p.admonition-title { font-weight: bold ; font-family: Corbel, sans-serif } div.attention p.admonition-title, div.caution p.admonition-title, div.danger p.admonition-title, div.error p.admonition-title, div.warning p.admonition-title { color: red ; font-weight: bold ; font-family: Corbel, sans-serif } /* Uncomment (and remove this text!) to get reduced vertical space in compound paragraphs. div.compound .compound-first, div.compound .compound-middle { margin-bottom: 0.5em } div.compound .compound-last, div.compound .compound-middle { margin-top: 0.5em } */ div.dedication { margin: 2em 5em ; text-align: center ; font-style: italic } div.dedication p.topic-title { font-weight: bold ; font-style: normal } div.figure { margin-left: 2em ; margin-right: 2em } div.footer, div.header { clear: both; font-size: smaller } div.line-block { display: block ; margin-top: 1em ; margin-bottom: 1em } div.line-block div.line-block { margin-top: 0 ; margin-bottom: 0 ; margin-left: 1.5em } div.sidebar { margin-left: 1em ; border: medium outset ; padding: 1em ; background-color: #ffffee ; width: 40% ; float: right ; clear: right } div.sidebar p.rubric { font-family: Corbel, sans-serif ; font-size: medium } div.system-messages { margin: 5em } div.system-messages h1 { color: red } div.system-message { border: medium outset ; padding: 1em } div.system-message p.system-message-title { color: red ; font-weight: bold } div.topic { margin: 2em } h1.section-subtitle, h2.section-subtitle, h3.section-subtitle, h4.section-subtitle, h5.section-subtitle, h6.section-subtitle { margin-top: 0.4em } h1.title { text-align: center } h2.subtitle { text-align: center } hr.docutils { width: 75% } img.align-left { margin: 4px; clear: left } img.align-right { margin: 4px; clear: right } ol.simple, ul.simple { margin-bottom: 1em } ol.arabic { list-style: decimal } ol.loweralpha { list-style: lower-alpha } ol.upperalpha { list-style: upper-alpha } ol.lowerroman { list-style: lower-roman } ol.upperroman { list-style: upper-roman } p.attribution { text-align: right ; margin-left: 50% } p.caption { font-style: italic } p.credits { font-style: italic ; font-size: smaller } p.label { white-space: nowrap } p.rubric { font-weight: bold ; font-size: larger ; color: maroon ; text-align: center } p.sidebar-title { font-family: Corbel, sans-serif ; font-weight: bold ; font-size: larger } p.sidebar-subtitle { font-family: Corbel, sans-serif ; font-weight: bold } p.topic-title { font-weight: bold } pre.address { margin-bottom: 0 ; margin-top: 0 ; font-family: Constantia, serif ; font-size: 100% } pre.literal-block, pre.doctest-block { margin-left: 2em ; margin-right: 2em } span.classifier { font-family: Corbel, sans-serif ; font-style: oblique } span.classifier-delimiter { font-family: Corbel, sans-serif ; font-weight: bold } span.interpreted { font-family: Corbel, sans-serif } span.option { white-space: nowrap } span.pre { white-space: pre } span.problematic { color: red } span.section-subtitle { /* font-size relative to parent (h1..h6 element) */ font-size: 80% } table.citation { border-left: solid 1px gray; margin-left: 1px } table.docinfo { margin: 2em 4em } table.docutils { margin-top: 0.5em ; margin-bottom: 0.5em } p + table.footnote { margin-top: 1em; border-top: 2px solid black; } table.footnote { /*border-left: solid 1px black; margin-left: 1px*/ font-size: smaller; } table.docutils td, table.docutils th, table.docinfo td, table.docinfo th { padding-left: 0.5em ; padding-right: 0.5em ; vertical-align: top } table.docutils th.field-name, table.docinfo th.docinfo-name { font-weight: bold ; text-align: left ; white-space: nowrap ; padding-left: 0 } h1 tt.docutils, h2 tt.docutils, h3 tt.docutils, h4 tt.docutils, h5 tt.docutils, h6 tt.docutils { font-size: 100% } ul.auto-toc { list-style-type: none } /***********************************************************************-*-c-*- Zwiki styles For easier upgrades, you may want to add your changes as overrides at the end *****************************************************************************/ /* colours and general styles */ /* default gray */ .shade1 {color:gray;} .shade1 a:link {color:gray;} .shade1 a:visited {color:gray;} .dimtext {font-size: 80%; color: gray;} .linkpanel td, .linkpanel_minimal td {color: gray;} .navpanel { color: gray; } .searchbox a:link {color: gray;} .searchbox a:visited {color: #aaaaaa;} .lasteditor {color:gray;} /* can't get these to combine with dimtext for some reason */ .highrated {font-size: 80%; color: green} .neutralrated {font-size: 80%; color: gray} .lowrated {font-size: 80%; color: red} .commentsheader {color:gray; font-weight:bold;} .commentform {color:gray;} .subscribed {} /* for subscribeform */ .notsubscribed {} /* background shade of form fields * input,textarea,select {} would be cleaner but changes buttons too * (except file upload buttons) * can select with input[type=submit] but not in IE */ .formfield {background: #f0f0f0;} .content pre, code, tt { padding: 4px; /*border-left: solid 1px #cc6600;*/ /* background-color: #ffeed0;*/ color: red; font-weight:bold; } .content { margin-top:2em; margin-left:2em; margin-right:2em; /* width: 80%; */ background-color: white; } td.content { border: 1px solid #EEEEEE; } .formcontent { margin:8px; } .error { color:red; background-color:#ffdddd; border:thin solid red; } /* top stuff */ .linkpanel td, .linkpanel_minimal td { font-size: 80%; } .linkpanel a { text-decoration:none; } .wikilinks { font-weight: normal; vertical-align: middle; } .otherlinks { vertical-align: middle; } .navpanel { font-size: 80%; } .pageheader ul { margin-top:0px; margin-bottom:0px; margin-left:6px; padding-left:6px; } .logo_full { /*td*/ padding-top: 0.5em; padding-right: 4px; } .logo_simple { /*td*/ padding-top: 1em; padding-right: 4px; } .logo_minimal { padding-top: 1em; padding-right: 4px; } .pagenameand { /*td*/ padding: 0; vertical-align: middle; } .pagenameonly { /*td*/ font-size: xx-large; font-weight: bold; vertical-align: middle; } .searchbox { vertical-align: top; font-size: 80%; } #searchinput { /* generated tag with id=searchinput already has a class (multiple classes bad for IE<5.5 ?) */ width:95%; margin-top: 0px; } .lasteditor { font-style: italic; } /* .preview { border:4px; width:100%; padding:10px; background-color:#ffffaa; } */ /* subtopics styling (also page context and wiki contents) * * a screenshot would be better, but here's what these intend to do * (known to work in at least firefox 2): * - gray subtopics heading * - no list bullets * - only slight indentation * - black links * - no link underlining * - top-level subtopics are bold * - links get smaller with depth (down to your browser's minimum if any) * * currently, hierarchical page links as seen in subtopics, page context * above the title and wiki contents use the "outline" and "expandable" classes * and subtopics are additionally in a div with "subtopics" class */ div.subtopics { margin-top:1em; } .subtopics h4 { color: gray; /* margin-bottom:0;*/ } .subtopics ul { margin-left:0; padding-left:0; font-weight:bold; } .subtopics ul ul { font-weight:normal; font-size:0.9em; } .outline ul { margin-left:1em; padding-left:0; } .outline li { list-style:none; } .outline a { text-decoration: none; color: black; } .expandable { } div#pagechildren { margin-left: 5em; } div#pagechildren .outline li { font-size: smaller; margin-bottom: 0; } /* bottom stuff */ .commentform { } /* misc stuff */ #youarehere { color:red; font-size:normal; font-weight:bold; } /* handy for displaying completion status */ .incomplete {color: red;} .zeropercent {color: red;} .tenpercent {color: red;} .twentypercent {color: red;} .thirtypercent {color: red;} .fortypercent {color: red;} .fiftypercent {color: red;} .sixtypercent {color: green;} .seventypercent {color: green;} .eightypercent {color: green;} .ninetypercent {color: green;} .onehundredpercent {color: green;} .complete {color: green;} /* .link-external { background: transparent url(http://plone.zwiki.org/link_icon.gif) 0px 1px no-repeat; padding: 1px 0px 1px 16px; } */ /* overrides for standard docutils styles on restructured text pages */ div.sidebar { margin-left: 1em ; margin-bottom: 1em; padding: 1em ; float: right ; clear: right ; border: none; /*background-color: #ddbbff;*/ } dt { font-weight:bold; } /* these two hide the blank line after comment headings on RST pages * NB commentbody is misleading, it's only the first paragraph for now. */ .commentheading { margin-bottom:0; } .commentbody { margin-top:0; } /***********************************************************************-*-c-*- MathAction styles For easier upgrades, you may want to add your changes as overrides at the end *****************************************************************************/ pre, code { white-space: pre; /* CSS2 */ white-space: -moz-pre-wrap; /* Mozilla */ white-space: -hp-pre-wrap; /* HP printers */ white-space: -o-pre-wrap; /* Opera 7 */ white-space: -pre-wrap; /* Opera 4-6 */ white-space: pre-wrap; /* CSS 2.1 */ white-space: pre-line; /* CSS 3 (and 2.1 as well, actually) */ word-wrap: break-word; /* IE */ } #axiomlabel { position:relative; right:0.8em; top:-0.2em; font-size:100%; color: #446644; font-family: Consolas, monospace; } #axiomcode { background-color: transparent; font-family: Consolas, monospace; font-size:100%; } #axiomtext { margin-left:2em; background-color: lightgrey; font-family: Consolas, monospace; font-size:85%; padding: 1ex; padding-top: 0; /* border-width: 0.5ex; border-style: inset; border-color:lightgrey;*/ } #axiomcode pre { margin-left:1.5em; background-color: lightgreen; font-family: Consolas, monospace; font-size:85%; padding: 1ex; padding-top: 0; /* border-width: 0.5ex; border-style: outset; border-color:lightgreen;*/ } #reducelabel { position:relative; right:0.8em; top:-0.2em; font-size:73%; color: #446644; font-family: Consolas, monospace; } #reducecode { width:98%; margin-left:1em; margin-bottom:1ex; margin-top:1ex; white-space:pre; background-color: lightpink; font-family: Consolas, monospace; font-size:85% /* border-width: 0.5ex; border-style: outset; border-color:lightpink;*/ padding: 1ex; padding-top: 0; } #reducecode pre { font-family: Consolas, monospace; font-size:85% } #maximalabel { position:relative; right:0.8em; top:-0.2em; font-size:100%; color: #448844; font-family: Consolas, monospace; } #maximacode { background-color: transparent; font-family: Consolas, monospace; font-size:85%; } #maximaoutput { margin-left:2em; background-color: transparent; font-family: Consolas, monospace; font-size:85%; } #maximainput { width:98%; margin-left:1em; margin-bottom:1ex; margin-top:1ex; background-color: lightblue; font-family: Consolas, monospace; font-size:85%; padding: 1ex; padding-top: 0; /* border-width: 0.5ex; border-style: inset; border-color:lightgrey;*/ } #maximacode pre { margin-left:1.5em; background-color: lightgrey; font-family: Consolas, monospace; font-size:85%; padding: 1ex; padding-top: 0; /* border-width: 0.5ex; border-style: outset; border-color:lightgrey;*/ } img.equation { vertical-align: middle; border: 0px } /***********************************************************************-*-c-*- Customizations for the above *****************************************************************************/ /* </dtml-with> */ /* DOCUMENTATION ON PRE-DEFINED PROPERTIES FROM PLONE */ /* You can insert colors and other variables from Plone's base_properties by doing: & dtml-variableName ; (without the spaces, excluded here to not make it render) Example: myLink { color: & dtml-fontColor ; (again, without the spaces) } This means you can generate your own elements that use Plone's defaults, and respect any customizations people have done. See base_properties for the default values. These are the available properties: logoName - the file name of the portal logo fontFamily - the font family used for all text that is not headers fontBaseSize - the base font size that everything is calculated from fontColor - the main font color fontSmallSize - used for various elements like buttons and discreet text discreetColor - the font color of discreet text backgroundColor - the background color linkColor - the color used on normal links linkActiveColor - color used on active links linkVisitedColor - color used on visited links borderWidth - the width of most borders in Plone borderStyle - the style of the border lines, normally solid borderStyleAnnotations - style of border lines on comments etc globalBorderColor - the border color used on the main tabs, the portlets etc globalBackgroundColor - background color for the selected tabs, portlet headings etc globalFontColor - the color of the font in the tabs and in portlet headings headingFontFamily - font family for h1/h2/h3/h4/h5/h6 headlines contentViewBorderColor - the content view tabs border color contentViewBackgroundColor - the content view tabs background color contentViewFontColor - the font color used in the content view tabs inputFontColor - the font color used for input elements textTransform - whether to lowercase text in portlets, tabs etc. evenRowBackgroundColor - the background color of even rows in listings oddRowBackgroundColor - the background color of even rows in listings notifyBorderColor - border color of notification elements like the status message, the calendar focus notifyBackgroundColor - background color of notification elements like the status message, the calendar focus helpBackgroundColor - background color of information pop-ups (currently not used) */
And after all of that ...
So, upshot is that it IS possible to run Plone on a 256Mb low end VPS though the running system will chew up a good ~500Mb so half your system is running out of swap which isn't as bad as it sounds when it's a static Plone site. However, while it functions okay for the £60 I was paying a year for the VPS, in the end we all tend to outgrow our early beginnings.
In the summer of 2009 I finally rented a fully dedicated server and then began the oh so fun process of migrating the Plone installation from the VPS onto the dedicated server. Do you think that would surely be easy? No, it wasn't!