Work continues on Spark, my Arch Linux provisioning system. As the project has progressed, it has created some useful tools that I’ve spun off into their own projects. One of those is nmtrust.
The idea is simple. As laptop users, we frequently connect our machines to a variety of networks. Some of those networks we trust, others we don’t. I trust my home and work networks because I administer both of them. I don’t trust networks at cafes, hotels or airports, but sometimes I still want to use them. There are certain services I want to run when connected to trusted networks: mail syncing, file syncing, online backups, instant messaging and the like. I don’t want to run these on untrusted networks, either out of concern over the potential leak of private information or simply to keep my network footprint small.
The solution is equally simple. I use NetworkManager to manage networks. NetworkManager creates a profile for every network connection. Every profile is assigned a UUID. I can decide which networks I want to trust, lookup their UUID with nmcli conn, and put those strings into a file somewhere. I keep them in /usr/local/etc/trusted_networks.
nmtrust is a small shell script which gets the UUIDs of all the active connections from NetworkManager and compares them to those in the trusted network file. It returns a different exit code depending on what it finds: 0 if all connections are trusted, 3 if one or more connections are untrusted, and 4 if there are no active connections.
This makes it extremely easy to write a script that executes nmtrust and takes certain action based on the exit code. For example, you may have a network backup script netbackup.sh that is executed every hour by cron. However, you only want the script to run when you are connected to a network that you trust.
123456789
#!/bin/sh# Execute nmtrust
nmtrust
# Execute backups if the current connection(s) are trusted.if[$? -eq 0];then
netbackup.sh
fi
On machines running systemd, most of the things that you want to start and stop based on the network are probably described by units. ttoggle is another small shell script which uses nmtrust to start and stop these units. The units that should only be run on trusted networks are placed into another file. I keep them in /usr/local/etc/trusted_units. ttoggle executes nmtrust and starts or stops everything in the trusted unit file based on the result.
For example, I have a timer mailsync.timer that periodically sends and receives my mail. I only want to run this on trusted networks, so I place it in the trusted unit file. If ttoggle is executed when I’m connected to a trusted network, it will start the timer. If it is run when I’m on an untrusted network or offline, it will stop the timer, ensuring my machine makes no connection to my IMAP or SMTP servers.
These scripts are easy to use, but they really should be automated so that nobody has to think about them. Fortunately, NetworkManager provides a dispatcher framework that we can hook into. When installed, the dispatcher will execute ttoggle whenever a connection is activated or deactivated.
The result of all of this is that trusted units are automatically started whenever all active network connections are trusted. Any other time, the trusted units are stopped. I can connect to shady public wifi without worrying about network services that may compromise my privacy running in the background. I can connect to my normal networks without needing to remember to start mail syncing, backups, etc.
All of this is baked in to Spark, but it’s really just two short shell scripts and a NetworkManager dispatcher. It provides a flexible framework to help preserve privacy that is fairly easy to use. If you use NetworkManager, try it out.
Arch has been my Linux distribution of choice for the past 5 years or so. It’s a fairly simple and versatile distribution that leaves most choices up the user, and then gets out of your way. Although I think it makes for a better end experience, the Arch Way does mean that it takes a bit more time to get a working desktop environment up and running.
At work I use Ansible to automate the provisioning of FreeBSD servers. It makes life easier by not only automating the provisioning of machines, but also by serving as reference documentation for The One True Way™. After a short time using Ansible to build servers, the idea of creating an Ansible playbook to provision my Arch desktop became attractive: I could pop a new drive into a machine, perform a basic Arch install, run the Ansible playbook and, in a very short period of time, have a fresh working environment – all without needing to worry about recalling arcane system configuration or which obscure packages I want installed. I found a few projects out there that had this same goal, but none that did things in the way I wanted them done. So I built my own.
Spark is an Ansible playbook meant to provision a personal machine running Arch Linux. It is intended to run locally on a fresh Arch install (ie, taking the place of any post-installation), but due to Ansible’s idempotent nature it may also be run on top of an already configured machine.
My machine is a Thinkpad, so Spark includes some tasks which are specific to laptops in general and others which only apply to Thinkpads. These tasks are tagged and isolated into their own roles, making it easy to use Spark to build desktops on other hardware. A community-contributed Macbook role exists to support Apple hardware. In fact, everything is tagged, and most of the user-specific stuff is accomplished with variables. The idea being that if you agree with my basic assumptions about what a desktop environment should be, you can use Spark to build your machine without editing much outside of the variables and perhaps the playbook.
The roles gather tasks into logical groups, and the tasks themselves are fairly simple. A quick skim through the repository will provide an understanding of everything Spark will do a matter of minutes. Basically: a simple i3 desktop environment, with GUI programs limited to web browsers and a few media and office applications (like GIMP and LibreOffice), everything else in the terminal, most network applications jailed with Firejail, and all the annoying laptop tasks like lid closure events and battery management automated away. If you’re familiar with my dotfiles, there won’t be any surprises.
Included in Spark is a file which describes how I install Arch. It is extremely brief, but provides everything needed to perform a basic installation – including full disk encryption with encrypted /boot – which can then be filled out with Ansible. I literally copy/paste from the doc when installing Arch. It takes about 15 minutes to complete the installation. Running Ansible after that takes about an hour, but requires no interaction after entering a passphrase for the SSH key used to clone the dotfiles. Combined with backups of the data in my home dir, this allows me to go from zero to hero in less than a couple hours without needing to really think about it.
If you use Arch, fork the repository and try it out.
The web browser is one of our computers’ primary means of interaction with the unwashed mashes. Combined with the unfortunately large attack surface of modern browsers, this makes a sandbox which does not depend on the browser itself an attractive idea.
Firejail is a simple, lightweight sandbox that uses linux namespaces to prevent programs from accessing things they do not need.
Firejail ships with default profiles for Firefox and Chromium. These profiles drop capabilities, filter syscalls, and prevent access to common directories like /sbin, ~/.gnupg and ~/.ssh. This is a good start, but I see little reason to give the browser access to much of anything in my home directory.
The --private flag instructs Firejail to mount a new user home directory in a temporary filesystem. The directory is empty and all changes are discarded when the sandbox is closed – think of it as a more effective private browsing or incognito mode that also resets your browser to factory defaults.
$ firejail --private firefox
A more useful option for normal browsing is to specify a directory that Firejail should use as the user home. This allows you to keep a consistent browser profile and downloads directory, but still prevents the browser from accessing anything else in the normal user home.
This is the method I default to for my browsing. I’ve created my own Firejail profile for Firefox at ~/.config/firejail/firefix.profile which implements this.
The only inconvenience I’ve discovered with this is that linking my Vimperator configuration files into the directory from my dotfiles repository creates a dangling link from the perspective of anything running within the jail. Since it cannot access my real home directory, it cannot see the link target in the ~/.dotfiles directory. I have to copy the configuration files into ~/firefox and then manually keep them in sync. I modify these files infrequently enough that for me this is worth the trade-off.
The temporary filesystem provided by --private is still useful when accessing websites that are especially sensitive (such as a financial institution) or especially shady. In my normal browser profiles, I have a number of extensions installed that block ads, disable scripts, etc. If these extensions completely break a website, and I don’t want to take the time to figure out which of the dozens of things I’m blocking are required for the website to function, I’ll just spin up a sandboxed browser with the --private flag, comfortable in the knowledge that whatever dirty scripts the site is running are limited in their ability to harm me.
I perform something like 90% of my web browsing in Firefox, but use Chromium for various tasks throughout the day. Both run in Firejail sandboxes, helping to keep me safe when surfing the information superhighway. Other programs, like torrent applications and PDF readers, also make good candidates for running within Firejail.
I store my photos in git-annex. A full copy of the annex exists on my laptop and on an external drive. Encrypted copies of all of my photos are stored on Amazon S3 (which I pay for) and box.com (which provides 50GB for free) via git-annex special remotes. The photos are backed-up to an external drive daily with the rest of my laptop hard drive via backitup.sh and cryptshot. My entire laptop hard drive is also mirrored monthly to an external drive stored off-site.
(The majority of my photos are also on Flickr, but I don’t consider that a backup or even reliable storage.)
All of this is what I consider to be the bare minimum for any redundant data storage. Photos have special value, above the value that I assign to most other data. This value only increases with age. As such they require an additional backup method, but due to the size of my collection I want to avoid backup methods that involve paying for more online storage, such as Tarsnap.
I choose optical discs as the medium for my photo backups. This has the advantage of being read-only, which makes it more difficult for accidental deletions or corruption to propagate through the backup system. DVD-Rs have a capacity of 4.7 GBs and a cost of around $0.25 per disc. Their life expectancy varies, but 10-years seem to be a reasonable low estimate.
Preparation
I keep all of my photos in year-based directories. At the beginning of every year, the previous year’s directory is burned to a DVD.
Certain years contain few enough photos that the entire year can fit on a single DVD. More recent years have enough photos of a high enough resolution that they require multiple DVDs.
Archive
My first step is to build a compressed archive of each year. I choose tar and bzip2 compression for this because they’re simple and reliable.
12
$ cd ~/pictures
$ tar cjhf ~/tmp/pictures/2012.tar.bz 2012
If the archive is larger than 3.7 GB, it needs to be split into multiple files. The resulting files will be burned to different discs. The capacity of a DVD is 4.7 GB, but I place the upper file limit at 3.7 GB so that the DVD has a minimum of 20% of its capacity available. This will be filled with parity information later on for redundancy.
1
$ split -d -b 3700M 2012.tar.bz 2012.tar.bz.
Encrypt
Leaving unencrypted data around is bad form. The archive (or each of the files resulting from splitting the large archive) is next encrypted and signed with GnuPG.
The encrypted archive and the detached signature of the encrypted archive are what will be burned to the disc. (Or, in the case of a large archive, the encrypted splits of the full archive and the associated signatures will be burned to one disc per split/signature combonation.) Rather than burning them directly, an image is created first.
If the year has a split archive requiring multiple discs, I modify the sequence number in the volume label. For example, a year requiring 3 discs will have the label Photos: 2012 1/3.
Parity
When I began this project I knew that I wanted some sort of parity information for each disc so that I could potentially recover data from slightly damaged media. My initial idea was to use parchive via par2cmdline. Further research led me to dvdisaster which, despite being a GUI-only program, seemed more appropriate for this use case.
I use dvdisaster with the RS02 error correction method, which augments the image before burning. Depending on the size of the original image, this will result in the disc having anywhere from 20% to 200% redundancy.
Verify
After the image has been augmented, I mount it and verify the signature of the encrypted file on the disc against the local copy of the signature. I’ve never had the signatures not match, but performing this step makes me feel better.
The final step is to burn the augmented image. I always burn discs at low speeds to diminish the chance of errors during the process.
1
$ cdrecord -v speed=4dev=/dev/sr0 2012.iso
Similar to the optical backups of my password database, I burn two copies of each disc. One copy is stored off-site. This provides a reasonably level of assurance against any loss of my photos.
In the past I’ve used OpenShot, but I was hit with some bugs that made me search for alternatives. Now I use Kdenlive, which I’ve found to be very capable. OpenShot currently has a Kickstarter campaign on to fund further development. Even though I’m no longer a user, I happily backed the project.
The first password manager I ever used was a simple text file encrypted with GnuPG. When I needed a password I would decrypt the file, read it in Vim, and copy the required entry to the system clipboard. This system didn’t last. At the time I wasn’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.
After some experimentation I landed on KeePassX, 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’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.
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’ve been successful with the system this time around. I believe the other reason is my more systematic approach.
Structure
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 PGP protocol, so the files can easily be decrypted anywhere. After they’ve been decrypted, there’s no fancy file formats to deal with. It’s all just text, which can be manipulated with a plethora of powerful tools. I favor reading the text in Vim, but any text editor will do the job.
All passwords are stored within a directory called ~/pw. Within this directory are multiple files. Each of these files can be thought of as a separate password database. I store bank information in financial.gpg. Login information for various shopping websites are in ecommerce.gpg. My email credentials are in email.gpg. 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.
Access
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’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.
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’t be prudent to do so on a machine that I do not fully trust, so the financial.gpg 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.
Git
The ~/pw directory is a git 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.
Keys
An advantage of using a directory full of encrypted files as my password manager is that I’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.
Markup
Each file is structured with Vim folds and indentation. There are various ways for Vim to fold text. I use markers, sticking with the default {{{/}}} characters. A typical password entry will look like this:
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.
12345678
Super Ecommerce{{{
user: foobar
pass: g0d
Comments{{{
birthday: 1/1/1911
first car: delorean
}}}
}}}
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.
Vim
I use Vim with the vim-gnupg 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 viminfo, swapfile, and undofile. I run gpg-agent 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 vimrc to facilitate working with passwords.
""""""""""""""""""""" GnuPG Extensions """""""""""""""""""""" Tell the GnuPG plugin to armor new files.letg:GPGPreferArmor=1" Tell the GnuPG plugin to sign new files.letg:GPGPreferSign=1
augroup GnuPGExtra
" Set extra file options.
autocmd BufReadCmd,FileReadCmd *.\(gpg\|asc\|pgp\)call SetGPGOptions()" Automatically close unmodified files after inactivity.
autocmd CursorHold *.\(gpg\|asc\|pgp\) quit
augroup END
function SetGPGOptions()" Set updatetime to 1 minute.setupdatetime=60000" Fold at markers.setfoldmethod=marker
" Automatically close all folds.setfoldclose=all" Only open folds with insert commands.setfoldopen=insert
endfunction
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.
The first autocmd 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’re intended primarily for use when Vim is acting as my password manager.
Folding
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 folds and is what most of these extra options address.
I set foldmethod to marker so that Vim knows to look for all the {{{/}}} characters and use them to build the folds. Then I set foldclose to all. This closes all folds unless the cursor is in them. This way only one fold can be open at a time – or, to put it another way, only one password entry is ever visible at once.
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 foldopen to insert 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.
I have my spacebar setup to toggle folds 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.
Locking
The other special option I set is updatetime. 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.
In the second autocmd I tell Vim to close itself on CursorHold. CursorHold is triggered whenever no key has been pressed for the time specified by updatetime. So the effect of this is that my password files are automatically closed after 1 minute of inactivity. This is similar to KeePassX’s behaviour of “locking the workspace” after a set period of inactivity.
Clipboard
To easily copy a credential to the system clipboard from Vim I have two shortcuts mapped.
12345
" Yank WORD to system clipboard in normal mode
nmap <leader>y "+yE
" Yank selection to system clipboard in visual mode
vmap <leader>y "+y
Selections are “owned” 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).
The result is that I can copy a username or password by placing the cursor on its first character and hitting <leader>y. 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’s behaviour of clearing the clipboard so many seconds after a username or password has been copied.
Generation
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 call and read external commands I can tell Vim to call the standard-issue pwgen program to generate a secure 24-character password utilizing special characters and insert the output at the cursor, like this:
1
:r!pwgen -sy241
Backups
The ~/pw directory is backed up in the same way as most other things on my hard drive: to Tarsnap via Tarsnapper, to an external drive via rsnapshot and cryptshot, rsync to a mirror drive. The issue with these standard backups is that they’re all encrypted and the keys to decrypt them are stored in the password manager. If I loose ~/pw I’ll have plenty of backups around, but none that I can actually access. I address this problem with regular backups to optical media.
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.
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’s GnuPG key is another problem entirely. I’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’m comfortable discussing publicly.
# Set the password database directory.PASSDIR=~/pw
# Create or edit password databases.
pw(){cd"$PASSDIR"if[ ! -z "$1"];then$EDITOR$(buildfile "$1")cd"$OLDPWD"fi}
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 pw() requires fewer keystrokes:
12
$ vim ~/pw/financial.gpg
$ pw financial
The function changes to the password directory before opening the file so that while I’m in Vim I can drop down to a shell with :sh and already be in the proper directory to manipulate the files. After I close Vim the function returns me to the previous working directory.
This still required a few more keystrokes than I like, so I configured my shell to perform autocompletion in the directory. If financial.gpg is the only file in the directory beginning with an “f”, typing pw f<tab> is all that is required to open the file.
Simplicity
This setup provides simplicity, 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’ve been happily utilizing it for about 6 months now.
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 grep, awk and cut, and then copy it to my clipboard via xsel. As it turns out, I haven’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.
Resources
I’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:
Mako has published a short essay in which he argues in favor of a conceptual shift, referring to “free users” rather than “free software”. It draws attention to the larger societal impact of proprietary software, and points out that software that is free-as-in-freedom can still create dependent and vulnerable users when used as a 3rd party service in the cloud.
As a long-time user of Unix-like 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 vim and take advantage of the pipeline to manipulate the data with powerful tools like awk and grep.
Notes are one such instance where plain text makes sense. All of my notes – which are scratch-pads for ideas, reference material, logs, and whatnot – are kept as individual text files in the directory ~/documents/notes. The entire ~/documents directory is synced between my laptop and my work computer, and of course it is backed up with tarsnap.
When I want to read, edit or create a note, my habit is simply to open the file in vim.
1
$ vim ~/documents/notes/todo.txt
When I want to view a list of my notes, I can just ls the directory. I pass along the -t and -r 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 – they’re the ones I’m currently doing something with, so they’re the ones I want to see. Using ls to see which files have been most recently modified is incredibly useful, and a behaviour that I use often enough to have created an alias for it.
The first function, n(), takes the name of the note as an argument – minus the file extension – and opens it.
123
function n {
nano ~/Dropbox/Notes/$1.txt
}
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.
1
$ n todo
I needed to make a few changes to the function to increase its flexibility.
First, the extension. Most of my notes have .txt extensions. Some have a .gpg extension.1 Some have no extension. I didn’t want to force the .txt extension in the function.
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 .gpg extension and open that if it was found. As a last resort, I wanted it to open the file with a .txt extension regardless of whether it existed or not. I implemented this behaviour in a separate function, buildfile(), so that I could take advantage of it wherever I wanted.
1 2 3 4 5 6 7 8 9101112131415161718192021
# Take a text file and build it with an extension, if needed.# Prefer gpg extension over txt.
buildfile(){# If an extension was given, use it.if[["$1"== *.* ]];thenecho"$1"# If no extension was given...else# ... try the file without any extension.if[ -e "$1"];thenecho"$1"# ... try the file with a gpg extension.elif[ -e "$1".gpg ];thenecho"$1".gpg
# ... use a txt extension.elseecho"$1".txt
fifi}
I then rewrote the original note function to take advantage of this.
1 2 3 4 5 6 7 8 910111213
# Set the note directory.NOTEDIR=~/documents/notes
# Create or edit notes.
n(){# If no note was given, list the notes.if[ -z "$1"];then
lt "$NOTEDIR"# If a note was given, open it.else$EDITOR$(buildfile "$NOTEDIR"/"$1")fi}
Now I can edit the note ~/documents/notes/todo.txt by simply passing along the filename without an extension.
1
$ n todo
If I want to specify the extension, that will work too.
1
$ n todo.txt
If I have a note called ~/documents/notes/world-domination.gpg, I can edit that without specifying the extension.
1
$ n world-domination
If I have a note called ~/documents/notes/readme, I can edit that and the function will respect the lack of an extension.
1
$ n readme
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 .txt extension.
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.
1
$ n
The original blog post also included a function ns() for searching notes by their title.
123
function ns {
ls -c ~/Dropbox/Notes | grep $1}
I thought this was a good idea, but I considered the behaviour to be finding a note rather than searching a note. I renamed the function to reflect its behaviour, took advantage of my ls 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 n().
12345678
# Find a note by title.
nf(){if[ -z "$1"];then
lt "$NOTEDIR"else
lt "$NOTEDIR"| grep -i $1fi}
I thought it would be nice to also have a quick way to search within notes. That was accomplished with ns(), the simplest function of the trio.
12
# Search within notes.
ns(){cd$NOTEDIR; grep -rin $1;cd"$OLDPWD";}
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’t need to specify the full path of the note to open it, and this makes the output easier to read.
123
$ ns "take over"
world-domination.txt:1:This is my plan to take over the world.
$ n world-domination
To finish it up, I added completion to zsh so that I can tab-complete filenames when using n.
12
# Set autocompletion for notes.
compctl -W $NOTEDIR -f n
If you’re interested in seeing some of the other way that I personalize my working environment, all of my dotfiles are on GitHub. The note functions are in shellrc, which is my shell-agnostic configuration file that I source from both zshrc and bashrc2.
Notes
↵ I use vim with the gnupg.vim plugin for seamless editing of PGP-encrypted files.
↵ I prefer zsh, but I do still find myself in bash 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 DRY principle.