<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.cloud.geek.nz/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" gd:etag="W/&quot;D0ABQn88fSp7ImA9WhRVGUo.&quot;"><id>tag:blogger.com,1999:blog-7615241590176793465</id><updated>2012-01-20T01:22:33.175+13:00</updated><category term="apache" /><category term="nzoss" /><category term="postgres" /><category term="optimisation" /><category term="sysadmin" /><category term="web" /><category term="security" /><category term="programming" /><category term="gerrit" /><category term="wine" /><category term="conference" /><category term="django" /><category term="rt" /><category term="firefox" /><category term="mutt" /><category term="privoxy" /><category term="gearman" /><category term="git" /><category term="python" /><category term="csp" /><category term="nginx" /><category term="ssl" /><category term="openwrt" /><category term="debian" /><category term="catalyst" /><category term="mozilla" /><category term="mahara" /><category term="launchpad" /><category term="ubuntu" /><category term="raid" /><category term="browserid" /><category term="plupload" /><category term="database" /><category term="backup" /><category term="gstreamer" /><category term="gargoyle" /><title type="text">Feeding the Cloud</title><subtitle type="html">Sharing technical tips and tricks, as well as things I figured out the hard way.</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://feeding.cloud.geek.nz/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://feeding.cloud.geek.nz/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>88</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.cloud.geek.nz/FeedingTheCloud" /><feedburner:info xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" uri="feedingthecloud" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><link rel="license" type="text/html" href="http://creativecommons.org/licenses/by-sa/3.0/" /><logo>http://creativecommons.org/images/public/somerights20.gif</logo><feedburner:emailServiceId xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">FeedingTheCloud</feedburner:emailServiceId><feedburner:feedburnerHostname xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">http://feedburner.google.com</feedburner:feedburnerHostname><entry gd:etag="W/&quot;C0QHQn4yeyp7ImA9WhRVFU4.&quot;"><id>tag:blogger.com,1999:blog-7615241590176793465.post-963415010433858516</id><published>2012-01-14T21:45:00.000+13:00</published><updated>2012-01-14T21:55:33.093+13:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-14T21:55:33.093+13:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="catalyst" /><category scheme="http://www.blogger.com/atom/ns#" term="debian" /><category scheme="http://www.blogger.com/atom/ns#" term="sysadmin" /><category scheme="http://www.blogger.com/atom/ns#" term="openwrt" /><category scheme="http://www.blogger.com/atom/ns#" term="ubuntu" /><category scheme="http://www.blogger.com/atom/ns#" term="nzoss" /><category scheme="http://www.blogger.com/atom/ns#" term="gargoyle" /><title>Debugging OpenWRT routers by shipping logs to a remote syslog server</title><content type="html">Trying to debug problems with consumer-grade routers is notoriously difficult due to a lack of decent debugging information. It's quite hard to know what's going on without at least a few good error messages.&lt;br /&gt;&lt;br /&gt;Here is how I made my &lt;a href="https://openwrt.org/"&gt;OpenWRT&lt;/a&gt;-based &lt;a href="http://gargoyle-router.org/"&gt;Gargoyle&lt;/a&gt; router send its log messages to a network server running &lt;a href="http://rsyslog.com/"&gt;rsyslog&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Server Configuration&lt;/h3&gt;Given that the router (&lt;tt&gt;192.168.1.1&lt;/tt&gt;) will be sending its log messages on UDP port 514, I started by opening that port in my firewall:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;iptables -A INPUT -s 192.168.1.1 -p udp --dport 514 -j ACCEPT&lt;/pre&gt;&lt;/blockquote&gt;Then I enabled the UDP module for rsyslog and redirected messages to a separate log file (so that it doesn't fill up &lt;tt&gt;/var/log/syslog&lt;/tt&gt;) by putting the following (a modified version of &lt;a href="http://rsyslog.com/storing-messages-from-a-remote-system-into-a-specific-file/"&gt;these instructions&lt;/a&gt;) in &lt;tt&gt;/etc/rsyslog.d/10-gargoyle-router.conf&lt;/tt&gt;:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;$ModLoad imudp&lt;br /&gt;$UDPServerRun 514&lt;br /&gt;:fromhost-ip, isequal, "192.168.1.1" /var/log/gargoyle-router.log&lt;br /&gt;&amp;amp; ~&lt;/pre&gt;&lt;/blockquote&gt;The name of the file is important because this configuration snipet needs to be loaded before the directive which writes to &lt;tt&gt;/var/log/syslog&lt;/tt&gt; for the discard statement (the "&amp;amp; ~" line) to &lt;a href="http://lists.adiscon.net/pipermail/rsyslog/2012-January/014201.html"&gt;work correctly&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Router Configuration&lt;/h3&gt;Finally, I followed the &lt;a href="http://www.gargoyle-router.com/wiki/doku.php?id=remote_syslog"&gt;instructions&lt;/a&gt; on the Gargoyle wiki to get the router to forward its log messages to my server (&lt;tt&gt;192.168.1.2&lt;/tt&gt;).&lt;br /&gt;&lt;br /&gt;After logging into the router via ssh, I ran the following commands:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;uci set system.@system[0].log_ip=192.168.1.2&lt;br /&gt;uci set system.@system[0].conloglevel=7&lt;br /&gt;uci commit&lt;/pre&gt;&lt;/blockquote&gt;before rebooting the router.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Now whenever I have to troubleshoot network problems, I can keep a terminal open on my server and get some visibility on what the router is doing:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;tail -f /var/log/gargoyle-router.log&lt;/pre&gt;&lt;/blockquote&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-963415010433858516?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=YTR_eRFOxBc:iLV8SmwjSi0:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=YTR_eRFOxBc:iLV8SmwjSi0:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?i=YTR_eRFOxBc:iLV8SmwjSi0:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=YTR_eRFOxBc:iLV8SmwjSi0:cGdyc7Q-1BI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=cGdyc7Q-1BI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=YTR_eRFOxBc:iLV8SmwjSi0:YwkR-u9nhCs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=YwkR-u9nhCs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/FeedingTheCloud/~4/YTR_eRFOxBc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://feeding.cloud.geek.nz/feeds/963415010433858516/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=963415010433858516" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/963415010433858516?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/963415010433858516?v=2" /><link rel="alternate" type="text/html" href="http://feeding.cloud.geek.nz/2012/01/debugging-openwrt-routers-by-shipping.html" title="Debugging OpenWRT routers by shipping logs to a remote syslog server" /><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;D0EEQXg9eip7ImA9WhRQGUo.&quot;"><id>tag:blogger.com,1999:blog-7615241590176793465.post-3658102873564371186</id><published>2011-12-16T08:00:00.000+13:00</published><updated>2011-12-16T08:00:00.662+13:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-16T08:00:00.662+13:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="catalyst" /><category scheme="http://www.blogger.com/atom/ns#" term="debian" /><category scheme="http://www.blogger.com/atom/ns#" term="ubuntu" /><category scheme="http://www.blogger.com/atom/ns#" term="nzoss" /><title>Installing Etherpad on Debian/Ubuntu</title><content type="html">&lt;a href="http://etherpad.org/"&gt;Etherpad&lt;/a&gt; is an excellent Open Source web application for collaborative text editing. Like Google Docs, it allows you to share documents with others through a secret URL or to set up private documents for which people need a login.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It's a little tricky to install so here's how I did it.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Build a Debian package&lt;/h3&gt;Because the &lt;a href="http://apt.etherpad.org/"&gt;official repository&lt;/a&gt; is not kept up to date, you must build the package yourself:&lt;ol&gt;&lt;li&gt;Grab the master branch from the official &lt;a href="https://github.com/ether/pad"&gt;git repository&lt;/a&gt;:&lt;br /&gt;&lt;pre&gt;git clone git://github.com/ether/pad.git etherpad&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;Build the package:&lt;br /&gt;&lt;pre&gt;dpkg-buildpackage -us -uc&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;Now, install some of its dependencies:&lt;br /&gt;&lt;pre&gt;apt-get install --no-install-recommends dbconfig-common python-uno mysql-server&lt;/pre&gt;&lt;br /&gt;before installing the .deb you built:&lt;br /&gt;&lt;pre&gt;dpkg -i etherpad_1.1.deb&lt;br /&gt;apt-get install --no-install-recommends -f&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Application configuration&lt;/h3&gt;You will likely need to change a few minor things in the default configuration at &lt;tt&gt;/etc/etherpad/etherpad.local.properties&lt;/tt&gt;:&lt;br /&gt;&lt;pre&gt;useHttpsUrls = true&lt;br /&gt;customBrandingName = ExamplePad&lt;br /&gt;customEmailAddress = etherpad@example.com&lt;br /&gt;topdomains = etherpad.example.com,&lt;i&gt;your.external.ip.address&lt;/i&gt;,127.0.0.1,localhost,localhost.localdomain&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Nginx configuration&lt;/h3&gt;If you use &lt;a href="http://nginx.org/"&gt;Nginx&lt;/a&gt; as your web server of choice, create a vhost file in &lt;tt&gt;/etc/nginx/sites-available/etherpad&lt;/tt&gt;:&lt;br /&gt;&lt;pre&gt;server {&lt;br /&gt;  listen 443;&lt;br /&gt;  server_name etherpad.example.com *.etherpad.example.com;&lt;br /&gt;  add_header Strict-Transport-Security max-age=15768000;&lt;br /&gt;&lt;br /&gt;  ssl on;&lt;br /&gt;  ssl_certificate  /etc/ssl/certs/etherpad.example.com.crt;&lt;br /&gt;  ssl_certificate_key  /etc/ssl/certs/etherpad.example.com.pem;&lt;br /&gt;&lt;br /&gt;  ssl_session_timeout  5m;&lt;br /&gt;  ssl_session_cache shared:SSL:1m;&lt;br /&gt;&lt;br /&gt;  ssl_protocols  TLSv1;&lt;br /&gt;  ssl_ciphers  RC4-SHA:HIGH:!kEDH;&lt;br /&gt;  ssl_prefer_server_ciphers   on;&lt;br /&gt;&lt;br /&gt;  access_log /var/log/nginx/etherpad.access.log;&lt;br /&gt;  error_log /var/log/nginx/etherpad.error.log;&lt;br /&gt;&lt;br /&gt;  location / {&lt;br /&gt;    proxy_pass http://localhost:9000/;&lt;br /&gt;    proxy_set_header Host $host;&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;and then enable it and restart Nginx:&lt;br /&gt;&lt;pre&gt;/etc/init.d/nginx restart&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Apache configuration&lt;/h3&gt;If you prefer to use &lt;a href="http://httpd.apache.org/"&gt;Apache&lt;/a&gt; instead, make sure that the required modules are enabled:&lt;br /&gt;&lt;pre&gt;a2enmod proxy&lt;br /&gt;a2enmod proxy_http&lt;/pre&gt;&lt;br /&gt;and then create a vhost file in &lt;tt&gt;/etc/apache2/sites-available/etherpad&lt;/tt&gt;:&lt;br /&gt;&lt;pre&gt;&amp;lt;VirtualHost *:443&amp;gt;&lt;br /&gt;   ServerName etherpad.example.com&lt;br /&gt;   ServerAlias *.etherpad.example.com&lt;br /&gt;&lt;br /&gt;   SSLEngine on&lt;br /&gt;   SSLCertificateFile /etc/apache2/ssl/etherpad.example.com.crt&lt;br /&gt;   SSLCertificateKeyFile /etc/apache2/ssl/etherpad.example.com.pem&lt;br /&gt;   SSLCertificateChainFile /etc/apache2/ssl/etherpad.example.com-chain.pem&lt;br /&gt;&lt;br /&gt;   SSLProtocol TLSv1&lt;br /&gt;   SSLHonorCipherOrder On&lt;br /&gt;   SSLCipherSuite RC4-SHA:HIGH:!kEDH&lt;br /&gt;   Header add Strict-Transport-Security: "max-age=15768000"&lt;br /&gt;&lt;br /&gt;   &amp;lt;Proxy&amp;gt;&lt;br /&gt;       Order deny,allow&lt;br /&gt;       Allow from all&lt;br /&gt;   &amp;lt;/Proxy&amp;gt;&lt;br /&gt;&lt;br /&gt;   Alias /sitemap.xml /ep/tag/\?format=sitemap&lt;br /&gt;   Alias /static /usr/share/etherpad/etherpad/src/static&lt;br /&gt;&lt;br /&gt;   ProxyPreserveHost On&lt;br /&gt;   SetEnv proxy-sendchunked 1&lt;br /&gt;   ProxyRequests Off&lt;br /&gt;   ProxyPass / http://localhost:9000/&lt;br /&gt;   ProxyPassReverse / http://localhost:9000/&lt;br /&gt;&amp;lt;/VirtualHost&amp;gt;&lt;/pre&gt;&lt;br /&gt;before enabling that new vhost and restarting Apache:&lt;br /&gt;&lt;pre&gt;a2ensite etherpad&lt;br /&gt;apache2ctl configtest&lt;br /&gt;apache2ctl graceful&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;DNS setup&lt;/h3&gt;The final step is to create these two DNS entries to point to your web server:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;*.etherpad.example.com&lt;/li&gt;&lt;li&gt;etherpad.example.com&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Also, as a precaution against an &lt;a href="https://github.com/ether/pad/issues/257"&gt;OpenOffice/LibreOffice-related bug&lt;/a&gt;, I suggest that you add the following entry to your web server's &lt;tt&gt;/etc/hosts&lt;/tt&gt; file to avoid flooding your DNS resolver with bogus queries:&lt;br /&gt;&lt;pre&gt;127.0.0.1 localhost.(none) localhost.(none).&lt;i&gt;fulldomain.example.com&lt;/i&gt;&lt;/pre&gt;where &lt;i&gt;fulldomain.example.com&lt;/i&gt; is the search base defined in &lt;tt&gt;/etc/resolv.conf&lt;/tt&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Other useful instructions&lt;/h3&gt;Here are the most useful pages I used while setting this up:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://code.google.com/p/etherpad/wiki/Instructions"&gt;Official installation instructions&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://wiki.ubuntu.com/Etherpad"&gt;Ubuntu Wiki instructions&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://sis.bthstudent.se/2011/525/how-to-install-etherpad-on-debian-lenny/"&gt;Instructions for Debian Lenny&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://mclear.co.uk/2010/08/11/etherpad-nginx-config/"&gt;Nginx Configuration&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-3658102873564371186?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=1rJJSvDfV6k:gz21hD5OCwo:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=1rJJSvDfV6k:gz21hD5OCwo:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?i=1rJJSvDfV6k:gz21hD5OCwo:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=1rJJSvDfV6k:gz21hD5OCwo:cGdyc7Q-1BI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=cGdyc7Q-1BI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=1rJJSvDfV6k:gz21hD5OCwo:YwkR-u9nhCs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=YwkR-u9nhCs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/FeedingTheCloud/~4/1rJJSvDfV6k" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://feeding.cloud.geek.nz/feeds/3658102873564371186/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=3658102873564371186" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/3658102873564371186?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/3658102873564371186?v=2" /><link rel="alternate" type="text/html" href="http://feeding.cloud.geek.nz/2011/12/installing-etherpad-on-debianubuntu.html" title="Installing Etherpad on Debian/Ubuntu" /><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg" /></author><thr:total>1</thr:total></entry><entry gd:etag="W/&quot;DUYASXc6eyp7ImA9WhRRGUo.&quot;"><id>tag:blogger.com,1999:blog-7615241590176793465.post-6531503891254391814</id><published>2011-12-04T18:30:00.001+13:00</published><updated>2011-12-04T18:39:08.913+13:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-04T18:39:08.913+13:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="catalyst" /><category scheme="http://www.blogger.com/atom/ns#" term="optimisation" /><category scheme="http://www.blogger.com/atom/ns#" term="debian" /><category scheme="http://www.blogger.com/atom/ns#" term="web" /><category scheme="http://www.blogger.com/atom/ns#" term="nzoss" /><title>Optimising PNG files</title><content type="html">I have written about &lt;a href="http://feeding.cloud.geek.nz/2009/10/reducing-website-bandwidth-usage.html"&gt;using lossless optimisations techniques to reduce the size of images&lt;/a&gt; before, but I recently learned of a few other &lt;a href="http://developer.yahoo.com/yslow/smushit/faq.html#faq_crushtool"&gt;tools&lt;/a&gt; to further reduce the size of &lt;a href="http://en.wikipedia.org/wiki/Portable_Network_Graphics"&gt;PNG&lt;/a&gt; images.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Basic optimisation&lt;/h3&gt;While you could use &lt;a href="http://www.smushit.com/"&gt;Smush.it&lt;/a&gt; to manually optimise your images, if you want a single Open Source tool you can use in your scripts, &lt;a href="http://optipng.sourceforge.net/"&gt;optipng&lt;/a&gt; is the most effective one:&lt;br /&gt;&lt;pre&gt;optipng -o9 image.png&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Removing unnecessary chunks&lt;/h3&gt;While not as effective as optipng in its basic optimisation mode, &lt;a href="http://pmt.sourceforge.net/pngcrush/"&gt;pngcrush&lt;/a&gt; can be used remove &lt;a href="http://en.wikipedia.org/wiki/Portable_Network_Graphics#Ancillary_chunks"&gt;unnecessary chunks&lt;/a&gt; from PNG files:&lt;br /&gt;&lt;pre&gt;pngcrush -q -rem gAMA -rem alla -rem text image.png image.crushed.png&lt;/pre&gt;Depending on the software used to produce the original PNG file, this can yield significant savings so I usually start with this.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Reducing the colour palette&lt;/h3&gt;When optimising images uploaded by users, it's not possible to know whether or not the palette size can be reduced without too much quality degradation. On the other hand, if you are optimising your own images, it might be worth trying this lossy optimisation technique.&lt;br /&gt;&lt;br /&gt;For example, &lt;a href="http://cdn.libravatar.org/nobody/512.png"&gt;this image&lt;/a&gt; went from 7.2 kB to 5.2 kB after running it through &lt;a href="http://pngnq.sourceforge.net/"&gt;pngnq&lt;/a&gt;:&lt;br /&gt;&lt;pre&gt;pngnq -f -n 32 -s 3 image.png&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Re-compressing final image&lt;/h3&gt;Most PNG writers use &lt;a href="http://zlib.net/"&gt;zlib&lt;/a&gt; to compress the final output but it turns out that there are better algorithms to do this.&lt;br /&gt;&lt;br /&gt;Using &lt;a href="http://advancemame.sourceforge.net/comp-readme.html"&gt;AdvanceCOMP&lt;/a&gt; I was able to bring the &lt;a href="http://cdn.libravatar.org/nobody/512.png"&gt;same image&lt;/a&gt; as above from 5.1kB to 4.6kB:&lt;br /&gt;&lt;pre&gt;advpng -z -4 image.png&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;When the source image is an SVG&lt;/h3&gt;Another thing I noticed while optimising PNG files is that rendering a PNG of the right size straight from an SVG file produces a smaller result than exporting a large PNG from that same SVG and then resizing the PNG to smaller sizes.&lt;br /&gt;&lt;br /&gt;Here's how you can use &lt;a href="http://inkscape.org/"&gt;Inkscape&lt;/a&gt; to generate an 80x80 PNG:&lt;br /&gt;&lt;pre&gt;inkscape --without-gui --export-width=80 --export-height=80 --export-png=80.png image.svg&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-6531503891254391814?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=xTGdPGp4ATs:h4ihXXFMzWA:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=xTGdPGp4ATs:h4ihXXFMzWA:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?i=xTGdPGp4ATs:h4ihXXFMzWA:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=xTGdPGp4ATs:h4ihXXFMzWA:cGdyc7Q-1BI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=cGdyc7Q-1BI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=xTGdPGp4ATs:h4ihXXFMzWA:YwkR-u9nhCs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=YwkR-u9nhCs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/FeedingTheCloud/~4/xTGdPGp4ATs" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://feeding.cloud.geek.nz/feeds/6531503891254391814/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=6531503891254391814" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/6531503891254391814?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/6531503891254391814?v=2" /><link rel="alternate" type="text/html" href="http://feeding.cloud.geek.nz/2011/12/optimising-png-files.html" title="Optimising PNG files" /><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg" /></author><thr:total>2</thr:total></entry><entry gd:etag="W/&quot;CE8ESXc9fSp7ImA9WhRSEkU.&quot;"><id>tag:blogger.com,1999:blog-7615241590176793465.post-6369920808204739806</id><published>2011-11-15T04:00:00.001+13:00</published><updated>2011-11-15T04:00:08.965+13:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-11-15T04:00:08.965+13:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="catalyst" /><category scheme="http://www.blogger.com/atom/ns#" term="debian" /><category scheme="http://www.blogger.com/atom/ns#" term="nginx" /><category scheme="http://www.blogger.com/atom/ns#" term="ssl" /><category scheme="http://www.blogger.com/atom/ns#" term="sysadmin" /><category scheme="http://www.blogger.com/atom/ns#" term="security" /><category scheme="http://www.blogger.com/atom/ns#" term="apache" /><category scheme="http://www.blogger.com/atom/ns#" term="web" /><category scheme="http://www.blogger.com/atom/ns#" term="ubuntu" /><category scheme="http://www.blogger.com/atom/ns#" term="nzoss" /><title>Ideal OpenSSL configuration for Apache and nginx</title><content type="html">After recently reading a number of &lt;a href="https://secure.wikimedia.org/wikipedia/en/wiki/Transport_Layer_Security"&gt;SSL/TLS&lt;/a&gt;-related articles, I decided to experiment and look for the ideal OpenSSL configuration for &lt;a href="http://httpd.apache.org/"&gt;Apache&lt;/a&gt; (using &lt;a href="https://httpd.apache.org/docs/2.2/mod/mod_ssl.html"&gt;mod_ssl&lt;/a&gt; since I haven't tried &lt;a href="http://modgnutls.sourceforge.net/"&gt;mod_gnutls&lt;/a&gt; yet) and &lt;a href="http://www.nginx.org/"&gt;nginx&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;By "ideal" I mean that this configuration needs to be &lt;b&gt;compatible&lt;/b&gt; with most user agents likely to interact with my website as well as being &lt;b&gt;fast&lt;/b&gt; and &lt;b&gt;secure&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt;Here is what I came up with for Apache:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;SSLProtocol TLSv1&lt;br /&gt;SSLHonorCipherOrder On&lt;br /&gt;SSLCipherSuite RC4-SHA:HIGH:!kEDH&lt;/pre&gt;&lt;/blockquote&gt;and for nginx:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;ssl_protocols  TLSv1;&lt;br /&gt;ssl_ciphers  RC4-SHA:HIGH:!kEDH;&lt;br /&gt;ssl_prefer_server_ciphers   on;&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;h3&gt;Cipher and protocol selection&lt;/h3&gt;In terms of choosing a cipher to use, this configuration does three things:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;disables all weak ciphers and protocols&lt;/li&gt;&lt;li&gt;disables &lt;a href="http://matt.io/technobabble/hivemind_devops_alert:_nginx_does_not_suck_at_ssl"&gt;very slow ciphers&lt;/a&gt; that use ephemeral Diffie-Hellman exchanges&lt;/li&gt;&lt;li&gt;gives priority to the &lt;a href="http://blog.ivanristic.com/2009/08/is-rc4-safe-for-use-in-ssl.html"&gt;RC4 cipher&lt;/a&gt; to &lt;a href="http://zombe.es/post/4078724716/openssl-cipher-selection"&gt;minimize CPU usage&lt;/a&gt; and &lt;a href="http://www.phonefactor.com/blog/slaying-beast-mitigating-the-latest-ssltls-vulnerability.php"&gt;defend against the BEAST attack&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h3&gt;Testing tools&lt;/h3&gt;The main tool I used while testing various configurations was the &lt;a href="https://www.ssllabs.com/ssldb/analyze.html"&gt;SSL labs online tool&lt;/a&gt;. The &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/cipherfox/"&gt;CipherFox extension&lt;/a&gt; for Firefox was also quite useful to quickly identify the selected cipher.&lt;br /&gt;&lt;br /&gt;Of course, you'll want to make sure that your configuration works in common browsers, but you should also test with tools like &lt;a href="https://www.gnu.org/software/wget/"&gt;wget&lt;/a&gt;, &lt;a href="http://curl.haxx.se/"&gt;curl&lt;/a&gt; and &lt;a href="http://www.vanheusden.com/httping/"&gt;httping&lt;/a&gt;. Many of the online monitoring services are based on these.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Other considerations&lt;/h3&gt;To increase the performance and security of your connections, you should ensure that the following features are enabled:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://hezmatt.org/~mpalmer/blog/2011/06/28/ssl-session-caching-in-nginx.html"&gt;SSL session caching&lt;/a&gt; with a session store shared between all of your web servers&lt;/li&gt;&lt;li&gt;&lt;a href="https://secure.wikimedia.org/wikipedia/en/wiki/HTTP_Strict_Transport_Security"&gt;HSTS headers&lt;/a&gt; to let browsers know that they should always visit your site over HTTPS&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Note: If you have different SSL-enabled name-based vhosts on the same IP address (using &lt;a href="https://secure.wikimedia.org/wikipedia/en/wiki/Server_Name_Indication"&gt;SNI&lt;/a&gt;), make sure that their SSL cipher and protocol settings are identical.&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-6369920808204739806?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=UJXTmRnm2vo:fI8EfIqc_IE:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=UJXTmRnm2vo:fI8EfIqc_IE:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?i=UJXTmRnm2vo:fI8EfIqc_IE:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=UJXTmRnm2vo:fI8EfIqc_IE:cGdyc7Q-1BI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=cGdyc7Q-1BI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=UJXTmRnm2vo:fI8EfIqc_IE:YwkR-u9nhCs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=YwkR-u9nhCs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/FeedingTheCloud/~4/UJXTmRnm2vo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://feeding.cloud.geek.nz/feeds/6369920808204739806/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=6369920808204739806" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/6369920808204739806?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/6369920808204739806?v=2" /><link rel="alternate" type="text/html" href="http://feeding.cloud.geek.nz/2011/11/ideal-openssl-configuration-for-apache.html" title="Ideal OpenSSL configuration for Apache and nginx" /><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg" /></author><thr:total>5</thr:total></entry><entry gd:etag="W/&quot;Ck8ERXwyfCp7ImA9WhRTEUk.&quot;"><id>tag:blogger.com,1999:blog-7615241590176793465.post-8275381676575805769</id><published>2011-11-01T22:40:00.002+13:00</published><updated>2011-11-01T22:46:44.294+13:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-11-01T22:46:44.294+13:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="catalyst" /><category scheme="http://www.blogger.com/atom/ns#" term="security" /><category scheme="http://www.blogger.com/atom/ns#" term="web" /><category scheme="http://www.blogger.com/atom/ns#" term="browserid" /><category scheme="http://www.blogger.com/atom/ns#" term="csp" /><category scheme="http://www.blogger.com/atom/ns#" term="nzoss" /><category scheme="http://www.blogger.com/atom/ns#" term="mozilla" /><title>Using BrowserID and Content Security Policy together</title><content type="html">While looking into why &lt;a href="https://browserid.org/"&gt;BrowserID&lt;/a&gt; logins on &lt;a href="https://www.libravatar.org/"&gt;Libravatar&lt;/a&gt; didn't work on &lt;a href="http://firefox.com/"&gt;Firefox&lt;/a&gt;, I remembered that I had recently added &lt;a href="https://developer.mozilla.org/en/Security/CSP"&gt;Content Security Policy&lt;/a&gt; headers. Here's what I had to do to make BrowserID work on a CSP-enabled site.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Create a hidden form and a login link&lt;/h3&gt;This is what the login button looked like before CSP:&lt;br /&gt;&lt;pre&gt;&amp;lt;form id="browserid-form" action="/account/login_browserid" method="post"&amp;gt;&lt;br /&gt;&amp;lt;input id="browserid-assertion" type="hidden" name="assertion" value=""&amp;gt;&lt;br /&gt;&amp;lt;input style="display: none" type="submit"&amp;gt;&lt;br /&gt;&amp;lt;/form&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;a href="javascript:try_browserid()"&amp;gt;Login with BrowserID&amp;lt;/a&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;script type="text/javascript"&amp;gt;&lt;br /&gt;function try_browserid() {&lt;br /&gt;navigator.id.getVerifiedEmail(function(assertion) {&lt;br /&gt;    if (assertion) {&lt;br /&gt;        document.getElementById('browserid-assertion').setAttribute('value', assertion);&lt;br /&gt;        document.getElementById('browserid-form').submit();&lt;br /&gt;    }&lt;br /&gt;});&lt;br /&gt;}&lt;br /&gt;&amp;lt;/script&amp;gt;&lt;/pre&gt;The hidden form is there because the assertion needs to be sent to the application via &lt;tt&gt;POST&lt;/tt&gt; to avoid leaking it out, but otherwise the code is pretty straightforward.&lt;br /&gt;&lt;br /&gt;Now of course, with CSP turned ON, there are two problems:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;links &lt;a href="https://developer.mozilla.org/en/Security/CSP/Default_CSP_restrictions#javascript:.C2.A0URIs"&gt;cannot use&lt;/a&gt; &lt;tt&gt;javascript:&lt;/tt&gt; URIs&lt;/li&gt;&lt;li&gt;inline Javascript is &lt;a href="https://developer.mozilla.org/en/Security/CSP/Default_CSP_restrictions#Internal_.3Cscript.3E_nodes"&gt;forbidden&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;So we can start by converting the login link to:&lt;br /&gt;&lt;pre&gt;&amp;lt;a id="browserid-link" href="#"&amp;gt;Login with BrowserID&amp;lt;/a&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;script src="browserid_stuff.js" type="text/javascript"&amp;gt;&lt;/pre&gt;then moving the &lt;tt&gt;try_browserid()&lt;/tt&gt; function to a separate file to be served from the same domain and finally hooking it up to the &lt;tt&gt;try_browserid()&lt;/tt&gt; function using Javascript (in that same &lt;tt&gt;browserid_stuff.js&lt;/tt&gt; file):&lt;br /&gt;&lt;pre&gt;var link = document.getElementById('browserid-link');&lt;br /&gt;link.onclick = try_browserid;&lt;br /&gt;link.addEventListener('click', try_browserid, false);&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Exposing the right X-Content-Security-Policy header&lt;/h3&gt;In order to load the Javascript code from &lt;tt&gt;browserid.org&lt;/tt&gt;, we need the following as part of the policy:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;script-src https://browserid.org&lt;/pre&gt;&lt;/blockquote&gt;but that's not enough since the BrowserID login form seems to use some sort of &lt;tt&gt;&amp;lt;iframe&amp;gt;&lt;/tt&gt; trick and so we need to add this extra permission as well:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;frame-src https://browserid.org&lt;/pre&gt;&lt;/blockquote&gt;Here is the final policy I ended up setting (using Apache &lt;a href="http://httpd.apache.org/docs/2.2/mod/mod_headers.html"&gt;mod_headers&lt;/a&gt;) for the Libravatar login page:&lt;br /&gt;&lt;pre&gt;&amp;lt;Location /account/login&amp;gt;&lt;br /&gt;Header set X-Content-Security-Policy: "default-src 'self'; frame-src 'self' https://browserid.org ; script-src 'self' https://browserid.org"&lt;br /&gt;&amp;lt;/Location&amp;gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-8275381676575805769?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=E36B9M2jAV0:5CijjGqBYAk:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=E36B9M2jAV0:5CijjGqBYAk:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?i=E36B9M2jAV0:5CijjGqBYAk:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=E36B9M2jAV0:5CijjGqBYAk:cGdyc7Q-1BI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=cGdyc7Q-1BI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=E36B9M2jAV0:5CijjGqBYAk:YwkR-u9nhCs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=YwkR-u9nhCs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/FeedingTheCloud/~4/E36B9M2jAV0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://feeding.cloud.geek.nz/feeds/8275381676575805769/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=8275381676575805769" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/8275381676575805769?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/8275381676575805769?v=2" /><link rel="alternate" type="text/html" href="http://feeding.cloud.geek.nz/2011/11/using-browserid-and-content-security.html" title="Using BrowserID and Content Security Policy together" /><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;DUMMQHY5eyp7ImA9WhdaE08.&quot;"><id>tag:blogger.com,1999:blog-7615241590176793465.post-7865698729490571205</id><published>2011-10-23T12:50:00.000+13:00</published><updated>2011-10-23T12:51:21.823+13:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-10-23T12:51:21.823+13:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="catalyst" /><category scheme="http://www.blogger.com/atom/ns#" term="optimisation" /><category scheme="http://www.blogger.com/atom/ns#" term="debian" /><category scheme="http://www.blogger.com/atom/ns#" term="sysadmin" /><category scheme="http://www.blogger.com/atom/ns#" term="apache" /><category scheme="http://www.blogger.com/atom/ns#" term="nzoss" /><title>Reducing the size of Apache 301 and 302 responses</title><content type="html">Looking through the &lt;a href="https://www.libravatar.org/"&gt;Libravatar&lt;/a&gt; access logs, I found that most of the traffic we currently serve consists of 302 redirects to Gravatar. Optimising that path is therefore very important.&lt;br /&gt;&lt;br /&gt;While Apache allows admins to provide &lt;a href="https://httpd.apache.org/docs/2.2/mod/core.html#errordocument"&gt;custom error pages&lt;/a&gt; for things like 404 or 500, it's not quite that straightforward for 30x return codes.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Standard 301 / 302 responses&lt;/h3&gt;By default, Apache (and most web servers out there) returns a fairly large HTML page along with a 30x redirection. Try it for yourself by disabling automatic redirections in Firefox (Preferences | Advanced | General | Accessibility) or by installing the &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/requestpolicy/?src=search"&gt;Request Policy&lt;/a&gt; add-on.&lt;br /&gt;&lt;br /&gt;The 302 responses sent by Libravatar looked like this:&lt;br /&gt;&lt;pre&gt;$ curl -i http://cdn.libravatar.org/avatar/12345678901234567890123456789012&lt;br /&gt;HTTP/1.1 302 Found&lt;br /&gt;Date: Wed, 21 Sep 2011 01:51:52 GMT&lt;br /&gt;Server: Apache&lt;br /&gt;Cache-Control: max-age=86400&lt;br /&gt;Location: http://www.gravatar.com/avatar/12345678901234567890123456789012.jpg?r=g&amp;amp;s=80&amp;amp;d=http://cdn.libravatar.org/nobody/80.png&lt;br /&gt;Vary: Accept-Encoding&lt;br /&gt;Content-Length: 310&lt;br /&gt;Content-Type: text/html; charset=iso-8859-1&lt;br /&gt;&lt;br /&gt;&amp;lt;!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"&amp;gt;&lt;br /&gt;&amp;lt;html&amp;gt;&amp;lt;head&amp;gt;&lt;br /&gt;&amp;lt;title&amp;gt;302 Found&amp;lt;/title&amp;gt;&lt;br /&gt;&amp;lt;/head&amp;gt;&amp;lt;body&amp;gt;&lt;br /&gt;&amp;lt;h1&amp;gt;Found&amp;lt;/h1&amp;gt;&lt;br /&gt;&amp;lt;p&amp;gt;The document has moved &amp;lt;a href="http://www.gravatar.com/avatar/12345678901234567890123456789012.jpg?r=g&amp;amp;s=80&amp;amp;d=http://cdn.libravatar.org/nobody/80.png"&amp;gt;here&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&lt;br /&gt;&lt;/pre&gt;As you can see, the body of the response is just as large as the headers and isn't really necessary.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Body-less 301 responses&lt;/h3&gt;After reading about the &lt;a href="https://httpd.apache.org/docs/2.2/custom-error.html"&gt;ErrorDocument directive&lt;/a&gt;, I created an empty file called &lt;tt&gt;302&lt;/tt&gt; in the root of the web server and included this directive in my vhost configuration file:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;ErrorDocument 302 /302&lt;/pre&gt;&lt;/blockquote&gt;which made the responses look like this:&lt;br /&gt;&lt;pre&gt;$ curl -i http://example.com/redir&lt;br /&gt;HTTP/1.1 302 Found&lt;br /&gt;Date: Wed, 21 Sep 2011 03:39:26 GMT&lt;br /&gt;Server: Apache&lt;br /&gt;Last-Modified: Wed, 21 Sep 2011 03:39:17 GMT&lt;br /&gt;ETag: "8024d-0-4ad6b52201036"&lt;br /&gt;Accept-Ranges: bytes&lt;br /&gt;Content-Length: 0&lt;br /&gt;Content-Type: text/plain&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;This one does have a completely empty body, however, there's an important problem with this solution: the &lt;tt&gt;Location&lt;/tt&gt; header is missing! Not much point in reducing the size of the redirect if it's no longer working.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Custom 302 response page&lt;/h3&gt;The next thing I tried (and ended up settling on) is this:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;ErrorDocument 302 " "&lt;/pre&gt;&lt;/blockquote&gt;which results in a 1-byte response (a single space) in the body:&lt;br /&gt;&lt;pre&gt;$ curl -i http://example.com/redir&lt;br /&gt;HTTP/1.1 302 Found&lt;br /&gt;Date: Wed, 21 Sep 2011 03:37:50 GMT&lt;br /&gt;Server: Apache&lt;br /&gt;Location: http://www.example.com&lt;br /&gt;Vary: Accept-Encoding&lt;br /&gt;Content-Length: 1&lt;br /&gt;Content-Type: text/html; charset=iso-8859-1&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;There is still a little bit of unnecessary information in this response (character set, &lt;tt&gt;Vary&lt;/tt&gt; and &lt;tt&gt;Server&lt;/tt&gt; headers), but it's a major improvement over the original.&lt;br /&gt;&lt;br /&gt;If you know of any other ways to reduce this further, please leave a comment!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-7865698729490571205?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=eLyeEHsuIAI:e4aEewMEuB8:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=eLyeEHsuIAI:e4aEewMEuB8:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?i=eLyeEHsuIAI:e4aEewMEuB8:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=eLyeEHsuIAI:e4aEewMEuB8:cGdyc7Q-1BI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=cGdyc7Q-1BI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=eLyeEHsuIAI:e4aEewMEuB8:YwkR-u9nhCs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=YwkR-u9nhCs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/FeedingTheCloud/~4/eLyeEHsuIAI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://feeding.cloud.geek.nz/feeds/7865698729490571205/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=7865698729490571205" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/7865698729490571205?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/7865698729490571205?v=2" /><link rel="alternate" type="text/html" href="http://feeding.cloud.geek.nz/2011/10/reducing-size-of-apache-301-and-302.html" title="Reducing the size of Apache 301 and 302 responses" /><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg" /></author><thr:total>2</thr:total></entry><entry gd:etag="W/&quot;CEABSXc8eCp7ImA9WhdUFk4.&quot;"><id>tag:blogger.com,1999:blog-7615241590176793465.post-9024083965487246703</id><published>2011-10-03T21:57:00.007+13:00</published><updated>2011-10-03T22:05:58.970+13:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-10-03T22:05:58.970+13:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="firefox" /><category scheme="http://www.blogger.com/atom/ns#" term="catalyst" /><category scheme="http://www.blogger.com/atom/ns#" term="debian" /><category scheme="http://www.blogger.com/atom/ns#" term="security" /><category scheme="http://www.blogger.com/atom/ns#" term="ubuntu" /><category scheme="http://www.blogger.com/atom/ns#" term="nzoss" /><category scheme="http://www.blogger.com/atom/ns#" term="mozilla" /><title>Three Firefox extensions to enhance SSL security</title><content type="html">There has been a lot of &lt;a href="http://www.youtube.com/watch?v=Z7Wl2FW2TcA"&gt;talk&lt;/a&gt; recently questioning the &lt;a href="https://freedom-to-tinker.com/blog/felten/mozilla-debates-whether-trust-chinese-ca"&gt;trust authorities&lt;/a&gt; that underpin the &lt;a href="https://secure.wikimedia.org/wikipedia/en/wiki/Transport_Layer_Security"&gt;SSL/TLS&lt;/a&gt; world. After &lt;a href="http://www.theregister.co.uk/2011/06/21/startssl_security_breach/"&gt;a few&lt;/a&gt; &lt;a href="http://www.f-secure.com/weblog/archives/00002128.html"&gt;high-profile&lt;/a&gt; &lt;a href="http://blog.gerv.net/2011/09/diginotar-compromise/"&gt;incidents&lt;/a&gt;, it is clear that there is &lt;a href="https://freedom-to-tinker.com/blog/sjs/web-security-trust-models"&gt;something wrong with this structure&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;While some people have suggested that &lt;a href="https://freedom-to-tinker.com/blog/sjs/major-internet-milestone-dnssec-and-ssl"&gt;DNSSEC might solve this problem&lt;/a&gt;, here are three Firefox add-ons that can be used today to enhance the security of HTTPS:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="https://addons.mozilla.org/en-US/firefox/addon/certificate-patrol/"&gt;Certificate Patrol&lt;/a&gt; applies the "trust on first use" principle familiar to most &lt;a href="https://secure.wikimedia.org/wikipedia/en/wiki/Secure_Shell"&gt;ssh&lt;/a&gt; users. It keeps track of the certificates you get for the sites you visit and displays a warning if important elements (e.g. the certificate authority) change.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="https://addons.mozilla.org/en-US/firefox/addon/perspectives/"&gt;Perspectives&lt;/a&gt; uses a network of certificate notaries to issue a warning when you encounter a certificate that is different from what other Perspectives users see.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="https://addons.mozilla.org/en-US/firefox/addon/monkeysphere/"&gt;Monkeysphere&lt;/a&gt; takes advantage of the PGP/GPG &lt;a href="https://secure.wikimedia.org/wikipedia/en/wiki/Web_of_trust"&gt;web-of-trust&lt;/a&gt; to verify the authenticity of certificates.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Unlike the &lt;a href="http://convergence.io/"&gt;Convergence&lt;/a&gt; approach which completely takes over certificate handling, all three of the above add-ons can be used together.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-9024083965487246703?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=qXoklAZUnSs:qbtv32Y1Jn0:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=qXoklAZUnSs:qbtv32Y1Jn0:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?i=qXoklAZUnSs:qbtv32Y1Jn0:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=qXoklAZUnSs:qbtv32Y1Jn0:cGdyc7Q-1BI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=cGdyc7Q-1BI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=qXoklAZUnSs:qbtv32Y1Jn0:YwkR-u9nhCs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=YwkR-u9nhCs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/FeedingTheCloud/~4/qXoklAZUnSs" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://feeding.cloud.geek.nz/feeds/9024083965487246703/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=9024083965487246703" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/9024083965487246703?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/9024083965487246703?v=2" /><link rel="alternate" type="text/html" href="http://feeding.cloud.geek.nz/2011/10/three-firefox-extensions-to-enhance-ssl.html" title="Three Firefox extensions to enhance SSL security" /><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg" /></author><thr:total>1</thr:total></entry><entry gd:etag="W/&quot;Ck4FRXwyfip7ImA9WhRTEUk.&quot;"><id>tag:blogger.com,1999:blog-7615241590176793465.post-9161927839724165607</id><published>2011-09-18T20:30:00.004+12:00</published><updated>2011-11-01T22:48:34.296+13:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-11-01T22:48:34.296+13:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="catalyst" /><category scheme="http://www.blogger.com/atom/ns#" term="django" /><category scheme="http://www.blogger.com/atom/ns#" term="debian" /><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="security" /><category scheme="http://www.blogger.com/atom/ns#" term="web" /><category scheme="http://www.blogger.com/atom/ns#" term="csp" /><category scheme="http://www.blogger.com/atom/ns#" term="nzoss" /><category scheme="http://www.blogger.com/atom/ns#" term="mozilla" /><title>Adding X-Content-Security-Policy headers in a Django application</title><content type="html">Content Security Policy is a &lt;a href="http://www.w3.org/Security/wiki/Content_Security_Policy"&gt;proposed HTTP extension&lt;/a&gt; which allows websites to restrict the external content that can be displayed by visiting web browsers. By expressing a set of rules to be enforced by the browser, a website is able to prevent the injection of outside resources by malicious users.&lt;br /&gt;&lt;br /&gt;While adding support for the &lt;a href="https://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-unofficial-draft-20110303.html"&gt;March 2011 draft&lt;/a&gt; in &lt;a href="https://www.libravatar.org/"&gt;Libravatar&lt;/a&gt;, I looked at three different approaches.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Controlling the headers in the application&lt;/h3&gt;The first approach I considered was to have the Django application output all of the headers, which is what the &lt;a href="https://github.com/mozilla/django-csp"&gt;django-csp&lt;/a&gt; module does. Unfortunately, I need to be able to vary the policy between pages (the views in Libravatar have different requirements) and that's one of the things that hasn't been implemented yet in that module.&lt;br /&gt;&lt;br /&gt;Producing the same headers by hand is fairly simple:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;response = render_to_response('app/view.html')&lt;br /&gt;response['X-Content-Security-Policy'] = "allow 'self'"&lt;br /&gt;return response&lt;/pre&gt;&lt;/blockquote&gt;but it would mean adding a bit of code to every view and/or writing a custom wrapper for &lt;tt&gt;render_to_response()&lt;/tt&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Setting a default header in Apache&lt;/h3&gt;Ideally, I'd like to be able to set a default header in Apache using &lt;a href="https://httpd.apache.org/docs/2.2/mod/mod_headers.html#header"&gt;mod_headers&lt;/a&gt; and then override it as needed inside the application.&lt;br /&gt;&lt;br /&gt;The first problem with this solution is that it's not possible (as far I can tell) for a Django application to override a header set by Apache:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;mod_headers adds its response header after &lt;a href="http://code.google.com/p/modwsgi/"&gt;mod_wsgi&lt;/a&gt; has returned (unless &lt;a href="https://httpd.apache.org/docs/2.2/mod/mod_headers.html#early"&gt;early processing&lt;/a&gt; is used).&lt;/li&gt;&lt;li&gt;Django's &lt;a href="https://docs.djangoproject.com/en/1.3/ref/request-response/#django.http.HttpResponse"&gt;response objects&lt;/a&gt; cannot see the early headers set by Apache.&lt;/li&gt;&lt;/ul&gt;The second problem is that mod_headers &lt;a href="https://issues.apache.org/bugzilla/show_bug.cgi?id=51842"&gt;doesn't have&lt;/a&gt; an &lt;i&gt;action&lt;/i&gt; that adds/sets a header only if it didn't already exist.  It does have &lt;tt&gt;append&lt;/tt&gt; and &lt;tt&gt;merge&lt;/tt&gt; actions which could in theory be used to add extra terms to the policy but it unfortunately uses a different separator (the comma) from the CSP spec (which uses semi-colons).&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Always set headers in Apache&lt;/h3&gt;While I would have liked to get the second approach working, in the end, I included all of the &lt;a href="https://developer.mozilla.org/en/Security/CSP/CSP_policy_directives"&gt;CSP directives&lt;/a&gt; within the main Apache config file:&lt;br /&gt;&lt;pre&gt;Header set X-Content-Security-Policy: "allow 'self'; options inline-script; img-src 'self' data:"&lt;br /&gt;&lt;br /&gt;&amp;lt;Location /account/confirm_email&amp;gt;&lt;br /&gt;  Header set X-Content-Security-Policy: "allow 'self'; options inline-script; img-src *"&lt;br /&gt;&amp;lt;/Location&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;Location /tools/check&amp;gt;&lt;br /&gt;  Header set X-Content-Security-Policy: "allow 'self'; options inline-script; img-src *"&lt;br /&gt;&amp;lt;/Location&amp;gt;&lt;/pre&gt;The first &lt;tt&gt;Header&lt;/tt&gt; call sets a default policy which is later overriden based on the path to the Django view that's being used.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Related technologies&lt;/h3&gt;If you are interested in Content Security Policy, you may also want to look into &lt;a href="http://noscript.net/abe/web-authors.html"&gt;Application Boundaries Enforcer&lt;/a&gt; (part of the &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/noscript/"&gt;NoScript&lt;/a&gt; Firefox extension) for more security rules that can be supplied by the server and enforced client-side.&lt;br /&gt;&lt;br /&gt;It's also worth mentioning the excellent &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/requestpolicy/"&gt;Request Policy&lt;/a&gt; extension which  solves the same problem by letting users whitelist the cross-site requests they want to allow.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-9161927839724165607?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=3x7gdb0pyyE:w63xjlRa4H4:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=3x7gdb0pyyE:w63xjlRa4H4:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?i=3x7gdb0pyyE:w63xjlRa4H4:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=3x7gdb0pyyE:w63xjlRa4H4:cGdyc7Q-1BI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=cGdyc7Q-1BI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=3x7gdb0pyyE:w63xjlRa4H4:YwkR-u9nhCs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=YwkR-u9nhCs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/FeedingTheCloud/~4/3x7gdb0pyyE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://feeding.cloud.geek.nz/feeds/9161927839724165607/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=9161927839724165607" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/9161927839724165607?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/9161927839724165607?v=2" /><link rel="alternate" type="text/html" href="http://feeding.cloud.geek.nz/2011/09/adding-x-content-security-policy.html" title="Adding X-Content-Security-Policy headers in a Django application" /><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg" /></author><thr:total>3</thr:total></entry><entry gd:etag="W/&quot;DUcEQX44cCp7ImA9WhdWE0o.&quot;"><id>tag:blogger.com,1999:blog-7615241590176793465.post-137589906968402321</id><published>2011-09-07T18:30:00.000+12:00</published><updated>2011-09-07T18:30:00.038+12:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-09-07T18:30:00.038+12:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="catalyst" /><category scheme="http://www.blogger.com/atom/ns#" term="sysadmin" /><category scheme="http://www.blogger.com/atom/ns#" term="rt" /><category scheme="http://www.blogger.com/atom/ns#" term="nzoss" /><title>RequestTracker workflow with only one person using the web UI to allocate tickets to external people</title><content type="html">Here are some notes on the way I set an instance of &lt;a href="http://bestpractical.com/rt/"&gt;RequestTracker&lt;/a&gt; up to support the following workflow:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Customers use a web form which sends an email to RT.&lt;/li&gt;&lt;li&gt;A contact person receives all new tickets by email and uses the RT web UI to allocate it to a another person.&lt;/li&gt;&lt;li&gt;The person handling the ticket gets a copy of the ticket (and doesn't see any of the other tickets) via email and never has to use RT.&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;Groups and Queues&lt;/h3&gt;For each product, I created the following RT groups:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;tt&gt;productname-contact&lt;/tt&gt;: Users receiving all emails sent to the ProductName queues&lt;/li&gt;&lt;li&gt;&lt;tt&gt;productname-support&lt;/tt&gt;: People who can be assigned to ProductName queues&lt;/li&gt;&lt;/ul&gt;as well as a &lt;tt&gt;ProductName&lt;/tt&gt; queue:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;tt&gt;productname-contact&lt;/tt&gt; is a &lt;b&gt;watcher&lt;/b&gt;&lt;/li&gt;&lt;li&gt;&lt;tt&gt;productname-contact&lt;/tt&gt; has all of the permissions under "Group Rights"&lt;/li&gt;&lt;li&gt;&lt;tt&gt;productname-support&lt;/tt&gt; has all of the permissions except for AdminCC under "Group Rights"&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;User accounts&lt;/h3&gt;The &lt;b&gt;contact person&lt;/b&gt; (can be more than one person), responsible for allocating tickets to support people gets a full RT account with a password and is a member of the &lt;tt&gt;productname-contact&lt;/tt&gt; group.&lt;br /&gt;&lt;br /&gt;The &lt;b&gt;support people&lt;/b&gt;, who reply to tickets, get a limited RT account without a password (so they cannot login) and are members of the &lt;tt&gt;productname-support&lt;/tt&gt; group.&lt;br /&gt;&lt;h3&gt;Forwarding the initial email&lt;/h3&gt;In a stock RT install, this workflow works well, except for one important point: when a ticket is allocated to someone else, all they get is a "this ticket has been assigned to you" email. They have no idea what the ticket is about and since they can't login into RT and are not CCed on the emails going into that queue, that's pretty useless to them.&lt;br /&gt;&lt;br /&gt;The solution I found is to add a new &lt;a href="http://requesttracker.wikia.com/wiki/ForwardFirstMessage"&gt;ForwardFirstMessage&lt;/a&gt; global template:&lt;br /&gt;&lt;pre&gt;{&lt;br /&gt; my $Transactions = $Ticket-&amp;gt;Transactions;&lt;br /&gt; $Transactions-&amp;gt;Limit( FIELD =&amp;gt; 'Type', VALUE =&amp;gt; 'Create' );&lt;br /&gt; my $first_message;&lt;br /&gt; my $CreateObj = $Transactions-&amp;gt;First;&lt;br /&gt; if( $CreateObj &amp;amp;&amp;amp; $CreateObj-&amp;gt;id ) {&lt;br /&gt;   $first_message = $CreateObj-&amp;gt;Content;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; $first_message;&lt;br /&gt;}&lt;/pre&gt;and then edit the &lt;tt&gt;On Owner Change Notify Owner&lt;/tt&gt; global scrip to make use of that template.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;With this, the person that is allocated to a ticket will receive the original email from the requestor as if it had been sent directly to their email address in the first place.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-137589906968402321?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=X8ZwB80r08s:AWvp1NDUFXU:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=X8ZwB80r08s:AWvp1NDUFXU:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?i=X8ZwB80r08s:AWvp1NDUFXU:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=X8ZwB80r08s:AWvp1NDUFXU:cGdyc7Q-1BI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=cGdyc7Q-1BI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=X8ZwB80r08s:AWvp1NDUFXU:YwkR-u9nhCs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=YwkR-u9nhCs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/FeedingTheCloud/~4/X8ZwB80r08s" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://feeding.cloud.geek.nz/feeds/137589906968402321/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=137589906968402321" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/137589906968402321?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/137589906968402321?v=2" /><link rel="alternate" type="text/html" href="http://feeding.cloud.geek.nz/2011/09/requesttracker-workflow-with-only-one.html" title="RequestTracker workflow with only one person using the web UI to allocate tickets to external people" /><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;CUIARH45eCp7ImA9WhdREkk.&quot;"><id>tag:blogger.com,1999:blog-7615241590176793465.post-2156738423220812325</id><published>2011-08-02T11:58:00.004+12:00</published><updated>2011-08-02T12:05:45.020+12:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-08-02T12:05:45.020+12:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="catalyst" /><category scheme="http://www.blogger.com/atom/ns#" term="launchpad" /><category scheme="http://www.blogger.com/atom/ns#" term="django" /><category scheme="http://www.blogger.com/atom/ns#" term="ubuntu" /><category scheme="http://www.blogger.com/atom/ns#" term="nzoss" /><title>Translating Django applications using Launchpad</title><content type="html">One of my &lt;a href="http://www.djangoproject.com"&gt;Django&lt;/a&gt;-based projects, &lt;a href="https://www.libravatar.org"&gt;Libravatar&lt;/a&gt;, makes use of Launchpad's interface to keep its &lt;a href="https://translations.launchpad.net/libravatar"&gt;translations&lt;/a&gt; current. Unfortunately, the &lt;a href="https://docs.djangoproject.com/en/1.3/howto/i18n/"&gt;PO file layout&lt;/a&gt; that Django insists on using isn't directly compatible with &lt;a href="https://help.launchpad.net/Translations/YourProject/ImportPolicy#Sample%20directory%20layout"&gt;the one that Launchpad needs&lt;/a&gt; in order to setup &lt;a href="https://help.launchpad.net/Translations/YourProject/ImportingTemplates#Enabling%20automatic%20template%20imports"&gt;automatic import&lt;/a&gt; of translations on a branch.&lt;br /&gt;&lt;br /&gt;The solution I found is to use the mandatory Launchpad layout and then create symlinks in the right places for Django.&lt;br /&gt;&lt;br /&gt;Here is where the Libravatar PO files are located in the &lt;a href="https://code.launchpad.net/~libravatar/libravatar/trunk"&gt;bzr repository&lt;/a&gt;:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;po/libravatar/de.po&lt;br /&gt;po/libravatar/fr.po&lt;br /&gt;po/libravatar/libravatar.pot&lt;/pre&gt;&lt;/blockquote&gt;and here are the (relative) symbolic links:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;libravatar/locale/de/LC_MESSAGES/django.po&lt;br /&gt;  -&gt; ../../../../po/libravatar/de.po&lt;br /&gt;libravatar/locale/en/LC_MESSAGES/django.po&lt;br /&gt;  -&gt; ../../../../po/libravatar/libravatar.pot&lt;br /&gt;libravatar/locale/fr/LC_MESSAGES/django.po&lt;br /&gt;  -&gt; ../../../../po/libravatar/fr.po&lt;/pre&gt;&lt;/blockquote&gt;Note that the "en" localization is left untranslated and used as the translation template (POT file).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-2156738423220812325?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=6fTnHFy1i3Y:JyLzNx4heJo:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=6fTnHFy1i3Y:JyLzNx4heJo:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?i=6fTnHFy1i3Y:JyLzNx4heJo:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=6fTnHFy1i3Y:JyLzNx4heJo:cGdyc7Q-1BI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=cGdyc7Q-1BI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=6fTnHFy1i3Y:JyLzNx4heJo:YwkR-u9nhCs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=YwkR-u9nhCs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/FeedingTheCloud/~4/6fTnHFy1i3Y" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://feeding.cloud.geek.nz/feeds/2156738423220812325/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=2156738423220812325" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/2156738423220812325?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/2156738423220812325?v=2" /><link rel="alternate" type="text/html" href="http://feeding.cloud.geek.nz/2011/08/translating-django-applications-using.html" title="Translating Django applications using Launchpad" /><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;CkMNRn4-fip7ImA9WhZaEkg.&quot;"><id>tag:blogger.com,1999:blog-7615241590176793465.post-6557949301809470081</id><published>2011-06-28T20:30:00.000+12:00</published><updated>2011-06-28T20:41:37.056+12:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-06-28T20:41:37.056+12:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="catalyst" /><category scheme="http://www.blogger.com/atom/ns#" term="optimisation" /><category scheme="http://www.blogger.com/atom/ns#" term="web" /><category scheme="http://www.blogger.com/atom/ns#" term="nzoss" /><title>Reducing the number of HTTP connections used by a webapp</title><content type="html">One of the factors affecting the speed (latency) of a web application is the &lt;a href="http://developer.yahoo.com/performance/rules.html#num_http"&gt;number of HTTP connections&lt;/a&gt; needed to download a given page. These connections are often &lt;a href="http://en.wikipedia.org/wiki/HTTP_persistent_connection"&gt;reused&lt;/a&gt; and some of the resources that a page needs will be downloaded in parallel, but that's not always the case (browsers will limit the number of connections they make to each web server for example) so it pays to take that into consideration while optimizing a page.&lt;br /&gt;&lt;br /&gt;Client-side &lt;a href="http://en.wikipedia.org/wiki/Image_map"&gt;image maps&lt;/a&gt; can often help, but here are two other ways to reduce the number of external images a page needs.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Embed images as Base64 data&lt;/h3&gt;Thanks to the &lt;a href="http://en.wikipedia.org/wiki/Data:_URI_scheme"&gt;Data:URI scheme&lt;/a&gt;, it is possible to embed images directly into a CSS file, as &lt;a href="https://secure.wikimedia.org/wikipedia/en/wiki/Base64"&gt;Base64&lt;/a&gt; data.&lt;br /&gt;&lt;br /&gt;Of course this trick only makes sense for very small images (typically a small PNG icons) since the Base64 version encoding of a binary image will be larger than the original image (though some of that will be reclaimed when the CSS file is &lt;a href="http://httpd.apache.org/docs/2.2/mod/mod_deflate.html"&gt;gzipped&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;First of all, make sure your PNG image is as small as possible using &lt;a href="http://optipng.sourceforge.net/"&gt;optipng&lt;/a&gt;:&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;optipng -o9 image.png&lt;/code&gt;&lt;/blockquote&gt;then convert it to text:&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;base64 image.png&lt;/code&gt;&lt;/blockquote&gt;and paste it into your CSS file:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;#object-id {&lt;br /&gt;    background: url('data:image/png;base64,iVBORw0KGgoAAAANSU...K5CYII=');&lt;br /&gt;}&lt;/pre&gt;&lt;/blockquote&gt;Make sure that the Base64 data is all on one line otherwise it apparently &lt;a href="http://en.wikipedia.org/wiki/Data_URI_scheme#CSS"&gt;won't work&lt;/a&gt; on old versions of Firefox.&lt;br /&gt;&lt;br /&gt;Note that the downside of this technique is that it doesn't work on IE7 so you might want to limit it to non-essential icons.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Use CSS sprites&lt;/h3&gt;This second technique does work on Internet Explorer and it can be particularly good when dealing with lots of small images. After all, a small image that weighs only a few bytes could be quite large once you factor in the HTTP response headers that will come with it when it is served to browsers.&lt;br /&gt;&lt;br /&gt;I suggest you get started by reading this &lt;a href="http://www.alistapart.com/articles/sprites"&gt;excellent introduction&lt;/a&gt;, but the idea is to create a single image which contains all of the smaller ones. Then each element displays a different region of the same image (which is only transferred once).&lt;br /&gt;&lt;br /&gt;Here's what the stylesheet looks like for two side-by-side clickable images:&lt;blockquote&gt;&lt;pre&gt;#sponsors {&lt;br /&gt;  position: relative;&lt;br /&gt;  height: 96px;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;#sponsors li {&lt;br /&gt;  background-image: url("sponsors.png");&lt;br /&gt;  height: 96px;&lt;br /&gt;  width: 96px;&lt;br /&gt;  list-style: none;&lt;br /&gt;  position: absolute;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;#sponsors li a {&lt;br /&gt;  height: 96px;&lt;br /&gt;  width: 96px;&lt;br /&gt;  display: block;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;#sponsor1-logo {&lt;br /&gt;  background-position: 96px 0px;&lt;br /&gt;  left: 0px;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;#sponsor2-logo {&lt;br /&gt;  background-position: 0px 0px;&lt;br /&gt;  left: 96px;&lt;br /&gt;}&lt;/pre&gt;&lt;/blockquote&gt;and the matching HTML fragment:&lt;blockquote&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;ul id="sponsors"&amp;gt;&lt;br /&gt;  &amp;lt;li id="logo1"&amp;gt;&amp;lt;a href="http://example.org" title="Sponsor1"&gt;&amp;lt;/a&gt;&amp;lt;/li&amp;gt;&lt;br /&gt;  &amp;lt;li id="logo2"&amp;gt;&amp;lt;a href="http://example.com" title="Sponsor2"&gt;&amp;lt;/a&gt;&amp;lt;/li&amp;gt;&lt;br /&gt;&amp;lt;/ul&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;/blockquote&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-6557949301809470081?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=jUSLP2PlTQI:aQSwhnX2urQ:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=jUSLP2PlTQI:aQSwhnX2urQ:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?i=jUSLP2PlTQI:aQSwhnX2urQ:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=jUSLP2PlTQI:aQSwhnX2urQ:cGdyc7Q-1BI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=cGdyc7Q-1BI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=jUSLP2PlTQI:aQSwhnX2urQ:YwkR-u9nhCs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=YwkR-u9nhCs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/FeedingTheCloud/~4/jUSLP2PlTQI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://feeding.cloud.geek.nz/feeds/6557949301809470081/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=6557949301809470081" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/6557949301809470081?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/6557949301809470081?v=2" /><link rel="alternate" type="text/html" href="http://feeding.cloud.geek.nz/2011/06/reducing-number-of-http-connections.html" title="Reducing the number of HTTP connections used by a webapp" /><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg" /></author><thr:total>1</thr:total></entry><entry gd:etag="W/&quot;DUcEQXoyfyp7ImA9WhZVF04.&quot;"><id>tag:blogger.com,1999:blog-7615241590176793465.post-2244704455594649203</id><published>2011-05-30T18:30:00.002+12:00</published><updated>2011-05-30T18:30:00.497+12:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-05-30T18:30:00.497+12:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="catalyst" /><category scheme="http://www.blogger.com/atom/ns#" term="launchpad" /><category scheme="http://www.blogger.com/atom/ns#" term="mahara" /><category scheme="http://www.blogger.com/atom/ns#" term="gerrit" /><category scheme="http://www.blogger.com/atom/ns#" term="ubuntu" /><category scheme="http://www.blogger.com/atom/ns#" term="nzoss" /><title>Integrating Launchpad and Gerrit Code Review</title><content type="html">While talking about how &lt;a href="http://mahara.org/"&gt;Mahara&lt;/a&gt; uses &lt;a href="http://feeding.cloud.geek.nz/2011/04/code-reviews-with-gerrit-and-gitorious.html"&gt;Gerrit and Gitorious for code reviews&lt;/a&gt;, I forgot to include details on how to &lt;a href="http://gerrit.googlecode.com/svn/documentation/2.1.6/config-gerrit.html#commentlink"&gt;link bugs&lt;/a&gt; in the commit logs to our bug tracker (&lt;a href="https://bugs.launchpad.net/mahara"&gt;Launchpad&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Links to Launchpad in the Gerrit review UI&lt;/h3&gt;Turning the bug numbers in git commit messages to links was just a matter of adding this to our &lt;tt&gt;etc/gerrit.config&lt;/tt&gt; file.&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;[commentlink "launchpad"]&lt;br /&gt;  match = "([Bb]ug\\s+#?)(\\d+)"&lt;br /&gt;  link = https://bugs.launchpad.net/mahara/+bug/$2&lt;/pre&gt;&lt;/blockquote&gt;All mentions of a bug number like "(bug #1234)" or "bug 5678" in a commit message or Gerrit review will be converted into links to the appropriate page on Launchpad.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Updating Launchpad bugs after Gerrit merges&lt;/h3&gt;Linking the original bug reports in Launchpad to committed code (and its review in Gerrit) was a little more complicated. It makes use of the &lt;a href="http://gerrit.googlecode.com/svn/documentation/2.1.6/config-hooks.html"&gt;hooks&lt;/a&gt; available in Gerrit.&lt;br /&gt;&lt;br /&gt;The first step was to create a &lt;a href="https://launchpad.net/+login"&gt;new Launchpad account&lt;/a&gt; for these automated updates and to confirm the email address it will be sending emails from. In our case, the account is &lt;a href="https://launchpad.net/~dev-mahara"&gt;MaharaBot&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Then, we wrote a &lt;a href="http://gitorious.org/mahara/mahara-scripts/blobs/master/change-merged"&gt;custom hook script&lt;/a&gt; (inspired by &lt;a href="https://github.com/hobbs/jirret"&gt;one for JIRA&lt;/a&gt;) which we placed in &lt;tt&gt;~/mahara_reviews/etc/gerrit.config&lt;/tt&gt;.&lt;br /&gt;&lt;br /&gt;For an example of what it sends to the bug tracker, see &lt;a href="https://bugs.launchpad.net/mahara/+bug/788457"&gt;bug 788457&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-2244704455594649203?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=qpISq3XfVic:r-bHWG-K8VQ:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=qpISq3XfVic:r-bHWG-K8VQ:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?i=qpISq3XfVic:r-bHWG-K8VQ:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=qpISq3XfVic:r-bHWG-K8VQ:cGdyc7Q-1BI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=cGdyc7Q-1BI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=qpISq3XfVic:r-bHWG-K8VQ:YwkR-u9nhCs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=YwkR-u9nhCs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/FeedingTheCloud/~4/qpISq3XfVic" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://feeding.cloud.geek.nz/feeds/2244704455594649203/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=2244704455594649203" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/2244704455594649203?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/2244704455594649203?v=2" /><link rel="alternate" type="text/html" href="http://feeding.cloud.geek.nz/2011/05/integrating-launchpad-and-gerrit-code.html" title="Integrating Launchpad and Gerrit Code Review" /><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;AkQDQ3g_eSp7ImA9WhZVF08.&quot;"><id>tag:blogger.com,1999:blog-7615241590176793465.post-7684808770197039262</id><published>2011-04-17T17:00:00.009+12:00</published><updated>2011-05-30T16:06:12.641+12:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-05-30T16:06:12.641+12:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="catalyst" /><category scheme="http://www.blogger.com/atom/ns#" term="debian" /><category scheme="http://www.blogger.com/atom/ns#" term="mahara" /><category scheme="http://www.blogger.com/atom/ns#" term="git" /><category scheme="http://www.blogger.com/atom/ns#" term="gerrit" /><category scheme="http://www.blogger.com/atom/ns#" term="programming" /><category scheme="http://www.blogger.com/atom/ns#" term="nzoss" /><title>Code reviews with Gerrit and Gitorious</title><content type="html">The &lt;a href="http://mahara.org/"&gt;Mahara&lt;/a&gt; &lt;a href="https://launchpad.net/mahara"&gt;project&lt;/a&gt; has just moved to mandatory code reviews for every commit that gets applied to core code.&lt;br /&gt;&lt;br /&gt;Here is a description of how &lt;a href="http://code.google.com/p/gerrit"&gt;Gerrit Code Review&lt;/a&gt;, the peer-review system used by &lt;a href="http://www.android.com"&gt;Android&lt;/a&gt;, was retrofitted into our existing &lt;a href="http://www.git-scm.com/"&gt;git&lt;/a&gt; repository on &lt;a href="http://www.gitorious.org/"&gt;Gitorious&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;(If you want to know more about Gerrit, listen to &lt;a href="http://www.twit.tv/floss118"&gt;this FLOSS Weekly interview&lt;/a&gt;.)&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Replacing existing Gitorious committers with a robot&lt;/h3&gt;The first thing to do was to log into Gitorious and remove commit rights from everyone in the &lt;a href="http://www.gitorious.org/mahara/mahara"&gt;main repository&lt;/a&gt;. Then I created a new &lt;a href="http://www.gitorious.org/~maharabot"&gt;maharabot&lt;/a&gt; account with a password-less SSH key (stored under &lt;tt&gt;/home/gerrit/.ssh/&lt;/tt&gt;) and made that new account the sole committer.&lt;br /&gt;&lt;br /&gt;This is to ensure that nobody pushes to the repository by mistake since all of these changes would be &lt;b&gt;overwritten by Gerrit&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Basic Gerrit installation&lt;/h3&gt;After going through the &lt;a href="https://gerrit.googlecode.com/svn/documentation/2.1.6/install.html"&gt;installation instructions&lt;/a&gt;, I logged into the Gerrit admin interface and created a new "mahara" &lt;a href="https://gerrit.googlecode.com/svn/documentation/2.1.6/project-setup.html"&gt;project&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I picked the "merge if necessary" submit action because "cherry-pick" would &lt;a href="http://groups.google.com/group/repo-discuss/browse_thread/thread/aa53fe63197183af"&gt;disable dependency tracking&lt;/a&gt; which is quite a handy feature.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Reverse proxy using Nginx&lt;/h3&gt;Since we wanted to offer Gerrit over HTTPS, I decided to run it behind an &lt;a href="http://www.nginx.org/"&gt;Nginx&lt;/a&gt; proxy. This is the Nginx configuration I ended up with:&lt;br /&gt;&lt;pre&gt;server {&lt;br /&gt;     listen 443;&lt;br /&gt;     server_name reviews.mahara.org;&lt;br /&gt;     add_header Strict-Transport-Security max-age=15768000;&lt;br /&gt;&lt;br /&gt;     ssl on;&lt;br /&gt;     ssl_certificate  /etc/ssl/certs/reviews.mahara.org.crt;&lt;br /&gt;     ssl_certificate_key  /etc/ssl/certs/reviews.mahara.org.pem;&lt;br /&gt;&lt;br /&gt;     ssl_session_timeout  5m;&lt;br /&gt;     ssl_session_cache shared:SSL:1m;&lt;br /&gt;&lt;br /&gt;     ssl_protocols  TLSv1;&lt;br /&gt;     ssl_ciphers  HIGH:!ADH;&lt;br /&gt;     ssl_prefer_server_ciphers   on;&lt;br /&gt;&lt;br /&gt;     location / {&lt;br /&gt;             proxy_pass   http://127.0.0.1:8081;&lt;br /&gt;             proxy_set_header X-Forwarded-For $remote_addr;&lt;br /&gt;             proxy_set_header Host $host;&lt;br /&gt;     }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;Things to note:&lt;ul&gt;&lt;li&gt;An HTTP to HTTPS redirection is &lt;a href="https://www.owasp.org/index.php/Transport_Layer_Protection_Cheat_Sheet#Rule_-_Do_Not_Perform_Redirects_from_Non-TLS_Page_to_TLS_Login_Page"&gt;not provided&lt;/a&gt;.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;The &lt;a href="https://secure.wikimedia.org/wikipedia/en/wiki/HTTP_Strict_Transport_Security"&gt;HSTS&lt;/a&gt; headers indicates to modern browsers that this URL should only be accessed via HTTPS.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Only strong SSL ciphers are enabled.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Before proxying requests to Gerrit, Nginx adds a few headers to identify the origin of the request. The &lt;tt&gt;Host&lt;/tt&gt; header in particular must be present otherwise &lt;a href="http://code.google.com/p/gerrit/issues/detail?id=905"&gt;built-in Gerrit redirections will fail&lt;/a&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h3&gt;Mail setup&lt;/h3&gt;To enable Gerrit to email reviewers and committers, I installed &lt;a href="http://www.postfix.org/"&gt;Postfix&lt;/a&gt; and used "reviews.mahara.org" as the "System mail name".&lt;br /&gt;&lt;br /&gt;Then I added the following to &lt;tt&gt;/home/gerrit/mahara_reviews/etc/gerrit.config&lt;/tt&gt;:&lt;br /&gt;&lt;pre&gt;[user]&lt;br /&gt; email = "gerrit@reviews.mahara.org"&lt;/pre&gt;to fix the From address in outgoing emails.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Init script and cron&lt;/h3&gt;Following the &lt;a href="https://gerrit.googlecode.com/svn/documentation/2.1.6/install.html"&gt;installation instructions&lt;/a&gt;, I created these symlinks:&lt;br /&gt;&lt;pre&gt;ln -s /home/gerrit/mahara_reviews/bin/gerrit.sh /etc/init.d/gerrit&lt;br /&gt;cd /etc/rc2.d &amp;amp;&amp;amp; ln -s ../init.d/gerrit S19gerrit&lt;br /&gt;cd /etc/rc3.d &amp;amp;&amp;amp; ln -s ../init.d/gerrit S19gerrit&lt;br /&gt;cd /etc/rc4.d &amp;amp;&amp;amp; ln -s ../init.d/gerrit S19gerrit&lt;br /&gt;cd /etc/rc5.d &amp;amp;&amp;amp; ln -s ../init.d/gerrit S19gerrit&lt;br /&gt;cd /etc/rc0.d &amp;amp;&amp;amp; ln -s ../init.d/gerrit K21gerrit&lt;br /&gt;cd /etc/rc1.d &amp;amp;&amp;amp; ln -s ../init.d/gerrit K21gerrit&lt;br /&gt;cd /etc/rc6.d &amp;amp;&amp;amp; ln -s ../init.d/gerrit K21gerrit&lt;/pre&gt;and put the following settings into &lt;tt&gt;/etc/default/gerritcodereview&lt;/tt&gt;:&lt;br /&gt;&lt;pre&gt;GERRIT_SITE=/home/gerrit/mahara_reviews&lt;br /&gt;GERRIT_USER=gerrit&lt;br /&gt;GERRIT_WAR=/home/gerrit/gerrit.war&lt;/pre&gt;to automatically start and stop Gerrit.&lt;br /&gt;&lt;br /&gt;I also added a cron job in &lt;tt&gt;/etc/cron.d/gitcleanup&lt;/tt&gt; to ensure that the built-in git repository doesn't get bloated:&lt;pre&gt;MAILTO=&lt;i&gt;admin@example.com&lt;/i&gt;&lt;br /&gt;20 4 * * * gerrit GIT_DIR=/home/gerrit/mahara_reviews/git/mahara.git git gc --quiet&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Configuration enhancements&lt;/h3&gt;To allow images in change requests to be displayed inside the browser, I &lt;a href="http://gerrit.googlecode.com/svn/documentation/2.1.6/config-gerrit.html#mimetype"&gt;marked them as safe&lt;/a&gt; in &lt;tt&gt;/home/gerrit/mahara_reviews/etc/gerrit.config&lt;/tt&gt;:&lt;br /&gt;&lt;pre&gt;[mimetype "image/*"]&lt;br /&gt; safe = true&lt;/pre&gt;&lt;br /&gt;Another thing I did to enhance the review experience was to enable the gitweb repository browser:&lt;br /&gt;&lt;pre&gt;apt-get install gitweb&lt;/pre&gt;&lt;br /&gt;and to make checkouts faster by enabling anonymous Git access:&lt;br /&gt;&lt;pre&gt;[gerrit]&lt;br /&gt;  canonicalGitUrl = git://reviews.mahara.org/git/&lt;br /&gt;[download]&lt;br /&gt;  scheme = ssh&lt;br /&gt;  scheme = anon_http&lt;br /&gt;  scheme = anon_git&lt;/pre&gt;&lt;br /&gt;which requires that you have a git daemon running and listening on port 9418:&lt;br /&gt;&lt;pre&gt;apt-get install git-daemon-run&lt;br /&gt;ln -s /home/gerrit/mahara_reviews/git/mahara.git /var/cache/git/&lt;br /&gt;touch /home/gerrit/mahara_reviews/git/mahara.git/git-daemon-export-ok&lt;/pre&gt;&lt;br /&gt;Finally, I included the Mahara branding in the header and footer of each page by &lt;a href="http://gerrit.googlecode.com/svn/documentation/2.1.6/config-headerfooter.html#toc0"&gt;providing valid XHTML fragments&lt;/a&gt; in &lt;tt&gt;/home/gerrit/mahara_reviews/etc/GerritSiteHeader.html&lt;/tt&gt; and &lt;tt&gt;GerritSiteFooter.html&lt;/tt&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Initial import and replication&lt;/h3&gt;Once Gerrit was fully working, I performed the initial code import by using my administrator account to push the exiting Gitorious branches to the internal git repository:&lt;br /&gt;&lt;pre&gt;git remote add gerrit ssh://&lt;i&gt;username&lt;/i&gt;@reviews.mahara.org:29418/mahara&lt;br /&gt;git push gerrit 1.2_STABLE&lt;br /&gt;git push gerrit 1.3_STABLE&lt;br /&gt;git push gerrit master&lt;/pre&gt;Note that I had to &lt;b&gt;temporarily disable "Require Change IDs"&lt;/b&gt; in the project settings in order to import the old commits which didn't have these.&lt;br /&gt;&lt;br /&gt;To replicate the internal Gerrit repository back to Gitorious, I created a new &lt;tt&gt;/home/gerrit/mahara_reviews/etc/replication.config&lt;/tt&gt; file:&lt;br /&gt;&lt;pre&gt;[remote "gitorious"]&lt;br /&gt; url = gitorious.org:mahara/${name}.git&lt;br /&gt; push = +refs/heads/*:refs/heads/*&lt;br /&gt; push = +refs/tags/*:refs/tags/*&lt;/pre&gt;(The ${name} variable is &lt;a href="https://code.google.com/p/gerrit/issues/detail?id=896"&gt;required&lt;/a&gt; even when you have a single project.)&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Contributor instructions&lt;/h3&gt;This is how developers can get a working checkout of our code now:&lt;br /&gt;&lt;pre&gt;git clone git://gitorious.org/mahara/mahara.git&lt;br /&gt;cd mahara&lt;br /&gt;git remote add gerrit ssh://&lt;i&gt;username&lt;/i&gt;@reviews.mahara.org:29418/mahara&lt;br /&gt;git fetch gerrit&lt;br /&gt;scp -p -P 29418 reviews.mahara.org:hooks/commit-msg .git/hooks/&lt;/pre&gt;and this is how they can submit local changes to Gerrit:&lt;pre&gt;git push gerrit HEAD:refs/for/master&lt;/pre&gt;&lt;br /&gt;Anybody can submit change requests or &lt;a href="https://reviews.mahara.org"&gt;comment on them&lt;/a&gt; but make sure you &lt;b&gt;do not have the Cookie Pie&lt;/b&gt; Firefox extension installed or you will be &lt;a href="https://code.google.com/p/gerrit/issues/detail?id=887"&gt;unable to log into Gerrit&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-7684808770197039262?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=JXL9Yzu_fOw:8UHYF9Ps8Dc:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=JXL9Yzu_fOw:8UHYF9Ps8Dc:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?i=JXL9Yzu_fOw:8UHYF9Ps8Dc:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=JXL9Yzu_fOw:8UHYF9Ps8Dc:cGdyc7Q-1BI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=cGdyc7Q-1BI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=JXL9Yzu_fOw:8UHYF9Ps8Dc:YwkR-u9nhCs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=YwkR-u9nhCs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/FeedingTheCloud/~4/JXL9Yzu_fOw" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://feeding.cloud.geek.nz/feeds/7684808770197039262/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=7684808770197039262" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/7684808770197039262?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/7684808770197039262?v=2" /><link rel="alternate" type="text/html" href="http://feeding.cloud.geek.nz/2011/04/code-reviews-with-gerrit-and-gitorious.html" title="Code reviews with Gerrit and Gitorious" /><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg" /></author><thr:total>2</thr:total></entry><entry gd:etag="W/&quot;DEMEQn87fip7ImA9WhZREk0.&quot;"><id>tag:blogger.com,1999:blog-7615241590176793465.post-1987025396304623895</id><published>2011-04-08T08:00:00.000+12:00</published><updated>2011-04-08T08:00:03.106+12:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-04-08T08:00:03.106+12:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="plupload" /><category scheme="http://www.blogger.com/atom/ns#" term="catalyst" /><category scheme="http://www.blogger.com/atom/ns#" term="django" /><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="programming" /><category scheme="http://www.blogger.com/atom/ns#" term="web" /><category scheme="http://www.blogger.com/atom/ns#" term="nzoss" /><title>Using plupload inside a Django application</title><content type="html">&lt;a href="http://plupload.org/"&gt;Plupload&lt;/a&gt; is a reusable component which fully takes advantage of the file upload capabilities of your browser and its plugins. It will use HTML5, Flash and Google Gears if they are available, but otherwise, it can gracefully degrade down to HTML4 it it needs to. Here's how it can be integrated within a Django web application.&lt;br /&gt;&lt;br /&gt;(I have posted the &lt;a href="https://gitorious.org/plupload_django/plupload_django"&gt;sample application&lt;/a&gt; I will refer to and you may use it &lt;a href="https://gitorious.org/plupload_django/plupload_django/blobs/master/license.txt"&gt;anyway you like&lt;/a&gt;.)&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Creating a basic upload form&lt;/h3&gt;The first step is to create a simple one-file upload form that will be used in the case where Javascript is disabled:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;class UploadForm(forms.Form):&lt;br /&gt;   file = forms.FileField()&lt;br /&gt;&lt;br /&gt;   def save(self, uploaded_file):&lt;br /&gt;       print 'File "%s" would presumably be saved to disk now.' % uploaded_file&lt;br /&gt;       pass&lt;br /&gt;&lt;/pre&gt;Then you can add this form to one of your templates:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&amp;lt;form enctype="multipart/form-data" action="{% url plupload_sample.upload.views.upload_file %}" method="post"&amp;gt;&lt;br /&gt;{% csrf_token %}&lt;br /&gt;&lt;br /&gt;&amp;lt;div id="uploader"&amp;gt;&lt;br /&gt; {{form.file.errors}}{{form.file}}&lt;br /&gt; &amp;lt;input type="submit" value="Upload" /&amp;gt;&lt;br /&gt;&amp;lt;/div&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;/form&amp;gt;&lt;/pre&gt;And create a new method to receive the form data:&lt;br /&gt;&lt;pre&gt;@csrf_protect&lt;br /&gt;def upload_file(request):&lt;br /&gt;   if request.method == 'POST':&lt;br /&gt;       form = UploadForm(request.POST, request.FILES)&lt;br /&gt;       if form.is_valid():&lt;br /&gt;           uploaded_file = request.FILES['file']&lt;br /&gt;           form.save(uploaded_file)&lt;br /&gt;&lt;br /&gt;           return HttpResponseRedirect(reverse('plupload_sample.upload.views.upload_file'))&lt;br /&gt;   else:&lt;br /&gt;       form = UploadForm()&lt;br /&gt;&lt;br /&gt;   return render_to_response('upload_file.html', {'form': form}, context_instance=RequestContext(request))&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Adding Plupload to the template&lt;/h3&gt;In order to display the right Javascript-based upload form, add the following code, based on the &lt;a href="http://plupload.org/example_queuewidget.php"&gt;official example&lt;/a&gt;, to the head of your template:&lt;br /&gt;&lt;pre&gt;&amp;lt;link rel="stylesheet" href="/css/plupload.queue.css" type="text/css"&amp;gt;&lt;br /&gt;&amp;lt;script type="text/javascript" src="/js/jquery.min.js"&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;&amp;lt;script type="text/javascript" src="/js/plupload.full.min.js"&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;&amp;lt;script type="text/javascript" src="/js/jquery.plupload.queue.min.js"&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;script type="text/javascript"&amp;gt;&lt;br /&gt;$(function() {&lt;br /&gt;   $("#uploader").pluploadQueue({&lt;br /&gt;       runtimes : 'html5,html4',&lt;br /&gt;       url : '{% url plupload_sample.upload.views.upload_file %}',&lt;br /&gt;       max_file_size : '1mb',&lt;br /&gt;       chunk_size: '1mb',&lt;br /&gt;       unique_names : false,&lt;br /&gt;       multipart: true,&lt;br /&gt;&lt;br /&gt;       headers : {'X-Requested-With' : 'XMLHttpRequest', 'X-CSRFToken' : '{{csrf_token}}'},&lt;br /&gt;   });&lt;br /&gt;&lt;br /&gt;   $('form').submit(function(e) {&lt;br /&gt;       var uploader = $('#uploader').pluploadQueue();&lt;br /&gt;&lt;br /&gt;       // Validate number of uploaded files&lt;br /&gt;       if (uploader.total.uploaded == 0) {&lt;br /&gt;           // Files in queue upload them first&lt;br /&gt;           if (uploader.files.length &amp;gt; 0) {&lt;br /&gt;               // When all files are uploaded submit form&lt;br /&gt;               uploader.bind('UploadProgress', function() {&lt;br /&gt;                   if (uploader.total.uploaded == uploader.files.length)&lt;br /&gt;                       $('form').submit();&lt;br /&gt;               });&lt;br /&gt;&lt;br /&gt;               uploader.start();&lt;br /&gt;           } else {&lt;br /&gt;               alert('You must at least upload one file.');&lt;br /&gt;           }&lt;br /&gt;&lt;br /&gt;           e.preventDefault();&lt;br /&gt;       }&lt;br /&gt;   });&lt;br /&gt;});&lt;br /&gt;&amp;lt;/script&amp;gt;&lt;/pre&gt;Pay close attention to the extra headers that need to be added to ensure that the AJAX requests will pass the &lt;a href="http://docs.djangoproject.com/en/1.3/ref/contrib/csrf/"&gt;Django CSRF checks&lt;/a&gt;:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;X-Requested-With: XMLHttpRequest&lt;br /&gt;X-CSRFToken: {{csrf_token}}&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;h3&gt;Adding Plupload to the view method&lt;/h3&gt;Now in order to properly receive the files uploaded by Plupload via AJAX calls, we need to revise our upload_file() method:&lt;br /&gt;&lt;pre&gt;@csrf_protect&lt;br /&gt;def upload_file(request):&lt;br /&gt;   if request.method == 'POST':&lt;br /&gt;       form = UploadForm(request.POST, request.FILES)&lt;br /&gt;       if form.is_valid():&lt;br /&gt;           uploaded_file = request.FILES['file']&lt;br /&gt;           form.save(uploaded_file)&lt;br /&gt;&lt;br /&gt;           &lt;b&gt;if request.is_ajax():&lt;br /&gt;               response = HttpResponse('{"jsonrpc" : "2.0", "result" : null, "id" : "id"}', mimetype='text/plain; charset=UTF-8')&lt;br /&gt;               response['Expires'] = 'Mon, 1 Jan 2000 01:00:00 GMT'&lt;br /&gt;               response['Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'&lt;br /&gt;               response['Pragma'] = 'no-cache'&lt;br /&gt;               return response&lt;br /&gt;           else:&lt;br /&gt;               return HttpResponseRedirect(reverse('plupload_sample.upload.views.upload_file'))&lt;/b&gt;&lt;br /&gt;   else:&lt;br /&gt;       form = UploadForm()&lt;br /&gt;&lt;br /&gt;   return render_to_response('upload_file.html', {'form': form}, context_instance=RequestContext(request))&lt;/pre&gt;The above includes the response (which is not really documented as far as I can tell) that needs to be sent back to Plupload to make sure it knows that the file has been received successfully:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;{"jsonrpc" : "2.0", "result" : null, "id" : "id"}&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;h3&gt;Adding support for multipart files&lt;/h3&gt;Our solution so far works fine except when uploading large files that need to be sent in multiple chunks.&lt;br /&gt;&lt;br /&gt;This involves writing to a temporary file until all parts have been received:&lt;br /&gt;&lt;pre&gt;@csrf_protect&lt;br /&gt;def upload_file(request):&lt;br /&gt;   if request.method == 'POST':&lt;br /&gt;       form = UploadForm(request.POST, request.FILES)&lt;br /&gt;       if form.is_valid():&lt;br /&gt;           uploaded_file = request.FILES['file']&lt;br /&gt;           &lt;b&gt;chunk = request.REQUEST.get('chunk', '0')&lt;br /&gt;           chunks = request.REQUEST.get('chunks', '0')&lt;br /&gt;           name = request.REQUEST.get('name', '')&lt;br /&gt;&lt;br /&gt;           if not name:&lt;br /&gt;               name = uploaded_file.name&lt;br /&gt;&lt;br /&gt;           temp_file = '/tmp/insecure.tmp'&lt;br /&gt;           with open(temp_file, ('wb' if chunk == '0' else 'ab')) as f:&lt;br /&gt;               for content in uploaded_file.chunks():&lt;br /&gt;                   f.write(content)&lt;br /&gt;&lt;br /&gt;           if int(chunk) + 1 &amp;gt;= int(chunks):&lt;br /&gt;               form.save(temp_file, name)&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;           if request.is_ajax():&lt;br /&gt;               response = HttpResponse('{"jsonrpc" : "2.0", "result" : null, "id" : "id"}', mimetype='text/plain; charset=UTF-8')&lt;br /&gt;               response['Expires'] = 'Mon, 1 Jan 2000 01:00:00 GMT'&lt;br /&gt;               response['Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'&lt;br /&gt;               response['Pragma'] = 'no-cache'&lt;br /&gt;               return response&lt;br /&gt;           else:&lt;br /&gt;               return HttpResponseRedirect(reverse('plupload_sample.upload.views.upload_file'))&lt;br /&gt;   else:&lt;br /&gt;       form = UploadForm()&lt;br /&gt;&lt;br /&gt;   return render_to_response('upload_file.html', {'form': form}, context_instance=RequestContext(request))&lt;/pre&gt;Note that I have used &lt;tt&gt;/tmp/insecure.tmp&lt;/tt&gt; for brevity. In a real application, you do need to use a &lt;a href="http://docs.python.org/library/tempfile.html"&gt;secure mechanism&lt;/a&gt; to create the temporary file or you would expose yourself to a &lt;a href="https://duckduckgo.com/?q=insecure+temp+file+creation"&gt;tempfile vulnerability&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-1987025396304623895?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=aqESyCrB5rg:Snj1PHKKGqE:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=aqESyCrB5rg:Snj1PHKKGqE:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?i=aqESyCrB5rg:Snj1PHKKGqE:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=aqESyCrB5rg:Snj1PHKKGqE:cGdyc7Q-1BI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=cGdyc7Q-1BI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=aqESyCrB5rg:Snj1PHKKGqE:YwkR-u9nhCs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=YwkR-u9nhCs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/FeedingTheCloud/~4/aqESyCrB5rg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://feeding.cloud.geek.nz/feeds/1987025396304623895/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=1987025396304623895" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/1987025396304623895?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/1987025396304623895?v=2" /><link rel="alternate" type="text/html" href="http://feeding.cloud.geek.nz/2011/04/using-plupload-inside-django.html" title="Using plupload inside a Django application" /><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg" /></author><thr:total>4</thr:total></entry><entry gd:etag="W/&quot;AkAGQ3Y7eCp7ImA9WhZSGE0.&quot;"><id>tag:blogger.com,1999:blog-7615241590176793465.post-5261816034331387077</id><published>2011-04-03T11:30:00.006+12:00</published><updated>2011-04-03T17:32:02.800+12:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-04-03T17:32:02.800+12:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="catalyst" /><category scheme="http://www.blogger.com/atom/ns#" term="debian" /><category scheme="http://www.blogger.com/atom/ns#" term="backup" /><category scheme="http://www.blogger.com/atom/ns#" term="sysadmin" /><category scheme="http://www.blogger.com/atom/ns#" term="ubuntu" /><category scheme="http://www.blogger.com/atom/ns#" term="nzoss" /><title>Encrypted system backup to DVD</title><content type="html">Inspired by &lt;a href="http://worldbackupday.net/"&gt;World Backup Day&lt;/a&gt;, I decided to take a backup of my laptop. Thanks to using a &lt;a href="http://www.debian.org/"&gt;free operating system&lt;/a&gt; I don't have to backup any of my software, just configuration and data files, which fit on a single DVD.&lt;br /&gt;&lt;br /&gt;In order to avoid worrying too much about secure storage and disposal of these backups, I have decided to encrypt them using a standard &lt;a href="http://feeding.cloud.geek.nz/2008/04/two-tier-encryption-strategy-archiving.html"&gt;encrypted loopback filesystem&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;(Feel free to leave a comment if you can suggest an easier way of doing this.)&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Cryptmount setup&lt;/h3&gt;Install &lt;a href="http://cryptmount.sourceforge.net/"&gt;cryptmount&lt;/a&gt;:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;apt-get install cryptmount&lt;/pre&gt;&lt;/blockquote&gt;and setup two encrypted mount points in &lt;tt&gt;/etc/cryptmount/cmtab&lt;/tt&gt;:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;backup {&lt;br /&gt;  dev=/backup.dat&lt;br /&gt;  dir=/backup&lt;br /&gt;  fstype=ext2    fsoptions=defaults     cipher=aes&lt;br /&gt;&lt;br /&gt;  keyfile=/backup.key&lt;br /&gt;  keyhash=sha1    keycipher=des3&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;testbackup {&lt;br /&gt;  dev=/cdrom/backup.dat&lt;br /&gt;  dir=/backup&lt;br /&gt;  fstype=ext2    fsoptions=defaults    cipher=aes&lt;br /&gt;&lt;br /&gt;  keyfile=/cdrom/backup.key&lt;br /&gt;  keyhash=sha1    keycipher=des3&lt;br /&gt;}&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;h3&gt;Initialize the encrypted filesystem&lt;/h3&gt;Make sure you have at least 4.3 GB of free disk space on &lt;tt&gt;/&lt;/tt&gt; and then run:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;mkdir /backup&lt;br /&gt;dd if=/dev/zero of=/backup.dat bs=1M count=4096&lt;br /&gt;cryptmount --generate-key 32 backup&lt;br /&gt;cryptmount --prepare backup&lt;br /&gt;mkfs.ext2 -m 0 /dev/mapper/backup&lt;br /&gt;cryptmount --release backup&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;h3&gt;Burn the data to a DVD&lt;/h3&gt;Mount the newly created partition:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;cryptmount backup&lt;/pre&gt;&lt;/blockquote&gt;and then copy the files you want to &lt;tt&gt;/backup/&lt;/tt&gt; before unmounting that partition:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;cryptmount -u backup&lt;/pre&gt;&lt;/blockquote&gt;Finally, use your favourite DVD-burning program to burn these two files:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;tt&gt;/backup.dat&lt;/tt&gt;&lt;/li&gt;&lt;li&gt;&lt;tt&gt;/backup.key&lt;/tt&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h3&gt;Test your backup&lt;/h3&gt;Before deleting these two files, test the DVD you've just burned by mounting it:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;mount /cdrom&lt;br /&gt;cryptmount testbackup&lt;/pre&gt;&lt;/blockquote&gt;and looking at a random sampling of the files contained in &lt;tt&gt;/backup&lt;/tt&gt;.&lt;br /&gt;&lt;br /&gt;Once you are satisfied that your backup is fine, umount the DVD:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;cryptmount -u testbackup&lt;br /&gt;umount /cdrom&lt;/pre&gt;&lt;/blockquote&gt;and remove the temporary files:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;rm /backup.dat /backup.key&lt;/pre&gt;&lt;/blockquote&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-5261816034331387077?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=I3p6a0Y7dfc:4vlKramc3fo:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=I3p6a0Y7dfc:4vlKramc3fo:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?i=I3p6a0Y7dfc:4vlKramc3fo:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=I3p6a0Y7dfc:4vlKramc3fo:cGdyc7Q-1BI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=cGdyc7Q-1BI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=I3p6a0Y7dfc:4vlKramc3fo:YwkR-u9nhCs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=YwkR-u9nhCs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/FeedingTheCloud/~4/I3p6a0Y7dfc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://feeding.cloud.geek.nz/feeds/5261816034331387077/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=5261816034331387077" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/5261816034331387077?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/5261816034331387077?v=2" /><link rel="alternate" type="text/html" href="http://feeding.cloud.geek.nz/2011/04/encrypted-system-backup-to-dvd.html" title="Encrypted system backup to DVD" /><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg" /></author><thr:total>5</thr:total></entry><entry gd:etag="W/&quot;DUIDRno4eip7ImA9WhZRE00.&quot;"><id>tag:blogger.com,1999:blog-7615241590176793465.post-3126746129127927655</id><published>2011-03-31T22:00:00.002+13:00</published><updated>2011-04-09T12:06:17.432+12:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-04-09T12:06:17.432+12:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="wine" /><category scheme="http://www.blogger.com/atom/ns#" term="ubuntu" /><title>Installing Fallout 3 on Ubuntu Lucid</title><content type="html">Here are some notes on what I did to install Fallout 3 (Game of the year edition) on Ubuntu &lt;a href="https://launchpad.net/ubuntu/lucid"&gt;Lucid&lt;/a&gt; using &lt;a href="http://winehq.com/"&gt;Wine&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Prerequisites&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;use the Lucid &lt;a href="https://launchpad.net/ubuntu/+source/nvidia-graphics-drivers"&gt;NVIDIA drivers&lt;/a&gt;, the ones in the &lt;a href="https://edge.launchpad.net/~ubuntu-x-swat/+archive/x-updates"&gt;X Updates PPA&lt;/a&gt; currently do not work&lt;/li&gt;&lt;br /&gt;&lt;li&gt;install the latest version (actually &lt;a href="http://appdb.winehq.org/objectManager.php?sClass=version&amp;amp;iId=14322#Comment-64482"&gt;1.3.6 is broken&lt;/a&gt;, so use &lt;a href="https://launchpad.net/~ubuntu-wine/+archive/ppa/+packages?field.name_filter=wine1.3&amp;amp;field.status_filter=superseded&amp;amp;field.series_filter=lucid"&gt;1.3.5&lt;/a&gt; in the meantime) of Wine using the &lt;a href="https://launchpad.net/~ubuntu-wine/+archive/ppa"&gt;Ubuntu Wine Team PPA&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h3&gt;Basic Installation&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;follow the &lt;a href="http://appdb.winehq.org/objectManager.php?sClass=version&amp;amp;iId=14322"&gt;WineHQ instructions&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;after installing both DVDs, apply the &lt;a href="http://download.zenimax.com/fallout/3/patches/1.7/Fallout3_1.7_English_UK.exe"&gt;patch&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;set the appropriate performance settings for your hardware (make sure you use the same resolution as the one you use in Xorg)&lt;/li&gt;&lt;br /&gt;&lt;li&gt;click on "Data Files" and enable all of the Fallout 3 add-ons you have installed&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h3&gt;Performance Tweaks&lt;/h3&gt;Change these settings in your &lt;tt&gt;~/My Games/Fallout 3/FALLOUT.INI&lt;/tt&gt;:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;bUseThreadedParticleSystem=1&lt;br /&gt;bUseThreadedBlood=1&lt;br /&gt;bUseThreadedMorpher=1&lt;br /&gt;bUseThreadedAI=1&lt;br /&gt;iNumHWThreads=2&lt;br /&gt;iNumHavokThreads=5&lt;br /&gt;bDoSpecularPass=0&lt;br /&gt;bUseRefractionShader=0&lt;br /&gt;bInvalidateOlderFiles=1&lt;/pre&gt;&lt;/blockquote&gt;Then create a &lt;tt&gt;direct3d.reg&lt;/tt&gt; file (replacing &lt;i&gt;512&lt;/i&gt; with the correct amount of video memory you have, in MB):&lt;blockquote&gt;&lt;pre&gt;Windows Registry Editor Version 5.00&lt;br /&gt;&lt;br /&gt;[HKEY_CURRENT_USER\SOFTWARE\Wine\Direct3D]&lt;br /&gt;"OffscreenRenderingMode"="backbuffer"&lt;br /&gt;"VideoMemorySize"="&lt;i&gt;512&lt;/i&gt;"&lt;br /&gt;&lt;br /&gt;[HKEY_CURRENT_USER\Control Panel\Desktop]&lt;br /&gt;"FontSmoothing"="2"&lt;br /&gt;"FontSmoothingType"=dword:00000002&lt;br /&gt;"FontSmoothingGamma"=dword:00000578&lt;br /&gt;"FontSmoothingOrientation"=dword:00000001&lt;/pre&gt;&lt;/blockquote&gt;and import these settings (which come from the Wine &lt;a href="http://wiki.winehq.org/UsefulRegistryKeys"&gt;wiki&lt;/a&gt; and its &lt;a href="http://appdb.winehq.org/objectManager.php?sClass=version&amp;amp;iId=19444"&gt;appdb&lt;/a&gt;) into the registry:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;regedit file.reg&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;h3&gt;Savegame corruption&lt;/h3&gt;At some point, I did run into savegame corruption (mostly due to my hard disk filling up) and I created this cronjob in &lt;tt&gt;/etc/cron.hourly/fallout3-savegames&lt;/tt&gt; to detect corrupt savegame:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;#!/bin/sh&lt;br /&gt;find /path/to/Fallout3/Savegames/ -type f -name "*.tmp" -print&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;h3&gt;Installing mods&lt;/h3&gt;If you want to install &lt;a href="http://www.fallout3nexus.com/"&gt;user-contributed mods&lt;/a&gt; (for example, if you &lt;a href="http://www.fallout3nexus.com/downloads/file.php?id=7546"&gt;can't be bothered&lt;/a&gt; with the lockpicking and terminal hacking &lt;a href="http://fallout.wikia.com/wiki/Mini_games"&gt;mini-games&lt;/a&gt;), you will most likely want to install &lt;a href="http://sourceforge.net/projects/fomm/"&gt;FOMM&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;To do that, follow &lt;a href="http://appdb.winehq.org/objectManager.php?sClass=version&amp;amp;iId=14322#Comment-62556"&gt;these instructions&lt;/a&gt; to install dotnet20 and gdiplus using &lt;a href="https://launchpad.net/~ubuntu-wine/+archive/ppa/+packages?field.name_filter=winetricks&amp;amp;field.status_filter=&amp;amp;field.series_filter=lucid"&gt;winetricks&lt;/a&gt; otherwise you may run into &lt;a href="http://bugs.winehq.org/show_bug.cgi?id=23692"&gt;this bug&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Finally, pay attention to the &lt;a href="http://amito.freehostia.com/Fallout/FO-mods-order.htm"&gt;order&lt;/a&gt; in which your selected mods are loaded since some of the mods will overwrite files provided by another.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Hints and tips&lt;/h3&gt;There is an incredibly dedicated community maintaining a &lt;a href="http://fallout.wikia.com/wiki/Portal:Fallout_3"&gt;wiki&lt;/a&gt; for this game as well as all of the other "chapters" of the Fallout series. Use it wisely!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-3126746129127927655?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=o8EMH1ZmIcs:TSc-q0z5ZYQ:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=o8EMH1ZmIcs:TSc-q0z5ZYQ:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?i=o8EMH1ZmIcs:TSc-q0z5ZYQ:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=o8EMH1ZmIcs:TSc-q0z5ZYQ:cGdyc7Q-1BI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=cGdyc7Q-1BI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=o8EMH1ZmIcs:TSc-q0z5ZYQ:YwkR-u9nhCs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=YwkR-u9nhCs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/FeedingTheCloud/~4/o8EMH1ZmIcs" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://feeding.cloud.geek.nz/feeds/3126746129127927655/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=3126746129127927655" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/3126746129127927655?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/3126746129127927655?v=2" /><link rel="alternate" type="text/html" href="http://feeding.cloud.geek.nz/2011/03/installing-fallout-3-on-ubuntu-lucid.html" title="Installing Fallout 3 on Ubuntu Lucid" /><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;D0ADRH45cSp7ImA9WhZTEEw.&quot;"><id>tag:blogger.com,1999:blog-7615241590176793465.post-6460371366857970364</id><published>2011-03-13T23:25:00.001+13:00</published><updated>2011-03-14T00:29:35.029+13:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-03-14T00:29:35.029+13:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="catalyst" /><category scheme="http://www.blogger.com/atom/ns#" term="raid" /><category scheme="http://www.blogger.com/atom/ns#" term="debian" /><category scheme="http://www.blogger.com/atom/ns#" term="sysadmin" /><category scheme="http://www.blogger.com/atom/ns#" term="ubuntu" /><category scheme="http://www.blogger.com/atom/ns#" term="nzoss" /><title>Setting up RAID on an existing Debian/Ubuntu installation</title><content type="html">I run &lt;a href="https://secure.wikimedia.org/wikipedia/en/wiki/Standard_RAID_levels#RAID_1"&gt;RAID1&lt;/a&gt; on all of the machines I support. While such hard disk mirroring is not a replacement for having good working backups, it means that a single drive failure is not going to force me to have to spend lots of time rebuilding a machine.&lt;br /&gt;&lt;br /&gt;The best possible time to set this up is of course when you first install the operating system. The &lt;a href="http://www.debian.org/"&gt;Debian&lt;/a&gt; installer will set everything up for you if you choose that option and Ubuntu has &lt;a href="http://www.ubuntu.com/desktop/get-ubuntu/alternative-download#alternate"&gt;alternate installation CDs&lt;/a&gt; which allow you to do the same.&lt;br /&gt;&lt;br /&gt;This post documents the steps I followed to retrofit RAID1 into an existing Debian squeeze installation. Getting a mirrored setup after the fact.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Overview&lt;/h3&gt;Before you start, make sure the following packages are installed:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;apt-get install mdadm rsync initramfs-tools&lt;/pre&gt;&lt;/blockquote&gt;Then go through these steps:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Partition the new drive.&lt;/li&gt;&lt;li&gt;Create new degraded RAID arrays.&lt;/li&gt;&lt;li&gt;Install GRUB2 on both drives.&lt;/li&gt;&lt;li&gt;Copy existing data onto the new drive.&lt;/li&gt;&lt;li&gt;Reboot using the RAIDed drive and test system.&lt;/li&gt;&lt;li&gt;Wipe the original drive by adding it to the RAID array.&lt;/li&gt;&lt;li&gt;Test booting off of the original drive.&lt;/li&gt;&lt;li&gt;Resync drives.&lt;/li&gt;&lt;li&gt;Test booting off of the new drive.&lt;/li&gt;&lt;li&gt;Reboot with the two drives and resync the array.&lt;/li&gt;&lt;/ol&gt;(My instructions are mostly based on this &lt;a href="http://wiki.xtronics.com/index.php/Raid"&gt;old tutorial&lt;/a&gt; but also on this &lt;a href="http://www.howtoforge.com/how-to-set-up-software-raid1-on-a-running-system-incl-grub2-configuration-ubuntu-10.04"&gt;more recent one&lt;/a&gt;.)&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;1- Partition the new drive&lt;/h3&gt;Once you have connected the new drive (&lt;tt&gt;&lt;b&gt;/dev/sdb&lt;/b&gt;&lt;/tt&gt;), boot into your system and use one of &lt;tt&gt;cfdisk&lt;/tt&gt; or &lt;tt&gt;fdisk&lt;/tt&gt; to display the partition information for the existing drive (&lt;tt&gt;&lt;b&gt;/dev/sda&lt;/b&gt;&lt;/tt&gt; on my system).&lt;br /&gt;&lt;br /&gt;The idea is to create partitions of the same size on the new drive. (If the new drive is bigger, leave the rest of the drive unpartitioned.)&lt;br /&gt;&lt;br /&gt;Partition types should all be: &lt;tt&gt;&lt;b&gt;fd&lt;/b&gt;&lt;/tt&gt; (or "linux raid autodetect").&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;2- Create new degraded RAID arrays&lt;/h3&gt;The newly partioned drive, consisting of a root and a swap partition, can be added to new RAID1 arrays using &lt;tt&gt;mdadm&lt;/tt&gt;:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;mdadm --create /dev/md0 --level=1 --raid-devices=2 missing /dev/sdb1&lt;br /&gt;mdadm --create /dev/md1 --level=1 --raid-devices=2 missing /dev/sdb2&lt;/pre&gt;&lt;/blockquote&gt;and formatted like this:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;mkswap /dev/md1&lt;br /&gt;mkfs.ext4 /dev/md0&lt;/pre&gt;&lt;/blockquote&gt;Specify these devices explicitly in &lt;tt&gt;/etc/mdadm/mdadm.conf&lt;/tt&gt;:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;DEVICE /dev/sda* /dev/sdb*&lt;/pre&gt;&lt;/blockquote&gt;and append the RAID arrays to the end of that file:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;mdadm --detail --scan &gt;&gt; /etc/mdadm/mdadm.conf&lt;br /&gt;dpkg-reconfigure mdadm&lt;/pre&gt;&lt;/blockquote&gt;You can check the status of your RAID arrays at any time by running this command:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;cat /proc/mdstat&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;h3&gt;3- Install GRUB2 on both drives&lt;/h3&gt;The best way to ensure that &lt;a href="https://help.ubuntu.com/community/Grub2"&gt;GRUB2&lt;/a&gt;, the default bootloader in Debian and Ubuntu, is installed on both drives is to reconfigure its package:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;dpkg-reconfigure grub-pc&lt;/pre&gt;&lt;/blockquote&gt;and select both &lt;tt&gt;/dev/sda&lt;/tt&gt; and &lt;tt&gt;/dev/sdb&lt;/tt&gt; (but not &lt;tt&gt;/dev/md0&lt;/tt&gt;) as installation targets.&lt;br /&gt;&lt;br /&gt;This should cause the init ramdisk (&lt;tt&gt;/boot/initrd.img-2.6.32-5-amd64&lt;/tt&gt;) and the grub menu (&lt;tt&gt;/boot/grub/grub.cfg&lt;/tt&gt;) to be rebuilt with RAID support.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;4- Copy existing data onto the new drive&lt;/h3&gt;Copy everything that's on the existing drive onto the new one using &lt;tt&gt;rsync&lt;/tt&gt;:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;mkdir /tmp/mntroot&lt;br /&gt;mount /dev/md0 /tmp/mntroot&lt;br /&gt;rsync -auHxv --exclude=/proc/* --exclude=/sys/* --exclude=/tmp/* /* /tmp/mntroot/&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;h3&gt;5- Reboot using the RAIDed drive and test system&lt;/h3&gt;Before rebooting, open &lt;tt&gt;/tmp/mntroot/etc/fstab&lt;/tt&gt;, and change &lt;tt&gt;/dev/sda1&lt;/tt&gt; and &lt;tt&gt;/dev/sda2&lt;/tt&gt; to &lt;tt&gt;/dev/md0&lt;/tt&gt; and &lt;tt&gt;/dev/md1 &lt;/tt&gt;respectively.&lt;div&gt;&lt;br /&gt;Then reboot and from within the GRUB menu, hit "e" to enter edit mode and make sure that you will be booting off of the new disk:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;set root='(&lt;b&gt;md/0&lt;/b&gt;)'&lt;br /&gt;linux /boot/vmlinuz-2.6.32-5-amd64 root=&lt;b&gt;/dev/md0&lt;/b&gt; ro quiet&lt;/pre&gt;&lt;/blockquote&gt;Once the system is up, you can check that the root partition is indeed using the RAID array by running &lt;tt&gt;mount&lt;/tt&gt; and looking for something like:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;&lt;b&gt;/dev/md0 on /&lt;/b&gt; type ext4 (rw,noatime,errors=remount-ro)&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;h3&gt;6- Wipe the original drive by adding it to the RAID array&lt;/h3&gt;Once you have verified that everything is working on &lt;tt&gt;/dev/sdb&lt;/tt&gt;, it's time to change the partition types on &lt;tt&gt;/dev/sda&lt;/tt&gt; to &lt;tt&gt;fd&lt;/tt&gt; and to add the original drive to the degraded RAID array:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;mdadm /dev/md0 -a /dev/sda1&lt;br /&gt;mdadm /dev/md1 -a /dev/sda2&lt;/pre&gt;&lt;/blockquote&gt;You'll have to wait until the two partitions are fully synchronized but you can check the sync status using:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;watch -n1 cat /proc/mdstat&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;h3&gt;7- Test booting off of the original drive&lt;/h3&gt;Once the sync is finished, update the boot loader menu:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;update-grub&lt;/pre&gt;&lt;/blockquote&gt;and shut the system down:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;shutdown -h now&lt;/pre&gt;&lt;/blockquote&gt;before physically disconnecting &lt;tt&gt;/dev/sdb&lt;/tt&gt; and turning the machine back on to test booting with only &lt;tt&gt;/dev/sda&lt;/tt&gt; present.&lt;br /&gt;&lt;br /&gt;After a successful boot, shut the machine down and plug the second drive back in before powering it up again.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;8- Resync drives&lt;/h3&gt;If everything works, you should see the following after running &lt;tt&gt;cat /proc/mdstat&lt;/tt&gt;:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;md0 : active raid1 sda1[1]&lt;br /&gt;280567040 blocks [2/1] [_U]&lt;/pre&gt;&lt;/blockquote&gt;indicating that the RAID array is incomplete and that the second drive is not part of it.&lt;br /&gt;&lt;br /&gt;To add the second drive back in and start the sync again:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;mdadm /dev/md0 -a &lt;b&gt;/dev/sdb1&lt;/b&gt;&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;h3&gt;9- Test booting off of the new drive&lt;/h3&gt;To complete the testing, shut the machine down, pull &lt;tt&gt;/dev/sda&lt;/tt&gt; out and try booting with &lt;tt&gt;/dev/sdb&lt;/tt&gt; only.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;10- Reboot with the two drives and resync the array&lt;/h3&gt;Once you are satisfied that it works, reboot with both drives plugged in and re-add the first drive to the array:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;mdadm /dev/md0 -a &lt;b&gt;/dev/sda1&lt;/b&gt;&lt;/pre&gt;&lt;/blockquote&gt;Your setup is now complete and fully tested.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Ongoing maintenance&lt;/h3&gt;I recommend making sure the two RAIDed drives stay in sync by enabling periodic RAID checks. The easiest way is to enable the checks that are built into the Debian package:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;dpkg-reconfigure mdadm&lt;/pre&gt;&lt;/blockquote&gt;but you can also create a weekly or monthly cronjob which does the following:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;echo "check" &gt; /sys/block/&lt;b&gt;md0&lt;/b&gt;/md/sync_action&lt;/pre&gt;&lt;/blockquote&gt;Something else you should seriously consider is to install the &lt;tt&gt;smartmontools&lt;/tt&gt; package and run weekly &lt;a href="https://secure.wikimedia.org/wikipedia/en/wiki/S.M.A.R.T."&gt;SMART&lt;/a&gt; checks by putting something like this in your  &lt;tt&gt;/etc/smartd.conf&lt;/tt&gt;:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;/dev/sda -a -d ata -o on -S on -s (S/../.././02|L/../../6/03)&lt;br /&gt;/dev/sdb -a -d ata -o on -S on -s (S/../.././02|L/../../6/03)&lt;/pre&gt;&lt;/blockquote&gt;These checks, performed by the hard disk controllers directly, could warn you of imminent failures ahead of time. Personally, when I start seeing errors in the SMART log (&lt;tt&gt;smartctl -a /dev/sda&lt;/tt&gt;), I order a new drive straight away.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-6460371366857970364?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=KyhmcmjydE4:huQJ6-NgVaA:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=KyhmcmjydE4:huQJ6-NgVaA:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?i=KyhmcmjydE4:huQJ6-NgVaA:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=KyhmcmjydE4:huQJ6-NgVaA:cGdyc7Q-1BI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=cGdyc7Q-1BI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=KyhmcmjydE4:huQJ6-NgVaA:YwkR-u9nhCs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=YwkR-u9nhCs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/FeedingTheCloud/~4/KyhmcmjydE4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://feeding.cloud.geek.nz/feeds/6460371366857970364/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=6460371366857970364" title="8 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/6460371366857970364?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/6460371366857970364?v=2" /><link rel="alternate" type="text/html" href="http://feeding.cloud.geek.nz/2011/03/setting-up-raid-on-existing.html" title="Setting up RAID on an existing Debian/Ubuntu installation" /><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg" /></author><thr:total>8</thr:total></entry><entry gd:etag="W/&quot;CkMEQn0-cSp7ImA9Wx9VFU0.&quot;"><id>tag:blogger.com,1999:blog-7615241590176793465.post-3355899107860783316</id><published>2011-02-01T08:00:00.001+13:00</published><updated>2011-02-01T08:00:03.359+13:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-02-01T08:00:03.359+13:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="catalyst" /><category scheme="http://www.blogger.com/atom/ns#" term="debian" /><category scheme="http://www.blogger.com/atom/ns#" term="git" /><category scheme="http://www.blogger.com/atom/ns#" term="nzoss" /><title>Keeping a log of branch updates on a git server</title><content type="html">Using a combination of bad luck and some of the more advanced &lt;a href="http://git-scm.com/"&gt;git&lt;/a&gt; options, it is possible to mess up a &lt;a href="http://feeding.cloud.geek.nz/2008/12/setting-up-centralied-git-repository.html"&gt;centralised repository&lt;/a&gt; by accidentally pushing a branch and overwriting the existing branch pointer (or "head") on the server.&lt;br /&gt;&lt;br /&gt;If you know where the head was pointing prior to that push, recovering it is a simple matter of running this on the server:&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;git update-ref /refs/heads/&lt;i&gt;branchname&lt;/i&gt; &lt;i&gt;commit_id&lt;/i&gt;&lt;/code&gt;&lt;/blockquote&gt;However, if you don't know the previous commit ID, then you pretty much have to dig through the history using &lt;tt&gt;git log&lt;/tt&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Enabling a server-side reflog&lt;/h3&gt;One option to prevent this from happening is to simply enable the &lt;a href="http://www.kernel.org/pub/software/scm/git/docs/git-reflog.html"&gt;reflog&lt;/a&gt;, which is disabled by default in bare repositories, on the server.&lt;br /&gt;&lt;br /&gt;Simply add this to your git config file on the server:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;[core]&lt;br /&gt;   logallrefupdates = true&lt;/pre&gt;&lt;/blockquote&gt;and then whenever a head is updated, an entry will be added to the reflog.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-3355899107860783316?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=98Ys86OwLYc:swNmL07xqpo:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=98Ys86OwLYc:swNmL07xqpo:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?i=98Ys86OwLYc:swNmL07xqpo:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=98Ys86OwLYc:swNmL07xqpo:cGdyc7Q-1BI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=cGdyc7Q-1BI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=98Ys86OwLYc:swNmL07xqpo:YwkR-u9nhCs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=YwkR-u9nhCs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/FeedingTheCloud/~4/98Ys86OwLYc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://feeding.cloud.geek.nz/feeds/3355899107860783316/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=3355899107860783316" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/3355899107860783316?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/3355899107860783316?v=2" /><link rel="alternate" type="text/html" href="http://feeding.cloud.geek.nz/2011/02/keeping-log-of-branch-updates-on-git.html" title="Keeping a log of branch updates on a git server" /><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg" /></author><thr:total>1</thr:total></entry><entry gd:etag="W/&quot;C0QHSH84fCp7ImA9Wx9VEE4.&quot;"><id>tag:blogger.com,1999:blog-7615241590176793465.post-6876231385995943320</id><published>2011-01-25T17:00:00.007+13:00</published><updated>2011-01-26T21:42:19.134+13:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-01-26T21:42:19.134+13:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="catalyst" /><category scheme="http://www.blogger.com/atom/ns#" term="optimisation" /><category scheme="http://www.blogger.com/atom/ns#" term="debian" /><category scheme="http://www.blogger.com/atom/ns#" term="nzoss" /><title>Serving pre-compressed files using Apache</title><content type="html">The easiest way to compress the data that is being served to the visitors of your web application is to make use of &lt;a href="http://httpd.apache.org/docs/2.2/mod/mod_deflate.html"&gt;mod_deflate&lt;/a&gt;. Once you have enabled that module and provided it with a &lt;a href="http://feeding.cloud.geek.nz/2009/10/reducing-website-bandwidth-usage.html"&gt;suitable&lt;/a&gt; configuration file, it will compress all releant files on the fly as it is serving them.&lt;br /&gt;&lt;br /&gt;Given that I was already going to minify my Javascript and CSS files ahead of time (i.e. not using &lt;a href="https://code.google.com/p/modpagespeed/"&gt;mod_pagespeed&lt;/a&gt;), I figured that there must be a way for me to serve gzipped files directly.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;"Compiling" Static Files&lt;/h3&gt;I decided to treat my &lt;a href="http://www.launchpad.net/libravatar"&gt;web application&lt;/a&gt; like a c program. After all, it starts as readable source code and ends up as an unreadable binary file.&lt;br /&gt;&lt;br /&gt;So I created a Makefile to minify and compress all CSS and Javascript files using &lt;a href="http://developer.yahoo.com/yui/compressor/"&gt;YUI Compressor&lt;/a&gt; and &lt;tt&gt;gzip&lt;/tt&gt;:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;all: build&lt;br /&gt;&lt;br /&gt;build:&lt;br /&gt;     find static/css -type f -name "[^.]*.css" -execdir yui-compressor -o {}.css {} \;&lt;br /&gt;     find static/js -type f -name "[^.]*.js"  -execdir yui-compressor -o {}.js {} \;&lt;br /&gt;     cd static/css &amp;amp;&amp;amp; for f in *.css.css ; do gzip -c $$f &gt; `basename $$f .css`.gz ; done&lt;br /&gt;     cd static/js &amp;amp;&amp;amp; for f in *.js.js ; do gzip -c $$f &gt; `basename $$f .js`.gz ; done&lt;br /&gt;&lt;br /&gt;clean:&lt;br /&gt;     find static/css -name "*.css.css" -delete&lt;br /&gt;     find static/js -name "*.js.js" -delete&lt;br /&gt;     find static/css -name "*.css.gz" -delete&lt;br /&gt;     find static/js -name "*.js.gz" -delete&lt;br /&gt;     find -name "*.pyc" -delete&lt;/pre&gt;&lt;br /&gt;This leaves the original files intact and adds minified &lt;tt&gt;.css.css&lt;/tt&gt; and &lt;tt&gt;.js.js&lt;/tt&gt; files as well as minified and compressed &lt;tt&gt;.css.gz&lt;/tt&gt; and &lt;tt&gt;.js.gz&lt;/tt&gt; files.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;How browsers advertise gzip support&lt;/h3&gt;The nice thing about serving compressed content to browsers is that browsers that support receiving gzipped content (almost all of them nowadays) include the following HTTP header in their requests:&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;Accept-Encoding = gzip,deflate&lt;/code&gt;&lt;/blockquote&gt;(Incidently, if you want to test what non-gzipped enable browsers see, just browse to &lt;tt&gt;about:config&lt;/tt&gt; and remove what's in the &lt;tt&gt;network.http.accept-encoding&lt;/tt&gt; variable.)&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Serving compressed files to clients&lt;/h3&gt;To serve different files to different browsers, all that's needed is to enable &lt;a href="http://httpd.apache.org/docs/2.2/content-negotiation.html#multiviews"&gt;Multiviews&lt;/a&gt; in our Apache configuration (as suggested on the &lt;a href="http://mail-archives.apache.org/mod_mbox/httpd-users/200404.mbox/%3CPine.WNT.4.58.0404231258350.2104@HEC-4949%3E"&gt;Apache mailing list&lt;/a&gt;):&lt;br /&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="font-size: 13px; white-space: pre;"&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="font-size: 16px; white-space: normal;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;pre&gt;&amp;lt;Directory /var/www/static/css&amp;gt;&lt;br /&gt; AddEncoding gzip gz&lt;br /&gt; ForceType text/css&lt;br /&gt; Options +Multiviews&lt;br /&gt; SetEnv force-no-vary&lt;br /&gt; Header set Cache-Control "private"&lt;br /&gt;&amp;lt;/Directory&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;Directory /var/www/static/js&amp;gt;&lt;br /&gt; AddEncoding gzip gz&lt;br /&gt; ForceType text/javascript&lt;br /&gt; Options +Multiviews&lt;br /&gt; SetEnv force-no-vary&lt;br /&gt; Header set Cache-Control "private"&lt;br /&gt;&amp;lt;/Directory&amp;gt;&lt;/pre&gt;&lt;br /&gt;The &lt;tt&gt;ForceType&lt;/tt&gt; directive is there to force the mimetype (as described in this &lt;a href="http://perfectionlabstips.wordpress.com/2008/12/30/serving-gzipped-gz-files-as-compressed-css-javascript-html-content/"&gt;solution&lt;/a&gt;) and to make sure that browsers (including Firefox) don't download the files to disk.&lt;br /&gt;&lt;br /&gt;As for the &lt;tt&gt;SetEnv&lt;/tt&gt; directive, it turns out that on Internet Explorer, most files with a Vary header (added by Apache) &lt;a href="https://code.google.com/speed/page-speed/docs/caching.html#LeverageBrowserCaching"&gt;are not cached&lt;/a&gt; and so we must make sure it gets &lt;a href="http://httpd.apache.org/docs/current/env.html#force-no-vary"&gt;stripped out&lt;/a&gt; before the response goes out.&lt;br /&gt;&lt;br /&gt;Finally, the &lt;tt&gt;Cache-Control&lt;/tt&gt; headers are set to &lt;tt&gt;private&lt;/tt&gt; to &lt;a href="https://code.google.com/speed/page-speed/docs/caching.html#LeverageProxyCaching"&gt;prevent intermediate/transparent proxies from caching&lt;/a&gt; our CSS and Javascript files, while allowing browsers to do so. If intermediate proxies start caching compressed content, they may incorrectly serve it to clients without gzip support.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-6876231385995943320?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=vBZB-PxEVxk:uzl8iNISDAk:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=vBZB-PxEVxk:uzl8iNISDAk:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?i=vBZB-PxEVxk:uzl8iNISDAk:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=vBZB-PxEVxk:uzl8iNISDAk:cGdyc7Q-1BI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=cGdyc7Q-1BI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=vBZB-PxEVxk:uzl8iNISDAk:YwkR-u9nhCs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=YwkR-u9nhCs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/FeedingTheCloud/~4/vBZB-PxEVxk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://feeding.cloud.geek.nz/feeds/6876231385995943320/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=6876231385995943320" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/6876231385995943320?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/6876231385995943320?v=2" /><link rel="alternate" type="text/html" href="http://feeding.cloud.geek.nz/2011/01/serving-pre-compressed-files-using.html" title="Serving pre-compressed files using Apache" /><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg" /></author><thr:total>5</thr:total></entry><entry gd:etag="W/&quot;AkMDQHw7cCp7ImA9WhZUFUs.&quot;"><id>tag:blogger.com,1999:blog-7615241590176793465.post-2550245813848115691</id><published>2011-01-12T20:20:00.003+13:00</published><updated>2011-06-09T09:27:51.208+12:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-06-09T09:27:51.208+12:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="gearman" /><category scheme="http://www.blogger.com/atom/ns#" term="catalyst" /><category scheme="http://www.blogger.com/atom/ns#" term="debian" /><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="ubuntu" /><category scheme="http://www.blogger.com/atom/ns#" term="nzoss" /><title>Sample Python application using Libgearman</title><content type="html">&lt;a href="http://www.gearman.org/"&gt;Gearman&lt;/a&gt; is a distributed queue with several &lt;a href="https://launchpad.net/gearman-interface"&gt;language bindings&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;While Gearman has a nice &lt;a href="http://www.python.org/"&gt;Python&lt;/a&gt; implementation (&lt;a href="http://pypi.python.org/pypi/gearman"&gt;python-gearman&lt;/a&gt;) of the client and worker, I chose to use the libgearman bindings (&lt;a href="http://pypi.python.org/pypi/python-libgearman"&gt;python-libgearman&lt;/a&gt;) directly since they are already packaged for &lt;a href="http://www.debian.org/"&gt;Debian&lt;/a&gt; (as &lt;a href="http://packages.debian.org/python-gearman.libgearman"&gt;python-gearman.libgearman&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;Unfortunately, these bindings are not very well documented, so here's the sample application I wished I had seen before I started.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Using the command-line tools&lt;/h3&gt;Before diving into the Python bindings, you should make sure that you can get a quick application working on the command line (using the &lt;a href="http://packages.debian.org/gearman-tools"&gt;gearman-tools&lt;/a&gt; package).&lt;br /&gt;&lt;br /&gt;Here's a very simple worker which returns verbatim the input it receives:&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;gearman -w -f myfunction cat&lt;/code&gt;&lt;/blockquote&gt;and here is the matching client:&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;gearman -f myfunction 'test'&lt;/code&gt;&lt;/blockquote&gt;You can have have a look at the &lt;a href="https://groups.google.com/group/gearman/browse_thread/thread/398b3493a3eacb71/cf7691c645d826e4?lnk=gst&amp;amp;q=monitor#cf7691c645d826e4"&gt;status of the queues in the server&lt;/a&gt; by connecting to gearmand via telnet (port 4730) and issuing the &lt;tt&gt;status&lt;/tt&gt; command.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Using the Python libgearman bindings&lt;/h3&gt;Once your gearman setup is working (debugging is easier with the command-line tools), you can roll the gearman connection code into your application.&lt;br /&gt;&lt;br /&gt;Here's a simple Python worker which returns what it receives:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;#!/usr/bin/python&lt;br /&gt;&lt;br /&gt;from gearman import libgearman&lt;br /&gt;&lt;br /&gt;def work(job):&lt;br /&gt;   workload = job.get_workload()&lt;br /&gt;   return workload&lt;br /&gt;&lt;br /&gt;gm_worker = libgearman.Worker()&lt;br /&gt;gm_worker.add_server('localhost')&lt;br /&gt;gm_worker.add_function('myfunction', work)&lt;br /&gt;&lt;br /&gt;while True:&lt;br /&gt;   gm_worker.work()&lt;/pre&gt;&lt;/blockquote&gt;and a matching client:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;#!/usr/bin/python&lt;br /&gt;&lt;br /&gt;from gearman import libgearman&lt;br /&gt;&lt;br /&gt;gm_client = libgearman.Client()&lt;br /&gt;gm_client.add_server('localhost')&lt;br /&gt;&lt;br /&gt;result = gm_client.do('myfunction', 'test')&lt;br /&gt;print result&lt;/pre&gt;&lt;/blockquote&gt;This should behave in exactly the same way as the command-line examples above.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Returning job errors&lt;/h3&gt;If you want to expose to the client errors in the processing done by the worker, modify the worker like this:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;#!/usr/bin/python&lt;br /&gt;&lt;br /&gt;from gearman import libgearman&lt;br /&gt;&lt;br /&gt;def work(job):&lt;br /&gt;   workload = job.get_workload()&lt;br /&gt;   &lt;b&gt;if workload == 'fail':&lt;br /&gt;       job.send_fail()&lt;/b&gt;&lt;br /&gt;   return workload&lt;br /&gt;&lt;br /&gt;gm_worker = libgearman.Worker()&lt;br /&gt;gm_worker.add_server('localhost')&lt;br /&gt;gm_worker.add_function('myfunction', work)&lt;br /&gt;&lt;br /&gt;while True:&lt;br /&gt;   gm_worker.work()&lt;/pre&gt;&lt;/blockquote&gt;and the client this way:&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;#!/usr/bin/python&lt;br /&gt;&lt;br /&gt;from gearman import libgearman&lt;br /&gt;&lt;br /&gt;gm_client = libgearman.Client()&lt;br /&gt;gm_client.add_server('localhost')&lt;br /&gt;&lt;br /&gt;result = gm_client.do('myfunction', '&lt;b&gt;fail&lt;/b&gt;')&lt;br /&gt;print result&lt;/pre&gt;&lt;/blockquote&gt;&lt;h3&gt;License&lt;/h3&gt;The above source code is released under the following terms:&lt;br /&gt;&lt;blockquote dct="http://purl.org/dc/terms/" vcard="http://www.w3.org/2001/vcard-rdf/3.0#"&gt;&lt;a rel="license" href="http://creativecommons.org/publicdomain/zero/1.0/"&gt;&lt;img src="http://i.creativecommons.org/p/zero/1.0/88x31.png" style="border-style: none;" alt="CC0" /&gt;&lt;/a&gt;&lt;br /&gt;To the extent possible under law, &lt;a rel="dct:publisher" href="http://feeding.cloud.geek.nz/"&gt;&lt;span property="dct:title"&gt;Francois Marier&lt;/span&gt;&lt;/a&gt; has waived all copyright and related or neighboring rights to this &lt;span property="dct:title"&gt;sample libgearman Python application&lt;/span&gt;. This work is published from: &lt;span property="vcard:Country" datatype="dct:ISO3166" content="NZ" about="http://feeding.cloud.geek.nz"&gt;New Zealand&lt;/span&gt;.&lt;/blockquote&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-2550245813848115691?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=iLOUaeZIco8:WhZebg-citg:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=iLOUaeZIco8:WhZebg-citg:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?i=iLOUaeZIco8:WhZebg-citg:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=iLOUaeZIco8:WhZebg-citg:cGdyc7Q-1BI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=cGdyc7Q-1BI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=iLOUaeZIco8:WhZebg-citg:YwkR-u9nhCs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=YwkR-u9nhCs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/FeedingTheCloud/~4/iLOUaeZIco8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://feeding.cloud.geek.nz/feeds/2550245813848115691/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=2550245813848115691" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/2550245813848115691?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/2550245813848115691?v=2" /><link rel="alternate" type="text/html" href="http://feeding.cloud.geek.nz/2011/01/sample-python-application-using.html" title="Sample Python application using Libgearman" /><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg" /></author><thr:total>0</thr:total></entry><entry gd:etag="W/&quot;CUEGQ3wzfip7ImA9Wx9RF0k.&quot;"><id>tag:blogger.com,1999:blog-7615241590176793465.post-8508576985428845094</id><published>2010-12-19T19:55:00.008+13:00</published><updated>2010-12-19T20:27:02.286+13:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-12-19T20:27:02.286+13:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="catalyst" /><category scheme="http://www.blogger.com/atom/ns#" term="gstreamer" /><category scheme="http://www.blogger.com/atom/ns#" term="debian" /><category scheme="http://www.blogger.com/atom/ns#" term="sysadmin" /><category scheme="http://www.blogger.com/atom/ns#" term="ubuntu" /><category scheme="http://www.blogger.com/atom/ns#" term="nzoss" /><title>Peer-to-peer video-conferencing using free software</title><content type="html">I was looking for a simple &lt;a href="http://www.gnu.org/philosophy/free-sw.html"&gt;free software&lt;/a&gt; solution which would allow me to have a video call with someone else (I don't care about sound since I've already got that working through &lt;a href="http://www.asterisk.org/"&gt;Asterisk&lt;/a&gt;) and I ended up writing a &lt;a href="http://www.gstreamer.net/"&gt;Gstreamer&lt;/a&gt;-based poor man's videoconf solution because I wasn't satisfied with the other options I considered.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Empathy&lt;/h2&gt;&lt;a href="http://live.gnome.org/Empathy"&gt;Empathy&lt;/a&gt; was my first choice since it seems to be the preferred &lt;a href="http://www.gnome.org/"&gt;GNOME&lt;/a&gt; communication software nowadays.&lt;br /&gt;&lt;br /&gt;While the quality of the video was excellent, the latency between New Zealand and Canada was unbearable: a full 6 seconds. I suspect that this is due to the fact that it runs everything through the Google Talk &lt;a href="https://secure.wikimedia.org/wikipedia/en/wiki/Session_Traversal_Utilities_for_NAT"&gt;STUN&lt;/a&gt; server and I couldn't find how to force it to go directly from one host to the other.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Ekiga&lt;/h2&gt;&lt;a href="http://www.ekiga.org/"&gt;Ekiga&lt;/a&gt; was my second choice since I had used it succesfully in the past.&lt;br /&gt;&lt;br /&gt;It was not too bad latency-wise, but the quality of the video was not as good as Empathy (it was smaller and choppier). Also, given that it was running over &lt;a href="https://secure.wikimedia.org/wikipedia/en/wiki/Session_Initiation_Protocol"&gt;SIP&lt;/a&gt;, it was interfering with my &lt;a href="http://wiki.snom.com/Snom300"&gt;VoIP phone&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Direct peer-to-peer streaming&lt;/h2&gt;Given that I wasn't gonna use the voice features of these video-conference tools, I figured that there must be an easy way to &lt;a href="http://www.olpcnews.com/forum/index.php?topic=1752.5;wap2"&gt;just stream video from one peer to the other&lt;/a&gt;. That's when I thought of looking into &lt;a href="http://wiki.laptop.org/go/GStreamer"&gt;Gstreamer&lt;/a&gt; (&lt;tt&gt;apt-get install gstreamer0.10-tools&lt;/tt&gt; on Debian/Ubuntu).&lt;br /&gt;&lt;br /&gt;To stream video from my webcam onto port 5000, I ran:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;gst-launch v4l2src device=/dev/video0 ! videorate ! video/x-raw-yuv,width=640,height=480,framerate=6/1 ! jpegenc quality=30 ! multipartmux ! tcpserversink port=5000&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;which is the best I could do within 85 kbps (100-120 kbps is about the maximum reliable synchronous bandwidth I get between New Zealand and Canada):&lt;ul&gt;&lt;li&gt;resolution of 640x480&lt;/li&gt;&lt;li&gt;6 frames per second&lt;/li&gt;&lt;li&gt;jpeg quality of 30%&lt;/li&gt;&lt;/ul&gt;On the other computer, I simply ran this to connect and display the remote stream:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;gst-launch tcpclientsrc host=&lt;i&gt;stream.example.com&lt;/i&gt; port=5000 ! multipartdemux ! jpegdec ! autovideosink&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Then I swapped the roles around to also stream video the other way around. That's it: two-way peer-to-peer video link!&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Small tweaks to the Gstreamer pipeline&lt;/h2&gt;There are quite a few &lt;a href="http://gstreamer.freedesktop.org/documentation/plugins.html"&gt;plugins&lt;/a&gt; that can be used within &lt;a href="http://www.gstreamer.net/data/doc/gstreamer/head/manual/html/section-intro-basics-bins.html"&gt;Gstreamer pipelines&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;If you have problems with &lt;tt&gt;autovideosink&lt;/tt&gt; refusing to load (I did on one of the two computers), you can also install the &lt;tt&gt;gstreamer0.10-sdl&lt;/tt&gt; package and replace &lt;tt&gt;autovideosink&lt;/tt&gt; with &lt;tt&gt;sdlvideosink&lt;/tt&gt;:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;gst-launch tcpclientsrc host=example.com port=5000 ! multipartdemux ! jpegdec ! &lt;b&gt;sdlvideosink&lt;/b&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Another change I had to make on one of the machines was to &lt;b&gt;flip the image&lt;/b&gt; coming out of the webcam (which insists on giving me a mirror image instead of acting like a real camera):&lt;br /&gt;&lt;br /&gt;&lt;code&gt;gst-launch v4l2src device=/dev/video0 ! videorate ! video/x-raw-yuv,width=640,height=480,framerate=6/1 ! &lt;b&gt;videoflip method=horizontal-flip&lt;/b&gt; ! jpegenc quality=30 ! multipartmux ! tcpserversink port=5000&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Possible improvements&lt;/h2&gt;I got down to about 1-2 seconds of latency, which isn't bad considering the processing to be done and the distance bits have to travel, but I would love to further reduce this.&lt;br /&gt;&lt;br /&gt;Using &lt;a href="http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-good-plugins/html/gst-plugins-good-plugins-jpegenc.html"&gt;jpegenc&lt;/a&gt; was a lot better than &lt;a href="http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-base-plugins/html/gst-plugins-base-plugins-theoraenc.html"&gt;theoraenc&lt;/a&gt; which added an extra 3-4 seconds of latency. Is there a better codec I should be using?&lt;br /&gt;&lt;br /&gt;Another thing I thought of trying was to switch from &lt;a href="https://secure.wikimedia.org/wikipedia/en/wiki/Transmission_Control_Protocol"&gt;TCP&lt;/a&gt; to &lt;a href="https://secure.wikimedia.org/wikipedia/en/wiki/User_Datagram_Protocol"&gt;UDP&lt;/a&gt;. I'm currently using &lt;a href="http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-base-plugins/html/gst-plugins-base-plugins-tcpserversink.html"&gt;tcpserversink&lt;/a&gt; and &lt;a href="http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-base-plugins/html/gst-plugins-base-plugins-tcpclientsrc.html"&gt;tcpclientsrc&lt;/a&gt; but since I don't care about having a few dropped frames, maybe I should look into the &lt;a href="http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-good-plugins/html/gst-plugins-good-plugins-plugin-udp.html"&gt;udp&lt;/a&gt; and &lt;a href="http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-good-plugins/html/gst-plugins-good-plugins-plugin-rtp.html"&gt;rtp&lt;/a&gt; plugins. It seems like it might help but it also seems to be quite a bit &lt;a href="http://cgit.freedesktop.org/gstreamer/gst-plugins-good/tree/gst/rtp/README#n251"&gt;more complicated&lt;/a&gt; and I have yet to find an easy way to make use of the &lt;a href="http://gstreamer.freedesktop.org/documentation/rtp.html"&gt;RTP stack&lt;/a&gt; in Gstreamer.&lt;br /&gt;&lt;br /&gt;Please feel free leave a comment if you can suggest ways of improving my quick 'n dirty solution.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-8508576985428845094?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=ajEpcH3rUwA:pvgWbiKTu9M:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=ajEpcH3rUwA:pvgWbiKTu9M:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?i=ajEpcH3rUwA:pvgWbiKTu9M:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=ajEpcH3rUwA:pvgWbiKTu9M:cGdyc7Q-1BI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=cGdyc7Q-1BI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=ajEpcH3rUwA:pvgWbiKTu9M:YwkR-u9nhCs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=YwkR-u9nhCs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/FeedingTheCloud/~4/ajEpcH3rUwA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://feeding.cloud.geek.nz/feeds/8508576985428845094/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=8508576985428845094" title="6 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/8508576985428845094?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/8508576985428845094?v=2" /><link rel="alternate" type="text/html" href="http://feeding.cloud.geek.nz/2010/12/peer-to-peer-video-conferencing-using.html" title="Peer-to-peer video-conferencing using free software" /><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg" /></author><thr:total>6</thr:total></entry><entry gd:etag="W/&quot;C04CRH48cCp7ImA9Wx5bFkU.&quot;"><id>tag:blogger.com,1999:blog-7615241590176793465.post-2737204561647806779</id><published>2010-11-02T20:10:00.000+13:00</published><updated>2010-11-02T20:12:45.078+13:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-11-02T20:12:45.078+13:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="catalyst" /><category scheme="http://www.blogger.com/atom/ns#" term="debian" /><category scheme="http://www.blogger.com/atom/ns#" term="sysadmin" /><category scheme="http://www.blogger.com/atom/ns#" term="ubuntu" /><category scheme="http://www.blogger.com/atom/ns#" term="nzoss" /><title>RAID1 alternative for SSD drives</title><content type="html">I recently added a &lt;a href="http://www.intel.com/design/flash/nand/value/overview.htm"&gt;solid-state drive&lt;/a&gt; to my desktop computer to take advantage of the performance boost rumored to come with these drives. For reliability reasons, I've always tried to use software &lt;a href="http://en.wikipedia.org/wiki/Raid1#RAID_1"&gt;RAID1&lt;/a&gt; to avoid having to reinstall my machine from backups should a hard drive fail. While this strategy is fairly cheap with regular hard drives, it's not really workable with SSD drives which are still an order of magnitude more expensive.&lt;br /&gt;&lt;br /&gt;The strategy I settled on is this one:&lt;ul&gt;&lt;li&gt;continue to have all partitions (&lt;tt&gt;/&lt;/tt&gt;, &lt;tt&gt;/home&lt;/tt&gt; and &lt;tt&gt;/data&lt;/tt&gt;) on my RAID1 hard drives,&lt;/li&gt;&lt;li&gt;put another copy of the root partition (&lt;tt&gt;/&lt;/tt&gt;) on the SSD drive, and&lt;/li&gt;&lt;li&gt;leave my &lt;tt&gt;/tmp&lt;/tt&gt; and swap partitions in &lt;a href="http://en.wikipedia.org/wiki/RAID0#RAID_0"&gt;RAID0&lt;/a&gt; arrays on my rotational hard drives to reduce the number of writes on the SSD.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;This setup has the benefit of using a very small SSD to speed up the main partition while keeping all important data on the larger mirrored drives.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Resetting the SSD&lt;/h2&gt;The first thing I did, given that I purchased a second-hand drive, was to &lt;b&gt;completely erase the drive&lt;/b&gt; and mark all sectors as empty using an &lt;a href="http://en.wikipedia.org/wiki/Write_amplification#Secure_erase"&gt;ATA secure erase&lt;/a&gt;. Because SSDs have a tendency to get slower as data is added to them, it is necessary to clear the drive in a way that will let the controller know that every byte is now free to be used again.&lt;br /&gt;&lt;br /&gt;There is a lot of advice on the web on how to do this and many tutorials refer to an old piece of software called &lt;a href="http://cmrr.ucsd.edu/people/Hughes/SecureErase.shtml"&gt;Secure Erase&lt;/a&gt;. There is a much better solution on Linux: &lt;a href="https://ata.wiki.kernel.org/index.php/ATA_Secure_Erase"&gt;issuing the commands directly using &lt;b&gt;hdparm&lt;/b&gt;&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Partitioning the SSD&lt;/h2&gt;Once the drive is empty, it's time to create partitions on it. I'm not sure how important it is to &lt;b&gt;align the partitions to the SSD erase block size&lt;/b&gt; on newer drives, but I decided to follow &lt;a href="http://thunk.org/tytso/blog/2009/02/20/aligning-filesystems-to-an-ssds-erase-block-size/"&gt;Ted Ts'o's instructions&lt;/a&gt; anyways.&lt;br /&gt;&lt;br /&gt;Another thing I did is leave &lt;b&gt;20% of the drive unpartitioned&lt;/b&gt;. I've often read that SSDs are faster the more free space they have so I figured that limiting myself to 80% of the drive should help the drive maintain its peak performance over time. In fact, I've heard that extra unused unpartitionable space is one of the main differences between the &lt;a href="http://www.intel.com/design/flash/nand/value/overview.htm"&gt;value&lt;/a&gt; and &lt;a href="http://www.intel.com/design/flash/nand/extreme/index.htm"&gt;extreme&lt;/a&gt; series of Intel SSDs. I'd love to see an official confirmation of this from Intel of course!&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Keeping the RAID1 array in sync with the SSD&lt;/h2&gt;Once I added the solid-state drive to my computer and copied my root partition on it, I adjusted my &lt;tt&gt;fstab&lt;/tt&gt; and &lt;a href="http://en.wikipedia.org/wiki/GNU_GRUB"&gt;grub&lt;/a&gt; settings to boot from that drive. I also setup the following cron job (running twice daily) to keep a copy of my root partition on the old RAID1 drives (mounted on &lt;tt&gt;/mnt&lt;/tt&gt;):&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;nice ionice -c3 rsync -aHx --delete --exclude=/proc/* --exclude=/sys/* --exclude=/tmp/* --exclude=/home/* --exclude=/mnt/* --exclude=/lost+found/* --exclude=/data/* /* /mnt/&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;h2&gt;Tuning the SSD&lt;/h2&gt;Finally, after reading this &lt;a href="http://lwn.net/Articles/408428/"&gt;excellent LWN article&lt;/a&gt;, I decided to tune the SSD drive (&lt;tt&gt;/dev/sda&lt;/tt&gt;) by adjusting three things:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Add the &lt;tt&gt;discard&lt;/tt&gt; mount option (also know as ATA &lt;a href="http://en.wikipedia.org/wiki/TRIM"&gt;TRIM&lt;/a&gt; and introduced in the 2.6.33 Linux kernel) to the root partition in &lt;tt&gt;/etc/fstab&lt;/tt&gt;:&lt;/li&gt;&lt;pre&gt;/dev/&lt;i&gt;sda1&lt;/i&gt;  /  ext4  &lt;b&gt;discard&lt;/b&gt;,errors=remount-ro,noatime  0  1&lt;/pre&gt;&lt;br /&gt;&lt;li&gt;Use the &lt;tt&gt;noop&lt;/tt&gt; IO scheduler by adding these lines to &lt;tt&gt;/etc/rc.local&lt;/tt&gt;:&lt;/li&gt;&lt;pre&gt;echo noop &gt; /sys/block/&lt;i&gt;sda&lt;/i&gt;/queue/scheduler&lt;br /&gt;echo 1 &gt; /sys/block/&lt;i&gt;sda&lt;/i&gt;/queue/iosched/fifo_batch&lt;/pre&gt;&lt;br /&gt;&lt;li&gt;Turn off &lt;a href="http://www.entropykey.co.uk/"&gt;entropy gathering&lt;/a&gt; (for kernels 2.6.36 or later) by adding this line to &lt;tt&gt;/etc/rc.local&lt;/tt&gt;:&lt;/li&gt;&lt;pre&gt;echo 0 &gt; /sys/block/&lt;i&gt;sda&lt;/i&gt;/queue/add_random&lt;/pre&gt;&lt;/ul&gt;&lt;br /&gt;Is there anything else I should be doing to make sure I get the most out of my SSD?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-2737204561647806779?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=tfZnnje03j4:Vxt7IUQAgxU:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=tfZnnje03j4:Vxt7IUQAgxU:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?i=tfZnnje03j4:Vxt7IUQAgxU:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=tfZnnje03j4:Vxt7IUQAgxU:cGdyc7Q-1BI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=cGdyc7Q-1BI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=tfZnnje03j4:Vxt7IUQAgxU:YwkR-u9nhCs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=YwkR-u9nhCs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/FeedingTheCloud/~4/tfZnnje03j4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://feeding.cloud.geek.nz/feeds/2737204561647806779/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=2737204561647806779" title="7 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/2737204561647806779?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/2737204561647806779?v=2" /><link rel="alternate" type="text/html" href="http://feeding.cloud.geek.nz/2010/11/raid1-alternative-for-ssd-drives.html" title="RAID1 alternative for SSD drives" /><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg" /></author><thr:total>7</thr:total></entry><entry gd:etag="W/&quot;DEEEQH06fCp7ImA9Wx5UFE8.&quot;"><id>tag:blogger.com,1999:blog-7615241590176793465.post-7580099125135859173</id><published>2010-10-19T07:30:00.000+13:00</published><updated>2010-10-19T07:30:01.314+13:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-10-19T07:30:01.314+13:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="catalyst" /><category scheme="http://www.blogger.com/atom/ns#" term="debian" /><category scheme="http://www.blogger.com/atom/ns#" term="sysadmin" /><category scheme="http://www.blogger.com/atom/ns#" term="ubuntu" /><category scheme="http://www.blogger.com/atom/ns#" term="nzoss" /><title>Manipulating debconf settings on the command line</title><content type="html">It's not very easy to find information on how to adjust &lt;a href="http://kitenet.net/~joey/code/debconf/"&gt;debconf&lt;/a&gt; settings after a package has been installed and configured. Most of the information out there is for Debian developers wanting to add &lt;a href="http://www.fifi.org/doc/debconf-doc/tutorial.html"&gt;support for debconf&lt;/a&gt; in their &lt;a href="http://www.debian.org/doc/debian-policy/ch-maintainerscripts.html"&gt;maintainer scripts&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I ran into the problem of being unable to change a package's configuration options through &lt;a href="http://manpages.debian.net/cgi-bin/man.cgi?query=dpkg-reconfigure"&gt;dpkg-reconfigure&lt;/a&gt; and I found the following commands to do it manually:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;debconf-show &lt;i&gt;packagename&lt;/i&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;to show the list of debconf values that a package has stored,&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;echo "get &lt;i&gt;packagename&lt;/i&gt;/pgsql/app-pass" | debconf-communicate&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;to query the current value of an option in the debconf database, and&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;echo "set &lt;i&gt;packagename&lt;/i&gt;/pgsql/app-pass password1" | debconf-communicate&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;to change that value.&lt;br /&gt;&lt;br /&gt;I'm not convinced that this is the easiest way for system administrators to manually lookup and modify debconf options, but that's the best I could find at the time.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-7580099125135859173?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=c1BzcRvUulc:xiYNCn4H7aU:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=c1BzcRvUulc:xiYNCn4H7aU:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?i=c1BzcRvUulc:xiYNCn4H7aU:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=c1BzcRvUulc:xiYNCn4H7aU:cGdyc7Q-1BI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=cGdyc7Q-1BI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=c1BzcRvUulc:xiYNCn4H7aU:YwkR-u9nhCs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=YwkR-u9nhCs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/FeedingTheCloud/~4/c1BzcRvUulc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://feeding.cloud.geek.nz/feeds/7580099125135859173/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=7580099125135859173" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/7580099125135859173?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/7580099125135859173?v=2" /><link rel="alternate" type="text/html" href="http://feeding.cloud.geek.nz/2010/10/manipulating-debconf-settings-on.html" title="Manipulating debconf settings on the command line" /><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg" /></author><thr:total>3</thr:total></entry><entry gd:etag="W/&quot;A0IHRHw9eSp7ImA9Wx5VF0g.&quot;"><id>tag:blogger.com,1999:blog-7615241590176793465.post-2844285854332126579</id><published>2010-10-11T14:00:00.002+13:00</published><updated>2010-10-11T14:12:15.261+13:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-10-11T14:12:15.261+13:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="catalyst" /><category scheme="http://www.blogger.com/atom/ns#" term="mahara" /><category scheme="http://www.blogger.com/atom/ns#" term="postgres" /><category scheme="http://www.blogger.com/atom/ns#" term="database" /><category scheme="http://www.blogger.com/atom/ns#" term="nzoss" /><title>Taking the max/min of two columns in PostgreSQL</title><content type="html">As part of a database &lt;a href="http://www.postgresql.org/docs/9.0/static/sql-createview.html"&gt;view&lt;/a&gt;, I found myself wanting to get &lt;a href="http://www.postgresql.org/"&gt;Postgres&lt;/a&gt; to display values from one of two columns, whichever was the largest.&lt;br /&gt;&lt;br /&gt;My first attempt was:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;SELECT id, &lt;b&gt;MAX&lt;/b&gt;(column1, column2) FROM table1;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;which of course didn't work because &lt;tt&gt;MAX()&lt;/tt&gt; is an &lt;a href="http://www.postgresql.org/docs/9.0/static/functions-aggregate.html"&gt;aggregate function&lt;/a&gt; which only takes a single parameter.&lt;br /&gt;&lt;br /&gt;What I was looking for instead, is something that was introduced in &lt;a href="http://www.postgresql.org/docs/9.0/static/release-8-1.html#AEN109656"&gt;8.1&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;SELECT id, &lt;b&gt;GREATEST&lt;/b&gt;(column1, column2) FROM table1;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;where &lt;tt&gt;GREATEST()&lt;/tt&gt; (and its opposite: &lt;tt&gt;LEAST()&lt;/tt&gt;) is a &lt;a href="http://www.postgresql.org/docs/9.0/static/functions-conditional.html"&gt;conditional expression&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-2844285854332126579?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=TkcFouer4dk:wL8UV_S5KpE:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=TkcFouer4dk:wL8UV_S5KpE:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?i=TkcFouer4dk:wL8UV_S5KpE:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=TkcFouer4dk:wL8UV_S5KpE:cGdyc7Q-1BI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=cGdyc7Q-1BI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=TkcFouer4dk:wL8UV_S5KpE:YwkR-u9nhCs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=YwkR-u9nhCs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/FeedingTheCloud/~4/TkcFouer4dk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://feeding.cloud.geek.nz/feeds/2844285854332126579/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=2844285854332126579" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/2844285854332126579?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/2844285854332126579?v=2" /><link rel="alternate" type="text/html" href="http://feeding.cloud.geek.nz/2010/10/taking-maxmin-of-two-columns-in.html" title="Taking the max/min of two columns in PostgreSQL" /><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg" /></author><thr:total>1</thr:total></entry><entry gd:etag="W/&quot;D04HSH08cCp7ImA9Wx5WFE0.&quot;"><id>tag:blogger.com,1999:blog-7615241590176793465.post-4654618801330075934</id><published>2010-09-25T21:02:00.003+12:00</published><updated>2010-09-25T21:12:19.378+12:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-09-25T21:12:19.378+12:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="catalyst" /><category scheme="http://www.blogger.com/atom/ns#" term="nzoss" /><title>Freedom in the Cloud Miniconf at linux.conf.au 2011</title><content type="html">&lt;a href="http://rejon.org/"&gt;Jon Phillips&lt;/a&gt; and I are putting together a &lt;a href="http://lca2011.linux.org.au/wiki/Miniconfs/FreedomInTheCloudMiniconf"&gt;Freedom in the Cloud&lt;/a&gt; Miniconf for &lt;a href="http://lca2011.linux.org.au/"&gt;linux.conf.au 2011&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;If you are interested in submitting a proposal (5, 25 or 45 minutes), we want to &lt;a href="mailto:proposals@cloud.geek.nz"&gt;hear from you&lt;/a&gt;!&lt;br /&gt;&lt;br /&gt;The deadline is &lt;b&gt;22 October 2010&lt;/b&gt;.&lt;br /&gt;&lt;hr /&gt;&lt;br /&gt;Whether or not 2011 will be known as the year of the Federated Social Web remains to be seen, but the cloud is certainly not going away and we'd like to propose a Miniconf to cater to all cloud enthusiasts.&lt;br /&gt;&lt;br /&gt;Specifically, we'd like to solicit proposals from two main groups. The first one is the &lt;b&gt;hard-core technical cloud engineers&lt;/b&gt; who build their tech on Open Source software. The kind of people who know so much about tweaking their kernels and scaling their infrastructures that they could out-Google Google.&lt;br /&gt;&lt;br /&gt;Possible topics could include: cloud storage, virtualization, high availability, security, cloud performance and infrastructure management.&lt;br /&gt;&lt;br /&gt;The second group of people we'd like to attract are the &lt;b&gt;free network services and federated social web people&lt;/b&gt;. These folks know that the growth of netbooks and smartphones has meant a great dependence on centralized network services. But as more and more computing moves into "the cloud", users and companies are losing direct control over their data and processes. They see a very high risk to businesses and individuals.&lt;br /&gt;&lt;br /&gt;This group would cover ways that individuals and organizations can ensure that their cloud computing is as dependable and freedom-respecting as running Open Source software on computers they own and control. They will concentrate on using Open Source, Open Data, and Open Standards to keep control in the hands of cloud computing users.&lt;br /&gt;&lt;br /&gt;Speakers will vary from theoretical discussions about Free network services; developers of Free and federated alternatives to popular centralized cloud services; and tutorials on how to build an Open Source cloud network that you and your business can depend on.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7615241590176793465-4654618801330075934?l=feeding.cloud.geek.nz' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=K-QtOWlDB3Q:SkfyBcWCXjg:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=K-QtOWlDB3Q:SkfyBcWCXjg:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?i=K-QtOWlDB3Q:SkfyBcWCXjg:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=K-QtOWlDB3Q:SkfyBcWCXjg:cGdyc7Q-1BI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=cGdyc7Q-1BI" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.cloud.geek.nz/~ff/FeedingTheCloud?a=K-QtOWlDB3Q:SkfyBcWCXjg:YwkR-u9nhCs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/FeedingTheCloud?d=YwkR-u9nhCs" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/FeedingTheCloud/~4/K-QtOWlDB3Q" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://feeding.cloud.geek.nz/feeds/4654618801330075934/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=7615241590176793465&amp;postID=4654618801330075934" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/4654618801330075934?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/7615241590176793465/posts/default/4654618801330075934?v=2" /><link rel="alternate" type="text/html" href="http://feeding.cloud.geek.nz/2010/09/freedom-in-cloud-miniconf-at.html" title="Freedom in the Cloud Miniconf at linux.conf.au 2011" /><author><name>François Marier</name><uri>http://www.blogger.com/profile/15799633745688818389</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://1.bp.blogspot.com/-h-oJr5p8TwE/TqNUQqrVjUI/AAAAAAAAAHM/iAXo1RkwGq8/s220/square.jpg" /></author><thr:total>0</thr:total></entry></feed>

