pig-monkey.com - codehttps://pig-monkey.com/2019-06-17T20:43:45-07:00GOESImage2019-06-17T00:00:00-07:002019-06-17T20:43:45-07:00Pig Monkeytag:pig-monkey.com,2019-06-17:/2019/06/goesimage/<p><a href="https://github.com/pigmonkey/goesimage">GOESImage</a> is a bash script which downloads the latest imagery from the <a href="https://www.star.nesdis.noaa.gov/GOES/index.php">NOAA Geostationary Operational Environment Satellites</a> and sets it as the desktop background via <a href="https://feh.finalrewind.org/">feh</a>. If you don&rsquo;t use feh, it should be easy to plug GOESImage into any desktop background control program.</p> <p><a href="/media/images/goesimage.jpg"><img src="/media/images/goesimage-thumb.jpg" width=800 alt="GOESImage Example"></a></p> <p>I wrote GOESImage after using …</p><p><a href="https://github.com/pigmonkey/goesimage">GOESImage</a> is a bash script which downloads the latest imagery from the <a href="https://www.star.nesdis.noaa.gov/GOES/index.php">NOAA Geostationary Operational Environment Satellites</a> and sets it as the desktop background via <a href="https://feh.finalrewind.org/">feh</a>. If you don&rsquo;t use feh, it should be easy to plug GOESImage into any desktop background control program.</p> <p><a href="/media/images/goesimage.jpg"><img src="/media/images/goesimage-thumb.jpg" width=800 alt="GOESImage Example"></a></p> <p>I wrote GOESImage after using <a href="https://github.com/boramalper/himawaripy/">himawaripy</a> for a few years, which is a program that provides imagery of the Asia-Pacific region from the <a href="https://en.wikipedia.org/wiki/Himawari_8">Himawari 8</a> Japanese weather satellite. I like seeing the Earth, and I&rsquo;ve found that real time imagery of my location is actually useful for identifying the approach of large-scale weather systems. NOAA&rsquo;s nighttime multispectral infrared coloring is pretty neat, too.</p>Password Management with Vim and GnuPG2013-04-04T00:00:00-07:002013-06-30T00:00:00-07:00Pig Monkeytag:pig-monkey.com,2013-04-04:/2013/04/password-management-vim-gnupg/<p>The first password manager I ever used was a simple text file encrypted with <a href="http://www.gnupg.org/">GnuPG</a>. When I needed a password I would decrypt the file, read it in <a href="http://www.vim.org/">Vim</a>, and copy the required entry to the system clipboard. This system didn&rsquo;t last. At the time I wasn&rsquo;t using …</p><p>The first password manager I ever used was a simple text file encrypted with <a href="http://www.gnupg.org/">GnuPG</a>. When I needed a password I would decrypt the file, read it in <a href="http://www.vim.org/">Vim</a>, and copy the required entry to the system clipboard. This system didn&rsquo;t last. At the time I wasn&rsquo;t using GnuPG for much else, and this was in the very beginning of my Vim days, when the program seemed cumbersome and daunting. I shortly moved to other, purpose-built password managers.</p> <p>After some experimentation I landed on <a href="http://www.keepassx.org/">KeePassX</a>, which I used for a number of years. Some time ago I decided that I wanted to move to a command-line solution. KeePassX and a web browser were the only graphical applications that I was using with any regularity. I could see no need for a password manager to have a graphical interface, and the GUI&rsquo;s dependency on a mouse decreased my productivity. After a cursory look at the available choices I landed right back where I started all those years ago: Vim and GnuPG.</p> <p>These days Vim is my most used program outside of a web browser and I use GnuPG daily for handling the majority of my encryption needs. My greater familiarity with both of these tools is one of the reasons I&rsquo;ve been successful with the system this time around. I believe the other reason is my more systematic approach.</p> <h2>Structure</h2> <p>The power of this system comes from its simplicity: passwords are stored in plain text files that have been encrypted with GnuPG. Every platform out there has some implementation of the <a href="https://en.wikipedia.org/wiki/Pretty_Good_Privacy#OpenPGP">PGP protocol</a>, so the files can easily be decrypted anywhere. After they&rsquo;ve been decrypted, there&rsquo;s no fancy file formats to deal with. It&rsquo;s all just text, which can be manipulated with a <a href="https://en.wikipedia.org/wiki/GNU_Core_Utilities">plethora of powerful tools</a>. I favor reading the text in Vim, but any text editor will do the job.</p> <p>All passwords are stored within a directory called <code>~/pw</code>. Within this directory are multiple files. Each of these files can be thought of as a separate password database. I store bank information in <code>financial.gpg</code>. Login information for various shopping websites are in <code>ecommerce.gpg</code>. My email credentials are in <code>email.gpg</code>. All of these entries could very well be stored in a single file, but breaking it out into multiple files allows me some measure of access control.</p> <h3>Access</h3> <p>I regularly use two computers: my laptop at home and a desktop machine at work. I trust my laptop. It has my GnuPG key on it and it should have access to all password database files. I do not place complete trust in my machine at work. I don&rsquo;t trust it enough to give it access to my GnuPG key, and as such I have a different GnuPG key on that machine that I use for encryption at work.</p> <p>Having passwords segregated into multiple database files allows me to encrypt the different files to different keys. Every file is encrypted to my primary GnuPG key, but only some are encrypted with my work key. Login credentials needed for work are encrypted to the work key. I have no need to login to my bank accounts at work, and it wouldn&rsquo;t be prudent to do so on a machine that I do not fully trust, so the <code>financial.gpg</code> file is not encrypted to my work key. If someone compromises my work computer, they still will be no closer to accessing my banking credentials.</p> <h3>Git</h3> <p>The <code>~/pw</code> directory is a <a href="http://git-scm.com/">git</a> repository. This gives me version control on all of my passwords. If I accidentally delete an entry I can always get it back. It also provides syncing and redundant storage without depending on a third-party like Dropbox.</p> <h3>Keys</h3> <p>An advantage of using a directory full of encrypted files as my password manager is that I&rsquo;m not limited to only storing usernames and passwords. Any file can be added to the repository. I keep keys for backups, SSH keys, and SSL keys (all of which have been encrypted with my GnuPG key) in the directory. This gives me one location for all of my authentication credentials, which simplifies the locating and backing up of these important files.</p> <h2>Markup</h2> <p>Each file is structured with <a href="http://vimdoc.sourceforge.net/htmldoc/fold.html">Vim folds</a> and indentation. There are various ways for Vim to fold text. I use markers, sticking with the default <code>{{{</code>/<code>}}}</code> characters. A typical password entry will look 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></pre></div></td><td class="code"><div><pre><span></span><code>Amazon{{{ user: foo@bar.com pass: supers3cr3t url: https://amazon.com }}} </code></pre></div></td></tr></table></div> <p>Each file is full of entries like this. Certain entries are grouped together within other folds for organization. Certain entries may have comments so that I have a record of the false personally identifiable information the service requested when I registered.</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></pre></div></td><td class="code"><div><pre><span></span><code>Super Ecommerce{{{ user: foobar pass: g0d Comments{{{ birthday: 1/1/1911 first car: delorean }}} }}} </code></pre></div></td></tr></table></div> <p>Following a consistent structure like this makes the file easier to navigate and allows for the possibility of the file being parsed by a script. The fold markers come into play with my Vim configuration.</p> <h2>Vim</h2> <p>I use Vim with the <a href="https://github.com/jamessan/vim-gnupg">vim-gnupg</a> plugin. This makes editing of encrypted files seamless. When opening existing files, the contents are decrypted. When opening new files, the plugin asks which recipients the file should be encrypted to. When a file is open, leaking the clear text is avoided by disabling <a href="http://vimdoc.sourceforge.net/htmldoc/starting.html#viminfo">viminfo</a>, <a href="http://vimdoc.sourceforge.net/htmldoc/options.html#%27swapfile%27">swapfile</a>, and <a href="http://vimdoc.sourceforge.net/htmldoc/options.html#%27undofile%27">undofile</a>. I run <code>gpg-agent</code> so that my passphrase is remembered for a short period of time after I use it. This makes it easy and secure to work with (and create) the encrypted files with Vim. I define a few extra options in my <a href="https://github.com/pigmonkey/dotfiles/blob/master/vimrc">vimrc</a> to facilitate working with passwords.</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> <span class="normal">23</span> <span class="normal">24</span> <span class="normal">25</span> <span class="normal">26</span> <span class="normal">27</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="c">&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;</span> <span class="c">&quot; GnuPG Extensions &quot;</span> <span class="c">&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;&quot;</span> <span class="c">&quot; Tell the GnuPG plugin to armor new files.</span> <span class="k">let</span> <span class="k">g</span>:GPGPreferArmor<span class="p">=</span><span class="m">1</span> <span class="c">&quot; Tell the GnuPG plugin to sign new files.</span> <span class="k">let</span> <span class="k">g</span>:GPGPreferSign<span class="p">=</span><span class="m">1</span> augroup GnuPGExtra <span class="c">&quot; Set extra file options.</span> autocmd <span class="nb">BufReadCmd</span><span class="p">,</span><span class="nb">FileReadCmd</span> *.\<span class="p">(</span>gpg\<span class="p">|</span><span class="k">asc</span>\<span class="p">|</span>pgp\<span class="p">)</span> <span class="k">call</span> SetGPGOptions<span class="p">()</span> <span class="c">&quot; Automatically close unmodified files after inactivity.</span> autocmd <span class="nb">CursorHold</span> *.\<span class="p">(</span>gpg\<span class="p">|</span><span class="k">asc</span>\<span class="p">|</span>pgp\<span class="p">)</span> quit augroup END <span class="k">function</span> SetGPGOptions<span class="p">()</span> <span class="c">&quot; Set updatetime to 1 minute.</span> <span class="k">set</span> <span class="nb">updatetime</span><span class="p">=</span><span class="m">60000</span> <span class="c">&quot; Fold at markers.</span> <span class="k">set</span> <span class="nb">foldmethod</span><span class="p">=</span>marker <span class="c">&quot; Automatically close all folds.</span> <span class="k">set</span> <span class="k">foldclose</span><span class="p">=</span><span class="k">all</span> <span class="c">&quot; Only open folds with insert commands.</span> <span class="k">set</span> <span class="k">foldopen</span><span class="p">=</span>insert <span class="k">endfunction</span> </code></pre></div></td></tr></table></div> <p>The first two options simply tell vim-gnupg to always ASCII-armor and sign new files. These have nothing particular to do with password management, but are good practices for all encrypted files.</p> <p>The first <code>autocmd</code> calls a function which holds the options that I wanted applied to my password files. I have these options apply to all encrypted files, although they&rsquo;re intended primarily for use when Vim is acting as my password manager.</p> <h3>Folding</h3> <p>The primary shortcoming with using an encrypted text file as a password database is the lack of protection against shoulder-surfing. After the file has been decrypted and opened, anyone standing behind you can look over your shoulder and view all the entries. This is solved with <a href="http://vim.wikia.com/wiki/Folding">folds</a> and is what most of these extra options address.</p> <p>I set <a href="http://vimdoc.sourceforge.net/htmldoc/options.html#%27foldmethod%27">foldmethod</a> to <code>marker</code> so that Vim knows to look for all the <code>{{{</code>/<code>}}}</code> characters and use them to build the folds. Then I set <a href="http://vimdoc.sourceforge.net/htmldoc/options.html#%27foldclose%27">foldclose</a> to <code>all</code>. This closes all folds unless the cursor is in them. This way only one fold can be open at a time &ndash; or, to put it another way, only one password entry is ever visible at once.</p> <p>The final fold option instructs Vim when it is allowed to open folds. Folds can always be opened manually, but by default Vim will also open them for many other cases: if you navigate to a fold, jump to a mark within a fold or search for a pattern within a fold, they will open. By setting <a href="http://vimdoc.sourceforge.net/htmldoc/options.html#%27foldopen%27">foldopen</a> to <code>insert</code> I instruct Vim that the only time it should automatically open a fold is if my cursor is in a fold and I change to insert mode. The effect of this is that when I open a file, all folds are closed by default. I can navigate through the file, search and jump through matches, all without opening any of the folds and inadvertently exposing the passwords on my screen. The fold will open if I change to insert mode within it, but it is difficult to do that by mistake.</p> <p>I have my <a href="https://github.com/pigmonkey/dotfiles/blob/master/vimrc#L116">spacebar setup to toggle folds</a> within Vim. After I have navigated to the desired entry, I can simply whack the spacebar to open it and copy the credential that I need to the system clipboard. At that point I can whack the spacebar again to close the fold, or I can quit Vim. Or I can simply wait.</p> <h3>Locking</h3> <p>The other special option I set is <a href="http://vimdoc.sourceforge.net/htmldoc/options.html#%27updatetime%27">updatetime</a>. Vim uses this option to determine when it should write swap files for crash recovery. Since vim-gnupg disables swap files for decrypted files, this has no effect. I use it for something else.</p> <p>In the second <code>autocmd</code> I tell Vim to close itself on <a href="http://vimdoc.sourceforge.net/htmldoc/autocmd.html#CursorHold">CursorHold</a>. <code>CursorHold</code> is triggered whenever no key has been pressed for the time specified by <code>updatetime</code>. So the effect of this is that my password files are automatically closed after 1 minute of inactivity. This is similar to KeePassX&rsquo;s behaviour of &ldquo;locking the workspace&rdquo; after a set period of inactivity.</p> <h3>Clipboard</h3> <p>To easily copy a credential to the system clipboard from Vim I have two <a href="https://github.com/pigmonkey/dotfiles/blob/master/vimrc#L175">shortcuts</a> mapped.</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>&quot; Yank WORD to system clipboard in normal mode nmap &lt;leader&gt;y &quot;+yE &quot; Yank selection to system clipboard in visual mode vmap &lt;leader&gt;y &quot;+y </code></pre></div></td></tr></table></div> <p>Vim can access the system clipboard using both the <code>*</code> and <code>+</code> registers. I opt to use <code>+</code> because <a href="http://vimdoc.sourceforge.net/htmldoc/gui_x11.html#x11-selection">X treats it as a selection rather than a cut-buffer</a>. As the Vim documentation explains:</p> <blockquote> <p>Selections are &ldquo;owned&rdquo; by an application, and disappear when that application (e.g., Vim) exits, thus losing the data, whereas cut-buffers, are stored within the X-server itself and remain until written over or the X-server exits (e.g., upon logging out).</p> </blockquote> <p>The result is that I can copy a username or password by placing the cursor on its first character and hitting <code>&lt;leader&gt;y</code>. I can paste the credential wherever it is needed. After I close Vim, or after Vim closes itself after 1 minute of inactivity, the credential is removed from the clipboard. This replicates KeePassX&rsquo;s behaviour of clearing the clipboard so many seconds after a username or password has been copied.</p> <h2>Generation</h2> <p>Passwords should be long and unique. To satisfy this any password manager needs some sort of password generator. Vim provides this with its ability to <a href="http://vim.wikia.com/wiki/Append_output_of_an_external_command.">call and read external commands</a> I can tell Vim to call the standard-issue <a href="http://linux.die.net/man/1/pwgen">pwgen</a> program to generate a secure 24-character password utilizing special characters and insert the output at the cursor, like this:</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="p">:</span><span class="k">r</span><span class="p">!</span>pwgen <span class="p">-</span><span class="k">sy</span> <span class="m">24</span> <span class="m">1</span> </code></pre></div></td></tr></table></div> <h2>Backups</h2> <p>The <code>~/pw</code> directory is backed up in the same way as most other things on my hard drive: to <a href="http://www.tarsnap.com/">Tarsnap</a> via <a href="/2012/09/tarsnapper-managing-tarsnap-backups/">Tarsnapper</a>, to an external drive via <a href="http://www.rsnapshot.org/">rsnapshot</a> and <a href="/2012/09/cryptshot-automated-encrypted-backups-rsnapshot/">cryptshot</a>, <a href="https://wiki.archlinux.org/index.php/Full_System_Backup_with_rsync">rsync to a mirror drive</a>. The issue with these standard backups is that they&rsquo;re all encrypted and the keys to decrypt them are stored in the password manager. If I loose <code>~/pw</code> I&rsquo;ll have plenty of backups around, but none that I can actually access. I address this problem with regular backups to optical media.</p> <p>At the beginning of every month I burn the password directory to two CDs. One copy is stored at home and the other at an off-site location. I began these optical media backups in December, so I currently have two sets consisting of five discs each. Any one of these discs will provide me with the keys I need to access a backup made with one of the more frequent methods.</p> <p>Of course, all the files being burned to these discs are still encrypted with my GnuPG key. If I loose that key or passphrase I will have no way to decrypt any of these files. Protecting one&rsquo;s GnuPG key is another problem entirely. I&rsquo;ve taken steps that make me feel confident in my ability to always be able to recover a copy of my key, but none that I&rsquo;m comfortable discussing publicly.</p> <h2>Shell</h2> <p>I&rsquo;ve defined a <a href="https://github.com/pigmonkey/dotfiles/blob/master/shellrc#L70">shell function</a>, <code>pw()</code>, that operates exactly like the function I use for <a href="/2012/12/notes-unix/">notes on Unix</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> <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></pre></div></td><td class="code"><div><pre><span></span><code><span class="c1"># Set the password database directory.</span> <span class="nv">PASSDIR</span><span class="o">=</span>~/pw <span class="c1"># Create or edit password databases.</span> pw<span class="o">()</span> <span class="o">{</span> <span class="nb">cd</span> <span class="s2">&quot;</span><span class="nv">$PASSDIR</span><span class="s2">&quot;</span> <span class="k">if</span> <span class="o">[</span> ! -z <span class="s2">&quot;</span><span class="nv">$1</span><span class="s2">&quot;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span> <span class="nv">$EDITOR</span> <span class="k">$(</span>buildfile <span class="s2">&quot;</span><span class="nv">$1</span><span class="s2">&quot;</span><span class="k">)</span> <span class="nb">cd</span> <span class="s2">&quot;</span><span class="nv">$OLDPWD</span><span class="s2">&quot;</span> <span class="k">fi</span> <span class="o">}</span> </code></pre></div></td></tr></table></div> <p>This allows me to easily open any password file from wherever I am in the filesystem without specifying the full path. These two commands are equivalent, but the one utilizing <code>pw()</code> requires fewer keystrokes:</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>$ vim ~/pw/financial.gpg $ pw financial </code></pre></div></td></tr></table></div> <p>The function changes to the password directory before opening the file so that while I&rsquo;m in Vim I can drop down to a shell with <code>:sh</code> and already be in the proper directory to manipulate the files. After I close Vim the function returns me to the previous working directory.</p> <p>This still required a few more keystrokes than I like, so I configured my shell to <a href="https://github.com/pigmonkey/dotfiles/blob/master/zshrc#L44">perform autocompletion in the directory</a>. If <code>financial.gpg</code> is the only file in the directory beginning with an &ldquo;f&rdquo;, typing <code>pw f&lt;tab&gt;</code> is all that is required to open the file.</p> <h2>Simplicity</h2> <p>This setup provides <a href="https://wiki.archlinux.org/index.php/The_Arch_Way#Simplicity">simplicity</a>, power, and portability. It uses the same tools that I already employ in my daily life, and does not require the use of the mouse or any graphical windows. I&rsquo;ve been happily utilizing it for about 6 months now.</p> <p>Initially I had thought I would supplement the setup with a script that would search the databases for a desired entry, using some combination of <code>grep</code>, <code>awk</code> and <code>cut</code>, and then copy it to my clipboard via <code>xsel</code>. As it turns out, I haven&rsquo;t felt the desire to do this. Simply opening the file in Vim, searching for the desired entry, opening the fold and copying the credential to the system clipboard is quick enough. The whole process, absent of typing in my passphrase, takes me only a couple of seconds.</p> <h2>Resources</h2> <p>I&rsquo;m certainly not the first to come up with the idea of managing password with Vim. These resources were particularly useful to me when I was researching the possibilities:</p> <ul> <li><a href="http://connermcd.com/blog/2012/05/01/file-encryption-and-password-management/">File encryption and password management</a> by Conner McDaniel</li> <li><a href="http://vim.wikia.com/wiki/Keep_passwords_in_encrypted_file">Keep passwords in encrypted file</a> on the Vim Wiki</li> <li><a href="http://www.noah.org/wiki/Password_Safe_with_Vim_and_OpenSSL">Password Safe with Vim and OpenSSL</a> by Noah</li> </ul> <p>If you&rsquo;re interesting in other ideas for password management, <a href="http://zx2c4.com/projects/password-store/">password-store</a> and <a href="http://raymontag.github.com/keepassc/">KeePassC</a> are both neat projects that I follow.</p> <div class="notice"> <p>2013 June 30: <a href="http://blog.oddbit.com/">larsks</a> has hacked together a <a href="https://gist.github.com/larsks/5868076">Python script</a> to convert KeepassX XML exports to the plain-text markup format that I use.</p> </div>Notes on Unix2012-12-22T00:00:00-08:002012-12-22T00:00:00-08:00Pig Monkeytag:pig-monkey.com,2012-12-22:/2012/12/notes-unix/<p>As a long-time user of <a href="https://en.wikipedia.org/wiki/Unix-like">Unix-like</a> systems, I prefer to do as much work in the command-line as possible. I store data in plain text whenever appropriate. I edit in <a href="http://www.vim.org/">vim</a> and take advantage of the <a href="https://en.wikipedia.org/wiki/Pipeline_(Unix)">pipeline</a> to manipulate the data with powerful tools like <a href="https://en.wikipedia.org/wiki/AWK">awk</a> and <a href="https://en.wikipedia.org/wiki/Grep">grep</a>.</p> <p>Notes are …</p><p>As a long-time user of <a href="https://en.wikipedia.org/wiki/Unix-like">Unix-like</a> systems, I prefer to do as much work in the command-line as possible. I store data in plain text whenever appropriate. I edit in <a href="http://www.vim.org/">vim</a> and take advantage of the <a href="https://en.wikipedia.org/wiki/Pipeline_(Unix)">pipeline</a> to manipulate the data with powerful tools like <a href="https://en.wikipedia.org/wiki/AWK">awk</a> and <a href="https://en.wikipedia.org/wiki/Grep">grep</a>.</p> <p>Notes are one such instance where plain text makes sense. All of my notes &ndash; which are scratch-pads for ideas, reference material, logs, and <a href="http://harrypotter.wikia.com/wiki/Pensieve">whatnot</a> &ndash; are kept as individual text files in the directory <code>~/documents/notes</code>. The entire <code>~/documents</code> directory is synced between my laptop and my work computer, and of course it is backed up with <a href="/2012/09/tarsnapper-managing-tarsnap-backups/">tarsnap</a>.</p> <p>When I want to read, edit or create a note, my habit is simply to open the file in vim.</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>$ vim ~/documents/notes/todo.txt </code></pre></div></td></tr></table></div> <p>When I want to view a list of my notes, I can just <code>ls</code> the directory. I pass along the <code>-t</code> and <code>-r</code> flags. The first flag sorts the files by modification date, newest first. The second flag reverses the order. The result is that the most recently modified files end up at the bottom of the list, nearest the prompt. This allows me to quickly see which notes I have recently created or changed. These notes are generally active &ndash; they&rsquo;re the ones I&rsquo;m currently doing something with, so they&rsquo;re the ones I want to see. Using <code>ls</code> to see which files have been most recently modified is <a href="http://sef.kloninger.com/2012/08/wip-folders-with-ls/">incredibly useful</a>, and a behaviour that I use often enough to have created <a href="https://github.com/pigmonkey/dotfiles/blob/master/aliases#L11">an alias</a> for 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>$ lt ~/documents/notes </code></pre></div></td></tr></table></div> <p>Recently I was inspired by some <a href="http://onethingwell.org/post/457674798/a-poor-mans-notational-velocity">extremely simple shell functions</a> on the <a href="http://onethingwell.org/">One Thing Well</a> blog to make working with my notes even easier.</p> <p>The first function, <code>n()</code>, takes the name of the note as an argument &ndash; minus the file extension &ndash; and opens 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></pre></div></td><td class="code"><div><pre><span></span><code><span class="k">function</span> n <span class="o">{</span> nano ~/Dropbox/Notes/<span class="nv">$1</span>.txt <span class="o">}</span> </code></pre></div></td></tr></table></div> <p>I liked the idea. It would allow me to open a note from anywhere in the filesystem without specifying the full path. After changing the editor and the path, I could open the same note as before with far fewer keystrokes.</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>$ n todo </code></pre></div></td></tr></table></div> <p>I needed to make a few changes to the function to increase its flexibility.</p> <p>First, the extension. Most of my notes have <code>.txt</code> extensions. Some have a <code>.gpg</code> extension.<sup class="footnote-ref" id="fnref:crypt"><a rel="footnote" href="#fn:crypt" title="see footnote">1</a></sup> Some have no extension. I didn&rsquo;t want to force the <code>.txt</code> extension in the function.</p> <p>If I specified a file extension, that extension should be used. If I failed to specify the extension, I wanted the function to open the file as I specified it only if it existed. Otherwise, I wanted it to look for that file with a <code>.gpg</code> extension and open that if it was found. As a last resort, I wanted it to open the file with a <code>.txt</code> extension regardless of whether it existed or not. I implemented this behaviour in a separate function, <code>buildfile()</code>, so that I could take advantage of it wherever I wanted.</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></pre></div></td><td class="code"><div><pre><span></span><code><span class="c1"># Take a text file and build it with an extension, if needed.</span> <span class="c1"># Prefer gpg extension over txt.</span> buildfile<span class="o">()</span> <span class="o">{</span> <span class="c1"># If an extension was given, use it.</span> <span class="k">if</span> <span class="o">[[</span> <span class="s2">&quot;</span><span class="nv">$1</span><span class="s2">&quot;</span> <span class="o">==</span> *.* <span class="o">]]</span><span class="p">;</span> <span class="k">then</span> <span class="nb">echo</span> <span class="s2">&quot;</span><span class="nv">$1</span><span class="s2">&quot;</span> <span class="c1"># If no extension was given...</span> <span class="k">else</span> <span class="c1"># ... try the file without any extension.</span> <span class="k">if</span> <span class="o">[</span> -e <span class="s2">&quot;</span><span class="nv">$1</span><span class="s2">&quot;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span> <span class="nb">echo</span> <span class="s2">&quot;</span><span class="nv">$1</span><span class="s2">&quot;</span> <span class="c1"># ... try the file with a gpg extension.</span> <span class="k">elif</span> <span class="o">[</span> -e <span class="s2">&quot;</span><span class="nv">$1</span><span class="s2">&quot;</span>.gpg <span class="o">]</span><span class="p">;</span> <span class="k">then</span> <span class="nb">echo</span> <span class="s2">&quot;</span><span class="nv">$1</span><span class="s2">&quot;</span>.gpg <span class="c1"># ... use a txt extension.</span> <span class="k">else</span> <span class="nb">echo</span> <span class="s2">&quot;</span><span class="nv">$1</span><span class="s2">&quot;</span>.txt <span class="k">fi</span> <span class="k">fi</span> <span class="o">}</span> </code></pre></div></td></tr></table></div> <p>I then rewrote the original note function to take advantage of 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> <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></pre></div></td><td class="code"><div><pre><span></span><code><span class="c1"># Set the note directory.</span> <span class="nv">NOTEDIR</span><span class="o">=</span>~/documents/notes <span class="c1"># Create or edit notes.</span> n<span class="o">()</span> <span class="o">{</span> <span class="c1"># If no note was given, list the notes.</span> <span class="k">if</span> <span class="o">[</span> -z <span class="s2">&quot;</span><span class="nv">$1</span><span class="s2">&quot;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span> lt <span class="s2">&quot;</span><span class="nv">$NOTEDIR</span><span class="s2">&quot;</span> <span class="c1"># If a note was given, open it.</span> <span class="k">else</span> <span class="nv">$EDITOR</span> <span class="k">$(</span>buildfile <span class="s2">&quot;</span><span class="nv">$NOTEDIR</span><span class="s2">&quot;</span>/<span class="s2">&quot;</span><span class="nv">$1</span><span class="s2">&quot;</span><span class="k">)</span> <span class="k">fi</span> <span class="o">}</span> </code></pre></div></td></tr></table></div> <p>Now I can edit the note <code>~/documents/notes/todo.txt</code> by simply passing along the filename without an extension.</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>$ n todo </code></pre></div></td></tr></table></div> <p>If I want to specify the extension, that will work too.</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>$ n todo.txt </code></pre></div></td></tr></table></div> <p>If I have a note called <code>~/documents/notes/world-domination.gpg</code>, I can edit that without specifying the extension.</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>$ n world-domination </code></pre></div></td></tr></table></div> <p>If I have a note called <code>~/documents/notes/readme</code>, I can edit that and the function will respect the lack of an extension.</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>$ n readme </code></pre></div></td></tr></table></div> <p>Finally, if I want to create a note, I can just specify the name of the note and a file with that name will be created with a <code>.txt</code> extension.</p> <p>The other change I made was to make the function print a reverse-chronologically sorted list of my notes if it was called with no arguments. This allows me to view my notes by typing a single character.</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>$ n </code></pre></div></td></tr></table></div> <p>The original blog post also included a function <code>ns()</code> for searching notes by their title.</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="k">function</span> ns <span class="o">{</span> ls -c ~/Dropbox/Notes <span class="p">|</span> grep <span class="nv">$1</span> <span class="o">}</span> </code></pre></div></td></tr></table></div> <p>I thought this was a good idea, but I considered the behaviour to be <em>finding</em> a note rather than <em>searching</em> a note. I renamed the function to reflect its behaviour, took advantage of my <code>ls</code> alias, and made the search case-insensitive. I also modified it so that if no argument was given, it would simply print an ordered list of the notes, just like <code>n()</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> <span class="normal">5</span> <span class="normal">6</span> <span class="normal">7</span> <span class="normal">8</span></pre></div></td><td class="code"><div><pre><span></span><code><span class="c1"># Find a note by title.</span> nf<span class="o">()</span> <span class="o">{</span> <span class="k">if</span> <span class="o">[</span> -z <span class="s2">&quot;</span><span class="nv">$1</span><span class="s2">&quot;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span> lt <span class="s2">&quot;</span><span class="nv">$NOTEDIR</span><span class="s2">&quot;</span> <span class="k">else</span> lt <span class="s2">&quot;</span><span class="nv">$NOTEDIR</span><span class="s2">&quot;</span> <span class="p">|</span> grep -i <span class="nv">$1</span> <span class="k">fi</span> <span class="o">}</span> </code></pre></div></td></tr></table></div> <p>I thought it would be nice to also have a quick way to search within notes. That was accomplished with <code>ns()</code>, the simplest function of the trio.</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><span class="c1"># Search within notes.</span> ns<span class="o">()</span> <span class="o">{</span> <span class="nb">cd</span> <span class="nv">$NOTEDIR</span><span class="p">;</span> grep -rin <span class="nv">$1</span><span class="p">;</span> <span class="nb">cd</span> <span class="s2">&quot;</span><span class="nv">$OLDPWD</span><span class="s2">&quot;</span><span class="p">;</span> <span class="o">}</span> </code></pre></div></td></tr></table></div> <p>I chose to change into the note directory before searching so that the results do not have the full path prefixed to the filename. Thanks to the first function I don&rsquo;t need to specify the full path of the note to open it, and this makes the output easier to read.</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>$ ns <span class="s2">&quot;take over&quot;</span> world-domination.txt:1:This is my plan to take over the world. $ n world-domination </code></pre></div></td></tr></table></div> <p>To finish it up, I added completion to <a href="https://en.wikipedia.org/wiki/Z_shell">zsh</a> so that I can tab-complete filenames when using <code>n</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></pre></div></td><td class="code"><div><pre><span></span><code><span class="c1"># Set autocompletion for notes.</span> compctl -W <span class="nv">$NOTEDIR</span> -f n </code></pre></div></td></tr></table></div> <p>If you&rsquo;re interested in seeing some of the other way that I personalize my working environment, all of my <a href="https://github.com/pigmonkey/dotfiles">dotfiles are on GitHub</a>. The note functions are in <a href="https://github.com/pigmonkey/dotfiles/blob/master/shellrc">shellrc</a>, which is my shell-agnostic configuration file that I source from both <a href="https://github.com/pigmonkey/dotfiles/blob/master/zshrc">zshrc</a> and <a href="https://github.com/pigmonkey/dotfiles/blob/master/bashrc">bashrc</a><sup class="footnote-ref" id="fnref:shell"><a rel="footnote" href="#fn:shell" title="see footnote">2</a></sup>.</p> <div id="footnotes"> <h2>Notes</h2> <ol> <li id="fn:crypt"><a rev="footnote" href="#fnref:crypt" class="footnote-return" title="return to article">&crarr;</a> I use vim with the <a href="http://www.vim.org/scripts/script.php?script_id=3645">gnupg.vim</a> plugin for seamless editing of PGP-encrypted files.</li> <li id="fn:shell"><a rev="footnote" href="#fnref:shell" class="footnote-return" title="return to article">&crarr;</a> I prefer zsh, but I do still find myself in <a href="https://en.wikipedia.org/wiki/Bash_(Unix_shell)">bash</a> on some machines. I find it prudent to maintain configurations for both shells. There's a lot of cross-over between them and I like to stick to the <a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY</a> principle.</li> </ol> </div>Back It Up: A Solution for Laptop Backups2012-10-03T00:00:00-07:002012-12-22T00:00:00-08:00Pig Monkeytag:pig-monkey.com,2012-10-03:/2012/10/back-it-up/<p>A laptop presents some problems for reliably backing up data. Unlike a server, the laptop may not always be turned on. When it is on, it may not be connected to the backup medium. If you&rsquo;re doing online backups, the laptop may be offline. If you&rsquo;re backing up …</p><p>A laptop presents some problems for reliably backing up data. Unlike a server, the laptop may not always be turned on. When it is on, it may not be connected to the backup medium. If you&rsquo;re doing online backups, the laptop may be offline. If you&rsquo;re backing up to an external drive, the drive may not be plugged in. To address these issues I wrote a shell script called <a href="https://github.com/pigmonkey/backitup">backitup.sh</a>.</p> <h2>The Problem</h2> <p>Let&rsquo;s say you want to backup a laptop to an external USB drive once per day with <a href="/2012/09/cryptshot-automated-encrypted-backups-rsnapshot/">cryptshot</a>.</p> <p>You could add a cron entry to call <code>cryptshot.sh</code> at a certain time every day. What if the laptop isn&rsquo;t turned on? What if the drive isn&rsquo;t connected? In either case the backup will not be completed. The machine will then wait a full 24 hours before even attempting the backup again. This could easily result in weeks passing without a successful backup.</p> <p>If you&rsquo;re using <a href="http://en.wikipedia.org/wiki/Anacron">anacron</a>, or one of its derivatives, things get slightly better. Instead of specifying a time to call <code>cryptshot.sh</code>, you set the cron interval to <code>@daily</code>. If the machine is turned off at whatever time anacron is setup to execute <code>@daily</code> scripts, all of the commands will simply be executed the next time the machine boots. But that still doesn&rsquo;t solve the problem of the drive not being plugged in.</p> <h2>The Solution</h2> <p><code>backitup.sh</code> attempts to perform a backup if a certain amount of time has passed. It monitors for a report of successful completion of the backup. Once configured, you no longer call the backup program directly. Instead, you call <code>backitup.sh</code>. It then decides whether or not to actually execute the backup.</p> <h3>How it works</h3> <p>The script is configured with the backup program that should be executed, the period for which you want to complete backups, and the location of a file that holds the timestamp of the last successful backup. It can be configured either by modifying the variables at the top of the script, or by passing in command-line arguments.</p> <div class="highlight"><pre><span></span><code>$ backitup.sh -h Usage: backitup.sh <span class="o">[</span>OPTION...<span class="o">]</span> Note that any <span class="nb">command</span> line arguments overwrite variables defined <span class="k">in</span> the source. Options: -p the period <span class="k">for</span> which backups should attempt to be executed <span class="o">(</span>integer seconds or <span class="s1">&#39;DAILY&#39;</span>, <span class="s1">&#39;WEEKLY&#39;</span> or <span class="s1">&#39;MONTHLY&#39;</span><span class="o">)</span> -b the backup <span class="nb">command</span> to execute<span class="p">;</span> note that this should be quoted <span class="k">if</span> it contains a space -l the location of the file that holds the timestamp of the last successful backup. -n the <span class="nb">command</span> to be executed <span class="k">if</span> the above file does not exist </code></pre></div> <p>When the script executes, it reads the timestamp contained in the last-run file. This is then compared to the user-specified period. If the difference between the timestamp and the current time is greater than the period, <code>backitup.sh</code> calls the backup program. If the difference between the stored timestamp and the current time is less than the requested period, the script simply exits without running the backup program.</p> <p>After the backup program completes, the script looks at the returned <a href="https://en.wikipedia.org/wiki/Exit_status">exit code</a>. If the exit code is 0, the backup was completed successfully, and the timestamp in the last-run file is replaced with the current time. If the backup program returns a non-zero exit code, no changes are made to the last-run file. In this case, the result is that the next time <code>backitup.sh</code> is called it will once again attempt to execute the backup program.</p> <p>The period can either be specified in seconds or with the strings <code>DAILY</code>, <code>WEEKLY</code> or <code>MONTHLY</code>. The behaviour of <code>DAILY</code> differs from <code>86400</code> (24-hours in seconds). With the latter configuration, the backup program will only attempt to execute once per 24-hour period. If <code>DAILY</code> is specified, the backup may be completed successfully at, for example, 23:30 one day and again at 00:15 the following day.</p> <h2>Use</h2> <p>You still want to backup a laptop to an external USB drive once per day with cryptshot. Rather than calling <code>cryptshot.sh</code>, you call <code>backitup.sh</code>.</p> <p>Tell the script that you wish to complete daily backups, and then use cron to call the script more frequently than the desired backup period. For my local backups, I call <code>backitup.sh</code> every hour.</p> <div class="highlight"><pre><span></span><code><span class="nv">@hourly</span><span class="w"> </span><span class="n">backitup</span><span class="p">.</span><span class="n">sh</span><span class="w"> </span><span class="o">-</span><span class="n">l</span><span class="w"> </span><span class="o">~/</span><span class="p">.</span><span class="n">cryptshot</span><span class="o">-</span><span class="n">daily</span><span class="w"> </span><span class="o">-</span><span class="n">b</span><span class="w"> </span><span class="ss">&quot;cryptshot.sh daily&quot;</span><span class="w"></span> </code></pre></div> <p>The default period of <code>backitup.sh</code> is <code>DAILY</code>, so in this case I don&rsquo;t have to provide a period of my own. But I also do weekly and monthly backups, so I need two more entries to execute cryptshot with those periods.</p> <div class="highlight"><pre><span></span><code><span class="nv">@hourly</span><span class="w"> </span><span class="n">backitup</span><span class="p">.</span><span class="n">sh</span><span class="w"> </span><span class="o">-</span><span class="n">l</span><span class="w"> </span><span class="o">~/</span><span class="p">.</span><span class="n">cryptshot</span><span class="o">-</span><span class="n">monthly</span><span class="w"> </span><span class="o">-</span><span class="n">b</span><span class="w"> </span><span class="ss">&quot;cryptshot.sh monthly&quot;</span><span class="w"> </span><span class="o">-</span><span class="n">p</span><span class="w"> </span><span class="n">MONTHLY</span><span class="w"></span> <span class="nv">@hourly</span><span class="w"> </span><span class="n">backitup</span><span class="p">.</span><span class="n">sh</span><span class="w"> </span><span class="o">-</span><span class="n">l</span><span class="w"> </span><span class="o">~/</span><span class="p">.</span><span class="n">cryptshot</span><span class="o">-</span><span class="n">weekly</span><span class="w"> </span><span class="o">-</span><span class="n">b</span><span class="w"> </span><span class="ss">&quot;cryptshot.sh weekly&quot;</span><span class="w"> </span><span class="o">-</span><span class="n">p</span><span class="w"> </span><span class="n">WEEKLY</span><span class="w"></span> </code></pre></div> <p>All three of these entries are executed hourly, which means that at the top of every hour, my laptop attempts to back itself up. As long as the USB drive is plugged in during one of those hours, the backup will complete. If cryptshot is executed, but fails, another attempt will be made the next hour. Daily backups will only be successfully completed, at most, once per day; weekly backups, once per week; and monthly backups, once per month. This setup works well for me, but if you want a higher assurance that your daily backups will be completed every day you could change the cron interval to <code>*/5 * * * *</code>, which will result in cron executing <code>backitup.sh</code> every 5 minutes.</p> <p>What if you want to perform daily online backups with <a href="/2012/09/tarsnapper-managing-tarsnap-backups/">Tarsnapper</a>?</p> <div class="highlight"><pre><span></span><code><span class="nv">@hourly</span><span class="w"> </span><span class="n">backitup</span><span class="p">.</span><span class="n">sh</span><span class="w"> </span><span class="o">-</span><span class="n">l</span><span class="w"> </span><span class="o">~/</span><span class="p">.</span><span class="n">tarsnapper</span><span class="o">-</span><span class="n">lastrun</span><span class="w"> </span><span class="o">-</span><span class="n">b</span><span class="w"> </span><span class="n">tarsnapper</span><span class="p">.</span><span class="n">py</span><span class="w"></span> </code></pre></div> <p>At the top of every hour your laptop will attempt to run <a href="http://www.tarsnap.com/">Tarsnap</a> via Tarsnapper. If the laptop is offline, it will try again the following hour. If Tarsnap begins but you go offline before it can complete, the backup will be resumed the following hour.</p> <p>The script can of course be called with something other than cron. Put it in your <code>~/.profile</code> and have you backups attempt to execute every time you login. Add it to your network manager and have your online backups attempt to execute every time you get online. If you&rsquo;re using something like <a href="https://en.wikipedia.org/wiki/Udev">udev</a>, have your local backups attempt to execute every time your USB drive is plugged in.</p> <h2>The Special Case</h2> <p>The final configuration option of <code>backitup.sh</code> represents a special case. If the script runs and it can&rsquo;t find the specified file, the default behaviour is to assume that this is the first time it has ever run: it creates the file and executes the backup. That is what most users will want, but this behaviour can be changed.</p> <p>When I first wrote <code>backitup.sh</code> it was to help manage backups of my <a href="https://www.dropbox.com/">Dropbox</a> folder. Dropbox doesn&rsquo;t provide support client-side encryption, which means users need to handle encryption themselves. The most common way to do this is to create an <a href="http://www.arg0.net/encfs">encfs</a> file-system or two and place those within the Dropbox directory. That&rsquo;s the way I use Dropbox.</p> <p>I wanted to backup all the data stored in Dropbox with Tarsnap. Unlike Dropbox, Tarsnap <em>does</em> do client-side encryption, so when I backup my Dropbox folder, I don&rsquo;t want to actually backup the encrypted contents of the folder &ndash; I want to backup the decrypted contents. That allows me to take better advantage of Tarsnap&rsquo;s deduplication and it makes restoring backups much simpler. Rather than comparing <a href="https://en.wikipedia.org/wiki/Inode">inodes</a> and restoring a file using an encrypted filename like <code>6,8xHZgiIGN0vbDTBGw6w3lf/1nvj1,SSuiYY0qoYh-of5YX8</code> I can just restore <code>documents/todo.txt</code>.</p> <p>If my encfs filesystem mount point is <code>~/documents</code>, I can configure Tarsnapper to create an archive of that directory, but if for some reason the filesystem is not mounted when Tarsnapper is called, I would be making a backup of an empty directory. That&rsquo;s a waste of time. The solution is to tell <code>backitup.sh</code> to put the last-run file <em>inside</em> the encfs filesystem. If it can&rsquo;t find the file, that means that the filesystem isn&rsquo;t mounted. If that&rsquo;s the case, I tell it to call the script I use to automatically mount the encfs filesystem (which, the way I have it setup, requires no interaction from me).</p> <div class="highlight"><pre><span></span><code><span class="nv">@hourly</span><span class="w"> </span><span class="n">backitup</span><span class="p">.</span><span class="n">sh</span><span class="w"> </span><span class="o">-</span><span class="n">l</span><span class="w"> </span><span class="o">~/</span><span class="n">documents</span><span class="o">/</span><span class="p">.</span><span class="n">lastrun</span><span class="w"> </span><span class="o">-</span><span class="n">b</span><span class="w"> </span><span class="n">tarsnapper</span><span class="p">.</span><span class="n">py</span><span class="w"> </span><span class="o">-</span><span class="n">n</span><span class="w"> </span><span class="o">~/</span><span class="n">bin</span><span class="o">/</span><span class="n">encfs_automount</span><span class="p">.</span><span class="n">sh</span><span class="w"></span> </code></pre></div> <h2>Problem Solved</h2> <p><code>backitup.sh</code> solves all of my backup scheduling problems. I only call backup programs directly if I want to make an on-demand backup. All of my automated backups go through <code>backitup.sh</code>. If you&rsquo;re interested in the script, you can <a href="https://github.com/pigmonkey/backitup">download it 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’ve written to manage different aspects of backing up data.</p> <p><a href="https://www.youtube.com/watch?v=F22yKJRZoZc&amp;t=2m51s">Hey yo but wait, back it up, hup, easy back it up</a></p>Cryptshot: Automated, Encrypted Backups with rsnapshot2012-09-24T00:00:00-07:002012-12-22T00:00:00-08:00Pig Monkeytag:pig-monkey.com,2012-09-24:/2012/09/cryptshot-automated-encrypted-backups-rsnapshot/<p>Earlier this year I switched from <a href="http://duplicity.nongnu.org/">Duplicity</a> to <a href="http://rsnapshot.org/">rsnapshot</a> for my local backups. Duplicity uses a full + incremental backup schema: the first time a backup is executed, all files are copied to the backup medium. Successive backups copy only the deltas of changed objects. Over time this results in a …</p><p>Earlier this year I switched from <a href="http://duplicity.nongnu.org/">Duplicity</a> to <a href="http://rsnapshot.org/">rsnapshot</a> for my local backups. Duplicity uses a full + incremental backup schema: the first time a backup is executed, all files are copied to the backup medium. Successive backups copy only the deltas of changed objects. Over time this results in a chain of deltas that need to be replayed when restoring from a backup. If a single delta is somehow corrupted, the whole chain is broke. To minimize the chances of this happening, the common practice is to complete a new full backup every so often &ndash; I usually do a full backup every 3 or 4 weeks. Completing a full backup takes time when you&rsquo;re backing up hundreds of gigabytes, even over USB 3.0. It also takes up disk space. I keep around two full backups when using Duplicity, which means I&rsquo;m using a little over twice as much space on the backup medium as what I&rsquo;m backing up.</p> <p>The backup schema that rsnapshot uses is different. The first time it runs, it completes a full backup. Each time after that, it completes what could be considered a &ldquo;full&rdquo; backup, but unchanged files are not copied over. Instead, rsnapshot simply <a href="http://en.wikipedia.org/wiki/Hard_link">hard links</a> to the previously copied file. If you modify very large files regularly, this model may be inefficient, but for me &ndash; and I think for most users &ndash; it&rsquo;s great. Backups are speedy, disk space usage on the backup medium isn&rsquo;t too much more than the data being backed up, and I have multiple full backups that I can restore from.</p> <p>The great strength of Duplicity &ndash; and the great weakness of rsnapshot &ndash; is encryption. Duplicity uses <a href="http://www.gnupg.org/">GnuPG</a> to encrypt backups, which makes it one of the few solutions appropriate for remote backups. In contrast, rsnapshot does no encryption. That makes it completely inappropriate for remote backups, but the shortcoming can be worked around when backing up locally.</p> <p>My local backups are done to an external, USB hard drive. Encrypting the drive is simple with <a href="http://en.wikipedia.org/wiki/Linux_Unified_Key_Setup">LUKS</a> and <a href="http://en.wikipedia.org/wiki/Dm-crypt">dm-crypt</a>. For example, to encrypt <code>/dev/sdb</code>:</p> <div class="highlight"><pre><span></span><code>$ cryptsetup --cipher aes-xts-plain --key-size <span class="m">512</span> --verify-passphrase luksFormat /dev/sdb </code></pre></div> <p>The device can then be opened, formatted, and mounted.</p> <div class="highlight"><pre><span></span><code>$ cryptsetup luksOpen /dev/sdb backup_drive $ mkfs.ext4 -L backup /dev/mapper/backup_drive $ mount /dev/mapper/backup_drive /mnt/backup/ </code></pre></div> <p>At this point, the drive will be encrypted with a passphrase. To make it easier to mount programatically, I also add a key file full of some random data generated from <code>/dev/urandom</code>.</p> <div class="highlight"><pre><span></span><code>$ dd <span class="k">if</span><span class="o">=</span>/dev/urandom <span class="nv">of</span><span class="o">=</span>/root/supersecretkey <span class="nv">bs</span><span class="o">=</span><span class="m">1024</span> <span class="nv">count</span><span class="o">=</span><span class="m">8</span> $ chmod <span class="m">0400</span> /root/supersecretkey $ cryptsetup luksAddKey /dev/sdb /root/supersecretkey </code></pre></div> <p>There are still a few considerations to address before backups to this encrypted drive can be completed automatically with no user interaction. Since the target is a USB drive and the source is a laptop, there&rsquo;s a good chance that the drive won&rsquo;t be plugged in when the scheduler kicks in the backup program. If it is plugged in, the drive needs to be decrypted before calling rsnapshot to do its thing. I wrote a wrapper script called <a href="https://github.com/pigmonkey/cryptshot">cryptshot</a> to address these issues.</p> <p>Cryptshot is configured with the <a href="http://en.wikipedia.org/wiki/Universally_unique_identifier">UUID</a> of the target drive and the key file used to decrypt the drive. When it is executed, the first thing it does is look to see if the UUID exists. If it does, that means the drive is plugged in and accessible. The script then decrypts the drive with the specified key file and mounts it. Finally, rsnapshot is called to execute the backup as usual. Any argument passed to cryptshot is passed along to rsnapshot. What that means is that cryptshot becomes a drop-in replacement for encrypted, rsnapshot backups. Where I previously called <code>rsnapshot daily</code>, I now call <code>cryptshot daily</code>. Everything after that point just works, with no interaction needed from me.</p> <p>If you&rsquo;re interested in cryptshot, you can <a href="https://github.com/pigmonkey/cryptshot">download it directly from GitHub</a>. The script could easily be modified to execute a backup program other than rsnapshot. 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>Tarsnapper: 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>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>