pig-monkey.com - pythonhttps://pig-monkey.com/2013-05-17T00:00:00-07:00Tarsnapper: Managing Tarsnap Backups2012-09-16T00:00:00-07:002012-12-22T00:00:00-08:00Pig Monkeytag:pig-monkey.com,2012-09-16:/2012/09/tarsnapper-managing-tarsnap-backups/<p><a href="http://www.tarsnap.com/">Tarsnap</a> bills itself as &ldquo;online backups for the truly paranoid&rdquo;. I began using the service last January. It fast became my preferred way to backup to the cloud. It stores data on <a href="http://aws.amazon.com/s3/">Amazon S3</a> and costs $0.30 per GB per month for storage and $0.30 per GB for …</p><p><a href="http://www.tarsnap.com/">Tarsnap</a> bills itself as &ldquo;online backups for the truly paranoid&rdquo;. I began using the service last January. It fast became my preferred way to backup to the cloud. It stores data on <a href="http://aws.amazon.com/s3/">Amazon S3</a> and costs $0.30 per GB per month for storage and $0.30 per GB for bandwidth. Those prices are higher than just using Amazon S3 directly, but Tarsnap implements some impressive data de-duplication and compression that results in the service costing very little. For example, I currently have 67 different archives stored in Tarsnap from my laptop. They total 46GB in size. De-duplicated that comes out to 1.9GB. After compression, I only pay to store 1.4GB. Peanuts.</p> <p>Of course, the primary requirement for any online backup service is encryption. <a href="http://www.tarsnap.com/security.html">Tarsnap delivers</a>. And, most importantly, the Tarsnap client is open-source, so the claims of encryption can actually be verified by the user. The majority of for-profit, online backup services out there fail on this critical point.</p> <p>So Tarsnap is amazing and you should use it. The client follows the <a href="https://en.wikipedia.org/wiki/Unix_philosophy">Unix philosophy</a>: &ldquo;do one thing and do it well&rdquo;. It&rsquo;s basically like <a href="https://www.gnu.org/software/tar/">tar</a>. It can create archives, read the contents of an archive, extract archives, and delete archives. For someone coming from an application like <a href="http://duplicity.nongnu.org/">Duplicity</a>, the disadvantage to the Tarsnap client is that it doesn&rsquo;t include any way to automatically manage backups. You can&rsquo;t tell Tarsnap how many copies of a backup you wish to keep, or how long backups should be allowed to age before deletion.</p> <p>Thanks to the de-duplication and compression, there&rsquo;s not a great economic incentive to not keep old backups around. It likely won&rsquo;t cost you <em>that</em> much extra. But I like to keep things clean and minimal. If I haven&rsquo;t used an online backup in 4 weeks, I generally consider it stale and have no further use for it.</p> <p>To manage my Tarsnap backups, I wrote a Python script called <a href="https://github.com/pigmonkey/backups/blob/master/tarsnapper.py">Tarsnapper</a>. The primary intent was to create a script that would automatically delete old archives. It does this by accepting a maximum age from the user. Whenever Tarsnapper runs, it gets a list of all Tarsnap archives. The timestamp is parsed out from the list and any archive that has a timestamp greater than the maximum allowed age is deleted. This is seamless, and means I never need to manually intervene to clean my archives.</p> <p>Tarsnapper also provides some help for creating Tarsnap archives. It allows the user to define any number of named archives and the directories that those archives should contain. On my laptop I have four different directories that I backup with Tarsnap, three of them in one archive and the last in another archive. Tarsnapper knows about this, so whenever I want to backup to Tarsnap I just call a single command.</p> <p>Tarsnapper also can automatically add a suffix to the end of each archive name. This makes it easier to know which archive is which when you are looking at a list. By default, the suffix is the current date and time.</p> <p>Configuring Tarsnapper can be done either directly by changing the variables at the top of the script, or by creating a configuration file named <code>tarsnapper.conf</code> in your home directory. The config file on my laptop looks like this:</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span> <span class="normal">3</span> <span class="normal">4</span> <span class="normal">5</span> <span class="normal">6</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="o">[</span>Settings<span class="o">]</span> tarsnap: /usr/bin/tarsnap <span class="o">[</span>Archives<span class="o">]</span> nous-cloud: /home/pigmonkey/work /home/pigmonkey/documents /home/pigmonkey/vault/ nous-config: /home/pigmonkey/.config </code></pre></div></td></tr></table></div> <p>There is also support for command-line arguments to specify the location of the configuration file to use, to delete old archives and exit without creating new archives, and to execute only a single named-archive rather than all of those that you may have defined.</p> <div class="highlight"><pre><span></span><code>$ tarsnapper.py --help usage: tarsnapper.py <span class="o">[</span>-h<span class="o">]</span> <span class="o">[</span>-c CONFIG<span class="o">]</span> <span class="o">[</span>-a ARCHIVE<span class="o">]</span> <span class="o">[</span>-r<span class="o">]</span> A Python script to manage Tarsnap archives. optional arguments: -h, --help show this <span class="nb">help</span> message and <span class="nb">exit</span> -c CONFIG, --config CONFIG Specify the configuration file to use. -a ARCHIVE, --archive ARCHIVE Specify a named archive to execute. -r, --remove Remove archives old archives and exit. </code></pre></div> <p>It makes using a great service very simple. My backups can all be executed simply by a single call to Tarsnapper. Stale archives are deleted, saving me precious picodollars. I use this system on my laptop, as well as multiple servers. If you&rsquo;re interested in it, Tarsnapper can be <a href="https://github.com/pigmonkey/backups/blob/master/tarsnapper.py">downloaded directly from GitHub</a>. You can clone my entire <a href="https://github.com/pigmonkey/backups">backups repository</a> if you&rsquo;re also interested in the other scripts I&rsquo;ve written to manage different aspects of backing up data.</p>An Ubuntu VPS for Django2011-07-19T00:00:00-07:002012-12-22T00:00:00-08:00Pig Monkeytag:pig-monkey.com,2011-07-19:/2011/07/ubuntu-vps-django/<p>Three years ago I wrote <a href="/2008/06/an-ubuntu-vps-on-slicehost-basic-setup/">a guide to building a VPS web server</a> for serving sites in a PHP environment. That setup served me well for some time, but most of the sites I run now &ndash; <a href="/2011/06/move-django/">including this one</a> &ndash; are now written in Python. Earlier this year I built another …</p><p>Three years ago I wrote <a href="/2008/06/an-ubuntu-vps-on-slicehost-basic-setup/">a guide to building a VPS web server</a> for serving sites in a PHP environment. That setup served me well for some time, but most of the sites I run now &ndash; <a href="/2011/06/move-django/">including this one</a> &ndash; are now written in Python. Earlier this year I built another web server to reflect this. It&rsquo;s similar to before; I still use Ubuntu and I still like to serve pages with nginx. But PHP has been replaced with Python, and many of the packages used to build the environment have changed as a result. As with the last time, I decided to compile my notes into a guide, both for my own reference and in case anyone else would like to duplicate it. So far, the server has proven to be fast and efficient. It serves Python using uWSGI, uses a PostgreSQL database, and includes a simple mail server provided by Postfix. I think it&rsquo;s a good setup for serving simple Django-based websites.</p> <h2>Basic Setup</h2> <p><a href="/2008/06/an-ubuntu-vps-on-slicehost-basic-setup">As with last time</a>, I recommend following <a href="http://articles.slicehost.com/2010/4/30/ubuntu-lucid-setup-part-1">Slicehost&rsquo;s basic server setup article</a>. It discusses user administration, SSH security, and firewalls. I no longer use Slicehost as my VPS provider, but I find that Slicehost&rsquo;s articles provide an excellent base regardless of the host.</p> <h3>Packages</h3> <p>Packages should upgraded immediately to address any known security vulnerabilities.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span></pre></div></td><td class="code"><div><pre><span></span><code>$ sudo apt-get update $ sudo apt-get upgrade </code></pre></div></td></tr></table></div> <p>After the repositories have been updated, I install some essential packages.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><code>$ sudo apt-get install build-essential screen dnsutils </code></pre></div></td></tr></table></div> <p><a href="http://packages.ubuntu.com/lucid/build-essential">Build-essential</a> includes necessary tools to compile programs. I am incapable of using a computer that does not have <a href="http://packages.ubuntu.com/lucid/screen">screen</a> on it, so that gets installed too. The third package, <a href="http://packages.ubuntu.com/lucid/dnsutils">dnsutils</a>, is optional, but includes <code>dig</code> which is useful for troubleshooting DNS issues.</p> <h3>DenyHosts</h3> <p>Slicehost&rsquo;s setup article recommends turning off password authentication in SSH, forcing users to login with keys only. I use keys whenever I can, but I appreciate the option of being able to login to my server from any computer, when I may or may not have my SSH key with me. So I leave password authentication enabled. This presents the possibility of brute-force attacks. Enter <a href="http://denyhosts.sourceforge.net/">DenyHosts</a>. DenyHosts, which I have <a href="/2008/10/thoughts-on-ssh-security/">discussed previously</a> attempts to protect against SSH attacks by banning hosts after a certain number of failed login attempts. When password authentication is enabled, running DenyHosts is a smart move.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span></pre></div></td><td class="code"><div><pre><span></span><code>$ sudo apt-get install denyhosts $ sudo vim /etc/denyhosts.conf </code></pre></div></td></tr></table></div> <h3>Personalize the Environment</h3> <p>I use <code>update-alternatives</code> to set my default editor to <a href="http://www.vim.org">vim</a>.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><code>$ sudo update-alternatives --config editor </code></pre></div></td></tr></table></div> <p>All of my personal configuration files are kept in a <a href="https://github.com/pigmonkey/dotfiles">github repository</a>. I&rsquo;ll check out that repository into <code>~/src</code> and install the files.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal"> 1</span> <span class="normal"> 2</span> <span class="normal"> 3</span> <span class="normal"> 4</span> <span class="normal"> 5</span> <span class="normal"> 6</span> <span class="normal"> 7</span> <span class="normal"> 8</span> <span class="normal"> 9</span> <span class="normal">10</span> <span class="normal">11</span> <span class="normal">12</span></pre></div></td><td class="code"><div><pre><span></span><code>$ mkdir ~/src $ <span class="nb">cd</span> ~/src $ sudo apt-get install git-core $ git clone git://github.com/pigmonkey/dotfiles.git $ ln -s ~/src/dotfiles/bash_profile ~/.bash_profile $ ln -s ~/src/dotfiles/bashrc ~/.bashrc $ ln -s ~/src/dotfiles/bash_aliases ~/.bash_aliases $ ln -s ~/src/dotfiles/bash_colors ~/.bash_colors $ ln -s ~/src/dotfiles/vimrc ~/.vimrc $ ln -s ~/src/dotfiles/vim ~/.vim $ ln -s ~/src/dotfiles/screenrc ~/.screenrc $ <span class="nb">source</span> ~/.bash_profile </code></pre></div></td></tr></table></div> <h3>Time</h3> <p>The next step is to set the server&rsquo;s timezone and sync the clock with <a href="http://www.ntp.org">NTP</a>.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span></pre></div></td><td class="code"><div><pre><span></span><code>$ sudo dpkg-reconfigure tzdata $ sudo apt-get install ntp </code></pre></div></td></tr></table></div> <h3>Rootkits</h3> <p>There&rsquo;s no reason not to run both <a href="http://www.chkrootkit.org/">chkrootkit</a> and <a href="http://rkhunter.sourceforge.net">rkhunter</a> to check for rootkits and related vulnerabilities.</p> <h4>chrkrootkit</h4> <p>Slicehost has <a href="http://articles.slicehost.com/2010/3/24/scanning-for-rootkits-with-chkrootkit-updated">an excellent article for setting up and using chkrootkit</a>.</p> <p>Later on I&rsquo;ll be installing some Python development packages. One of them creates a directory called <code>/usr/lib/pymodules/python2.6/.path</code>, which sets off a warning in chkrootkit. Part of chkrootkit&rsquo;s desgin philosophy is to not include any whitelists: if chkrootkit finds something that it doesn&rsquo;t like, you&rsquo;re going to hear about it. I have cron run chkrootkit nightly and I want to receive any warnings, but I don&rsquo;t want to receive the same false positive every morning in my inbox.</p> <p>The solution is to create a file that contains chkrootkit&rsquo;s warning. I call that file <code>whitelist</code> and store it in the same directory as chkrootkit. When chkrootkit is run, any output is redirected to a file. That file is compared to the whitelist using <code>diff</code> and the output <em>of that</em> &ndash; if any &ndash; is then read. At the end, the file containing chkrootkit&rsquo;s output is deleted so that the working directory is ready for the next run. The effect is that I only hear warnings from chkrootkit that I <em>have not explicit whitelisted</em>. All of this can be accomplished in a single crontab entry.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="m">0</span> <span class="m">3</span> * * * <span class="o">(</span><span class="nb">cd</span> /home/demo/src/chkrootkit-0.49<span class="p">;</span> ./chkrootkit -q &gt; message <span class="m">2</span>&gt;<span class="p">&amp;</span><span class="m">1</span><span class="p">;</span> diff -w whitelist message<span class="p">;</span> rm -f message<span class="o">)</span> </code></pre></div></td></tr></table></div> <h4>rkhunter</h4> <p>I&rsquo;m sure it doesn&rsquo;t surprise you that I&rsquo;m going to recommend reading Slicehost&rsquo;s <a href="http://articles.slicehost.com/2010/3/24/scanning-for-rootkits-with-rkhunter-updated">article on rkhunter</a>.</p> <p>Unlike chkrootkit, rkhunter does allow for whitelists. On a clean Ubuntu 10.04 system, I find that I need to whitelist a few items in the config.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span> <span class="normal">3</span> <span class="normal">4</span> <span class="normal">5</span> <span class="normal">6</span> <span class="normal">7</span></pre></div></td><td class="code"><div><pre><span></span><code>$ sudo vim /etc/rkhunter.conf <span class="nv">SCRIPTWHITELIST</span><span class="o">=</span><span class="s2">&quot;/usr/sbin/adduser /usr/bin/ldd /bin/which&quot;</span> <span class="nv">ALLOWHIDDENDIR</span><span class="o">=</span><span class="s2">&quot;/dev/.udev /dev/.initramfs&quot;</span> <span class="nv">APP_WHITELIST</span><span class="o">=</span><span class="s2">&quot;openssl:0.9.8k gpg:1.4.10&quot;</span> $ sudo /usr/local/bin/rkhunter --propupd </code></pre></div></td></tr></table></div> <p>The script that my cronjob runs is slightly different from the one demonstrated in the Slicehost article. Their script executes a few commands, groups the output together, and sends it to <code>mail</code> to email the system administrator. This results in daily emails, regardless of whether rkhunter finds any warnings or not. My script is simpler and does not result in so many messages.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span> <span class="normal">3</span> <span class="normal">4</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="ch">#!/bin/sh</span> /usr/local/bin/rkhunter --versioncheck -q /usr/local/bin/rkhunter --update -q /usr/local/bin/rkhunter --cronjob --report-warnings-only </code></pre></div></td></tr></table></div> <p>The version check and update commands both have the <code>-q</code> switch, which disables any output &ndash; I don&rsquo;t care to know whether rkhunter updated itself or not. The final line actually executes the scan. Notice that there&rsquo;s no reference to <code>mail</code>. This script does not send any messages. The reason for that is that rkhunter itself provides the mail functionality. Inside of <code>/etc/rkhunter.conf</code> there is a <code>MAIL-ON-WARNING</code> variable. As long as the machine has an smtp server on it (which I&rsquo;ll get to later in this guide), simply filling in this variable will result in any warnings being emailed to the system administrator.</p> <h2>Web Server</h2> <p>With the basics complete, it&rsquo;s time to start serving something! In my <a href="/2008/06/an-ubuntu-vps-on-slicehost-web-server/">previous article</a> I covered serving a PHP-based Wordpress site via FastCGI and nginx. This time around the stack will be different: <a href="http://nginx.org/">nginx</a>, <a href="http://projects.unbit.it/uwsgi/">uWSGI</a>, <a href="http://www.python.org/">Python</a>, and <a href="http://www.djangoproject.com/">Django</a>.</p> <p>A few basic packages will help flesh out the server&rsquo;s Python development environment:</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><code>$ sudo apt-get install python-psycopg2 python-setuptools python2.6-dev psmisc python-imaging locate python-dateutil libxml2-dev python-software-properties </code></pre></div></td></tr></table></div> <h3>uWSGI</h3> <p>Installing <a href="http://projects.unbit.it/uwsgi/">uWSGI</a> is a simple matter of compiling it and moving the resulting binary into one of your system&rsquo;s bin directories.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span> <span class="normal">3</span> <span class="normal">4</span> <span class="normal">5</span> <span class="normal">6</span></pre></div></td><td class="code"><div><pre><span></span><code>$ <span class="nb">cd</span> ~/src/ $ wget http://projects.unbit.it/downloads/uwsgi-0.9.8.tar.gz $ tar xvzf ~/uwsgi-0.9.8.tar.gz $ <span class="nb">cd</span> uwsgi-0.9.8/ $ make -f Makefile.Py26 $ sudo cp uwsgi /usr/local/sbin </code></pre></div></td></tr></table></div> <h3>nginx</h3> <p>The <a href="http://nginx.org/">nginx</a> package in Ubuntu&rsquo;s official repositories is always notoriously outdated. It used to be you had to compile the server from source, but there is now an <a href="https://launchpad.net/ubuntu/+ppas">Ubuntu PPA</a> for the latest stable versions. <a href="http://wiki.nginx.org/Install#Ubuntu_PPA">As described by the nginx wiki</a>, all that is needed is to add the PPA to your <code>sources.list</code> and <code>apt-get</code> away!</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span> <span class="normal">3</span></pre></div></td><td class="code"><div><pre><span></span><code>$ sudo add-apt-repository ppa:nginx/stable $ sudo apt-get update $ sudo apt-get install nginx </code></pre></div></td></tr></table></div> <h3>Python and Django</h3> <p>If you do Python development and haven&rsquo;t heard of <a href="http://pypi.python.org/pypi/virtualenv">virtualenv</a>, it is well worth reading up on. It allows the user to create an isolated, virtual Python environment for each project. This helps immensely when developing (or serving) multiple projects on a single machine. Needless to say, I consider it to be a required package.</p> <h4>Install</h4> <p>I&rsquo;ll be installing virtualenv and <a href="http://www.doughellmann.com/projects/virtualenvwrapper">virtualenvwrapper</a> (a set of scripts to facilitate working with virtual environments). I also prefer <a href="http://pypi.python.org/pypi/pip">pip</a> over <code>easy_install</code> for managing Python packages.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span> <span class="normal">3</span></pre></div></td><td class="code"><div><pre><span></span><code>$ sudo easy_install -U pip $ sudo pip install virtualenv $ sudo pip install virtualenvwrapper </code></pre></div></td></tr></table></div> <h4>Setup a virtual environment</h4> <p>Virtual environments can be stored wherever you fancy. For now, I keep them in a hidden folder in my home directory. For these examples, I&rsquo;ll setup an environment called <code>myproject</code>.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span> <span class="normal">3</span></pre></div></td><td class="code"><div><pre><span></span><code>$ mkdir ~/.virtualenvs $ <span class="nb">cd</span> ~/.virtualenvs $ virtualenv --no-site-packages --distribute myproject </code></pre></div></td></tr></table></div> <p>Notice the <code>--no-site-packages</code> switch. That tells <code>virtualenv</code> to create this environment without any of the Python packages already installed, creating a completely fresh, clean environment. The <code>--distribute</code> switch causes the new virtual environment to be setup with <a href="http://packages.python.org/distribute/">distribute</a>, a replacement for the old and rather broken <a href="http://pypi.python.org/pypi/setuptools">setuptools</a>.</p> <p>All that&rsquo;s needed to get <code>virtualenvwrapper</code> up and running is to add two lines to your <code>.bashrc</code> and re-source the file.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span> <span class="normal">3</span> <span class="normal">4</span> <span class="normal">5</span> <span class="normal">6</span></pre></div></td><td class="code"><div><pre><span></span><code>$ vim ~/.bashrc <span class="nb">export</span> <span class="nv">WORKON_HOME</span><span class="o">=</span><span class="nv">$HOME</span>/.virtualenvs <span class="nb">source</span> /usr/local/bin/virtualenvwrapper.sh $ . ~/.bashrc </code></pre></div></td></tr></table></div> <p>We can now use commands like <code>workon</code> to ease the process of activating a certain environment.</p> <p>I&rsquo;ll go ahead and install <a href="http://pypi.python.org/pypi/yolk">yolk</a> in the environment to help manage packages.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span> <span class="normal">3</span></pre></div></td><td class="code"><div><pre><span></span><code>$ workon myproject $ pip install yolk $ yolk -l </code></pre></div></td></tr></table></div> <p>The last command will cause <code>yolk</code> to list all packages installed in the environment. Try deactivating the environment and then running <code>yolk</code> again.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span> <span class="normal">3</span></pre></div></td><td class="code"><div><pre><span></span><code>$ deactivate $ yolk -l yolk: <span class="nb">command</span> not found </code></pre></div></td></tr></table></div> <p>&lsquo;yolk&rsquo; wasn&rsquo;t found, because it was only installed within the virtual environment. Neat!</p> <h4>Install Django</h4> <p>Finally, it&rsquo;s time to install Django! The process is simple enough.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span></pre></div></td><td class="code"><div><pre><span></span><code>$ workon myproject $ pip install django </code></pre></div></td></tr></table></div> <p>And that&rsquo;s it!</p> <p>The <a href="http://www.pythonware.com/products/pil/">Python Imaging Library</a> is likely to be needed for any Django project. I installed it in the beginning of this section, but because I used the <code>--no-site-packages</code> when creating my virtual environment, it is not available for use within the project. To fix that, I&rsquo;ll just link the package in. I also previously installed <a href="http://initd.org/psycopg/">psyopg2</a>, which Python will need to communicate with my PostgreSQL database, so I&rsquo;ll link that in as well. <code>psyopg2</code> depends on <a href="http://www.egenix.com/products/python/mxBase/">mx</a>, which was also previously installed but still must be made available in the environment.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span> <span class="normal">3</span> <span class="normal">4</span> <span class="normal">5</span></pre></div></td><td class="code"><div><pre><span></span><code>$ cdsitepackages $ ln -s /usr/lib/python2.6/dist-packages/PIL $ ln -s /usr/lib/python2.6/dist-packages/PIL.pth $ ln -s /usr/lib/python2.6/dist-packages/psycopg2 $ ln -s /usr/lib/python2.6/dist-packages/mx </code></pre></div></td></tr></table></div> <p>That wasn&rsquo;t too painful!</p> <h4>Create a Django project</h4> <p>While I&rsquo;m still in the virtual environment, I&rsquo;ll go ahead and create a new Django project. The project will have the same name as the environment: <code>myproject</code>. For this tutorial, I&rsquo;ll stick with the precedence set by the Slicehost tutorials and use <code>demo</code> as the name of both my user and group on the server.</p> <p>I like to keep my sites in the <code>/srv/</code> directory. I structure them so that the code that runs the site, any public files, logs, and backups are all stored in separate sub-directories.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span> <span class="normal">3</span> <span class="normal">4</span> <span class="normal">5</span> <span class="normal">6</span> <span class="normal">7</span> <span class="normal">8</span> <span class="normal">9</span></pre></div></td><td class="code"><div><pre><span></span><code>$ <span class="nb">cd</span> /srv/ $ sudo mkdir -p myproject.com/<span class="o">{</span>code,public,logs,backup<span class="o">}</span> $ sudo mkdir -p myproject.com/public/<span class="o">{</span>media,static<span class="o">}</span> $ sudo chown -R demo:demo myproject.com $ <span class="nb">cd</span> myproject.com $ sudo chown -R :www-data logs public $ sudo chmod -R g+w logs public $ <span class="nb">cd</span> code/ $ django-admin.py startproject myproject </code></pre></div></td></tr></table></div> <p>Notice that the <code>logs</code> and <code>public</code> directories were <code>chown</code>ed to the www-data group. That is the name of the user and group that nginx will run as. The web server will need permissions to write to those locations.</p> <h4>Save Requirements</h4> <p>With the environment setup and all the necessary packages installed, now is a good time to tell pip to <a href="http://www.pip-installer.org/en/latest/#freezing-requirements">freeze</a> all the packages and their versions. I keep this file in a <code>deploy</code> folder in my project.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span></pre></div></td><td class="code"><div><pre><span></span><code>$ mkdir /srv/myproject.com/code/deploy $ pip freeze &gt; /srv/myproject.com/code/deploy/requirements.txt </code></pre></div></td></tr></table></div> <p>Now, if I needed to recreate the virtual environment somewhere else, I could just tell pip to install all the packages from that file.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><code>$ pip install -r /srv/myproject.com/code/deploy/requirements.txt </code></pre></div></td></tr></table></div> <h3>Configure uWSGI</h3> <p>Now that I have something to serve, I&rsquo;ll configure uWSGI to serve it. The first step is to create a configuration file for the project. I call mine <code>wsgi.py</code> and store it in <code>/srv/myproject.com/code/myproject/</code>. It appends the current directory to the Python path, specifies the Django settings file for the project, and registers the WSGI handler.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span> <span class="normal">3</span> <span class="normal">4</span> <span class="normal">5</span> <span class="normal">6</span> <span class="normal">7</span> <span class="normal">8</span> <span class="normal">9</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="kn">import</span> <span class="nn">sys</span> <span class="kn">import</span> <span class="nn">os</span> <span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">abspath</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)))</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">&#39;DJANGO_SETTINGS_MODULE&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s1">&#39;myproject.settings&#39;</span> <span class="kn">import</span> <span class="nn">django.core.handlers.wsgi</span> <span class="n">application</span> <span class="o">=</span> <span class="n">django</span><span class="o">.</span><span class="n">core</span><span class="o">.</span><span class="n">handlers</span><span class="o">.</span><span class="n">wsgi</span><span class="o">.</span><span class="n">WSGIHandler</span><span class="p">()</span> </code></pre></div></td></tr></table></div> <p>With that done, the next step is to decide how uWSGI should be run. I&rsquo;m going to use Ubuntu&rsquo;s <a href="http://upstart.ubuntu.com/">upstart</a> to supervise the service. I keep the upstart script in my project&rsquo;s <code>deploy/</code> directory.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal"> 1</span> <span class="normal"> 2</span> <span class="normal"> 3</span> <span class="normal"> 4</span> <span class="normal"> 5</span> <span class="normal"> 6</span> <span class="normal"> 7</span> <span class="normal"> 8</span> <span class="normal"> 9</span> <span class="normal">10</span> <span class="normal">11</span> <span class="normal">12</span> <span class="normal">13</span> <span class="normal">14</span> <span class="normal">15</span> <span class="normal">16</span> <span class="normal">17</span> <span class="normal">18</span></pre></div></td><td class="code"><div><pre><span></span><code>$ vim /srv/myproject.com/code/deploy/uwsgi.conf description <span class="s2">&quot;uWSGI server for My Project&quot;</span> start on runlevel <span class="o">[</span><span class="m">2345</span><span class="o">]</span> stop on runlevel <span class="o">[</span>!2345<span class="o">]</span> respawn <span class="nb">exec</span> /usr/local/sbin/uwsgi <span class="se">\</span> --home /home/demo/.virtualenvs/myproject/ <span class="se">\</span> --socket /var/run/myproject.com.sock <span class="se">\</span> --chmod-socket <span class="se">\</span> --pythonpath /srv/myproject.com/code/ <span class="se">\</span> --module myproject.wsgi <span class="se">\</span> --process <span class="m">2</span> <span class="se">\</span> --harakiri <span class="m">30</span> <span class="se">\</span> --master <span class="se">\</span> --logto /srv/myproject.com/logs/uwsgi.log </code></pre></div></td></tr></table></div> <p>Sadly, upstart doesn&rsquo;t seem to recognize links. Rather than linking the config file into <code>/etc/init/</code>, I have to copy it.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><code>$ sudo cp /srv/myproject.com/code/deploy/uwsgi.conf /etc/init/uwsgi-myproject.conf </code></pre></div></td></tr></table></div> <h3>Configure nginx</h3> <p>Nginx&rsquo;s configuration is pretty straight-forward. If you&rsquo;ve never configured the server before, <a href="http://articles.slicehost.com/nginx">Slicehost&rsquo;s articles</a> can set you down the right path. My own nginx config looks something like this:</p> <div class="highlight"><pre><span></span><code><span class="n">user</span><span class="w"> </span><span class="n">www</span><span class="o">-</span><span class="n">data</span><span class="w"> </span><span class="n">www</span><span class="o">-</span><span class="n">data</span><span class="p">;</span><span class="w"></span> <span class="n">worker_processes</span><span class="w"> </span><span class="mi">4</span><span class="p">;</span><span class="w"></span> <span class="n">pid</span><span class="w"> </span><span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="n">run</span><span class="o">/</span><span class="n">nginx</span><span class="o">.</span><span class="n">pid</span><span class="p">;</span><span class="w"></span> <span class="n">events</span><span class="w"> </span><span class="p">{</span><span class="w"></span> <span class="w"> </span><span class="n">worker_connections</span><span class="w"> </span><span class="mi">768</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="n">use</span><span class="w"> </span><span class="n">epoll</span><span class="p">;</span><span class="w"></span> <span class="p">}</span><span class="w"></span> <span class="n">http</span><span class="w"> </span><span class="p">{</span><span class="w"></span> <span class="w"> </span><span class="c1">##</span><span class="w"></span> <span class="w"> </span><span class="c1"># Basic Settings</span><span class="w"></span> <span class="w"> </span><span class="c1">##</span><span class="w"></span> <span class="w"> </span><span class="n">sendfile</span><span class="w"> </span><span class="n">on</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="n">tcp_nopush</span><span class="w"> </span><span class="n">on</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="n">tcp_nodelay</span><span class="w"> </span><span class="n">on</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="n">keepalive_timeout</span><span class="w"> </span><span class="mi">30</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="n">types_hash_max_size</span><span class="w"> </span><span class="mi">2048</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="c1"># server_tokens off;</span><span class="w"></span> <span class="w"> </span><span class="c1"># server_names_hash_bucket_size 64;</span><span class="w"></span> <span class="w"> </span><span class="c1"># server_name_in_redirect off;</span><span class="w"></span> <span class="w"> </span><span class="n">include</span><span class="w"> </span><span class="o">/</span><span class="n">etc</span><span class="o">/</span><span class="n">nginx</span><span class="o">/</span><span class="n">mime</span><span class="o">.</span><span class="n">types</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="n">default_type</span><span class="w"> </span><span class="n">application</span><span class="o">/</span><span class="n">octet</span><span class="o">-</span><span class="n">stream</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="c1">##</span><span class="w"></span> <span class="w"> </span><span class="c1"># Logging Settings</span><span class="w"></span> <span class="w"> </span><span class="c1">##</span><span class="w"></span> <span class="w"> </span><span class="n">access_log</span><span class="w"> </span><span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="nb">log</span><span class="o">/</span><span class="n">nginx</span><span class="o">/</span><span class="n">access</span><span class="o">.</span><span class="n">log</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="n">error_log</span><span class="w"> </span><span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="nb">log</span><span class="o">/</span><span class="n">nginx</span><span class="o">/</span><span class="n">error</span><span class="o">.</span><span class="n">log</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="c1">##</span><span class="w"></span> <span class="w"> </span><span class="c1"># Gzip Settings</span><span class="w"></span> <span class="w"> </span><span class="c1">##</span><span class="w"></span> <span class="w"> </span><span class="n">gzip</span><span class="w"> </span><span class="n">on</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="n">gzip_disable</span><span class="w"> </span><span class="s2">&quot;msie6&quot;</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="n">gzip_types</span><span class="w"> </span><span class="n">text</span><span class="o">/</span><span class="n">plain</span><span class="w"> </span><span class="n">text</span><span class="o">/</span><span class="n">css</span><span class="w"> </span><span class="n">application</span><span class="o">/</span><span class="n">json</span><span class="w"> </span><span class="n">application</span><span class="o">/</span><span class="n">x</span><span class="o">-</span><span class="n">javascript</span><span class="w"> </span><span class="n">text</span><span class="o">/</span><span class="n">xml</span><span class="w"> </span><span class="n">application</span><span class="o">/</span><span class="n">xml</span><span class="w"> </span><span class="n">application</span><span class="o">/</span><span class="n">xml</span><span class="o">+</span><span class="n">rss</span><span class="w"> </span><span class="n">text</span><span class="o">/</span><span class="n">javascript</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="n">gzip_proxied</span><span class="w"> </span><span class="n">any</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="n">gzip_comp_level</span><span class="w"> </span><span class="mi">2</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="c1"># gzip_vary on;</span><span class="w"></span> <span class="w"> </span><span class="c1"># gzip_buffers 16 8k;</span><span class="w"></span> <span class="w"> </span><span class="c1"># gzip_http_version 1.1;</span><span class="w"></span> <span class="w"> </span><span class="c1">##</span><span class="w"></span> <span class="w"> </span><span class="c1"># Virtual Host Configs</span><span class="w"></span> <span class="w"> </span><span class="c1">##</span><span class="w"></span> <span class="w"> </span><span class="n">include</span><span class="w"> </span><span class="o">/</span><span class="n">etc</span><span class="o">/</span><span class="n">nginx</span><span class="o">/</span><span class="n">conf</span><span class="o">.</span><span class="n">d</span><span class="o">/*.</span><span class="n">conf</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="n">include</span><span class="w"> </span><span class="o">/</span><span class="n">etc</span><span class="o">/</span><span class="n">nginx</span><span class="o">/</span><span class="n">sites</span><span class="o">-</span><span class="n">enabled</span><span class="o">/*</span><span class="p">;</span><span class="w"></span> <span class="p">}</span><span class="w"></span> </code></pre></div> <p>I keep the virtual host config for my project inside the project&rsquo;s <code>code/deploy/</code> directory. A basic virtual host for a Django project would looks like this:</p> <div class="highlight"><pre><span></span><code><span class="n">server</span><span class="w"> </span><span class="p">{</span><span class="w"></span> <span class="w"> </span><span class="n">listen</span><span class="w"> </span><span class="mi">80</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="n">server_name</span><span class="w"> </span><span class="n">www</span><span class="o">.</span><span class="n">myproject</span><span class="o">.</span><span class="n">com</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="n">rewrite</span><span class="w"> </span><span class="o">^/</span><span class="p">(</span><span class="o">.*</span><span class="p">)</span><span class="w"> </span><span class="n">http</span><span class="p">:</span><span class="o">//</span><span class="n">myproject</span><span class="o">.</span><span class="n">com</span><span class="o">/$</span><span class="mi">1</span><span class="w"> </span><span class="n">permanent</span><span class="p">;</span><span class="w"></span> <span class="p">}</span><span class="w"></span> <span class="n">server</span><span class="w"> </span><span class="p">{</span><span class="w"></span> <span class="w"> </span><span class="n">listen</span><span class="w"> </span><span class="mi">80</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="n">server_name</span><span class="w"> </span><span class="n">myproject</span><span class="o">.</span><span class="n">com</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="n">access_log</span><span class="w"> </span><span class="o">/</span><span class="n">srv</span><span class="o">/</span><span class="n">myproject</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">logs</span><span class="o">/</span><span class="n">access</span><span class="o">.</span><span class="n">log</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="n">error_log</span><span class="w"> </span><span class="o">/</span><span class="n">srv</span><span class="o">/</span><span class="n">myproject</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">logs</span><span class="o">/</span><span class="n">error</span><span class="o">.</span><span class="n">log</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="n">location</span><span class="w"> </span><span class="o">/</span><span class="n">media</span><span class="w"> </span><span class="p">{</span><span class="w"></span> <span class="w"> </span><span class="n">root</span><span class="w"> </span><span class="o">/</span><span class="n">srv</span><span class="o">/</span><span class="n">myproject</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">public</span><span class="o">/</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="p">}</span><span class="w"></span> <span class="w"> </span><span class="n">location</span><span class="w"> </span><span class="o">/</span><span class="k">static</span><span class="w"> </span><span class="p">{</span><span class="w"></span> <span class="w"> </span><span class="n">root</span><span class="w"> </span><span class="o">/</span><span class="n">srv</span><span class="o">/</span><span class="n">myproject</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">public</span><span class="o">/</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="p">}</span><span class="w"></span> <span class="w"> </span><span class="n">location</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="p">{</span><span class="w"></span> <span class="w"> </span><span class="n">uwsgi_pass</span><span class="w"> </span><span class="n">unix</span><span class="p">:</span><span class="o">///</span><span class="k">var</span><span class="o">/</span><span class="n">run</span><span class="o">/</span><span class="n">myproject</span><span class="o">.</span><span class="n">com</span><span class="o">.</span><span class="n">sock</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="n">include</span><span class="w"> </span><span class="n">uwsgi_params</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="p">}</span><span class="w"></span> <span class="p">}</span><span class="w"></span> </code></pre></div> <p>To install and enable the virtual host, I&rsquo;ll link the configuration file first to the nginx <code>sites-available</code> directory, and then link that link to the <code>sites-enabled</code> directory.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span></pre></div></td><td class="code"><div><pre><span></span><code>$ sudo ln -s /srv/myproject.com/code/deploy/nginx.conf /etc/nginx/sites-available/myproject.com $ sudo ln -s /etc/nginx/sites-available/myproject.com /etc/nginx/sites-enabled/myproject.com </code></pre></div></td></tr></table></div> <h3>SSL</h3> <p>If you need to encrypt communications, <a href="http://library.linode.com/web-servers/nginx/configuration/ssl">Linode has a tutorial</a> on using both self-signed certificates and commercial certificates with nginx.</p> <h3>Fire it Up</h3> <p>Nginx should be set to talk to uWSGI, which should be set to talk to the Django project. Time for a test run!</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span></pre></div></td><td class="code"><div><pre><span></span><code>$ sudo start uwsgi-myproject $ sudo /etc/init.d/nginx start </code></pre></div></td></tr></table></div> <h3>memcached</h3> <p>Django has a very good built-in <a href="https://docs.djangoproject.com/en/dev/topics/cache/">cache framework</a>. I like to take advantage of it with a memory-based backend: namely, <a href="http://memcached.org/">memcached</a>. It&rsquo;s fast, efficient, and easy to setup.</p> <p>All that&rsquo;s needed is to install memcached on the server, followed by the Python API <a href="http://www.tummy.com/Community/software/python-memcached/">python-memcached</a>. </p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span> <span class="normal">3</span></pre></div></td><td class="code"><div><pre><span></span><code>$ sudo apt-get install memcached $ workon myproject $ pip install python-memcached </code></pre></div></td></tr></table></div> <p>The default configuration file in Ubuntu lives at <code>/etc/memcached.conf</code>. I usually stick with the defaults, but sometimes end up changing the port that memchached runs on or the amount of memory it is allowed to use.</p> <h3>logrotate</h3> <p>With the web server more-or-less complete, I like to setup logrotate to manage the logs in my project&rsquo;s directory. Once again, Slicehost has <a href="http://articles.slicehost.com/2010/6/30/understanding-logrotate-on-ubuntu-part-1">an excellent introduction to logrotate</a> and <a href="http://articles.slicehost.com/2010/6/30/understanding-logrotate-on-ubuntu-part-2">an example config for virtual hosts</a>.</p> <p>I maintain a configuration file for each of the domains being served by the machine. The file for a domain lives in &ndash; you guessed it &ndash; the associated project&rsquo;s <code>deploy/</code> folder. Each contains two entries: one for the nginx virtual host and one for the uWSGI instance. The reason for this is that each config block needs a <code>postrotate</code> section to restart the associated server after the logs have been rotated. I don&rsquo;t want nginx to be restarted everytime a uWSGI log is rotated, and I don&rsquo;t want uWSGI restarted everytime an nginx log is rotated.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal"> 1</span> <span class="normal"> 2</span> <span class="normal"> 3</span> <span class="normal"> 4</span> <span class="normal"> 5</span> <span class="normal"> 6</span> <span class="normal"> 7</span> <span class="normal"> 8</span> <span class="normal"> 9</span> <span class="normal">10</span> <span class="normal">11</span> <span class="normal">12</span> <span class="normal">13</span> <span class="normal">14</span> <span class="normal">15</span> <span class="normal">16</span> <span class="normal">17</span> <span class="normal">18</span> <span class="normal">19</span> <span class="normal">20</span> <span class="normal">21</span> <span class="normal">22</span></pre></div></td><td class="code"><div><pre><span></span><code>$ vim /srv/myproject.com/code/deploy/logrotate /srv/myproject.com/logs/access.log /srv/myproject.com/logs/error.log <span class="o">{</span> rotate <span class="m">14</span> daily compress delaycompress sharedscripts postrotate <span class="o">[</span> ! -f /var/run/nginx.pid <span class="o">]</span> <span class="o">||</span> <span class="nb">kill</span> -USR1 <span class="sb">`</span>cat /var/run/nginx.pid<span class="sb">`</span> endscript <span class="o">}</span> /srv/myproject.com/logs/uwsgi.log <span class="o">{</span> rotate <span class="m">14</span> daily compress delaycompress postrotate restart --quiet uwsgi-myproject endscript <span class="o">}</span> </code></pre></div></td></tr></table></div> <p>This file is linked in to the <code>/etc/logrotate.d/</code> directory. Logrotate will automatically include any file in that directory inside its configuration.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><code>$ sudo ln -s /srv/myproject.com/code/deploy/logrotate /etc/logrotate.d/myproject </code></pre></div></td></tr></table></div> <h2>Database Server</h2> <p>A web server isn&rsquo;t much use without a database these days. I use <a href="http://www.postgresql.org/">PostgreSQL</a>.</p> <h3>Install</h3> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><code>$ sudo apt-get install postgresql </code></pre></div></td></tr></table></div> <h3>Configure</h3> <p>PostgreSQL has some unique terminology and ways of doing things. When I first set it up for the first time, having coming from a MySQL background, not everything was completely straightforward. As usual, <a href="http://articles.slicehost.com/postgresql">Slicehost has a number of articles</a> that will provide a foundation.</p> <p>In the <code>/etc/postgresql/8.4/main/postgresql.conf</code> file, I uncomment the following two lines:</p> <div class="highlight"><pre><span></span><code>track_counts = on autovacuum = on </code></pre></div> <p>Then restart the database server.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><code>$ sudo /etc/init.d/postgresql-8.4 restart </code></pre></div></td></tr></table></div> <p>After that I&rsquo;ll change the password for the postgres user and the postgres database role.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span> <span class="normal">3</span> <span class="normal">4</span></pre></div></td><td class="code"><div><pre><span></span><code>$ sudo passwd postgres $ sudo -u postgres psql <span class="se">\p</span>assword postgres <span class="se">\q</span> </code></pre></div></td></tr></table></div> <p>To allow local socket connections to the database using passwords, I open up <code>/etc/postgresql/8.4/main/pg_hba.conf</code> and find the following line:</p> <div class="highlight"><pre><span></span><code>local all all ident </code></pre></div> <p>Which I then change to:</p> <div class="highlight"><pre><span></span><code>local all all md5 </code></pre></div> <p>After which another restart is in order.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><code>$ sudo /etc/init.d/postgresql-8.4 restart </code></pre></div></td></tr></table></div> <h3>Create a database</h3> <p>The next step is to create a user (or role, in PostgreSQL&rsquo;s parlance) and database for the project. I use the same name for both.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span></pre></div></td><td class="code"><div><pre><span></span><code>$ sudo -u postgres createuser -PE myproject $ sudo -u postgres createdb -O myproject myproject </code></pre></div></td></tr></table></div> <p>After that, I should be able to connect.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><code>$ psql -U myproject </code></pre></div></td></tr></table></div> <h4>Import the Database</h4> <p>If I&rsquo;m restoring a previous database from a backup, now would be the time to import the backup.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><code>$ psql -U myproject &lt; myproject.postgresql </code></pre></div></td></tr></table></div> <p>And now Django should be able to connect!</p> <p>The basic server is setup and secure. Django, uWSGI, nginx and PostgreSQL are all running and getting along swimmingly. At this point, many people would be done, but I also like to have a minimal mail server.</p> <h2>Mail Server</h2> <p>Most of my domains use <a href="http://www.google.com/apps/">Google Apps</a>, so I don&rsquo;t need a full-blown mail server. I do want programs and scripts to be able to send mail, and I prefer not to do so through an external SMTP server &ndash; I&rsquo;d rather just deal with having sendmail running on my own box. And I do have a few domains that do not use Google Apps. They have one or two aliases associated with them, so the server needs to receive messages for those domains and forward them off to an external address. If any of this sounds vaguely familiar, it&rsquo;s because it&rsquo;s the same thing I detailed <a href="/2008/06/an-ubuntu-vps-on-slicehost-mail/">last time</a>. My setup now is the same as then, so I won&rsquo;t repeat any of it here.</p> <p>For a more detailed explanation of running <a href="http://www.postfix.org/">Postfix</a>, you can <a href="http://articles.slicehost.com/email">read the Slicehost articles</a>.</p> <h2>A Note on Git</h2> <p>I use <a href="http://git-scm.com/">Git</a> to keep track of the code for all my projects. (If you&rsquo;re new to Git, you ought to skim the <a href="http://gitref.org/">Git Reference</a> or <a href="https://mirrors.edge.kernel.org/pub/software/scm/git/docs/giteveryday.html">Everyday GIT With 20 Commands Or So</a>). To manage websites, I create a repository of the directory with the code that runs the site (in this case, <code>/srv/myproject.com/code/</code>) and another empty, bare repository to work as a hub. With a <code>post-update</code> and <code>post-commit</code>, the end result is an excellent web workflow:</p> <ul> <li>A copy of the hub can be checked out on a local machine for development. Whenever a change is committed, a simple <code>git push</code> will push the code to the web server and automatically make it live.</li> <li>Changes can be made on the server in the actual live website directory. (This is not a best practice, but I do it more often than I should probably admit.) Whenever a change is committed, it is automatically pushed to the hub, so that a simple <code>git pull</code> is all that&rsquo;s needed on the development machine to update its repository.</li> </ul> <p>A more detailed explanation of this workflow is at <a href="http://joemaller.com/990/a-web-focused-git-workflow/">Joe Maller&rsquo;s blog</a>.</p> <p>To start, I need to create a repository for the new project I created in this tutorial. And, since this is a new server, I need to give Git my name and email address to record with every commit.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span> <span class="normal">3</span> <span class="normal">4</span></pre></div></td><td class="code"><div><pre><span></span><code>$ git config --global user.name <span class="s2">&quot;Pig Monkey&quot;</span> $ git config --global user.email <span class="s2">&quot;pm@pig-monkey.com&quot;</span> $ <span class="nb">cd</span> /srv/myproject.com/code/ $ git init </code></pre></div></td></tr></table></div> <p>Before adding the files, I create a <code>.gitignore</code> file in the repository root to tell Git to ignore compiled Python files.</p> <div class="highlight"><pre><span></span><code>$ vim .gitignore *.pyc </code></pre></div> <p>Now I add all the files to the repository, confirm that it worked, and commit the files.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span> <span class="normal">3</span></pre></div></td><td class="code"><div><pre><span></span><code>$ git add . $ git status -s $ git commit -m <span class="s2">&quot;Initial commit of myproject.com&quot;</span> </code></pre></div></td></tr></table></div> <p>I create the bare hub directory directly along side the projects <code>code/</code>.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span> <span class="normal">3</span> <span class="normal">4</span></pre></div></td><td class="code"><div><pre><span></span><code>$ <span class="nb">cd</span> ../ $ mkdir hub.git $ <span class="nb">cd</span> hub.git $ git --bare init </code></pre></div></td></tr></table></div> <p>With the hub created, I need to add it as the remote for the main repository and push the master branch to it.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span> <span class="normal">3</span> <span class="normal">4</span></pre></div></td><td class="code"><div><pre><span></span><code>$ <span class="nb">cd</span> ../code $ git remote add hub /srv/myproject.com/hub.git $ git remote show hub $ git push hub master </code></pre></div></td></tr></table></div> <p>Now the hub needs a <code>post-update</code> script so that every time something is pushed to it, that change is automagically pulled into the live website directory.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal"> 1</span> <span class="normal"> 2</span> <span class="normal"> 3</span> <span class="normal"> 4</span> <span class="normal"> 5</span> <span class="normal"> 6</span> <span class="normal"> 7</span> <span class="normal"> 8</span> <span class="normal"> 9</span> <span class="normal">10</span> <span class="normal">11</span> <span class="normal">12</span> <span class="normal">13</span> <span class="normal">14</span></pre></div></td><td class="code"><div><pre><span></span><code>$ vim /srv/myproject.com/hub.git/hooks/post-update <span class="c1">#!/bin/sh</span> <span class="nb">echo</span> <span class="nb">echo</span> <span class="s2">&quot;**** Pulling changes into live&quot;</span> <span class="nb">echo</span> <span class="nb">cd</span> /srv/myproject.com/code <span class="o">||</span> <span class="nb">exit</span> <span class="nb">unset</span> GIT_DIR git pull hub master <span class="nb">exec</span> git-update-server-info $ chmod +x /srv/myproject.com/hub.git/hooks/post-update </code></pre></div></td></tr></table></div> <p>And the live website directory requires a <code>post-commit</code> script so that every time something is committed inside of it, that change is automagically pushed to the hub.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal"> 1</span> <span class="normal"> 2</span> <span class="normal"> 3</span> <span class="normal"> 4</span> <span class="normal"> 5</span> <span class="normal"> 6</span> <span class="normal"> 7</span> <span class="normal"> 8</span> <span class="normal"> 9</span> <span class="normal">10</span></pre></div></td><td class="code"><div><pre><span></span><code>$ vim /srv/myproject.com/code/.git/hooks/post-commit <span class="c1">#!/bin/sh</span> <span class="nb">echo</span> <span class="nb">echo</span> <span class="s2">&quot;**** pushing changes to Hub&quot;</span> <span class="nb">echo</span> git push hub $ chmod +x /srv/myproject.com/code/.git/hooks/post-commit </code></pre></div></td></tr></table></div> <p>All that&rsquo;s left is to check out the hub onto the development machine &ndash; my laptop, in this case!</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span> <span class="normal">3</span></pre></div></td><td class="code"><div><pre><span></span><code>$ mkdir ~/work/myproject/ $ <span class="nb">cd</span> ~/work/myproject/ $ git clone ssh://myserver.com/srv/myproject.com/hub.git code </code></pre></div></td></tr></table></div> <p>To test things out, we can add a file to the repository on the development machine.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span> <span class="normal">3</span> <span class="normal">4</span> <span class="normal">5</span></pre></div></td><td class="code"><div><pre><span></span><code>$ <span class="nb">cd</span> code/ $ touch <span class="nb">test</span> $ git add <span class="nb">test</span> $ git commit -m <span class="s2">&quot;A test&quot;</span> $ git push </code></pre></div></td></tr></table></div> <p>Now go back to the server, and the file should be there! To test things the other way around, I&rsquo;ll delete the file from the live repository.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span> <span class="normal">3</span> <span class="normal">4</span> <span class="normal">5</span></pre></div></td><td class="code"><div><pre><span></span><code>$ <span class="nb">cd</span> /srv/myproject.com/code/ $ ls myproject <span class="nb">test</span> $ git rm <span class="nb">test</span> $ git commit -m <span class="s2">&quot;Removing the test file&quot;</span> </code></pre></div></td></tr></table></div> <p>And once again to the development machine:</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span> <span class="normal">3</span></pre></div></td><td class="code"><div><pre><span></span><code>$ git pull $ ls deploy myproject </code></pre></div></td></tr></table></div> <p>No more test! It&rsquo;s pretty dandy.</p> <h3>Restoring</h3> <p>If I was building a new server and restoring a project from an old server, I would simply mirror the old hub and then clone that in the live directory.</p> <div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span> <span class="normal">2</span> <span class="normal">3</span></pre></div></td><td class="code"><div><pre><span></span><code>$ <span class="nb">cd</span> /srv/myproject.com/ $ git clone --mirror ssh://myoldserver.com/srv/myproject.com/hub.git $ git clone hub.git code/ </code></pre></div></td></tr></table></div> <h2>Resources</h2> <p>Prior to building this server, I was new to a lot of this &ndash; particularly, uWSGI and virtualenv. The following tutorials helped me a good deal in putting together the perfect setup for my needs.</p> <ul> <li><a href="http://iamzed.com/2009/05/07/a-primer-on-virtualenv/">A Primer on virtualenv</a> by Chris Scott</li> <li><a href="http://joemaller.com/990/a-web-focused-git-workflow/">A web-focused Git workflow</a> by Joe Maller</li> <li><a href="http://blog.zacharyvoase.com/2010/03/05/django-uwsgi-nginx/">Deployment with uWSGI and nginx</a> by Zachary Voase</li> <li><a href="http://brandonkonkle.com/blog/2010/sep/14/django-uwsgi-and-nginx/">Django on uWSGI and Nginx</a> by Brandon Konkle</li> <li><a href="http://www.jeremybowers.com/blog/post/5/django-nginx-and-uwsgi-production-serving-millions-page-views/">Django, Nginx and uWSGI in production</a> by Jeremy Bowers</li> <li><a href="http://www.saltycrane.com/blog/2009/05/notes-using-pip-and-virtualenv-django/">Notes on using pip and virtualenv with Django</a> by Eliot</li> <li><a href="http://mathematism.com/2009/07/30/presentation-pip-and-virtualenv/">Presentation: pip and virtualenv</a> by Rich Leland</li> <li><a href="http://brandonkonkle.com/blog/2010/jun/25/provisioning-new-ubuntu-server-django/">Provisioning a new Ubuntu server for Django</a> by Brandon Konkle</li> <li><a href="http://www.westphahl.net/blog/2010/4/8/running-django-nginx-and-uwsgi/">Running Django with Nginx and uWSGI</a> by Simon Westphahl</li> <li><a href="http://www.doughellmann.com/docs/virtualenvwrapper/command_ref.html">virtualenvwrapper Command Reference</a></li> <li><a href="http://www.arthurkoziel.com/2008/10/22/working-virtualenv/">Working with virtualenv</a> by Arthur Koziel</li> </ul>A Move to Django2011-06-11T00:00:00-07:002012-12-22T00:00:00-08:00Pig Monkeytag:pig-monkey.com,2011-06-11:/2011/06/move-django/<p>You may not notice much, but this blog has been completely rewritten.</p> <p>I started developing in <a href="http://djangoproject.com">Django</a> last winter and quickly became smitten with both the Django framework and the <a href="http://python.org">Python</a>. Most of the coding I&rsquo;ve done this year has been in Python. Naturally, I had thoughts of moving …</p><p>You may not notice much, but this blog has been completely rewritten.</p> <p>I started developing in <a href="http://djangoproject.com">Django</a> last winter and quickly became smitten with both the Django framework and the <a href="http://python.org">Python</a>. Most of the coding I&rsquo;ve done this year has been in Python. Naturally, I had thoughts of moving this website from <a href="http://wordpress.org">Wordpress</a> over to a Django-based blog.</p> <p>For a while I did nothing about it. Then I had another project come up that required some basic blog functionality be added to a Django-based site. A blog is &ndash; or, at least, can be &ndash; a fairly simple affair, but before writing my own I decided to look around and see what else was out there. There&rsquo;s a number of Django-based blogs floating around (<a href="http://blog.montylounge.com/2010/02/10/eleven-django-blog-engines-you-should-know/">Kevin Fricovsky has a list</a>), but few of them jumped out at me. Most were not actively developed and depended on too many stale packages for my taste, or they just had a feature set that I didn&rsquo;t like.</p> <p>Out of all of them, two presented themselves as possibilities: <a href="https://github.com/montylounge/django-mingus">Mingus</a> (written by the previously mentioned Kevin) and Nathan Borror&rsquo;s <a href="https://github.com/nathanborror/django-basic-apps">django-basic-apps</a>. Mingus tries to be a full-featured blogging application and was much too complex for the simple project I was then working on. But the blog application in django-basic-apps (a fork of which provides Mingus with its core blog functionality) looked like it would fit the bill. As the name implies, it is meant to be a very basic blog. I dived in to the code I discovered that, with a few modifications, it would do what I needed.</p> <p>So I finished that project. But now having messed with blogging in Django I was more motivated to get started on rewriting my own site. I took another look at Mingus. Although it was too complex for the previous project, the features it provides are very similar to the features I wanted for this website. I looked at and thought about Mingus for a time, repeatedly turning it down and then coming back to it. The question centered around the project&rsquo;s staleness more than anything else. Currently, Mingus is built for Django 1.1. That&rsquo;s an old version. As of this writing, the current version is 1.3. Many improvements have been made in Django since 1.1 and I was not too keen to forgo them and run an old piece of code. Mingus is under active development, and will be updated for Django 1.3, but it&rsquo;s a hobby-project, so the work is understandably slow.</p> <p>In the end, I decided that the best thing to do was go my own route, but take some pointers and inspiration from Mingus. I would make my own fork of django-basic-apps, using that blog as the basis, and build a system on top of that. I created <a href="https://github.com/pigmonkey/django-vellum">my fork</a> last month and have been steadily plodding away on it in my free time. Over the course of the development I created <a href="https://github.com/pigmonkey/django-wmd">a few</a> <a href="https://github.com/pigmonkey/django-badgr">simple</a> <a href="https://github.com/pigmonkey/django-twat">applications</a> to complement the core blog, and <a href="https://github.com/bartTC/django-markup/commit/13654d7159a7c8b82f1fc3e5bd222441448b3f47">contributed code</a> to another project.</p> <p>It&rsquo;s not quite done &ndash; there&rsquo;s still a few things I want to improve &ndash; but it&rsquo;s good enough to launch. (If you notice any kinks, let me know.) I&rsquo;m quite pleased with it.</p> <p>This is a notable occasion. I&rsquo;ve been using Wordpress since before it was Wordpress, but it is time to move on. (Wordpress is a fork of an old piece of code called <a href="http://cafelog.com/">b2/cafelog</a>. My database tables have been rocking the <code>b2</code> prefix since 2002.)</p> <p>As you&rsquo;ve no doubt noticed, the look of the site hasn&rsquo;t changed much. I tweaked a few things here and there, but for the most part just recreated the same template as what I had written for Wordpress. I am planning on a redesign eventually. For now, I wanted to spend my time developing the actual blog rather than screwing with CSS.</p> <p>So, there you have it. <a href="https://github.com/pigmonkey/django-vellum">Everything is open source</a>. Download it, fork it, hack it (and don&rsquo;t forget to send your code changes back my way). Let me know what you think. Build your own blog with it! (There&rsquo;s even a <a href="https://github.com/pigmonkey/django-vellum/blob/master/vellum/management/commands/wordpress_import.py">script to import data from Wordpress</a>.) I think it&rsquo;s pretty sweet. The only thing lacking is documentation, and that&rsquo;s my next goal.</p> <h2>Disqus</h2> <p>The biggest change for the user is probably the comments, which are now powered by <a href="http://disqus.com/">Disqus</a>. Consider it a trial. I&rsquo;ve seen Disqus popping up on a number of sites the past year or so. At first it annoyed me, mostly because I use <a href="http://noscript.net/">NoScript</a> and did not want to enable JavaScript for another domain just to comment on a site. But after I got over that I found that Disqus wasn&rsquo;t too bad. As a user I found it to be on par with the standard comment systems provided by Wordpress, Blogger, and the like. The extra features don&rsquo;t appeal to me. But as an administrator, Disqus appeals to me more because it means that I no longer have to manage comments myself! And as a developer, I&rsquo;m attracted to <a href="http://blog.disqus.com/post/3879996850/scaling-disqus-at-pycon-2011">some of the things</a> that Disqus has done (they&rsquo;re a Python shop, and run on top of Django) and their <a href="https://github.com/disqus">open source contributions</a>.</p> <p>So I&rsquo;m giving it a shot. Disqus will happily export comments, so if I (or you) decide that I don&rsquo;t like it, it will be easy to move to another system.</p> <h2>Markdown</h2> <p>One final note: I like <a href="http://daringfireball.net/projects/markdown/">Markdown</a>. That might be an understatement.</p> <p>I first starting using Markdown on <a href="https://github.com">GitHub</a>, which I signed up for about the same time I started with Django and Python. After learning the <a href="http://daringfireball.net/projects/markdown/syntax">syntax</a> and playing with it for a few weeks, I discovered that I had a very hard time writing prose in anything else. In fact, the desire to write blog posts in Markdown was probably the biggest factor that influenced me to get off my butt and move away from Wordpress.</p> <p>So, I incorporated Markdown into the blog. But rather than just making the blog Markdown-only, I took a hint from Mingus and included <a href="https://github.com/bartTC/django-markup">django-markup</a>, which supports rendering in many <a href="https://secure.wikimedia.org/wikipedia/en/wiki/Lightweight_markup_language">lightweight markup languages</a>.</p> <p>Because I&rsquo;m still new to Markdown and occasionally cannot remember the correct syntax, I wanted to include some version of WMD. WMD is a <a href="https://secure.wikimedia.org/wikipedia/en/wiki/WYSIWYM">What You See Is What You Mean</a> editor for Markdown, a sort of alternative to <a href="https://secure.wikimedia.org/wikipedia/en/wiki/WYSIWYG">WYSIWYG</a> editors like TinyMCE. (It is my believe that WYSIWYG editors are one of the worst things to happen to the Internet.) All WMD consists of is a JavaScript library. The original was written by a guy named John Fraser, who was abducted by aliens some time in 2008. Since his disappearance from the interwebs, WMD has been forked countless times. I looked around at a few found a version that I was happy with (which happens to be a fork of a fork of a fork of a fork), and rolled it into <a href="https://github.com/pigmonkey/django-wmd">a reusable app</a>. While I was at it, I made some visual changes to the editing area for the post body. The result is an attractive post editing area that is simple to use and produces clean code. I think it is much better than what is offered by Wordpress.</p>Wishlist2011-01-10T00:00:00-08:002012-12-22T00:00:00-08:00Pig Monkeytag:pig-monkey.com,2011-01-10:/2011/01/wishlist/<p><a href="https://github.com/pigmonkey/django-wishlist">Wishlist</a> is a Django application for creating wishlists.</p> <p>I used Amazon Wishlist for a number of years, but my paranoia finally caught up to me and I decided that I didn&rsquo;t need to give Amazon that much more information about my interests.</p> <p>I tried a few substitutes and found …</p><p><a href="https://github.com/pigmonkey/django-wishlist">Wishlist</a> is a Django application for creating wishlists.</p> <p>I used Amazon Wishlist for a number of years, but my paranoia finally caught up to me and I decided that I didn&rsquo;t need to give Amazon that much more information about my interests.</p> <p>I tried a few substitutes and found that my requirements for a wishlist were less than common. I don&rsquo;t often use wishlists in the usual way of asking people for gifts on special occasions. Instead, I use wishlists privately to keep track of items that I wish to purchase myself. It helps me to determine savings goals, to track books that I want to read, etc. As such, I usually do not want items on my wishlist to be publicly viewable.</p> <p>Out of the substitutes I tried, <a href="http://www.wishlistr.com/">Wishlistr</a> was undoubtedly the best, but there were some aspects of it that I didn&rsquo;t like. After using it for a while, I decided to write my own app.</p> <p>As with <a href="http://pig-monkey.com/2010/12/24/gear-tracker/">Gear Tracker</a>, Wishlist is open source. <a href="https://github.com/pigmonkey/django-wishlist">You can get the code on GitHub</a>.</p>Gear Tracker2010-12-24T00:00:00-08:002013-05-17T00:00:00-07:00Pig Monkeytag:pig-monkey.com,2010-12-24:/2010/12/gear-tracker/<p><a href="/gear/">Gear Tracker</a> is an <a href="https://github.com/pigmonkey/django-geartracker">open-source</a> inventory system for wilderness travel gear.</p> <p>When I first <a href="http://pig-monkey.com/2009/06/19/digital-scale/">bought my scale</a>, I started <a href="https://spreadsheets.google.com/pub?key=rkWMdPMTMCFFQjvJwrWaB9A">a spreadsheet</a> containing the weights of various pieces of gear. It seemed like a good idea &ndash; I knew I wanted some sort of database to store my measured weights and other …</p><p><a href="/gear/">Gear Tracker</a> is an <a href="https://github.com/pigmonkey/django-geartracker">open-source</a> inventory system for wilderness travel gear.</p> <p>When I first <a href="http://pig-monkey.com/2009/06/19/digital-scale/">bought my scale</a>, I started <a href="https://spreadsheets.google.com/pub?key=rkWMdPMTMCFFQjvJwrWaB9A">a spreadsheet</a> containing the weights of various pieces of gear. It seemed like a good idea &ndash; I knew I wanted some sort of database to store my measured weights and other notes in &ndash; but I never got around to updating it. Data in a spreadsheet is too static. You can&rsquo;t do much with it. I think that characteristic contributed to my disinterest with the spreadsheet.</p> <p>So for a while now I&rsquo;ve had the idea of writing a web application to track my gear. Over the past week, I finally got around to doing it.</p> <p>Gear Tracker is built on <a href="http://www.djangoproject.com/">Django</a>, a web application framework. (If you&rsquo;re not familiar with Django, and you have anything to do with making websites, it&rsquo;s probably worth your time to <a href="http://www.djangobook.com/">learn a thing or two</a>.)</p> <!--more--> <h2>Gear</h2> <p>Gear Tracker&rsquo;s primary purpose is to track gear.</p> <p><a href="/media/geartracker/images/demo/admin-item.png"><img src="/media/geartracker/images/demo/admin-item_thumb.png" alt="Screenshot of item admin" /></a></p> <p>Each item has a weight and acquisition date associated with it. It can be categorized, tagged, and related to other items. There are fields to input size, a link to the manufacturer&rsquo;s page, a link to a review, and to upload an image. A text area allows the user to store any notes related to the item.</p> <p>Items can be archived. This provides a way to not list gear that the user no longer owns, but to keep it in the database for future reference of its weight or other attributes.</p> <h3>Weights</h3> <p>Weights are always input in grams.</p> <p>The metric system makes the most sense and is the easiest to work with. An item&rsquo;s weight can be displayed in grams or, if the item weighs more than 1,000 grams, kilograms. But because some of us are crippled and still like to see imperial weights, Gear Tracker can also display the item&rsquo;s weight in ounces or pounds.</p> <h2>Gear Lists</h2> <p>Gear Tracker can also generate gear lists.</p> <p>One of the things that has prevented me from doing many gear lists in the past is that they&rsquo;re a pain in the rear to create. It takes a while to manually write out every item of gear that I take on a trip. If I want to add the weight of each item &ndash; well, that&rsquo;s asking too much! It&rsquo;s not worth it.</p> <p><a href="/media/geartracker/images/demo/admin-gear_list.png"><img src="/media/geartracker/images/demo/admin-gear_list_thumb.png" alt="Screenshot of gear list admin" /></a></p> <p>Now, making gear lists is easy. Gear Tracker already has detailed knowledge about each piece of gear. All it takes to create a gear list is to select the item, specify how many of that item I took, and whether the item was packed or carried. The result is an organized, detailed gear list for every trip. Total weights are calculated, of course, and can be output in either metric or imperial units.</p> <h3>Private Gear Lists</h3> <p>Gear lists can be made private.</p> <p>I generally create gear lists when I&rsquo;m packing before a trip. But I don&rsquo;t like to publish the lists until I actually return from the trip and also have a report and photos for people to peruse. So, Gear Tracker allows a gear list to be marked as private.</p> <h2>Download It, Hack It, Use It</h2> <p>I&rsquo;m running Gear Tracker at <a href="/gear/">/gear</a>, but if you want to grab your own copy and run it yourself, you can! I&rsquo;ve open-sourced the code under a BSD-license. You can <a href="https://github.com/pigmonkey/django-geartracker">find it at GitHub</a>.</p>