going-flying.com gemini git repository
cbb7ce635bbb441aab48008196477bb40cd20125 - Matthew Ernisse - 1620156270
new post
diff --git a/users/mernisse/articles/23.gmi b/users/mernisse/articles/23.gmi new file mode 100644 index 0000000..474f549 --- /dev/null +++ b/users/mernisse/articles/23.gmi @@ -0,0 +1,142 @@ +--- +Title: Shell Quickie: Trying Emacs, dotfile update +Date: 2021-05-04 14:58 + +## Managing Environments +A tremendous has been written (both in natural languages and in software) about +managing the various configuration files that follow you and I around systems. +This is usually a *nix problem but as more and more tools migrate over to +Windows I suspect it will become essentially pervasive. In my case I wrote a +function in my .profile that relies on a git post-receive hook to allow +automatic updates of my .profile and several associated files. I find this +really helpful as it also means I can bootstrap my environment this way (it handles +absence as a signal to update). + +=> 14.gmi Breakdown of my .profile + +## In Comes Emacs +For some reason I decided recently to take another look at Emacs. I have +several thoughts about that but I'll leave that for another time. The +reality of giving Emacs a fair shake at becoming a staple of my workflow +is managing the sprawl of configuration files that seem to grow around it. +I tried simply adding them to my existing dotfiles management but that got +cumbersome quickly so I redesigned it. If you look at the breakdown of +my .profile that I did you'll see that it operates on a list of files -- +this seemed perfectly fine, but the difficult part was the git hook that +generated the checksum files. It seemed a bit insane to vomit out more and +more checksum files as I expanded the files under management so I decided +to use just one. That changed my git post-receive hook to the following. + +### post-receive hook +``` +#!/bin/sh + +set -e + +SHADIR="/var/www/www.ub3rgeek.net" +GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) + +calculate_hash() +{ + if [ -z "$1" ] || [ -z "$2" ]; then + echo "usage: calculate_hash filename rev" + return 127 + fi + + git show "$2:$1" | sha256sum | awk '{ print $1 }' +} + +if [ -z "$GIT_DIR" ]; then + echo >&2 "fatal: post-receive GIT_DIR not set" + exit 1 +fi + +if [ ! -f "$SHADIR/dotfiles.lst" ]; then + echo > "$SHADIR/dotfiles.lst" +fi + +while read oldrev newrev refname; do + for fn in $(git show --pretty=oneline --name-only "$newrev" | sed 1d) + do + csums=$(sed '#${fn}#d' "$SHADIR/dotfiles.lst") + csums="${csums}\n${fn}:$(calculate_hash "$fn" "$newrev")" + echo "$csums" > "$SHADIR/dotfiles.lst" + echo "Updated dotfiles.lst for $fn" + done +done + +``` + +The clever bit here is that this uses sed to delete the existing checksum +from the file prior to re-computing it. This lets me only compute +checksums for updated files instead of regenerating them all every time. +Now that we have the list of checksums available I just updated the dotfiles +function in my .profile. + +### .profile update +``` +dotfiles() +{ + # dotfiles to pull in format src:dst <whitespace> + local _dotfiles=" + ssh_config:.ssh/config + tmux.conf:.tmux.conf + gitconfig:.gitconfig + githelpers:.githelpers + emacs.d/init.el:.emacs.d/init.el + emacs.d/highlight-beyond-fill-column.el:.emacs.d/highlight-beyond-fill-column.el + emacs.d/mernisse-new-blog-post.el:.emacs.d/mernisse-new-blog-post.el + " + + local _gitpath="git/?p=dotfiles.git;a=blob_plain;hb=HEAD;f=" + local _url="https://ssl.ub3rgeek.net" + + local csum dest fn fl src + + fl=$(fetch_contents "https://ssl.ub3rgeek.net/dotfiles.lst") + if [ -z "$fl" ]; then + echo "Failed to retrieve file list" + return + fi + + # each line in the dotfiles.lst will refer to a single file in the + # repository. The format will be: + # file:sha256sum + # + # the _dotfiles parameter is in the format of: + # repo_file:local_file + for entry in $fl; do + csum="${entry##*:}" + dest="" + fn="${entry%%:*}" + + for dotfile in $_dotfiles; do + if [ "${dotfile%%:*}" = "$fn" ]; then + dest="${dotfile##*:}" + break + fi + done + + if [ -z "$dest" ]; then + continue + fi + + if ! check_hash "$HOME/$dest" "$csum"; then + echo "Updating $dest" + fetch_contents "${_url}/${_gitpath}${src}" "$HOME/$dest" + fi + done +} +``` + +All this does it iterate through the files in the remote +checksum file, compare to the list of files that we care about +and then if the stored hash is different than the hash of the +file on disk then we replace it. + +## Results +So far it has worked swimmingly in managing the contents of my +.emacs.d directory, which means that I have the same environment +on the several computers that I regularly use. It doesn't require +any special packages and works on Debian, OpenBSD and macOS +(likely any *nix / BSD given it's all pure POSIX shell).