My girlfriend took this photo of a striking tree in Seoul yesterday.
Something that I really enjoy is making old hardware far more useful than new, off-the-shelf laptops and workstations by means of carefully configured installations of Debian Stable. To me there is great aesthetic value in making a system useful in this way, that puts to shame the aesthetic values the computer industry would have us adopt: faster, shinier, newer.
Here is my desk here in Tucson. I arrived with my 2009 laptop, which you can see on the right. I bought this Amazon Basics keyboard for a few dollars. And my housemate gave me use of the mouse and monitor. The pillows are pile of books are a new attempt to improve the ergonomic situation. I can get so much done at this workstation!
When I video call my girlfriend over in South Korea, we like to watch films together and sometimes play Minecraft. And for simply calling, watching videos and playing Minecraft she is continually running into problems with her fancy laptop which is only a couple of years old and has hardware much more capable than my laptop. Why is it that Windows installations slow down so dramatically over time? I’ve been told that it’s partly a result of the NTFS file system not scaling well. She’s planning to take it to a Samsung shop where they’ll soup it up for her. Of course it’ll still be less useful than my Debian installation is.
Back in Europe, my grandfather is using an ancient laptop from the mid-2000s to read and write e-mails and browse the web. On that machine I set up Debian with the LXDE desktop environment. He collects his e-mail by the POP3 protocol, and reads it in Mozilla Thunderbird. In the UK, my mother is using my desktop workstation that I built for myself back in 2008 when I played a lot of video games. I added a second hard drive for her and installed Debian with the Cinnamon desktop environment. She connects into her university’s LAN using a remote desktop client.
The most fun thing about this is the software I use to manage these configurations: Propellor. My computer in Tucson, the ancient laptop deep in the Limousin, and the workstation in my parent’s house are all configured to periodically pull from a central git repository which contains a statement of how they should all be configured (stated in fairly terse Haskell). (The host of this central git repo also updates its own configuration in the same way. And this host can be a VPS provided by any standard VPS provider because the configuration as a git host can be very quickly applied to a fresh host.) I cryptographically sign commits to the git repo and once this signature is verified, new configuration directives are applied. Here is an (edited) sample of the full configuration:
-- #### Sean's desktop workstation
zephyrSda :: Host
zephyrSda = spwWorkstation "zephyr.silentflame.com" "i386"
& "/etc/timezone" `File.hasContent` ["Europe/London"]
-- Default to booting from sdb.
& "/etc/default/grub" `File.containsLine` "GRUB_DEFAULT=2"
`onChange` cmdProperty "update-grub" []
`describe` "GRUB default entry /dev/sdb"
-- SSH in only from LAN
& "/etc/hosts.allow" `File.containsLine` "sshd: 192.168.0.0/255.255.255.0"
`describe` "ssh login permitted from LAN"
& "/etc/hosts.allow" `File.containsLine` "sshd: localhost"
`describe` "ssh login permitted from localhost"
& "/etc/hosts.deny" `File.containsLine` "sshd: ALL"
`describe` "ssh login denied from elsewhere"
-- #### Sean's desktop workstation, secondary HDD
zephyrSdb :: Host
zephyrSdb = spwMachine "zephyr.abbeydaled.local" (Stable "jessie") "i386"
& "/etc/timezone" `File.hasContent` ["Europe/London"]
& User.accountFor (User "hschra")
& LightDM.autoLogin (User "hschra")
& userStandardGroups (User "hschra")
-- #### Sean's laptop
artemis :: Host
artemis = spwWorkstation "artemis.silentflame.com" "i386"
& "/etc/timezone" `File.hasContent` ["America/Phoenix"]
-- #### Old laptop deep in the Limousin
-- Under Wheezy, quentin was known as 'quentinou'
quentin :: Host
quentin = spwMachine "quentin.silentflame.com" (Stable "jessie") "i386"
& "/etc/timezone" `File.hasContent` ["Europe/Paris"]
& User.accountFor (User "pawhitton")
& userStandardGroups (User "pawhitton")
& LightDM.autoLogin (User "pawhitton")
& quietGrub
& Apt.installed ["icedove"]
A few years ago I made an effort to dismantle my little empire of computers all talking to each other because I was sinking too much time into maintaining and fiddling with their configurations. I moved all my stuff to a hosting provider as part of this effort. Now I’ve gone back the other way, but with a new purpose in mind. It’s about the lessons that can be learnt and taught by doing more with less in the face of contemporary consumerism.
Rationale
It turns out that the Emacs package management system, package.el, doesn’t perform SSL certificate verification without some fairly involved wrangling. My Emacs configuration is something that I want to be able to clone and run on systems where it might be a real pain to perform the wrangling needed to ensure packages may be downloaded securely over encrypted HTTP.
Another issue with downloading packages from MELPA, the most popular repository for package.el, is that some packages are pulled into that repository from the EmacsWiki over unencrypted HTTP.
A further problem with MELPA is that it moves very fast, and new versions of packages that are not compatible with each other or perhaps your configuration means that you can find yourself with a broken editor in the middle of trying to get work done. To deal with this issue there is MELPA Stable, which contains hopefully-stable releases of packages that are more likely to be compatible with other packages. The problem is that many packages are in MELPA but not MELPA Stable because the author has not tagged any releases, and of the packages that are in MELPA Stable, many require newer versions of their dependencies than the versions of those dependencies available in MELPA Stable.
In short, package.el and MELPA are not dpkg, apt and the Debian Stable archive. Hopefully someday they will be. But for the moment, I don’t want to manage my Emacs packages this way.
Managing packages as git subtrees
An alternative is to manage package repositories as
git subtrees.
Assuming that your ~/.emacs.d/
is kept in a git repository, we can
run
$ cd ~/.emacs.d
$ git subtree add --squash -P pkg/magit https://github.com/magit/magit 2.3.0
and then Magit becomes available in ~/.emacs.d/pkg/magit
. The
following lisp will add all the dirs ~/.emacs.d/pkg/*
and
~/.emacs.d/pkg/*/lisp
to your load-path; you can modify this by
changing the variable globs
:
;;;; ---- package management ----
;; be sure not to load stale bytecode-compiled lisp
(setq load-prefer-newer t)
;; this is where all subtree packages are
(defconst emacs-pkg-dir (concat user-emacs-directory "pkg"))
;; load up f, and its dependencies s and dash, so we can use `f-glob'
;; and `f-join'
(dolist (pkg '("f.el" "dash.el" "s.el"))
(add-to-list 'load-path (concat emacs-pkg-dir "/" pkg)))
(require 'f) (require 's) (require 'dash)
;; helper function
(defun expand-all-globs (root globs)
(let ((do-glob (lambda (glob) (f-glob (f-join root glob)))))
(apply 'nconc (mapcar do-glob globs))))
;; now add all my pkg lisp directories
(let* ((globs '("*" "*/lisp"))
(dirs (expand-all-globs emacs-pkg-dir globs)))
(dolist (dir dirs)
(when (file-directory-p dir)
(add-to-list 'load-path dir))))
;; finally put my own site-lisp at the front of `load-path'
(add-to-list 'load-path (concat user-emacs-directory "site-lisp"))
;; we will use use-package to load everything else
(require 'use-package)
When you want to update to a new version of a package,
$ cd ~/.emacs.d
$ git subtree pull --squash -P pkg/magit https://github.com/magit/magit 2.3.1
Note:
- This commits the source code of Magit to your
~/.emacs.d/
git repository. So when you clone your config to a new machine, all your packages will already be there and Emacs won’t have to download them (potentially insecurely). - There’s no dependency management. You’ll have to add subtrees for every dependency. At present, if you don’t update your packages often, this is not too onerous.
- You should run
C-u 0 M-x byte-recompile-directory ~/.emacs.d/pkg RET
periodically (normally, package.el would do this for you).
Shell script
Here is a shell script to reduce typing in adding and updating
subtrees. It also logs git repository clone URIs and versions fetched
to a file ~/.emacs.d/pkg/subtrees
so that you can find the URI to
use when you want to do an update:
$ cat ~/.emacs.d/pkg/subtrees
https://github.com/magit/magit 2.3.1
https://github.com/lewang/flx v0.6.1
Use it like this:
$ emacs-pkg-subtree add https://github.com/magit/magit 2.3.0
$ emacs-pkg-subtree pull https://github.com/magit/magit 2.3.1
#!/bin/bash
# emacs-pkg-subtree --- manage Emacs packages as git subtrees in your dotfiles git repo
# Author/maintainer : Sean Whitton <spwhitton //ANTI-SPAM \\ spwhitton.name>
# Instructions for use : https://spwhitton.name/blog/entry/emacs-pkg-subtree/
# Copyright (C) 2015 Sean Whitton. Released under the GNU GPL 3.
DEST="$HOME/.emacs.d/pkg"
set -e
if [ "$3" = "" ]; then
echo "$(basename $0): usage: $(basename $0) add|pull git_clone_uri ref" >&2
exit 1
fi
cd $DEST
op="$1"
uri="$2"
repo="$(basename $2)"
pkg="${repo%%\.git}"
ref="$3"
top="$(git rev-parse --show-toplevel)"
prefix="${DEST##$top/}/$pkg"
cd $top
clean="$(git status --porcelain)"
if [ ! -z "$clean" ]; then
echo "commit first" >&2
exit 1
fi
if [ "$op" = "add" ]; then
if [ ! -e "$DEST/$pkg" ]; then
git subtree add --squash --prefix $prefix $uri $ref
echo "$uri $ref" >> $DEST/subtrees
git add $DEST/subtrees
git commit -m "updated Emacs packages record"
else
echo "you already have a subtree by that name" >&2
exit 1
fi
elif [ "$op" = "pull" ]; then
git subtree pull --squash --prefix $prefix $uri $ref
sed -i -e "s|^${uri} .*$|${uri} ${ref}|" $DEST/subtrees
git add $DEST/subtrees
git commit -m "updated Emacs packages record"
else
echo "$(basename $0): usage: $(basename $0) add|pull git_clone_uri ref" >&2
exit 1
fi