Complete Computing Environment: Core Configurations

Table of Contents

I pride myself in my workflow and in the experience and knowledge I have of my toolset. The act of writing code is simple, the act of building systems is unrelated. I know how to build a table, but I don't have mastery over that skill because the tools, while simple and accessible, are still foreign to me. That is not the case with my working environment, where I have spent hours honing my experience with the planer, I have sawn more than my share of boards to the right length, I have worked a nailgun without err. I have become a craftsman with and within my environment. Along the way, I have taken some core philosophies in how I use my tools. These are core to my experiences, and should be inherent in all that I design.

(provide 'cce-core)

Bootstrapping the Environment

Make sure our customizations, if they exist, aren't made inside of the init file itself, since they'll be wiped out.

(setq custom-file "~/.emacs.d/custom.el")

I have some packages that are vendorized in this repository, make sure they get loaded.

(add-to-list 'load-path "~/.emacs.d/cce/vendor")

package.el Setup

My strategy with regard to packaging is simple – let the system handle it. In the past, I've tried to use things like Cask3 or use-package4, but Emacs bundles a package manager that is, frankly, Good Enough for my tastes. Cask requires a python bootstrap script, and Use Package's declarative syntax doesn't fit with how I want to structure CCE or my previous literate configurations. So I just use package.el, especially since I don't need to support Emacs 23 or anything crazy like that.

I mainly use Melpa for my packages, I am going to experiment with using only Org, ELPA and MELPA, but I probably will end up having to pull in Marmalade as well.

(package-initialize)

(add-to-list 'package-archives
             '("org" . "http://orgmode.org/elpa/") t)
(add-to-list 'package-archives
             '("gnu" . "http://elpa.gnu.org/packages/") t)
(add-to-list 'package-archives
             '("melpa" . "http://melpa.org/packages/") t)

I define cce/did-refresh-packages, which is used as a signal in install-pkgs that we need to refresh the package archives.

(setq cce/did-refresh-packages nil)

install-pkgs is a simple elisp function that will iterate over a list, and install each package in it, if it is not installed. If cce/did-refresh-packages is set to nil, it'll also refresh the package manager.

(defun install-pkgs (list)
  (dolist (pkg list)
    (unless (package-installed-p pkg)
      (unless cce/did-refresh-packages
        (package-refresh-contents)
        (setq cce/did-refresh-packages t))
      (package-install pkg))))

Make sure org always comes from the Org ELPA.

(setq package-pinned-archives '())
(add-to-list 'package-pinned-archives '(org-plus-contrib . "org"))

el-get Setup

El-get is a recipe-based package manager that can be used to easily install things that aren't in the Package archives, such as random libraries that are only hosted on Emacswiki, or some random github gist or text file somewhere.

(add-to-list 'load-path "~/.emacs.d/el-get/el-get")
(setq el-get-notify-type 'message)

(unless (require 'el-get nil 'noerror)
  (with-current-buffer
      (url-retrieve-synchronously
       "https://raw.githubusercontent.com/dimitri/el-get/master/el-get-install.el")
    (goto-char (point-max))
    (eval-print-last-sexp)))

(setq cce/el-get-packages nil)

After every other module is loaded, we should install their dependent packages, which is done using (el-get 'sync).

(add-hook 'after-cce-hook (lambda ()
                            (el-get 'sync cce/el-get-packages)) t)

Helper Methods

I define a bunch of weird little helper methods all over the place, these are simple, DRY methods that I can use everywhere in my system.

I am fairly certain there is a chomp equivalent in the emacs standard library, but I've not been able to figure out what it's called yet, so I have my own for now.

(defun cce/str-chomp (str)
  "Chomp leading and trailing whitespace from STR."
  (while (string-match "\\`\n+\\|^\\s-+\\|\\s-+$\\|\n+\\'" str)
    (setq str (replace-match "" t t str)))
  str)

These are shell script helpers, a cache warming function that'll automatically get added to cce-core.sh, and some functions that'll need to be sourced in from other scripts.

if [[ "x$HOSTNAME" = "xmanager" ]];
then
       DOMAINS="base fedora-23"
       for domain in $DOMAINS; do qvm-run $domain sudo dnf makecache; done
else
       . /etc/os-release
       [[ $ID = "debian" ]] && sudo apt-get update
       [[ $ID = "fedora" ]] && sudo dnf makecache
fi
function rpm-install () {
    if [[ "x$HOSTNAME" != "xmanager" ]];
    then
        . /etc/os-release
        [[ $ID = "fedora" ]] && [[ -z "$QUBES_ENV_SOURCED" ]] && sudo dnf -y install $@
    else
        qvm-run base sudo dnf install -y $@
    fi
}
function deb-install () {
    if [[ "x$HOSTNAME" != "xmanager" ]];
    then
        . /etc/os-release
        [[ $ID = "debian" ]] && [[ -z "$QUBES_ENV_SOURCED" ]] && sudo apt -y install $@
    fi
}
function link-this () {
   readlink $2 || ln -sf $1 $2
}
(defun scissors ()
  (interactive)
  (insert "--8<---------------cut here---------------start------------->8---\n")
  (save-excursion
    (insert "\n--8<---------------cut here---------------end--------------->8---\n")))

Bash Configuration Loader

It's possible for multiple CCE modules to push out Bash configurations, so each one tangles to its own file which is then executed as so:

for file in ~/.bashrc.d/*; do
  . $file
done
for file in ~/.bash_profile.d/*; do
  . $file
done
readlink ~/.bashrc.d || link-this $PWD/out/bashrc.d/ ~/.bashrc.d
readlink ~/.bash_profile.d || link-this $PWD/out/bash_profile.d/ ~/.bash_profile.d
link-this $PWD/out/bashrc ~/.bashrc
link-this $PWD/out/bash_profile ~/.bash_profile
export PS1="\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w \[\033[00m\]\`if [ \$? == 0 ]; then echo \:\); else echo \:\(; fi\` "
export PATH=$PATH:~/bin/

test -f /etc/bash_profile && . /etc/bash_profile

I am in San Francisco. This used to be a UTC setup, but it's really hard to make that work sanely, so I don't.

export TZ=America/Los_Angeles

Qubes Domain Support

I still need to do a lot of work to flesh this out, but the basic idea is to modify the /etc/cce stuff which I have been using, to work with the hostnames of Qubes VMs. I'm not so sure how this will work out in practice, but I need some way of doing this, if I'm going to actually use Qubes.

I'll start simple, and replace the /etc/cce/formfactor file with cce/with-any-domain which will take a list of Domains which the code is Okay to evaluate within. This should allow me to pretty easily minimize the amount of code and data that bleeds across domains.

Simple sample usecases would be:

  • Load per-domain custom-files
  • Run different Unison syncs per-domain
  • Don't install matrix-client in my work domain
(defmacro cce/with-any-domain (envs &rest body)
  "Run BODY if the current CCE Env is one of ENVS."
  ` (when (member (getenv "HOSTNAME") ,envs)
      ,@body))

These are helper functions which other parts of CCE can use to run commands in the context of Qubes domains.

function domain-copy () {
    DOMAIN="$1"
    shift
    [[ "x$DOMAIN" != "x" ]] && [[ "x$(hostname)" = "xmanager" ]] && qvm-copy-to-vm $DOMAIN $@
}

function domain-run () {
    DOMAIN="$1"
    shift
    [[ "x$DOMAIN" != "x" ]] && [[ "x$(hostname)" = "xmanager" ]] && qvm-run $DOMAIN $@
}

function domain-install () {
    DOMAIN="$1"
    shift
    [[ "x$DOMAIN" != "x" ]] && [[ "x$(hostname)" = "xmanager" ]] && qvm-run $DOMAIN dnf install $@
}

Booting Up the System

I have a lot of background services on my machine, some run within Emacs but more and more, I am embracing SystemD's ability to run services under the context of a user session. It's one of the best things to happen to Linux plumbing since NetworkManager. It's fast, it exposes an easy to use and simple interface, and it moves past all the fucking jankiness that we've been forced to deal with in system management and service configuration.

Services in Systemd

Running commands before suspend

[Unit]
Description=User suspend actions for %I
Before=sleep.target

[Service]
User=%I
Type=simple
Environment=DISPLAY=:0
ExecStart=/usr/bin/bash /home/%I/bin/pre-suspend.sh

[Install]
WantedBy=sleep.target
link-this $PWD/out/pre-suspend.sh ~/bin/pre-suspend.sh
chmod +x ~/bin/pre-suspend.sh

sudo cp $PWD/out/suspend@.service /etc/systemd/system/suspend@.service
sudo systemctl enable suspend@rrix.service

Quit all ZNC servers before suspending.

emacsclient --eval '(erc-cmd-GQUIT "")'

Pause MPD.

mpc pause

Lock all screen sessions

screen -X lockscreen

Redshift

Redshift is a piece of software that removes blue hues from your screen, making it easier to use at night without blinding yourself, or fucking with your sleep cycle.

[Unit]
Description=Redshift

[Service]
Type=simple
ExecStart=/usr/bin/redshift -l geoclue2 -b 1.0:0.7 -t 6500:3000
ExecStop=/usr/bin/pkill redshift
Environment=DISPLAY=:0
Restart=always

[Install]
WantedBy=default.target
if [[ "$HOSTNAME" != "hypervisor01" ]]; then
  deb-install redshift
  rpm-install redshift
  link-this $PWD/out/redshift.service ~/.config/systemd/user/redshift.service
  systemctl --user daemon-reload
  systemctl --user enable redshift
  systemctl --user start redshift
fi

Long Running Jobs within Emacs

I have a few long running jobs that I leave terminals open for. It's not great, and it's just un-needed extra overhead for things that I don't touch more than twice a day. They're not things that I can easily migrate to SystemD user services, since they require interactive input such as SSH key passphrases and that sort of things.

Instead, we can run them inside of emacs in a comint buffer using async-shell-command. I built a helper function around that to make them run forever in their own buffer.

rpm-install unison240-text
deb-install unison2.40.102
(defun cce/async-forever (command buffer-name)
  "Run a command in an async buffer forever"
  (async-shell-command (concat "while true; do " command "; sleep 5; done")
                       (get-buffer-create buffer-name)))

File Sync using Unison

I use Unison for syncing my files between different machines, mainly my Org-mode files, but also other files that are also stored in git, and things like my private dotfiles such as ~/.authinfo and my ~/News. cce/kick-off-unison is a function that'll start a number of Unison sync programs, including one for work files.

(defun cce/kick-off-unison (profile)
  "Start my unison syncs if they arent already running"
  (interactive "MProfile? ")
  (let ((unison-cmd (cond ((file-exists-p "/usr/bin/unison-2.40") "/usr/bin/unison-2.40")
                          (t "/usr/bin/unison")))
        (delay "30"))
    (if (and (not (get-buffer (concat "*unison-log " profile " *")))
             (file-exists-p unison-cmd))
        (cce/async-forever (concat unison-cmd " " profile " -ui text -auto -batch -terse -repeat " delay)
                           (concat "*unison-log " profile " *")))))

(cce/with-any-domain
 '("work" "main")
 (add-hook 'after-init-hook
           (lambda () (cce/kick-off-unison "org"))))

I have a number of different Unison hosts that I use for different operating systems; my Fedora 22 machines use a different, incompatible version of OCaml than Fedora 21, so I have to have two, and there is a third one for my Nokia n900 which runs a random Unison package that I found on the interbutts, and has to go to a third one-off Unison host.

root = /home/user/sync
root = ssh://rrix@hypervisor01.pss9.kickass.systems//srv/files/files/rrix/org/sync

servercmd = /usr/bin/unison

ignore = Path Uploads/
ignore = Path pictures/
ignore = Regex .*~

merge = Name *.org -> emacsclient -c --eval '(ediff-merge-files "CURRENT1" "CURRENT2" nil "NEW")'
root = /home/user/sync
root = ssh://user@10.137.2.13//home/user/sync

servercmd = /usr/bin/unison

ignore = Path Uploads/
ignore = Path pictures/
ignore = Regex .*~

ignore = Path domains/work
ignore = Path domains/motoko
ignore = Path domains/manager
ignore = Path domains/data
root = /home/user/sync
root = ssh://user@10.137.2.13//home/user/sync

servercmd = /usr/bin/unison

ignore = Path Uploads/
ignore = Path pictures/
ignore = Regex .*~

ignore = Path domains/main
ignore = Path domains/motoko
ignore = Path domains/manager
ignore = Path domains/data
root = /home/user/sync
root = ssh://user@10.137.2.13//home/user/sync

servercmd = /usr/bin/unison

ignore = Path Uploads/
ignore = Path pictures/
ignore = Regex .*~

ignore = Path domains/main
ignore = Path domains/motoko
ignore = Path domains/manager
ignore = Path domains/data
if [[ "x" != "x$QUBES_ENV_SOURCED" ]] && [[ "x$(hostname)" = "xmanager" ]]; then
    for domain in work main motoko manager data; do
        domain-copy $domain $PWD/out/org-$domain.prf
        domain-run $domain mkdir -p ~/.unison
        domain-run $domain mkdir -p ~/sync
        domain-run $domain cp QubesIncoming/manager/org-$domain.prf ~/.unison/org.prf
    done
else
    cat ./out/org-data.prf | sed -e "s/user/rrix/g" > ~/.unison/org.prf
fi

Desktop Window Manager

I have found a really good system in EXWM; it's a really nicely functional window manager with incredibly tight integration with Emacs, given that it is written in Emacs Lisp. You can do some really neat things with it, including one that I just recently found, which is to run it inside of KDE as the KDE window manager. By setting the standard KDEWM environment variable, KDE's session manager will automatically start Emacs as the Window Manager if you run startx from a tty.

xrdb ~/.Xresources
feh --bg-fill ~/Downloads/2010-07-22-CactusMonsoon_MG_0640_38_39.jpg
export KDEWM=awesome
exec dbus-launch startkde
link-this $PWD/out/xinitrc ~/.xinitrc
#exec xcompmgr &
#exec xmonad

With this in place, we can make sure that X11 automatically starts when I boot up, since my computers require a password to unlock the full-disk encryption.

Start out by making any interactive shell check if it is not running in an X11 session, and is running on tty1, and if so, starting X11.

[ -z "$DISPLAY" -a "$(fgconsole)" -eq 1 ] && exec startx

And then making sure that tty1 is always automatically logged in to my user account.

[Service]
ExecStart=
ExecStart=-/usr/bin/agetty --autologin rrix --noclear %I $TERM
sudo mkdir -p /etc/systemd/system/getty@tty1.service.d/
sudo cp $PWD/out/getty-override.conf /etc/systemd/system/getty@tty1.service.d/

Core Workflow and Computing Environment

I almost always want Emacs's server running. The ability to be able to attach to Emacs to edit files from the command line, or to rescue a broken X11 session, or to do batch operations using Emacs's text-processing libraries are all super powerful, and it makes little sense to not use this.

(unless (boundp 'server-process)
  (server-start))

Desktop Save Mode5 is the session management system for Emacs; it holds state of open buffers and session variables across instantiation of Emacs, which is super useful in mobile setups like laptops which reboot a lot. To make startup sane, I'm choosing to eagerly restore the 10 most recently used buffers on startup, and then in Idle the system will restore the remaining buffers.

(desktop-save-mode 1)
(setq desktop-restore-eager 10)
(setq desktop-files-not-to-save "\\(^/[^/:]*:\\|(ftp)$\\|KILL\\)")
(setq desktop-restore-frames nil)

Emacs should automatically save my state, and does so every five minutes.

(defun cce/desktop-save ()
  "Write the desktop save file to ~/.emacs.d"
  (desktop-save user-emacs-directory))
(if (not (boundp 'cce/desktop-save-timer))
    (setq cce/desktop-save-timer
          (run-with-idle-timer 300 t 'cce/desktop-save)))

The splashscreen is useful if you've never used Emacs before, but I'd rather just drop straight in to the Scratch buffer.

(setq inhibit-splash-screen t)

Uniquify buffer names; this makes working on multiple codebases more sane. Sometimes a single project can have multiple files with the same name, for example tests/index.js and tests/units/index.js, tests/lib/index.js, etc. The buffers will be listed as index.js:tests, index.js:units, and index.js:lib respectively. You can tweak that by looking at the eldoc fo uniquify-buffer-name-style.

(require 'uniquify)
(setq uniquify-buffer-name-style 'post-forward
      uniquify-separator ":")

Diminish some common modes; Diminishing a mode makes it eat less space in the modeline, by replacing the text on the modeline with something else. The Emacs config I stole these from uses a bunch of unicode characters, so I kind of backfill that, too. I also hide some modes that I don't give any shits about, so.

(install-pkgs '(diminish))
(require 'diminish)
(diminish 'visual-line-mode "")
(diminish 'isearch-mode "?")

By default, my machine drops me in to a *scratch* buffer. Originally designed to be an lisp playground that you could dive right in to on start up, it's sort of eclipsed that for me in to a general purpose buffer, where I will put things like elisp I am prototyping or playtesting, small snippets of code that I want to use in dayjob, etc. But when you kill emacs, or it dies, that buffer disappears. This code will save the Scratch buffer every five minutes and restores it on Emacs startup.

(defun save-persistent-scratch ()
  "Write the contents of *scratch* to the file name
`persistent-scratch-file-name'."
  (with-current-buffer (get-buffer-create "*scratch*")
    (write-region (point-min) (point-max) "~/.emacs.d/persistent-scratch")))

(defun load-persistent-scratch ()
  "Load the contents of `persistent-scratch-file-name' into the
  scratch buffer, clearing its contents first."
  (if (file-exists-p "~/.emacs-persistent-scratch")
      (with-current-buffer (get-buffer "*scratch*")
        (delete-region (point-min) (point-max))
        (insert-file-contents "~/.emacs.d/persistent-scratch"))))

(add-hook 'after-init-hook 'load-persistent-scratch)
(add-hook 'kill-emacs-hook 'save-persistent-scratch)

(if (not (boundp 'cce/save-persistent-scratch-timer))
    (setq cce/save-persistent-scratch-timer
          (run-with-idle-timer 300 t 'save-persistent-scratch)))

I restart emacs a lot, especially on my n900, and it would be nice to have the history of things like M-x saved across those sessions. savehist mode gives us that6.

(require 'savehist)
(setq savehist-file (concat user-emacs-directory "savehist"))
(savehist-mode 1)
(setq savehist-save-minibuffer-history 1)
(setq savehist-additional-variables
      '(kill-ring
        search-ring
        regexp-search-ring))

A lot of systems outside of the Emacs environment will edit files for me, especially Org-mode and Git. global-auto-revert-mode will make that sane.

(global-auto-revert-mode t)

Jump to the *scratch* buffer easily with bh/switch-to-scratch

(defun bh/switch-to-scratch ()
  (interactive)
  (switch-to-buffer "*scratch*"))

Toggle line wrapping with <f7>

(global-set-key (kbd "<f7>") 'bh/set-truncate-lines)
(defun bh/set-truncate-lines ()
  "Toggle value of truncate-lines and refresh window display."
  (interactive)
  (setq truncate-lines (not truncate-lines))
  ;; now refresh window display (an idiom from simple.el):
  (save-excursion
    (set-window-start (selected-window)
                      (window-start (selected-window)))))

recentf gives us a "Recent Files" menu and also some pretty neat Projectile integrations

(recentf-mode 1)

Use ibuffer instead of list-buffers

(global-set-key (kbd "C-x C-b") 'ibuffer)

The Macbook that I am using at work has a goofy kernel bug where the Tilde/Grave key becomes less than/greater than… You have to disable it with a modprobe option.

echo "options hid_apple iso_layout=0 fnmode=2" | sudo tee /etc/modprobe.d/hid_apple.conf
echo 'SUBSYSTEM=="pci", KERNEL=="0000:00:14.0", ATTR{power/wakeup}="disabled"' | sudo tee /etc/udev/rules.d/90-xhc_sleep.rules
(defun kill-this-buffer-y-or-n ()
  (interactive)
  (let ((buf (current-buffer)))
    (if (y-or-n-p (format "Kill %s?" (buffer-name buf)))
        (kill-buffer buf))))

Modal Editing with Evil Mode

(install-pkgs '(evil evil-args evil-avy evil-leader evil-lispy evil-magit evil-matchit evil-surround syndicate))
(require 'evil)
(evil-mode 1)
(global-evil-leader-mode 1)
(setq evil-emacs-state-cursor '("red" box))
(setq evil-normal-state-cursor '("red" box))
(setq evil-visual-state-cursor '("orange" box))
(setq evil-insert-state-cursor '("orange" box))
(setq evil-replace-state-cursor '("red" bar))
(setq evil-operator-state-cursor '("red" hollow))
(loop for (mode . state) in '((text-mode . normal)
                              (exwm-mode . emacs)
                              (cider-stacktrace-mode . emacs)
                              (sauron-mode . emacs)
                              (prog-mode . normal)
                              (comint-mode . normal)
                              (shell-mode . insert)
                              (git-commit-mode . insert)
                              (git-rebase-mode . emacs)
                              (grep-mode . emacs)
                              (bc-menu-mode . emacs)
                              (magit-branch-manager-mode . emacs)
                              (mingus-playlist-mode . emacs)
                              (mingus-browse-mode . emacs)
                              (mpc-mode . emacs)
                              (mpc-status-mode . emacs)
                              (mpc-tagbrowser-mode . emacs)
                              (mpc-tagbrowser-dir-mode . emacs)
                              (mpc-songs-mode . emacs)
                              (rdictcc-buffer-mode . emacs)
                              (dired-mode . emacs)
                              (wdired-mode . normal))
      do (evil-set-initial-state mode state))

Set up and configure a Leader key.

(eval-after-load 'evil (lambda ()
                         (add-hook 'evil-normal-state-entry-hook 'evil-leader-mode)
                         (evil-leader/set-leader "SPC")))
(eval-after-load "org"
  (progn
    (load-library "syndicate")
    (add-hook 'org-mode-hook #'syndicate-mode)
    (evil-define-key 'normal syndicate-mode-map
      "gc" 'org-cycle)))

Toggle internal keyboard with shell script

I have a small planck keyboard that I use for day-to-day computing on my laptop since the keyboard on my work ThinkPad T440s is abysmal. The planck is really great and I am looking forward to side-grading to a Preonic which provides an extra row of keys to bind to Emacs functions, but its greatest feat is that it sits perfectly on top of my laptop's internal keyboard. This works great, except that they Planck is heavy enough that it depresses keys, so I disable the internal keyboard's X11 device thusly, and bind it to hydra-sysint below.

INPUT=$(xinput list | grep AT | grep -Eo 'id=([0-9]+)' | cut -f2 -d=)
MASTER=$(xinput list | grep 'Virtual core keyboard' | grep -Eo 'id=([0-9]+)' | cut -f2 -d=)
FLOATING=$(xinput list | grep 'Internal Keyboard' | cut -f 3 | grep floating)

test -z "$FLOATING" && xinput float $INPUT
test -z "$FLOATING" || xinput reattach $INPUT $MASTER
link-this $PWD/out/toogle-kb.sh $HOME/bin/toggle-kb
chmod +x $HOME/bin/toggle-kb

TMUX Configuration

On my servers, I often run an Emacs session inside of TMUX, especially when working through my Chromebook, which is a workflow I am moving more and more towards. As such, I provide some minor creature comforts, which keep tmux from conflicting with my Emacs workflow too much. The biggest one is that I set the tmux prefix key to C-q instead of the default and important to Emacs C-b. This conflicts with my Modalka toggle, which is something I need to re-evaluate, but this seems mostly Okay.

unbind C-b
set -g prefix C-q
bind C-q send-prefix

set -g default-terminal "xterm-256color"

set-option -g status-bg colour235 #base02
set-option -g status-fg colour136 #yellow

set -g status-interval 1
set -g status-justify left
set -g status-left-length 20
set -g status-right-length 140
set -g status-left '#[fg=blue]#H#[default]'
set -g status-right '#[fg=green,bg=default,bright]#(tmux-mem-cpu-load) #[fg=red,dim,bg=default]#(uptime | cut -f 4-5 -d " " | cut -f 1 -d ",") #[fg=white,bg=default]%a%l:%M:%S %p#[default] #[fg=blue]%Y-%m-%d'
link-this $PWD/out/tmux.conf ~/.tmux.conf

SSH and GPG using GnuPG

Getting ssh and friends to use gpg-agent is as simple as pointing them to it:

I use gpg-agent 7 as an ssh agent as a means to use a Yubikey Neo PGP smartcard as physical login tokens. Without a pair of Yubikeys and their passphrases you can't log in to any of my assets or as me to any of my work assets.

(defun cce/gpg-version ()
  "Return the version of gpg as a string"
  (save-window-excursion
    (with-temp-buffer
      (shell-command (concat epg-gpg-program " --version") (current-buffer))
      (goto-char (point-min))
      (string-match "gpg (GnuPG) \\(.*\\)" (buffer-string))
      (cce/str-chomp
       (match-string 1)))))
SSH_AUTH_SOCK=/run/user/1000/gnupg/S.gpg-agent.ssh; export SSH_AUTH_SOCK;

Let's make sure that all the GPG things we need are installed, and GPG-Agent is configured to enable the SSH agent support.

rpm-install gnupg2 gnupg2-smime
deb-install gnupg2
echo enable-ssh-support > ~/.gnupg/gpg-agent.conf
link-this $PWD/out/gpg.conf ~/.gnupg/gpg.conf
default-key a5fce951
default-recipient-self

ask-cert-level
auto-check-trustdb
no-greeting
no-expert

cert-policy-url http://whatthefuck.computer/new-key.txt

auto-key-locate keyserver cert pka
keyserver hkp://pool.sks-keyservers.net

list-options no-show-photos show-uid-validity no-show-unusable-uids no-show-unusable-subkeys show-keyring show-policy-urls show-notations show-keyserver-urls show-sig-expire
verify-options show-uid-validity
fixed-list-mode
keyid-format 0xlong

personal-digest-preferences SHA512
personal-cipher-preferences AES256 AES192 AES
cert-digest-algo SHA512
default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed

s2k-cipher-algo AES256
s2k-digest-algo SHA512
s2k-mode 3
s2k-count 65011712

completes-needed 2
marginals-needed 5
max-cert-depth 7
min-cert-level 2

I have a simple script which tells me which card I have plugged in, which I then turn around and use in my Xmobar configuration. It relies on the existence of a .yubikeys file, which has a list of exports, a la:

export PRIMARYCARD=cardno:12345678980
export SECONDARYCARD=cardno:2345678901

These Card identifiers can be seen in taking the third column of ssh-add -L, which is incidentally what the script parses.

. ~/.yubikeys

CARD=$(ssh-add -L 2>&1 | tail -n1 | awk '{ print $3 }')

case $CARD in
    $PRIMARYCARD)
        echo "primary"
        ;;
    $SECONDARYCARD)
        echo "secondary"
        ;;
    "has")
        echo "none"
        ;;
    "open")
        echo "ERR"
esac

I have a function that runs this script and changes my modeline colors accordingly, as well.

(defun cce/set-mode-line-to-card ()
  (interactive)
  (with-temp-buffer
    (call-process "/usr/bin/env" nil (current-buffer) nil "bash" "/home/cce/bin/which-card.sh")
    (let ((card (cce/str-chomp (buffer-string))))
      (cond
       ((string-match "none" card) (set-face-background 'mode-line "dark slate gray"))
       ((string-match "primary" card) (set-face-background 'mode-line "black"))
       ((string-match "secondary" card) (set-face-background 'mode-line "red"))))
    nil))
;; (if (or (not (boundp 'cce/set-mode-line-to-card-timer))
;;         (not cce/set-mode-line-to-card-timer))
;;     (setq cce/set-mode-line-to-card-timer
;;           (run-with-timer 5 5 'cce/set-mode-line-to-card)))

udev makes the GPG keys owned by root in the most recent Fedoras for some reason; I have to install a udev rule to make sure they are properly owned:

echo 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0111", OWNER="rrix"' | sudo tee /etc/udev/rules.d/99-yubikeys.rules
echo 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="16d0", ATTRS{idProduct}=="09a0", OWNER="rrix"' | sudo tee /etc/udev/rules.d/99-mooltipass.rules

I use gpg2 everywhere, including in Emacs.

(setq epg-gpg-program "gpg2")

GPG Encrypted Backups using Duplicity

deb-install duplicity
rpm-install duplicity

gpg2 --recv-keys a5fce951
#!/bin/bash

REMOTE=rsync://rrix@hypervisor01.pss9.kickass.ssytems//srv/backups/$(hostname)

duplicity incremental --full-if-older-than 1W --asynchronous-upload \
          --encrypt-key a5fce951 --exclude-other-filesystems \
          --progress /home/rrix/ $REMOTE
duplicity remove-all-inc-of-but-n-full 2 --force $REM

Common Keybindings with Hydra

In the past, I would provide myself with jumps in to other modes and features that I use often using evil-leader. However, in this new brave world that I am working in, I will rarely be in evil-normal-state, which makes Leader inaccessible. This leads me to Hydra8, which allows you to define, basically, easily accessed minor modes such as a "window management" minor-mode9, etc.

It's basically little more than a big-hairy-macro, but it works nicely. I use C-x x as a sort of self-tracked prefix for these, and then a Hydra after that.

I have a set of prefix mnemonics that I try to use for my bindings:

  • b: Things that operate on buffers
  • p: Things that projectile does
  • e: Things that operate on Errors

Let's install and load Hydra.

(install-pkgs '(hydra))
(require 'hydra)

First, we define, hydra-window, a hydra that allows you to move between frames and windows, using a combination of windmove, ace-window and winner. We bind that to C-x w.

(install-pkgs '(ace-window
                transpose-frame))
(winner-mode)
(require 'hydra-examples)
(require 'transpose-frame)
(defhydra hydra-window (:color red :hint nil :columns 8)
  ("h" windmove-left "Left")
  ("j" windmove-down "Right")
  ("k" windmove-up "Up")
  ("l" windmove-right "Down")

  ("H" hydra-move-splitter-left "Splitter Left")
  ("J" hydra-move-splitter-down "Splitter Right")
  ("K" hydra-move-splitter-up "Splitter Up")
  ("L" hydra-move-splitter-right "Splitter Right")
  ("|" (lambda ()
         (interactive)
         (split-window-right)
         (windmove-right)) "Split | and Right")
  ("_" (lambda ()
         (interactive)
         (split-window-below)
         (windmove-down))"Split - and Down")
  ("v" split-window-right "Split | and Left")
  ("x" split-window-below "Split - and Up")

  ("u" winner-undo "Undo")
  ("r" winner-redo "Redo")

  ("o" other-window "Other Window")
  ("a" ace-window "Select Window" :exit t)
  ("s" ace-swap-window "Swap Windows")

  ("f" make-frame "New Frame" :exit t)

  ("da" ace-delete-window "Select + Delete Window")
  ("dw" delete-window "Delete Window")
  ("db" kill-this-buffer "Kill Buffer")
  ("df" delete-frame "Delete Frame" :exit t)
  ("i" ace-maximize-window "Select and Maximize")
  ("b" ido-switch-buffer "Buffer")
  ("t" transpose-frame "Transpose")

  ("p" previous-buffer "Previous Buffer")
  ("n" next-buffer "Next Buffer")

  ("q" nil))

(evil-leader/set-key "w" 'hydra-window/body)

The showme hydra on C-c s is various things that pop up mini buffers for more information; kill rings, history, and that sort of thing, searching using surfraw10.

(install-pkgs '(sauron))
(defhydra hydra-showme ()
  ("s" sauron-toggle-hide-show "Sauron" :exit t)
  ("k" counsel-yank-pop "Kills" :exit t)
  ("b" ibuffer "Buffers" :exit t)
  ("m" list-marks "Marks" :exit t)
  ("c" calculator "Calculator" :exit t)
  ("g" eww "Browse" :exit t)
  ("C" display-time-world "Times" :exit t)
  ("w" wttrin "Weather" :exit t)
  ("d" calendar "Calendar" :exit t))

(evil-leader/set-key "s" 'hydra-showme/body)

The zoom hydra allows me to easily decrease and increase text size in a buffer-local fashion.

(defhydra hydra-zoom ()
  ("g" text-scale-increase "in")
  ("l" text-scale-decrease "out"))

(evil-leader/set-key "z" 'hydra-zoom/body)

System Integrations Hydra

(install-pkgs '(gnomenm))
(require 'kv)
(require 'gnomenm)
(defhydra hydra-sysint (:columns 4 :exit t)
  "Systems Integration"
  ("c" gnomenm-connect "Connect to Wifi")
  ("v" (async-shell-command "sudo /usr/sbin/openvpn --config ~/vpn/operator.ovpn --dev tun0" "*openvpn*") "Connect to VPN")
  ("d" gnomenm-disconnect "Go offline")
  ("w" (async-shell-command "sh ~/.screenlayout/work-walking.sh" "*arandr*") "Walking")
  ("p" (async-shell-command "sh ~/.screenlayout/work-pluggedin.sh" "*arandr*") "Plugged In"))

(evil-leader/set-key "i" 'hydra-sysint/body)

Dired

I'm trying to use dired instead of a graphical file manager like Dolphin.

When you have two dired buffers side by side, if you rename (R) or copy (C) in a dired buffer, with another dired buffer next to it, settings dired-dwim-target to non-nil will auto-fill the adjacent buffer in those operations.

(setq dired-dwim-target t)

Password Store

password-store is a simple interface to search and yank things out of pass11 my no-nonsense GPG based password manager. It uses Helm which makes it really easy to filter and search for the things I want to yank.

(install-pkgs '(password-store))
(require 'password-store)
rpm-install pass
deb-install pass

We default to long-ass keys for the generator when using password-store-generate.

(setq password-store-password-length 24)

And a Hydra to bind them to easy keybindings.

(defhydra hydra-password-store (global-map "C-c a")
  "
_c_: copy,      _e_: edit,   _n_: new
_s_: Work SAML, _p_: Work PD
"
  ("s" (lambda () (interactive) (password-store-copy "Work/saml")))
  ("p" (lambda () (interactive) (password-store-copy "Work/pagerduty")))
  ("c" password-store-copy)
  ("e" password-store-edit)
  ("n" password-store-generate))

Emacs Appearance

I choose to hide the tool bar chrome by default because I don't feel like they're very useful. I also disable scrollbars and menu bars; the modeline contains the buffer's progress, and I don't actually use the scrollbar to navigate. This leaves a tiny bit more room for the fringe and the buffers, which I consider a win.

(menu-bar-mode 0)
(when (functionp 'tool-bar-mode) (tool-bar-mode 0))
(scroll-bar-mode 0)
(column-number-mode -1)
(line-number-mode -1)

I don't disable visual-line-mode any more and am using auto-fill-mode in text-mode buffers to make them legible. The way I figure it, in prog-mode I should be keeping my code concise enough to not need a wrap, and if it does I should be thinking better about it.

I wrap somewhat arbitrarily at 100 columns; 80 is too wide for vertical-splits and is too narrow to be useful. 100 provides a good, legible length and is not too narrow.

(setq-default fill-column 100)
(add-hook 'text-mode-hook 'auto-fill-mode)
(diminish 'auto-fill-function "F")

I have my own color theme called "fireside" which used to be a red and white on black theme, but has slowly morphed in to a low-contrast grayscale theme which I waffle on quite a bit. Right now, I'm actually using Cyberpunk.

(add-hook 'after-cce-hook (lambda ()
                            (when (file-exists-p custom-file)
                              (load custom-file))
                            (add-to-list 'custom-theme-load-path (expand-file-name "~/.emacs.d/cce/vendor"))
                            (load-theme 'fireside)))
(install-pkgs '(cyberpunk-theme))
(add-hook 'after-cce-hook (lambda ()
                            (when (file-exists-p custom-file)
                              (load custom-file))
                            (load-theme 'cyberpunk)))
(set-frame-parameter (selected-frame) 'alpha '(100 100))
(add-to-list 'default-frame-alist '(alpha 100 100))

We put some properties to disable warning about certain obvious features, namely narrowing, which I use when refactoring code.

(add-hook 'after-cce-hook (lambda ()
                            (put 'narrow-to-page 'disabled nil)
                            (put 'narrow-to-region 'disabled nil)))

Right now, I am using Fira Mono for my code, with a fallback set for Deja Vu Sans if Fira isn't installed. I find Fira to be a nice font, easy on the eyes and scales up and down well. In terms of scaling, I have my font set for text-mode things to be derived from Fira Sans instead of Fira Mono, which I make a bit larger than the normal, because it doesn't quite feel as heavy, especially in the whitespace.

(add-hook 'prog-mode-hook (lambda () (variable-pitch-mode -1)))
(add-hook 'text-mode-hook (lambda () (variable-pitch-mode 1)))

(defun cce/set-font-scale (size)
  (interactive "nWhat font size do you want? ")
  (set-face-attribute 'mode-line nil :inherit 'default :height (+ 10 size))
  (eval-after-load "org"
    (set-face-attribute 'org-block nil :inherit 'fixed-pitch))
  (eval-after-load "linum"
    (lambda () (set-face-attribute 'linum nil :inherit 'default :height size)))
  (set-face-attribute 'variable-pitch nil :family "Fira Sans Regular" :height (+ 10 size))
  (cond
   ((find-font (font-spec :family "Fira Mono"))
    (progn
      (set-face-attribute 'default nil :family "Fira Mono" :weight 'light :height size)
      (set-face-attribute 'fixed-pitch nil :family "Fira Mono" :inherit 'default)))
   ((find-font (font-spec :family "DejaVu Sans Mono"))
    (progn
      (set-face-attribute 'default nil :family "DejaVu Sans Mono" :weight 'light :height size)
      (set-face-attribute 'fixed-pitch nil :family "DejaVu Sans Mono" :inherit 'default)))))

(defun cce/external-display-connected ()
  (let ((xrandr-buffer (get-buffer-create " *xrandr*")))
    (shell-command "xrandr | grep DP | grep '\sconnected'" xrandr-buffer)
    (not
     (string-blank-p
      (with-current-buffer xrandr-buffer
        (buffer-string))))))

(if (or (cce/external-display-connected)
        (equal (getenv "HOSTNAME") "work"))
    (add-hook 'after-cce-hook (lambda () (cce/set-font-scale 150)))
  (add-hook 'after-cce-hook (lambda () (cce/set-font-scale 105))))

I explicitely disable some common modes that I don't think should always be enabled. Prose has no use for line numbers. Whitespace mode should only turn on for certain buffers. I do this explicitely because it seems like Emacs turns some things on by default.

(global-linum-mode 0)
(global-whitespace-mode 0)
(global-hl-line-mode 0)
(hl-line-mode 0)
(add-hook 'before-save-hook 'delete-trailing-whitespace)

Enable hl-line-mode for certain modes, primarly ones where I am doing read-only actions such as moving through Gnus folders or Messages. Interestingly, I also like having it turned on in Eshell and ERC modes because I find that these are sorts of buffers which I do a lot of reading in, and being able to move along logfile and keep track of where I am like that is quite nice.

(defun rrix/enable-hl-line ()
       (hl-line-mode 1))

(mapc (function (lambda (mode)
                 (message (symbol-name mode))
                 (add-hook mode 'rrix/enable-hl-line)))
      '(erc-mode-hook
        mingus-mode-hook
        gnus-group-mode-hook
        gnus-summary-mode-hook
        org-agenda-mode-hook
        eshell-mode-hook))

Lastly, some color customizations that I make since I think cyberpunk-theme just barely misses the mark here:

(require 'hl-line)

I'm starting to turn off transient-mark mode12 and move towards a model of actually treating the Mark as more than just a selection tool, and towards treating it as a navigation tool. In light of that, I'm going to start disabling transient-mark-mode and only relying on visible-mark-mode for showing the mark, which alleviates most of the issues you see with disabling transient-mark-mode

(install-pkgs '(visible-mark))
(require 'visible-mark)
(defun rrix/disable-transient-mark ()
  ""
  (transient-mark-mode -1)
  (visible-mark-mode))
(add-hook 'text-mode-hook 'rrix/disable-transient-mark)
(add-hook 'prog-mode-hook 'rrix/disable-transient-mark)

(setq visible-mark-max 1)
(setq visible-mark-faces '(visible-mark-active))

(set-face-attribute 'visible-mark-active nil :background nil)
(set-face-attribute 'visible-mark-active nil :foreground nil)
(set-face-attribute 'visible-mark-active nil :inherit 'cursor)

mark-tools provides a buffer that allows you to view the global and local mark rings and navigate to them with RET. This is used in hydra-showme.

(install-pkgs '(mark-tools))

browse-kill-ring+ is the enhanced version of browse-kill-ring, which gives you a buffer to paste in an arbitrary entry in your kill ring to the active buffer. The enhanced version adds some nice keybindings and takes up little more space than the regular version.

(install-pkgs '(browse-kill-ring+))

Ivy and Counsel

Ivy is a completion/selection framework similar to helm, but probablymaybe a bit more performant. It is a lightweight core, with a bunch of nice addons built in to the counsel and swiper packages. My system will have all of them installed, and then bound to useful key bindings.

(install-pkgs '(ivy
                ivy-hydra
                imenu-anywhere
                flyspell-correct-ivy
                counsel-projectile
                counsel
                swiper))
(ivy-mode 1)
(diminish 'ivy-mode)
(setq magit-completing-read-function 'ivy-completing-read)
(setq projectile-completion-system 'ivy)
(global-set-key (kbd "M-u") 'ivy-resume)

(global-set-key (kbd "C-h f") 'counsel-describe-function)
(global-set-key (kbd "C-h v") 'counsel-describe-variable)
(global-set-key (kbd "C-h w") 'counsel-descbinds)

(defhydra hydra-help (:columns 4)
  ("v" counsel-describe-variable "Desc Variable")
  ("w" counsel-descbinds "Desc Bindings")
  ("s" describe-syntax "Desc Syntax")
  ("P" describe-package "Desc Package")
  ("o" describe-symbol "Desc Symbols")
  ("m" describe-mode "Desc Mode")
  ("k" describe-key "Desc Keys")
  ("f" counsel-describe-function "Desc Functions")
  ("d" apropos-documentation "Apropos Docs")
  ("a" apropos-command "Apropos Commands")
  ("j" cce/man-at-point "Man for symbol at point.")
  ("r" info-emacs-manual "Emacs Info Pages")
  ("p" finder-by-keyword "Find Package by Keyword")
  ("l" view-lossage "Recent Commands")
  ("i" info "Info Index")
  ("e" view-echo-area-messages "*Messages*")
  ("b" describe-bindings "List Bindings")
  ("S" info-lookup-symbol "Lookup Symbol in Info")
  ("q" nil))
(evil-leader/set-key "h" 'hydra-help/body)

(global-set-key (kbd "C-x C-f") 'counsel-find-file)

(global-set-key (kbd "C-x b") 'ivy-switch-buffer)
(evil-leader/set-key "b" 'ivy-switch-buffer)

(setq counsel-find-file-at-point t)
(setq counsel-find-file-ignore-regexp
      (concat
       ;; file names beginning with # or .
       "\\(?:\\`[#.]\\)"
       ;; file names ending with # or ~
       "\\|\\(?:[#~]\\'\\)"))

(with-eval-after-load 'org
  (define-key org-mode-map (kbd "C-c C-q") #'counsel-org-tag))
(with-eval-after-load 'org-agenda
  (define-key org-agenda-mode-map (kbd "C-c C-q") #'counsel-org-tag-agenda))
(define-key evil-normal-state-map (kbd "/") 'swiper)
(evil-leader/set-key "SPC" 'counsel-M-x)
    (defun ivy--matcher-desc () ; used in `hydra-ivy'
      (if (eq ivy--regex-function
              'ivy--regex-fuzzy)
          "fuzzy"
"ivy"))

(defhydra hydra-ivy (:hint nil :color pink)
  "
^^_,_        _f_ollow      occ_u_r      _g_o          ^^_c_alling %-7s(if ivy-calling \"on\" \"off\")      _w_/_s_/_a_: %-14s(ivy-action-name)
_p_/_n_      _d_one        ^^           _i_nsert      ^^_m_atcher %-7s(ivy--matcher-desc)^^^^^^^^^^^^      _C_ase-fold: %-10`ivy-case-fold-search
^^_._        _D_o it!      ^^           _q_uit        _<_/_>_ shrink/grow^^^^^^^^^^^^^^^^^^^^^^^^^^^^      _t_runcate: %-11`truncate-lines
"
  ;; arrows
  ("," ivy-beginning-of-buffer) ; default h
  ("p" ivy-previous-line) ; default j
  ("n" ivy-next-line) ; default k
  ("." ivy-end-of-buffer) ; default l
  ;; quit ivy
  ("q" keyboard-escape-quit :exit t) ; default o
  ("C-g" keyboard-escape-quit :exit t)
  ;; quit hydra
  ("i" nil)
  ("C-o" nil)
  ;; actions
  ("f" ivy-alt-done :exit nil)
  ;; Exchange the default bindings for C-j and C-m
  ("C-m" ivy-alt-done :exit nil) ; RET, default C-j
  ("C-j" ivy-done :exit t) ; default C-m
  ("d" ivy-done :exit t)
  ("g" ivy-call)
  ("D" ivy-immediate-done :exit t)
  ("c" ivy-toggle-calling)
  ("m" ivy-toggle-fuzzy)
  (">" ivy-minibuffer-grow)
  ("<" ivy-minibuffer-shrink)
  ("w" ivy-prev-action)
  ("s" ivy-next-action)
  ("a" ivy-read-action)
  ("t" (setq truncate-lines (not truncate-lines)))
  ("C" ivy-toggle-case-fold)
  ("u" ivy-occur :exit t))

(define-key ivy-minibuffer-map  (kbd "C-m")
  'ivy-alt-done)                                        ; RET, default C-j
(define-key ivy-minibuffer-map (kbd "C-j")
  ' ivy-done)                        ; default C-m
(define-key ivy-minibuffer-map (kbd "C-S-m")
  'ivy-immediate-done)
(define-key ivy-minibuffer-map (kbd "C-t") 'ivy-toggle-fuzzy)
(define-key ivy-minibuffer-map (kbd "C-o") 'hydra-ivy/body)

Text Manipulation

My rules of choice: 4 space tabstops, no tabs.

(setq-default indent-tabs-mode nil
              tab-width 4)

expand-region is a neat little guy that lets you grow your region just by calling it. Pretty self explanatory, but it's a great tool to have in your belt. I bind it over the mostly-useless M-o.

(install-pkgs '(expand-region))
(global-set-key (kbd "M-o") 'er/expand-region)

Undo Tree

Undo Tree is a neat visualization and navigation tool for walking through a tree history.

(install-pkgs '(undo-tree))
(require 'undo-tree)
(global-undo-tree-mode 1)
(diminish 'undo-tree-mode "")

Pasting and Sharing Code

scpaste13 is a neat package that takes a region or buffer, renders it to HTML, and uses scp to push it somewhere. I have it wired up to Hydra to paste to either work shared HTML space or to my personal site.

(install-pkgs '(scpaste))
(defhydra hydra-scpaste (global-map "C-c j")
  ("p" (let ((scpaste-http-destination "http://whatthefuck.computer/~rrix/p/")
             (scpaste-scp-destination "li01.rix.si:/home/rrix/public_html/p/"))
         (call-interactively 'scpaste-region)))
  ("w" (let ((scpaste-http-destination scpaste-work-http)
             (scpaste-scp-destination scpaste-work-scp))
         (call-interactively 'scpaste-region))))

Buffer Navigation

Moving around with Avy

Avy is the framework that powers ace-jump but it's a hell of a lot more now; it's a generic framework for moving about, and is pretty incredibly powerful.

(install-pkgs '(avy avy-zap ace-link))
(defhydra hydra-avy (global-map "M-i" :color teal)
  ("c" avy-goto-char "char")
  ("w" avy-goto-word-0 "word")
  ("l" avy-goto-line "line")
  ("p" avy-pop-mark "pop")
  ("o" ace-link-info "url"))

(ace-link-setup-default)
(evil-leader/set-key "a" 'hydra-avy/body)

Focusing, Dimming and Highlighting

Focusing on a bit of code or text comes in two varieties for me; one is the highlighting of a symbol, either manually or with auto-highlight-symbol. The other is the opposite, dimming everything but exactly what I'm trying to grok right now.

This little fellow, auto-highlight-symbol, tags all occurrences of the symbol under the cursor.

(install-pkgs '(highlight-symbol auto-highlight-symbol))
(require 'highlight-symbol)

Focus Mode14 provides a way to limit what you're focusing on to a function or sentence at a time. This makes it easy for me to read

(install-pkgs '(focus))

And we define a hydra to make them accessible, via C-c h.

(defhydra hydra-highlight-symbol (global-map "C-c h")
  "
Highlight -----------> Dim
_h_: Highlight at Point _f_: focus-mode
_j_: Previous Symbol    _r_: focus-ro
_k_: Next Symbol
_d_: Clear All Symbols
_a_: Toggle AHS
"
  ("h" highlight-symbol-at-point)
  ("j" highlight-symbol-prev)
  ("k" highlight-symbol-next)
  ("d" (progn (highlight-symbol-remove-all)))
  ("a" auto-highlight-symbol-mode)
  ("f" focus-mode)
  ("r" focus-read-only-mode))

Footnotes:

Author: Ryan Rix

Created: 2017-03-24 Fri 22:05

Validate XHTML 1.0