@ 260802.2

260615.3 · 98792b1dab553d0ac9a206e40c10643d28cc57f5

*: a near complete overhaul of some fundamental components

Particularly, the `video` script and how Wingo boots. We consolidate much here and avoid separate init scripts for each machine. They were a huge pain to maintain and frequently got out of sync from one another.

We do still have some case analysis, which is annoying. But a foolish consistency is the hobgoblin of little minds.

reading declarations

.aliasrc

@@ -69,8 +69,8 @@ nh() { }  reenv() {-  ssh_reagent-  eval $(tmux show-environment -s | rg DISPLAY)+  relink-ssh-auth-sock+  eval $(tmux show-environment -s DISPLAY) }  ssh_reagent() {@@ -149,5 +149,5 @@ o() {  p() {   pattern="$1"-  pgrep -a "$1" | rg "$1"+  pgrep -a "$pattern" | rg "$1" }

.codex/config.toml

@@ -29,6 +29,9 @@ trust_level = "trusted" [projects."/home/andrew/code/rust/regex/master"] trust_level = "trusted" +[projects."/home/andrew"]+trust_level = "trusted"+ [tui.model_availability_nux] "gpt-5.5" = 4 

.config/.gitignore

@@ -14,9 +14,11 @@ /coc /Code /Code\ -\ OSS+/composer /configstore /dconf /dolphinrc+/dolphin.kmessagebox /Element /emaildefaults /enchant@@ -66,8 +68,10 @@ /mirage /monitors.xml* /mozilla+/naps2 /nautilus /nitrogen+/nushell /okularpartrc /okularrc /op@@ -88,11 +92,12 @@ /session /snes9x /sniffnet-/systemd+/spotify /totem /trashrc /Unknown Organization /user-dirs.*+/variety /vivaldi /vivaldi-snapshot /vlc

.config/ag/alacritty/bart/alacritty.toml

@@ -6,7 +6,7 @@ TERM = "xterm-256color"  [font]-size = 7.0+size = 7.5  [font.normal] family = "Hack Nerd Font"

.config/ag/gohead/bart/config.ini

@@ -1,3 +1,3 @@ [Monitors]-laptop = eDP-1+main = eDP-1 topleft = DP-3

.config/ag/gohead/flanders/config.ini

@@ -1,5 +1,5 @@ [Monitors]-tv = DP1+main = DP1 -[tv]+[main] mode = 1920x1080

.config/ag/gohead/milhouse/config.ini

@@ -1,2 +1,2 @@ [Monitors]-tv = DP1+main = DP1

.config/ag/gohead/otto/config.ini

@@ -1,5 +1,5 @@ [Monitors]-tv = DP1+main = DP1 -[tv]+[main] mode = 1920x1080

.config/ag/gohead/wiggum/config.ini

@@ -1,5 +1,5 @@ [Monitors]-tv = DP1+main = DP1 -[tv]+[main] mode = 1920x1080

.config/ag/startup/wingo-bart.xinitrc

deleted file mode 100755@@ -1,24 +0,0 @@-#!/bin/sh--set -x-set -e--startup-nitrogen --restore &-video laptop &-dunst &-nm-applet &-ag-blueberry-tray &-(volume mute ; volume mute) &-restart-compositor &-autobright &--# An agent to make the fingerprint sensor work.-/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1 &-# Run the 1password tray.-1password \-  --silent \-  --force-device-scale-factor=1.5 \-  --log=debug &> /tmp/1password.log &--exec run-wingo-environment

.config/ag/startup/wingo-duff.xinitrc

deleted file mode 100755@@ -1,8 +0,0 @@-#!/bin/sh--startup &-media-video left middle right-nitrogen --restore-dunst &-exec run-wingo-environment

.config/ag/startup/wingo-flanders.xinitrc

deleted file mode 100755@@ -1,14 +0,0 @@-#!/bin/sh--startup &-sleep 2-video tv-nitrogen --restore-idesk &-no-blanking-loop &-dunst &-nm-applet &-restart-compositor &-(sleep 2 && video tv) &--exec bash -c '(dbus-launch run-wingo ; (while true; do sleep 1000; done))'

.config/ag/startup/wingo-kang.xinitrc

deleted file mode 100755@@ -1,13 +0,0 @@-#!/bin/sh--startup &-nitrogen --restore-(-  sleep 0.3-  video laptop-  dunst &-  nm-applet &-  blueberry-tray &-  (volume mute ; volume mute) &-) &-exec bash -c '(dbus-launch run-wingo ; (while true; do sleep 1000; done))'

.config/ag/startup/wingo-milhouse.xinitrc

deleted file mode 100755@@ -1,12 +0,0 @@-#!/bin/sh--startup &-nitrogen --restore-idesk &-no-blanking-loop &-dunst &-nm-applet &-restart-compositor &-(sleep 1 && video tv) &--exec bash -c '(dbus-launch run-wingo ; (while true; do sleep 1000; done))'

.config/ag/startup/wingo-nas2.xinitrc

deleted file mode 100755@@ -1,15 +0,0 @@-#!/bin/sh--startup-video main &-dunst &-ag-blueberry-tray &-restart-compositor &--# Run the 1password tray.-1password \-  --silent \-  --force-device-scale-factor=1.5 \-  --log=debug &> /tmp/1password.log &--exec run-wingo-environment

.config/ag/startup/wingo-otto.xinitrc

deleted file mode 100755@@ -1,30 +0,0 @@-#!/usr/bin/env zsh--# startup &-# sleep 2-# video tv-# nitrogen --restore-# gkrellm &-# idesk &-# no-blanking-loop &-# dunst &-# nm-applet &-# restart-compositor &-#-# exec bash -c '(dbus-launch run-wingo ; (while true; do sleep 1000; done))'--startup &-nitrogen --restore &-video laptop &-dunst &-nm-applet &-ag-blueberry-tray &-restart-compositor &--# Run the 1password tray.-1password \-  --silent \-  --force-device-scale-factor=1.5 \-  --log=debug &> /tmp/1password.log &--exec run-wingo-environment

.config/ag/startup/wingo-wiggum.xinitrc

deleted file mode 100755@@ -1,15 +0,0 @@-#!/bin/sh--startup &-sleep 2-video tv-nitrogen --restore-gkrellm &-idesk &-no-blanking-loop &-dunst &-nm-applet &-restart-compositor &-(sleep 2 && video tv) &--exec bash -c '(dbus-launch run-wingo ; (while true; do sleep 1000; done))'

.config/ag/startup/wingo.xinitrc

@@ -1,31 +1,3 @@-#!/bin/sh+#!/usr/bin/env bash -# vim: ft=sh sw=2 ts=2 sts=2--base="$HOME/.config/ag/startup"-case "$(myhostname)" in-  bart)-    exec "$base/wingo-bart.xinitrc" &> /tmp/run-wingo-environment.log-    ;;-  kang)-    exec "$base/wingo-kang.xinitrc"-    ;;-  nas2)-    exec "$base/wingo-nas2.xinitrc"-    ;;-  duff)-    exec "$base/wingo-duff.xinitrc"-    ;;-  flanders)-    exec "$base/wingo-flanders.xinitrc"-    ;;-  milhouse)-    exec "$base/wingo-milhouse.xinitrc"-    ;;-  wiggum)-    exec "$base/wingo-wiggum.xinitrc"-    ;;-  otto)-    exec "$base/wingo-otto.xinitrc"-    ;;-esac+x11-init &> /tmp/x11-init.log

.config/nvim/lua/lang.lua

@@ -204,7 +204,7 @@ vim.api.nvim_create_autocmd('FileType', {   end, }) vim.api.nvim_create_autocmd('FileType', {-  pattern = 'sh',+  pattern = {'sh', 'bash', 'zsh'},   callback = function()     spacing.space2()     vim.b.comment = '#'@@ -278,10 +278,3 @@ vim.api.nvim_create_autocmd('FileType', {     -- inoremap # X#   end, })-vim.api.nvim_create_autocmd('FileType', {-  pattern = 'zsh',-  callback = function()-    spacing.space4()-    vim.b.comment = '#'-  end,-})

.config/nvim/lua/options.lua

@@ -2,6 +2,11 @@ -- https://neovim.io/doc/user/lua.html#vim.hl -- https://neovim.io/doc/user/undo.html#undo-persistence +-- Enable project local configuration. Just drop in a `.nvim.lua` in a+-- repository root directory.+vim.o.exrc = true+vim.o.secure = true+ -- Show line numbers. vim.opt.number = true @@ -18,9 +23,29 @@ spacing.space2() -- the current one isn't saved. vim.opt.hidden = true +-- This makes neovim use OSC 52 escapes for clipboard integration. The nice+-- benefit here is that it doesn't require X11 forwarding. Yay!+-- vim.g.clipboard = 'osc52'++vim.g.clipboard = {+  name = 'OSC52 copy, xcp paste',++  copy = {+    ['+'] = require('vim.ui.clipboard.osc52').copy('+'),+    ['*'] = require('vim.ui.clipboard.osc52').copy('*'),+  },++  paste = {+    ['+'] = { 'xcp', '--output' },+    ['*'] = { 'xcp', '--output' },+  },++  cache_enabled = 0,+}+ -- This causes neovim to use the system clipboard for all yanking operations, -- instead of needing to use the '+' or '*' registers explicitly.-vim.opt.clipboard:append('unnamedplus')+vim.opt.clipboard = 'unnamedplus'  -- Always disable code folding. vim.opt.foldenable = false@@ -78,21 +103,3 @@ vim.opt.completeopt = 'noselect' -- [1]: https://old.reddit.com/r/neovim/comments/f0qx2y/automatically_reload_file_if_contents_changed/ -- [2]: https://github.com/neovim/neovim/issues/1936 vim.opt.autoread = true---- Make copy/paste from the unnamed register work well.--- I don't remember exactly why I need this.-if vim.fn.executable('xsel') == 1 then-  -- Adapted from runtime/autoload/provider/clipboard.vim.-  vim.g.clipboard = {-    name = 'myxsel',-    copy = {-      ['+'] = 'xsel --nodetach --input --clipboard',-      ['*'] = 'xsel --nodetach --input --primary',-    },-    paste = {-      ['+'] = 'xsel --output --clipboard',-      ['*'] = 'xsel --output --primary',-    },-    cache_enabled = 1,-  }-end

.config/systemd/user/.gitignore

new file mode 100644@@ -0,0 +1 @@+/default.target.wants

.config/systemd/user/mount-nas.service

new file mode 100644@@ -0,0 +1,20 @@+[Unit]+Description=Mount NAS+After=network-online.target++[Service]+Type=simple+Environment=PATH=/home/andrew/bin:/usr/local/bin:/usr/bin:/bin+ExecStart=/home/andrew/bin/mount-nas+ExecStop=/usr/bin/fusermount3 -u /m+Restart=on-failure+RestartSec=30++# Let the wrapper wait instead of failing quickly.+TimeoutStartSec=0++# Avoid tripping the limiter during transient boot weirdness.+StartLimitBurst=20++[Install]+WantedBy=default.target

.envrc

@@ -134,3 +134,7 @@ if is-mac; then   # Our TERM is typically tmux-256color. macOS doesn't seem to recognize it.   export TERM=xterm fi++# Bring in secret environment variables. These live in a separate encrypted+# file because I don't really want them to be public.+. "$HOME/.secretenv"

.gitattributes

@@ -1,3 +1,4 @@ /.ssh/config filter=git-crypt-home diff=git-crypt-home /.msmtprc filter=git-crypt-home diff=git-crypt-home /.secretastralrc filter=git-crypt-home diff=git-crypt-home+/.secretenv filter=git-crypt-home diff=git-crypt-home

.gitignore

@@ -34,6 +34,7 @@ /.emscripten_cache /.falcon.* /.fceux+/.fehbg /.fontconfig /.fzf* /.ghcup@@ -55,6 +56,8 @@ /.ICEauthority /.isheadless /.islaptop+/.islowerres+/.ismedia /.iswork /.java /.k5login

.idesktop/kids.lnk

@@ -3,6 +3,6 @@ table Icon   Icon: /usr/share/icons/oxygen/base/64x64/places/folder-blue.png   X: 502   Y: 62-  Command[0]: dolphin /m/media/kids+  Command[0]: dolphin --new-window /m/media/kids & end 

.idesktop/movies.lnk

@@ -3,6 +3,6 @@ table Icon   Icon: /usr/share/icons/oxygen/base/64x64/places/folder-blue.png   X: 402   Y: 62-  Command[0]: dolphin /m/media/movies+  Command[0]: dolphin --new-window /m/media/movies & end 

.idesktop/stage.lnk

@@ -3,6 +3,6 @@ table Icon   Icon: /usr/share/icons/oxygen/base/64x64/places/folder-blue.png   X: 202   Y: 62-  Command[0]: dolphin /m/media/stage+  Command[0]: dolphin --new-window /m/media/stage & end 

.idesktop/tv.lnk

@@ -3,6 +3,6 @@ table Icon   Icon: /usr/share/icons/oxygen/base/64x64/places/folder-blue.png   X: 302   Y: 62-  Command[0]: dolphin /m/media/tv+  Command[0]: dolphin --new-window /m/media/tv & end 

.pathrc

@@ -13,6 +13,9 @@ prependpath() {   esac } +# Set the Cargo installation directory.+export CARGO_INSTALL_ROOT="$HOME/.local/cargo"+ # Setup PATH. # shellcheck disable=SC2123 PATH=""@@ -26,38 +29,24 @@ PATH="$(prependpath "$PATH" /usr/bin/vendor_perl)" PATH="$(prependpath "$PATH" /usr/local/sbin)" PATH="$(prependpath "$PATH" /usr/local/bin)" PATH="$(prependpath "$PATH" /usr/local/go/bin)"-# Home specific paths.-PATH="$(prependpath "$PATH" "$HOME/.local/bin")"-PATH="$(prependpath "$PATH" "$HOME/.gem/ruby/2.5.0/bin")"-PATH="$(prependpath "$PATH" "$HOME/go/bin")"-PATH="$(prependpath "$PATH" "$CARGO_INSTALL_ROOT/bin")"-PATH="$(prependpath "$PATH" "$HOME/.cargo/bin")"-PATH="$(prependpath "$PATH" "$HOME/.local/cargo/bin")"-PATH="$(prependpath "$PATH" "$HOME/.ghcup/bin")"+# This is for emscripten. Whhhyyyyyyyyyyy is it in /usr/lib? Wtf?+PATH="$(prependpath "$PATH" "/usr/lib/emscripten")"+# macOS paths+PATH="$(prependpath "$PATH" "$HOME/tmp/nvim-osx64/bin")"+# These come from my m2 mac mini via homebrew.+if [ -r "/opt/homebrew" ]; then+  PATH="$(prependpath "$PATH" "/opt/homebrew/bin")"+  PATH="$(prependpath "$PATH" "/opt/homebrew/sbin")"+fi # My paths.+PATH="$(prependpath "$PATH" "$CARGO_INSTALL_ROOT/bin")" PATH="$(prependpath "$PATH" "$HOME/bin")" PATH="$(prependpath "$PATH" "$HOME/bin/x86_64")" PATH="$(prependpath "$PATH" "$HOME/bin/aarch64")" PATH="$(prependpath "$PATH" "$HOME/rust/ripgrep/target/release")"-PATH="$(prependpath "$PATH" "$HOME/rust/ripgrep/target/release/examples")"-PATH="$(prependpath "$PATH" "$HOME/rust/xsv/target/release")"-# macOS paths-PATH="$(prependpath "$PATH" "$HOME/tmp/nvim-osx64/bin")"-# These come from my m2 mac mini.-PATH="$(prependpath "$PATH" "/opt/homebrew/bin")"-PATH="$(prependpath "$PATH" "/opt/homebrew/sbin")"--# This is for pyenv.-PATH="$(prependpath "$PATH" "$HOME/.pyenv/shims")"--# This is for emscripten. Whhhyyyyyyyyyyy is it in /usr/lib? Wtf?-PATH="$(prependpath "$PATH" "/usr/lib/emscripten")"  export PATH -# Set the Cargo installation directory.-export CARGO_INSTALL_ROOT="$HOME/.local/cargo"- # If homebrew was installed in /opt, then it wants all these vars set to # point it in the right direction. if [ -r "/opt/homebrew" ]; then@@ -66,8 +55,7 @@ if [ -r "/opt/homebrew" ]; then   export HOMEBREW_REPOSITORY="/opt/homebrew";   export MANPATH="/opt/homebrew/share/man${MANPATH+:$MANPATH}:";   export INFOPATH="/opt/homebrew/share/info:${INFOPATH:-}";-  # This apparently tells C/C++ compiles where to find header files?+  # This apparently tells C/C++ compilers where to find header files?   # Not sure if it's appropriate to set this unconditionally, alas...   export CPATH="/opt/homebrew/include" fi-

.secretenv

new file mode 100644Binary file changed

.ssh/.gitignore

@@ -3,6 +3,7 @@ /id_rsa.pub /keys /known_hosts*+/ssh_auth_sock  # I can't seem to figure out how to stop Google's tooling from writing SSH # keys to this directory.

.ssh/config

Binary file changed

.systemd.sh

@@ -2,78 +2,114 @@  # simplified systemd command, for instance # "sudo systemctl stop xxx" - > "0.stop xxx"-if ! cmd-exists systemd-notify || ! systemd-notify --booted;-then  # for not systemd-    0.start() {-        sudo rc.d start "$@"-    }+0.reboot() {+    systemctl reboot "$@"+} -    0.restart() {-        sudo rc.d restart "$@"-    }+0.off() {+    systemctl poweroff "$@"+} -    0.stop() {-        sudo rc.d stop "$@"-    }-else-    0.reboot() {-        systemctl reboot "$@"-    }+# start systemd service+0.start() {+    sudo systemctl start "$@"+}+# restart systemd service+0.restart() {+    sudo systemctl restart "$@"+}+# stop systemd service+0.stop() {+    sudo systemctl stop "$@"+}+# enable systemd service+0.enable() {+    sudo systemctl enable "$@"+}+# disable a systemd service+0.disable() {+    sudo systemctl disable "$@"+}+# show the status of a service+0.status() {+    systemctl status "$@"+}+# reload a service configuration+0.reload() {+    sudo systemctl --system daemon-reload+}+# list all running service+0.list() {+    systemctl+}+# list all failed service+0.failed () {+    systemctl --failed+}+# list all systemd available unit files+0.list-files() {+    systemctl list-unit-files+}+# check the log+0.log() {+    sudo journalctl --follow --this-boot "$@"+}+# show wants+0.wants() {+    systemctl show -p "Wants" $1.target+}+# analyze the system+0.analyze() {+    systemd-analyze "$@"+} -    0.off() {-        systemctl poweroff "$@"-    }+# Same as above, but for user services. -    # start systemd service-    0.start() {-        sudo systemctl start "$@"-    }-    # restart systemd service-    0.restart() {-        sudo systemctl restart "$@"-    }-    # stop systemd service-    0.stop() {-        sudo systemctl stop "$@"-    }-    # enable systemd service-    0.enable() {-        sudo systemctl enable "$@"-    }-    # disable a systemd service-    0.disable() {-        sudo systemctl disable "$@"-    }-    # show the status of a service-    0.status() {-        systemctl status "$@"-    }-    # reload a service configuration-    0.reload() {-        sudo systemctl --system daemon-reload-    }-    # list all running service-    0.list() {-        systemctl-    }-    # list all failed service-    0.failed () {-        systemctl --failed-    }-    # list all systemd available unit files-    0.list-files() {-        systemctl list-unit-files-    }-    # check the log-    0.log() {-        sudo journalctl --follow --this-boot "$@"-    }-    # show wants-    0.wants() {-        systemctl show -p "Wants" $1.target-    }-    # analyze the system-    0.analyze() {-        systemd-analyze "$@"-    }-fi+# start systemd service+1.start() {+    systemctl --user start "$@"+}+# restart systemd service+1.restart() {+    systemctl --user restart "$@"+}+# stop systemd service+1.stop() {+    systemctl --user stop "$@"+}+# enable systemd service+1.enable() {+    systemctl --user enable "$@"+}+# disable a systemd service+1.disable() {+    systemctl --user disable "$@"+}+# show the status of a service+1.status() {+    systemctl --user status "$@"+}+# reload a service configuration+1.reload() {+    systemctl --user daemon-reload+}+# list all running service+1.list() {+    systemctl --user+}+# list all failed service+1.failed () {+    systemctl --user --failed+}+# list all systemd available unit files+1.list-files() {+    systemctl --user list-unit-files+}+# check the log+1.log() {+    journalctl --user --follow --this-boot "$@"+}+# show wants+1.wants() {+    systemctl show -p "Wants" $1.target+}

.tmux.conf

@@ -27,6 +27,10 @@ set-option -sa terminal-overrides ',*-256color:RGB' # Archlinux.) set-option -sa terminal-features ",*:hyperlinks" +# Let OSC 52 escape sequences work for copy/paste.+set -g set-clipboard on+set -g allow-passthrough on+ # Set history limit ridiculously high. set -g history-limit 1073741824 @@ -104,6 +108,5 @@ set-hook -g session-closed "run-shell $HOME/bin/tmux-cleanup \; run-shell $HOME/ # Open a new terminal in the CWD. bind C-n run-shell -b "$HOME/bin/terminal -d #{pane_current_path} -- tmux -u new -c #{pane_current_path} zsh" -# set -g update-environment "SSH_ASKPASS SSH_AGENT_PID SSH_CONNECTION"-# setenv -g SSH_AUTH_SOCK "/tmp/andrew_ssh_auth_sock"-# if-shell 'ls /tmp/andrew_ssh_auth_sock' 'setenv -g SSH_AUTH_SOCK "/tmp/andrew_ssh_auth_sock"'+# Track fresh values for new windows.+set -g update-environment "DISPLAY SSH_AUTH_SOCK SSH_AGENT_PID SSH_CONNECTION"

.zshrc

@@ -1,3 +1,6 @@+# shellcheck disable=SC2034,SC2206+# vim: ft=zsh sw=2 ts=2 sts=2+ # Define aliases and generic shell variables. [[ -f ~/.pathrc ]] && . ~/.pathrc [[ -f ~/.aliasrc ]] && . ~/.aliasrc@@ -19,13 +22,8 @@ else   umask 0002 fi -# Disable beeping.-setterm --blength 0 > /dev/null 2>&1-# Disable software flow control (Ctrl+Q/Ctrl+S)-# See: https://unix.stackexchange.com/questions/545045/what-is-the-difference-between-ixon-and-ixoff-tty-attributes-stty -ixon- # Don't remove a space before the pipe symbol.+# shellcheck disable=SC2034 ZLE_REMOVE_SUFFIX_CHARS=$' \t\n;&'  # Setup LS_COLORS.@@ -36,6 +34,7 @@ fi # Set fpath for custom completions. fpath=(~/.zsh-complete $fpath) if [[ -f /usr/local/share/zsh-completions ]]; then+  # shellcheck disable=SC2128   fpath=(/usr/local/share/zsh-completions $fpath) fi @@ -43,6 +42,7 @@ fi # See: https://superuser.com/questions/458906/zsh-tab-completion-of-git-commands-is-very-slow-how-can-i-turn-it-off __git_other_files() {   if [[ "$PWD" = "$HOME" ]]; then+    # shellcheck disable=SC2034     local -a expl     _wanted files expl 'other file' _files   fi@@ -78,30 +78,6 @@ bindkey '\e[4~' end-of-line # SHIFT-TAB should go backwards during auto-completion. bindkey '\e[Z' reverse-menu-complete -# Setup history paging. We bind UP/DOWN to a special variant of history-# searching that matches according to prefix (not just the first word).-# This will also move the cursor to the end of the line.-autoload -Uz history-search-end-zle -N history-beginning-search-backward-end history-search-end-zle -N history-beginning-search-forward-end history-search-end-# UP does history prefix search, backwards.-# DOWN does history prefix search, forwards.-if is-work; then-  # For Ubuntu machines, the normal '\u[A' combo doesn't seem to work for-  # arrow keys. It's not clear why. This formulation was taken from:-  # https://github.com/zsh-users/zsh-history-substring-search/issues/92-  bindkey "$terminfo[kcuu1]" history-beginning-search-backward-end-  bindkey "$terminfo[kcud1]" history-beginning-search-forward-end-else-  bindkey '\e[A' history-beginning-search-backward-end-  bindkey '\e[B' history-beginning-search-forward-end-fi--# CTRL-X CTRL-E edits current command in editor.-autoload -Uz edit-command-line-zle -N edit-command-line-bindkey '^x^e' edit-command-line- # Make it so `command $foo` will potential split "$foo" into multiple # parameters, which is how most other shells work. setopt shwordsplit@@ -162,17 +138,6 @@ add-zsh-hook -Uz preexec preexec # Enable fzf integration. if [[ -f ~/.fzf.zsh ]]; then   . ~/.fzf.zsh-else-  fzf=(-    /usr/share/fzf-    /usr/share/doc/fzf/examples-  )-  for p in $fzf; do-    if [[ -d "$p" ]]; then-      . "$p/key-bindings.zsh" && . "$p/completion.zsh"-      break-    fi-  done fi  # Enable auto suggestions when typing commands.@@ -183,7 +148,7 @@ zshauto=(   /usr/share/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh   /usr/share/zsh-autosuggestions/zsh-autosuggestions.zsh )-for p in $zshauto; do+for p in "${zshauto[@]}"; do   if [[ -f "$p" ]]; then     . "$p"     break@@ -204,21 +169,23 @@ zshsyntax=(   /usr/local/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh   /usr/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh )-for p in $zshsyntax; do+for p in "${zshsyntax[@]}"; do   if [[ -f "$p" ]]; then     . "$p"     break   fi done -# Add gvm environment. gvm is used for managing Go installations.-if [[ -f ~/.gvm/scripts/gvm ]]; then-  . ~/.gvm/scripts/gvm+# Run commands for configuring tty related options.+# We guard this to ensure it won't run for shells+# not connected to a tty.+if [ -t 0 ]; then+  . ~/.zshtty fi -# Try to reconnect to ssh agent?-# if is-work; then-  # if ! ssh-add -L > /dev/null 2>&1; then-    # ssh_reagent-  # fi-# fi+# Try to setup a symlink for our ssh agent authorization socket. This gives+# it a stable path so that we can reference it in various contexts and+# without needing to change the SSH_AUTH_SOCK environment variable. e.g., For+# reconstructing a tmux session or running sshfs via a systemd user service+# that utilizes the currently running agent.+relink-ssh-auth-sock &> /dev/null

.zshtty

new file mode 100644@@ -0,0 +1,32 @@+# vim: ft=zsh sw=2 ts=2 sts=2++# Disable beeping.+setterm --blength 0 > /dev/null 2>&1+# Disable software flow control (Ctrl+Q/Ctrl+S)+# See: https://unix.stackexchange.com/questions/545045/what-is-the-difference-between-ixon-and-ixoff-tty-attributes+stty -ixon++# Setup history paging. We bind UP/DOWN to a special variant of history+# searching that matches according to prefix (not just the first word).+# This will also move the cursor to the end of the line.+autoload -Uz history-search-end+zle -N history-beginning-search-backward-end history-search-end+zle -N history-beginning-search-forward-end history-search-end+# UP does history prefix search, backwards.+# DOWN does history prefix search, forwards.+if is-work; then+  # For Ubuntu machines, the normal '\u[A' combo doesn't seem to work for+  # arrow keys. It's not clear why. This formulation was taken from:+  # https://github.com/zsh-users/zsh-history-substring-search/issues/92+  # shellcheck disable=SC2154+  bindkey "${terminfo[kcuu1]}" history-beginning-search-backward-end+  bindkey "${terminfo[kcud1]}" history-beginning-search-forward-end+else+  bindkey '\e[A' history-beginning-search-backward-end+  bindkey '\e[B' history-beginning-search-forward-end+fi++# CTRL-X CTRL-E edits current command in editor.+autoload -Uz edit-command-line+zle -N edit-command-line+bindkey '^x^e' edit-command-line

bin/A

@@ -1,9 +1,9 @@-#!/bin/sh+#!/usr/bin/env bash  host="$1" summary="$2" body="$3"-if [ -z "$host" ]; then+if [ -z "$host" ] || [ "$host" = "self" ]; then   host=$(myhostname) fi if [ -z "$summary" ]; then@@ -11,8 +11,13 @@ if [ -z "$summary" ]; then fi if [ "$host" = "$(myhostname)" ]; then   notify-send "$summary" "$body" &-  audio-play /usr/share/sounds/freedesktop/stereo/complete.oga &+  pw-play /usr/share/sounds/freedesktop/stereo/complete.oga &   wait else-  sshrun "$host" A "$host" "$summary" "$body"+  if timeout 2 ssh-add -l &> /dev/null; then+    sshrun "$host" A "$host" "$summary" "$body"+  else+    echo $MY_ALERT_URL+    curl -H "Title: $summary" -d "$body" "$MY_ALERT_URL" &> /dev/null+  fi fi

bin/arch-install

@@ -197,7 +197,7 @@ pkgs() {     terminus-font \     words \     keyd \-    bash-language-server+    bash-language-server lua-language-server typescript-language-server   pkgfile --update   systemctl enable keyd }@@ -207,7 +207,7 @@ xpkgs() {   pacman --noconfirm -S \     xterm \     alacritty \-    k3b konversation konsole dolphin okular \+    k3b konversation konsole dolphin okular ffmpegthumbs \     qt6ct oxygen oxygen-icons kde-cli-tools \     xbindkeys \     libnotify \@@ -223,10 +223,13 @@ xpkgs() {     scrot \     freerdp \     gvim \+    vorbis-tools \     noto-fonts noto-fonts-cjk noto-fonts-emoji noto-fonts-extra \     pipewire-audio pipewire-pulse pipewire-alsa wireplumber \     pamixer pavucontrol \     recordmydesktop \+    feh variety \+    stalonetray \     firefox   systemctl --user enable wireplumber }@@ -296,30 +299,13 @@ aurpkgs() { }  aurxpkgs() {-  # It looks like gkrellm has finally gone the way of the dodo. This is what-  # I used to install:-  #-  #   gkrellm-  #   gkleds-  #   gkrellsun-  #   gkrellweather-  #-  # Also, these programs require gtk2. Which now requires going through the AUR.-  # This is bad. Let's try and use stalonetray instead. And find an alternative-  # program to set wallpaper. No reason to use gtk2 for that.-  #-  #   trayer-  #   nitrogen-  #-  # The only other thing on my system requiring gtk2 is XSANE, but it looks like-  # there are many alternatives there too. NAPS2 looks promising:-  # https://wiki.archlinux.org/title/SANE   yay --noconfirm -S \     dzen2-xft-xpm-xinerama-git \     google-chrome \     1password 1password-cli \     idesk \     gmrun \+    naps2-bin \     ttf-hack-nerd \     ttf-dejavu \     ttf-fira-mono \@@ -391,7 +377,7 @@ xenvinit() {   # in order to make file associations in Dolphin work. WTF.   #   # See: https://old.reddit.com/r/kde/comments/1bd313p/dolphin_not_recognizing_file_associations/l26i3m2/-  mkdir "$HOME/.config/menus"+  mkdir -p "$HOME/.config/menus"   url="https://github.com/KDE/plasma-workspace/raw/11e7f5306fa013ec5c2b894a28457dabf5c42bad/menu/desktop/plasma-applications.menu"   curl -L "$url" -o "$HOME/.config/menus/applications.menu" }

bin/create-bluetooth-speaker-sinks

new file mode 100755@@ -0,0 +1,76 @@+#!/usr/bin/env bash++set -euo pipefail++VIRTUAL_SINK="bluetooth_speakers"+DESC="Bluetooth-Speakers"+TAG="bluetooth-speaker-loopback"++find_speaker_sinks() {+  pactl list sinks |+    awk '+      /^Sink #/ { sink="" }+      /^\s*Name:/ { sink=$2 }+      /^\s*Description:/ {+        desc=$0+        if (sink ~ /^bluez_output\./ && desc ~ /Denon|JBL Flip/) {+          print sink+        }+      }+    '+}++ensure_virtual_sink() {+  if ! pactl list short sinks | awk '{print $2}' | rg -qx "$VIRTUAL_SINK"; then+    pactl load-module module-null-sink \+      sink_name="$VIRTUAL_SINK" \+      sink_properties="device.description=\"$DESC\"" >/dev/null+  fi+  # pactl set-default-sink "$VIRTUAL_SINK"+  # pactl set-sink-volume "$VIRTUAL_SINK" 100%+  pactl set-sink-mute "$VIRTUAL_SINK" 0+}++unload_loopbacks() {+  pactl list modules |+    awk -v tag="$TAG" '+      /^Module #/ {+        id = $2+        sub(/^#/, "", id)+      }+      $0 ~ tag {+        print id+      }+    ' |+    while read -r id; do+      pactl unload-module "$id" || true+    done+}++create_loopbacks() {+  local sinks=()+  mapfile -t sinks < <(find_speaker_sinks)++  if ((${#sinks[@]} == 0)); then+    echo "No connected bluetooth speaker sinks found."+    exit 0+  fi++  for sink in "${sinks[@]}"; do+    # pactl set-sink-volume "$sink" 100% || true+    pactl set-sink-mute "$sink" 0 || true++    pactl load-module module-loopback \+      source="${VIRTUAL_SINK}.monitor" \+      sink="$sink" \+      latency_msec=100 \+      sink_input_properties="application.name=\"$TAG\"" >/dev/null+  done++  echo "Routing $VIRTUAL_SINK to:"+  printf '  %s\n' "${sinks[@]:0:4}"+}++ensure_virtual_sink+unload_loopbacks+create_loopbacks

bin/dzen

new file mode 100755@@ -0,0 +1,3 @@+#!/usr/bin/env bash++dzen2 -dock -xs "$monitor" -h "$height" -fn "$font" -ta l -bg '#ffffff' -y "$y"

bin/dzen-laptop-status

new file mode 100755@@ -0,0 +1,19 @@+#!/usr/bin/env bash++if [ $# != 5 ]; then+  echo "$(basename "$0") <font-size> <x> <y> <w> <h>" >&2+  exit 1+fi++font_size="$1"+x="$2"+y="$3"+w="$4"+h="$5"+(+  while "true"; do+    laptop-status "$font_size"+    sleep 10s+  done+) | dzen2 -dock -fn Cousine -fg black -bg white -ta l -sa c \+      -x "$x" -y "$y" -w "$w" -h "$h"

bin/dzen-wingo-workspaces

new file mode 100755@@ -0,0 +1,19 @@+#!/usr/bin/env bash++. "$HOME/bin/x11-go"++if [ $# != 5 ]; then+  echo "$(basename "$0") <font-size> <x> <y> <w> <h>" >&2+  exit 1+fi++font_size="$1"+x="$2"+y="$3"+w="$4"+h="$5"++script="$HOME/.config/wingo/scripts/dzen-workspaces/dzen-workspaces"+"$script" | \+  dzen2 -dock -fn "Cousine:size=$font_size" -fg black -bg white -ta l -sa c \+    -x "$x" -y "$y" -w "$w" -h "$h"

bin/google-chrome

@@ -2,12 +2,13 @@  # This used to do more when I used different versions of Chrome on different # machines. But now I just use stable when I use Chrome.-case "$(myhostname)" in-  bart)-    # And we set the scaling factor for when the DPI is too high.-    exec google-chrome-stable --force-device-scale-factor=1.5 "$@"-    ;;-  *)-    exec google-chrome-stable "$@"-    ;;-esac+#+# And we set the scaling factor for when the DPI is too high.+# Or when the screen is set far away.+if is-laptop; then+  exec google-chrome-stable --force-device-scale-factor=1.5 "$@"+elif is-media; then+  exec google-chrome-stable --force-device-scale-factor=1.25 "$@"+else+  exec google-chrome-stable "$@"+fi

bin/i3-volume

@@ -31,7 +31,7 @@ # Play volume ding when it changes. ding() {     sound=/usr/share/sounds/freedesktop/stereo/audio-volume-change.oga-    audio-play "$sound"+    pw-play "$sound" }  # Get the volume as a percentage.

bin/is-lower-res

new file mode 100755@@ -0,0 +1,6 @@+#!/usr/bin/env bash++if [ -f "$HOME/.islowerres" ]; then+    exit 0+fi+exit 1

bin/is-media

new file mode 100755@@ -0,0 +1,6 @@+#!/bin/sh++if [ -f "$HOME/.ismedia" ]; then+    exit 0+fi+exit 1

bin/laptop-status

@@ -1,12 +1,26 @@ #!/bin/sh -battery="$(batrem -s | tr -d '\n')"-if echo "$battery" | rg -q 'AC'; then-  batcolor=77dd77-else-  batcolor=ff6961+if [ $# != 1 ]; then+  echo "$(basename "$0") <font-size>" >&2+  exit 1 fi+font_size="$1"+ nowcolor=3b82ff-font='^fn(Cousine:bold:size=18)' now="$(bttf time fmt now -f "^fg(#$nowcolor)%a, %B %d, %Y, %H:%M %Z")"-exec printf "%s%s  ^fg(#$batcolor)%s\n" "$font" "$now" "$battery"+if is-laptop; then+  battery="$(batrem -s | tr -d '\n')"+  if echo "$battery" | rg -q 'AC'; then+    batcolor=77dd77+  else+    batcolor=ff6961+  fi+  exec printf \+    "^fn(Cousine:size=$font_size)%s  ^fn(Cousine:bold:size=$font_size)^fg(#$batcolor)%s\n" \+    "$now" \+    "$battery"+else+  exec printf \+    "^fn(Cousine:size=$font_size)%s\n" \+    "$now"+fi

bin/mount-nas

new file mode 100755@@ -0,0 +1,64 @@+#!/usr/bin/env bash++# This should generally be run via `systemctl --user start mount-nas` instead+# of run directly. See `~/.config/systemd/user/mount-nas.service`. ---AG++set -euo pipefail++export SSH_AUTH_SOCK=/home/andrew/.ssh/ssh_auth_sock++# Wait until the ssh agent socket exists.+until [ -S "$SSH_AUTH_SOCK" ]; do+  echo "waiting for SSH_AUTH_SOCK=$SSH_AUTH_SOCK"+  sleep 1+done++# Wait until the agent has at least one key loaded.+until ssh-add -l >/dev/null 2>&1; do+  echo "waiting for ssh-agent keys"+  sleep 1+done++# Wait until the host resolves.+until getent ahosts home.burntsushi.net >/dev/null 2>&1; do+  echo "waiting for DNS: home.burntsushi.net"+  sleep 2+done++# Wait until SSH auth actually works.+until ssh -o BatchMode=yes -o ConnectTimeout=3 nas true >/dev/null 2>&1; do+  echo "waiting for SSH auth to nas"+  sleep 2+done++# For our media PCs, we mount the NAS as read-only.+# Why? Because I have a 5.5 year old (as of 2026-06-15)+# and sometimes he does wild things.+read_only=+if is-media; then+  read_only="-o ro"+fi+sshfs nas:/m /m \+  -f \+  $read_only \+  -o allow_other \+  -o reconnect \+  -o follow_symlinks \+  -o writeback_cache=no \+  -o kernel_cache \+  -o ServerAliveInterval=15 \+  -o ServerAliveCountMax=3 \+  -o BatchMode=yes &+sshfs_pid=$!++until findmnt -T /m >/dev/null 2>&1; do+  if ! kill -0 "$sshfs_pid" 2>/dev/null; then+    wait "$sshfs_pid"+    exit $?+  fi+  echo "waiting for /m to be mounted"+  sleep 1+done++A self 'NAS is mounted'+wait "$sshfs_pid"

bin/mountnas

new file mode 100755@@ -0,0 +1,23 @@+#!/usr/bin/env bash++down="$1"+exist="$(findmnt /m 2>&1)"++case "$(myhostname)" in+  nas|nas2)+    echo "cannot mount media on nas; the nas IS the media!" >&2+    exit 1+    ;;+  duff)+    [ $down = down ] && [ -n "$exist" ] && sudo umount /m+    [ $down != down ] && [ -z "$exist" ] && sudo mount nas2:/m /m+    ;;+  flanders|wiggum|milhouse|otto)+    [ $down = down ] && [ -n "$exist" ] && msshfs-readonly down /m+    [ $down != down ] && [ -z "$exist" ] && msshfs-readonly nas2:/m /m+    ;;+  *)+    [[ $down = down ]] && [[ -n "$exist" ]] && msshfs down /m+    [[ $down != down ]] && [[ -z "$exist" ]] && msshfs nas2:/m /m+    ;;+esac

bin/my-killall

new file mode 100755@@ -0,0 +1,13 @@+#!/usr/bin/env bash++# A better version of `killall` that seems to work more reliably. The key trick+# here is pgrep's `-f` flag, which will search the full command name.+#+# This probably isn't fully robust, but good enough for me.++process="$1"+if [ -z "$process" ]; then+  echo "Usage: $(basename "$0") <process-name>" >&2+  exit 1+fi+pgrep -f "(^|/)$process($|\\s+)" | xargs -r kill

bin/my-stalonetray

new file mode 100755@@ -0,0 +1,19 @@+#!/usr/bin/env bash++if [ $# != 3 ]; then+  echo "$(basename "$0") <x> <y> <height>" >&2+  exit 1+fi+x="$1"+y="$2"+h="$3"+shift 3++stalonetray \+  --background '#ffffff' \+  --geometry 1x1-$x-$y \+  --icon-size "$h" \+  --icon-gravity E \+  --grow-gravity E \+  --window-strut bottom \+  "$@"

bin/op-migrate

@@ -3,11 +3,15 @@ case "$1" in   todo)     passwords="$(-      (cd ~/.password-store && rg --files --sort path) \-        | rg -or '$1' '(.*)\.gpg$'+      (+        cd ~/.password-store || exit 1+        rg --files --sort path \+          | rg -v '^astral/' \+          | rg -v '^(andrew|kait)/(machine|ssh)'+      ) | rg -or '$1' '(.*)\.gpg$'     )"     for f in $passwords; do-      (pass "$f" | rg -q '1PASS') && echo $f+      (pass "$f" | rg -q -e 1PASS -e 1STALE) || echo $f     done     ;;   untagged)

bin/op-migrate-env.zsh

new file mode 100755@@ -0,0 +1,34 @@+#!/usr/bin/env zsh++append() {+  local entry="$1"+  local append="$2"+  { pass show $entry && echo "$append" } \+      | pass insert --multiline --force $entry+}++stale() {+  # local entry="$1"+  for entry; do+    append "$entry" 1STALE+  done+}++fin() {+  local entry="$1"+  append "$entry" 1PASS+}++_entry() {+  local store="${PASSWORD_STORE_DIR:-$HOME/.password-store}"+  local -a entries++  entries=("${store}"/**/*.gpg(N))+  entries=("${entries[@]#$store/}")+  entries=("${entries[@]%.gpg}")++  _describe 'pass entry' entries+}++compdef _entry stale+compdef _entry fin

bin/place

deleted file mode 100755@@ -1,69 +0,0 @@-#!/bin/sh--if [ $# != 5 ]; then-  echo "Usage: $(basename): monitor-number x y width height" >&2-  exit 1-fi--# Nice names for args.-monitor=$1-mx=$2-my=$3-mwidth=$4-mheight=$5-font="Cousine:size=18"--# Calculate the height of the "panel" and the y position of the "panel."-height=40-y=$((mheight - height + my))--# Bump font size for some machines.-case "$(myhostname)" in-  kang|bart)-    font="Cousine:size=25"-    height=50-    ;;-  *)-    ;;-esac--# Width constants for the system tray and battery indicator.-tray_width=300-bat_width=650--# Determine the width of the pager based on the width of the system tray and-# battery indicator. If it's a laptop, include the battery indicator. Otherwise,-# leave it out.-if is-laptop; then-  pager_width=$((mwidth - bat_width - tray_width))-else-  pager_width=$((mwidth - tray_width))-fi--# Always kill the current pager and system tray.-# shellcheck disable=SC2009-ps aux | grep -v grep | grep dzen-workspaces | awk '{print $2}' | xargs -r kill-killall trayer-killall dzen2--# Restart the pager and system tray with appropriate geometry.-# (pager-multihead -x $mx -y $y --width $pager_width --height $height &)--($HOME/.config/wingo/scripts/dzen-workspaces/dzen-workspaces \-  | dzen2 -dock -xs 2 -h "$height" -fn "$font" -ta l -bg '#ffffff' -y "$y") &-(trayer --widthtype pixel --width $tray_width \-        --heighttype pixel --height $height \-        --edge bottom --align right \-        --monitor $monitor \-        --SetDockType true &)--# If this is a laptop, kill the battery notifier and restart it too.-if is-laptop; then-  # shellcheck disable=SC2009-  ps aux | grep -v grep | grep batrem | awk '{print $2}' | xargs -r kill-  ((while true; do laptop-status && sleep 10s; done) | \-    dzen2 -dock -fn "Cousine:size=18" -fg black -bg white \-      -x $((mx + pager_width)) -y $y -w $bat_width -h $height &)-fi--exit 0

bin/process-restart

@@ -6,5 +6,5 @@ if [ -z "$process" ]; then   echo "Usage: $(basename "$0") <process-name>" >&2   exit 1 fi-pgrep -f "(^|/)$process($|\\s+)" | xargs -r kill+my-killall "$process" exec "$process" "$@"

bin/relink-ssh-auth-sock

new file mode 100755@@ -0,0 +1,32 @@+#!/usr/bin/env bash++set -eu++link="$HOME/.ssh/ssh_auth_sock"++if [[+  -n "${SSH_CONNECTION:-}" &&+  -n "${SSH_AUTH_SOCK:-}" &&+  "$SSH_AUTH_SOCK" != "$link" &&+  -S "$SSH_AUTH_SOCK"+]]; then+  mkdir -p "$HOME/.ssh"+  chmod 700 "$HOME/.ssh"+  ln -snf "$SSH_AUTH_SOCK" "$link"+fi++# When our link isn't valid, try to make it so.+if ! [ -S "$link" ]; then+  for agent in "$HOME/.ssh/agent"/*; do+    if SSH_AUTH_SOCK="$agent" timeout 2s ssh-add -l > /dev/null 2>&1; then+      ln -snf "$agent" "$link"+      break+    fi+  done+fi++if [ -S "$link" ]; then+  printf '%s\n' "$link"+else+  exit 1+fi

bin/rust/freq/main.rs

@@ -10,10 +10,7 @@ Usage: See `freq --help` for more detail. */ -#![allow(warnings)]- use std::{-    borrow::Cow,     io::Write,     path::{Path, PathBuf}, };@@ -368,8 +365,6 @@ impl FreqsOtherMany {     fn into_table(self, config: &Config) -> Table {         let mut table = Table::default();         for (units, freq) in self.map {-            use std::fmt::Write;-             table.total += freq;             if !units.iter().all(|unit| config.include_unit(&unit)) {                 continue;

bin/rust/setup-system-links/Cargo.lock

@@ -1,39 +1,35 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing.+version = 4+ [[package]] name = "anyhow"-version = "1.0.32"+version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b"+checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"  [[package]] name = "bitflags"-version = "1.2.1"+version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"  [[package]] name = "bstr"-version = "0.2.13"+version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931"+checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" dependencies = [  "lazy_static",  "memchr",  "regex-automata", ] -[[package]]-name = "byteorder"-version = "1.3.4"-source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"- [[package]] name = "clap"-version = "2.33.3"+version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"+checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [  "bitflags",  "textwrap",@@ -42,24 +38,21 @@ dependencies = [  [[package]] name = "lazy_static"-version = "1.4.0"+version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"  [[package]] name = "memchr"-version = "2.3.3"+version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"+checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"  [[package]] name = "regex-automata"-version = "0.1.9"+version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4"-dependencies = [- "byteorder",-]+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"  [[package]] name = "same-file"@@ -92,48 +85,40 @@ dependencies = [  [[package]] name = "unicode-width"-version = "0.1.8"+version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"+checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"  [[package]] name = "walkdir"-version = "2.3.1"+version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d"+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [  "same-file",- "winapi",  "winapi-util", ]  [[package]]-name = "winapi"-version = "0.3.9"+name = "winapi-util"+version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [- "winapi-i686-pc-windows-gnu",- "winapi-x86_64-pc-windows-gnu",+ "windows-sys", ]  [[package]]-name = "winapi-i686-pc-windows-gnu"-version = "0.4.0"+name = "windows-link"+version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"  [[package]]-name = "winapi-util"-version = "0.1.5"+name = "windows-sys"+version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [- "winapi",+ "windows-link", ]--[[package]]-name = "winapi-x86_64-pc-windows-gnu"-version = "0.4.0"-source = "registry+https://github.com/rust-lang/crates.io-index"-checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

bin/rust/setup-system-links/Cargo.toml

@@ -4,6 +4,10 @@ version = "0.0.1" authors = ["Andrew Gallant <jamslam@gmail.com>"] edition = "2018" +[[bin]]+name = "setup-system-links"+path = "main.rs"+ [dependencies] anyhow = "1.0.32" bstr = "0.2.13"

bin/rust/setup-system-links/main.rs

new file mode 100644@@ -0,0 +1,446 @@+// Add a convention that lets us avoid doing symlinks. e.g., For PAM.++#[cfg(not(unix))]+compile_error!("setup-system-links only works on Unix systems");++use std::collections::{BTreeMap, BTreeSet};+use std::env;+use std::ffi::{OsStr, OsString};+use std::io::{self, Write};+#[cfg(unix)]+use std::os::unix;+use std::path::{Path, PathBuf};++use anyhow::Context;+use bstr::{BString, ByteSlice, ByteVec};+use walkdir::WalkDir;++fn main() -> anyhow::Result<()> {+    let args = Args::parse()?;+    let mut links = Links::discover(&args.system, &args.hostname)?;+    if let Some(ref backup_dir) = args.backup {+        links.backup(backup_dir.to_owned());+    }+    if args.dry_run {+        links.dry_run(std::io::stdout())?;+    } else {+        links.run(std::io::stdout())?;+    }+    Ok(())+}++/// Represents a collection of links derived from this machine's ~/system+/// directory.+#[derive(Debug)]+struct Links {+    links: Vec<Link>,+    backup: Option<PathBuf>,+}++impl Links {+    /// Determine the full set of system config links for this machine given+    /// the system configuration directory and this machine's hostname.+    fn discover(system: &Path, hostname: &str) -> anyhow::Result<Links> {+        // Collect all leaf files in ~/system, creating a map, e.g.,+        // etc/default/grub |--> {default, duff, frink, ...}+        // Note that the key lacks a leading slash. We add that below.+        #[derive(Debug)]+        struct Leaf {+            machine_config_name: String,+            copy: bool,+        }++        impl Eq for Leaf {}++        impl PartialEq for Leaf {+            fn eq(&self, rhs: &Leaf) -> bool {+                self.machine_config_name == rhs.machine_config_name+            }+        }++        impl Ord for Leaf {+            fn cmp(&self, rhs: &Leaf) -> std::cmp::Ordering {+                self.machine_config_name.cmp(&rhs.machine_config_name)+            }+        }++        impl PartialOrd for Leaf {+            fn partial_cmp(&self, rhs: &Leaf) -> Option<std::cmp::Ordering> {+                Some(self.cmp(rhs))+            }+        }++        let mut all: BTreeMap<PathBuf, BTreeSet<Leaf>> = BTreeMap::new();+        for result in WalkDir::new(system) {+            let dent = result.with_context(|| {+                format!("{}: failed to walk directory", system.display())+            })?;+            if dent.file_type().is_dir() {+                continue;+            }++            // Should never fail since 'system' is our root and is guaranteed+            // to be non-empty.+            let dst = dent+                .path()+                .strip_prefix(system)+                .unwrap()+                .parent()+                .unwrap()+                .to_path_buf();+            if dst.as_os_str().is_empty() {+                // These are files directly in ~/system/ like+                // ~/system/README.md and are not part of our config.+                continue;+            }++            let machine_config_name =+                dent.file_name().to_str().with_context(|| {+                    format!("{} is not valid UTF-8", dent.path().display())+                })?;+            let mut leaf = Leaf {+                machine_config_name: machine_config_name.to_string(),+                copy: false,+            };+            if let Some(hostname) = machine_config_name.strip_prefix("copy:") {+                leaf.machine_config_name = hostname.to_string();+                leaf.copy = true;+            }+            all.entry(dst).or_insert_with(|| BTreeSet::new()).insert(leaf);+        }+        // Now organize them into links by choosing the appropriate specific+        // config for this machine.+        let mut links = vec![];+        let attempts = [+            Leaf { machine_config_name: hostname.to_string(), copy: false },+            Leaf { machine_config_name: "default".to_string(), copy: false },+        ];+        for (relative_dst, configs) in all {+            let dst = Path::new("/").join(&relative_dst);+            for attempt in &attempts {+                let Some(config) = configs.get(attempt) else { continue };+                let src = system.join(relative_dst).join(if !config.copy {+                    attempt.machine_config_name.to_string()+                } else {+                    format!("copy:{}", attempt.machine_config_name)+                });+                links.push(Link { src, dst, copy: config.copy });+                break;+            }+        }+        Ok(Links { links, backup: None })+    }++    /// Indicates that the destination of each link should be backed up+    /// first.+    fn backup(&mut self, dir: PathBuf) {+        self.backup = Some(dir);+    }++    /// Create the links that do not yet exist.+    ///+    /// When backup is enabled (the default), then all config files will be+    /// backed up before attempting any link. If any backup fails, then no+    /// link will be created.+    ///+    /// The equivalent Unix command to each operation will be written to the+    /// given writer (like during a dry run).+    fn run<W: Write>(&self, mut wtr: W) -> anyhow::Result<()> {+        let mut to_link = vec![];+        for link in &self.links {+            if link.exists()? {+                continue;+            }+            to_link.push(link);+        }+        if let Some(ref backup_dir) = self.backup {+            for link in &to_link {+                link.backup(&mut wtr, backup_dir)?;+            }+        }+        for link in to_link {+            link.install(&mut wtr)?;+        }+        Ok(())+    }++    /// Print the equivalent Unix commands that this would execute if we were+    /// running for real, but make no changes to the file system.+    fn dry_run<W: Write>(&self, mut wtr: W) -> anyhow::Result<()> {+        let mut to_link = vec![];+        for link in &self.links {+            if link.exists()? {+                continue;+            }+            to_link.push(link);+        }+        if let Some(ref backup_dir) = self.backup {+            for link in &to_link {+                link.dry_backup(&mut wtr, backup_dir)?;+            }+        }+        for link in to_link {+            link.dry_install(&mut wtr)?;+        }+        Ok(())+    }+}++/// Represents a link between a file in ~/system and an actual system+/// configuration path on the system. e.g., ~/system/etc/default/grub/krusty+/// will link to /etc/default/grub on my 'krusty' laptop.+#[derive(Debug)]+struct Link {+    /// The ~/system/ path to this machine's config.+    src: PathBuf,+    /// The path to where this config lives on the system.+    dst: PathBuf,+    /// When true, we create a completely new file that is+    /// an identical copy of the source. This is needed in+    /// some cases (like for `/etc/pam.d/system-auth`) where+    /// symlinks are tricky because of permissions. Namely,+    /// the permission of the source file are relevant.+    ///+    /// We can't use hard-links either, because for PAM, files+    /// in `/etc/pam.d` need to be owned by root. But we don't+    /// want this for `~/system`, where files should be owned by+    /// the user. Hardlinks don't allow it.+    ///+    /// So... we copy.+    copy: bool,+}++impl Link {+    /// Write the equivalent Unix command we would execute if we were running+    /// for real.+    fn dry_install<W: Write>(&self, mut wtr: W) -> io::Result<()> {+        let (prog, flags) = if self.copy { ("cp", "") } else { ("ln", " -s") };+        writeln!(+            wtr,+            "{prog}{flags} \"{}\" \"{}\"",+            self.src.display(),+            self.dst.display()+        )+    }++    /// Write the equivalent Unix command we would execute to backup the config+    /// file if we were running for real.+    fn dry_backup<W: Write>(&self, mut wtr: W, dir: &Path) -> io::Result<()> {+        if !self.dst.exists() {+            return Ok(());+        }+        let backup_dst = dir.join(self.relative_dst());+        // OK since self.dst is guaranteed to be non-empty.+        let parent = backup_dst.parent().unwrap();+        writeln!(wtr, "mkdir -p \"{}\"", parent.display(),)?;+        writeln!(+            wtr,+            "cp \"{}\" \"{}\"",+            self.dst.display(),+            backup_dst.display()+        )+    }++    /// Link (or install) the source to the destination, overwriting the+    /// destination if it exists. Callers should ensure that the destination is+    /// backed up before executing.+    fn install<W: Write>(&self, wtr: W) -> anyhow::Result<()> {+        if self.dst.exists() {+            std::fs::remove_file(&self.dst).with_context(|| {+                format!(+                    "failed to delete {} before creating symlink from {}",+                    self.dst.display(),+                    self.src.display()+                )+            })?;+        }+        if self.copy {+            std::fs::copy(&self.src, &self.dst).with_context(|| {+                format!(+                    "failed to copy {} to {}",+                    self.src.display(),+                    self.dst.display()+                )+            })?;+        } else {+            unix::fs::symlink(&self.src, &self.dst).with_context(|| {+                format!(+                    "failed to link {} to {}",+                    self.src.display(),+                    self.dst.display()+                )+            })?;+        }+        self.dry_install(wtr)?;+        Ok(())+    }++    /// Backup the destination to the directory given.+    fn backup<W: Write>(&self, wtr: W, dir: &Path) -> anyhow::Result<()> {+        if !self.dst.exists() {+            return Ok(());+        }++        let backup_dst = dir.join(self.relative_dst());+        // OK since self.dst is guaranteed to be non-empty.+        let parent = backup_dst.parent().unwrap();+        std::fs::create_dir_all(parent).with_context(|| {+            format!(+                "failed to create parent directory {} for backup",+                parent.display()+            )+        })?;+        std::fs::copy(&self.dst, &backup_dst).with_context(|| {+            format!(+                "failed to backup {} to {}",+                self.dst.display(),+                backup_dst.display()+            )+        })?;+        self.dry_backup(wtr, dir)?;+        Ok(())+    }++    /// Returns true if and only if this link already exists in the file+    /// system. That is, when the src and dst refer to the same underlying+    /// file.+    fn exists(&self) -> anyhow::Result<bool> {+        if !self.dst.exists() {+            return Ok(false);+        }+        same_file::is_same_file(&self.src, &self.dst).with_context(|| {+            format!(+                "failed to determine if {} == {}",+                self.src.display(),+                self.dst.display(),+            )+        })+    }++    /// Returns the destination path without its leading /.+    fn relative_dst(&self) -> &Path {+        // OK because dst is always absolute, by construction.+        self.dst.strip_prefix("/").unwrap()+    }+}++#[derive(Debug)]+struct Args {+    /// The directory in which to copy the contents of system configuration+    /// files before removing them and putting a symlink in its place.+    backup: Option<PathBuf>,+    /// When true, make no changes to the file system and instead print what+    /// we would do instead.+    dry_run: bool,+    /// The hostname of this machine (possibly derived from ~/.myhostname).+    hostname: String,+    /// The path to the directory containing this machine's system+    /// configuration. This is currently hard-coded to ~/system.+    system: PathBuf,+}++impl Args {+    fn parse() -> anyhow::Result<Args> {+        use clap::{crate_authors, crate_version, App, Arg};++        let parsed = App::new("Install system configuration symlinks.")+            .author(crate_authors!())+            .version(crate_version!())+            .max_term_width(80)+            .arg(Arg::with_name("no-backup").long("no-backup"))+            .arg(Arg::with_name("dry-run").long("dry-run"))+            .get_matches();++        let home = match env::var_os("HOME") {+            None => anyhow::bail!("HOME environment variable not set"),+            Some(home) => PathBuf::from(home),+        };+        let system = home.join("system");+        if !system.is_dir() {+            anyhow::bail!(+                "{}: system directory does not exist",+                system.display()+            );+        }++        let dry_run = parsed.is_present("dry-run");+        let hostname = runcmd(&["myhostname"])?;+        let backup = if parsed.is_present("no-backup") {+            None+        } else if dry_run {+            let parent = home.join("tmp").join("setup-system-links-backup");+            Some(parent.join("backup-{timestamp}"))+        } else {+            let parent = home.join("tmp").join("setup-system-links-backup");+            Some(+                create_unique_dir_in(parent, "backup")+                    .context("failed to create backup directory")?,+            )+        };+        Ok(Args { backup, dry_run, hostname, system })+    }+}++fn runcmd<A: AsRef<OsStr>>(+    argv: impl IntoIterator<Item = A>,+) -> anyhow::Result<String> {+    let args: Vec<OsString> =+        argv.into_iter().map(|a| a.as_ref().to_os_string()).collect();+    let output = std::process::Command::new(&args[0])+        .args(&args[1..])+        .output()+        .context("failed to run 'myhostname'")?;+    if !output.status.success() {+        let stderr = BString::from(output.stderr.trim().to_vec());+        let args: Vec<Vec<u8>> = args+            .into_iter()+            .map(|a| Vec::from_os_str_lossy(&a).into_owned())+            .collect();+        let mut command = BString::from(bstr::join(" ", args));+        if let Some(code) = output.status.code() {+            command =+                BString::from(format!("`{}` (exit code {})", command, code));+        } else {+            command = BString::from(format!("`{}`", command));+        }+        anyhow::bail!("failed to run {}, stderr:\n\n{}", command, stderr)+    }++    let stdout = output+        .stdout+        .to_str()+        .context("hostname is not valid UTF-8")?+        .trim()+        .to_string();+    Ok(stdout)+}++fn create_unique_dir_in<P: AsRef<Path>, S: AsRef<str>>(+    parent: P,+    prefix: S,+) -> anyhow::Result<PathBuf> {+    use std::time::{Duration, SystemTime};++    let parent = parent.as_ref();+    let prefix = prefix.as_ref();+    std::fs::create_dir_all(&parent).with_context(|| {+        format!("{}: failed to create parent directory", parent.display())+    })?;+    for _ in 0..100 {+        let ts = SystemTime::now()+            .duration_since(SystemTime::UNIX_EPOCH)+            .context("system time is borked")?;+        let name =+            format!("{}-{}-{}", prefix, ts.as_secs(), ts.subsec_nanos());+        let dir = parent.join(name);+        if std::fs::create_dir(&dir).is_err() {+            std::thread::sleep(Duration::from_secs(1));+            continue;+        }+        return Ok(dir);+    }+    anyhow::bail!(+        "failed to create unique directory inside of {}",+        parent.display()+    );+}

bin/rust/setup-system-links/src/main.rs

deleted file mode 100644@@ -1,446 +0,0 @@-// Add a convention that lets us avoid doing symlinks. e.g., For PAM.--#[cfg(not(unix))]-compile_error!("setup-system-links only works on Unix systems");--use std::collections::{BTreeMap, BTreeSet};-use std::env;-use std::ffi::{OsStr, OsString};-use std::io::{self, Write};-#[cfg(unix)]-use std::os::unix;-use std::path::{Path, PathBuf};--use anyhow::Context;-use bstr::{BString, ByteSlice, ByteVec};-use walkdir::WalkDir;--fn main() -> anyhow::Result<()> {-    let args = Args::parse()?;-    let mut links = Links::discover(&args.system, &args.hostname)?;-    if let Some(ref backup_dir) = args.backup {-        links.backup(backup_dir.to_owned());-    }-    if args.dry_run {-        links.dry_run(std::io::stdout())?;-    } else {-        links.run(std::io::stdout())?;-    }-    Ok(())-}--/// Represents a collection of links derived from this machine's ~/system-/// directory.-#[derive(Debug)]-struct Links {-    links: Vec<Link>,-    backup: Option<PathBuf>,-}--impl Links {-    /// Determine the full set of system config links for this machine given-    /// the system configuration directory and this machine's hostname.-    fn discover(system: &Path, hostname: &str) -> anyhow::Result<Links> {-        // Collect all leaf files in ~/system, creating a map, e.g.,-        // etc/default/grub |--> {default, duff, frink, ...}-        // Note that the key lacks a leading slash. We add that below.-        #[derive(Debug)]-        struct Leaf {-            machine_config_name: String,-            copy: bool,-        }--        impl Eq for Leaf {}--        impl PartialEq for Leaf {-            fn eq(&self, rhs: &Leaf) -> bool {-                self.machine_config_name == rhs.machine_config_name-            }-        }--        impl Ord for Leaf {-            fn cmp(&self, rhs: &Leaf) -> std::cmp::Ordering {-                self.machine_config_name.cmp(&rhs.machine_config_name)-            }-        }--        impl PartialOrd for Leaf {-            fn partial_cmp(&self, rhs: &Leaf) -> Option<std::cmp::Ordering> {-                Some(self.cmp(rhs))-            }-        }--        let mut all: BTreeMap<PathBuf, BTreeSet<Leaf>> = BTreeMap::new();-        for result in WalkDir::new(system) {-            let dent = result.with_context(|| {-                format!("{}: failed to walk directory", system.display())-            })?;-            if dent.file_type().is_dir() {-                continue;-            }--            // Should never fail since 'system' is our root and is guaranteed-            // to be non-empty.-            let dst = dent-                .path()-                .strip_prefix(system)-                .unwrap()-                .parent()-                .unwrap()-                .to_path_buf();-            if dst.as_os_str().is_empty() {-                // These are files directly in ~/system/ like-                // ~/system/README.md and are not part of our config.-                continue;-            }--            let machine_config_name =-                dent.file_name().to_str().with_context(|| {-                    format!("{} is not valid UTF-8", dent.path().display())-                })?;-            let mut leaf = Leaf {-                machine_config_name: machine_config_name.to_string(),-                copy: false,-            };-            if let Some(hostname) = machine_config_name.strip_prefix("copy:") {-                leaf.machine_config_name = hostname.to_string();-                leaf.copy = true;-            }-            all.entry(dst).or_insert_with(|| BTreeSet::new()).insert(leaf);-        }-        // Now organize them into links by choosing the appropriate specific-        // config for this machine.-        let mut links = vec![];-        let attempts = [-            Leaf { machine_config_name: hostname.to_string(), copy: false },-            Leaf { machine_config_name: "default".to_string(), copy: false },-        ];-        for (relative_dst, configs) in all {-            let dst = Path::new("/").join(&relative_dst);-            for attempt in &attempts {-                let Some(config) = configs.get(attempt) else { continue };-                let src = system.join(relative_dst).join(if !config.copy {-                    attempt.machine_config_name.to_string()-                } else {-                    format!("copy:{}", attempt.machine_config_name)-                });-                links.push(Link { src, dst, copy: config.copy });-                break;-            }-        }-        Ok(Links { links, backup: None })-    }--    /// Indicates that the destination of each link should be backed up-    /// first.-    fn backup(&mut self, dir: PathBuf) {-        self.backup = Some(dir);-    }--    /// Create the links that do not yet exist.-    ///-    /// When backup is enabled (the default), then all config files will be-    /// backed up before attempting any link. If any backup fails, then no-    /// link will be created.-    ///-    /// The equivalent Unix command to each operation will be written to the-    /// given writer (like during a dry run).-    fn run<W: Write>(&self, mut wtr: W) -> anyhow::Result<()> {-        let mut to_link = vec![];-        for link in &self.links {-            if link.exists()? {-                continue;-            }-            to_link.push(link);-        }-        if let Some(ref backup_dir) = self.backup {-            for link in &to_link {-                link.backup(&mut wtr, backup_dir)?;-            }-        }-        for link in to_link {-            link.install(&mut wtr)?;-        }-        Ok(())-    }--    /// Print the equivalent Unix commands that this would execute if we were-    /// running for real, but make no changes to the file system.-    fn dry_run<W: Write>(&self, mut wtr: W) -> anyhow::Result<()> {-        let mut to_link = vec![];-        for link in &self.links {-            if link.exists()? {-                continue;-            }-            to_link.push(link);-        }-        if let Some(ref backup_dir) = self.backup {-            for link in &to_link {-                link.dry_backup(&mut wtr, backup_dir)?;-            }-        }-        for link in to_link {-            link.dry_install(&mut wtr)?;-        }-        Ok(())-    }-}--/// Represents a link between a file in ~/system and an actual system-/// configuration path on the system. e.g., ~/system/etc/default/grub/krusty-/// will link to /etc/default/grub on my 'krusty' laptop.-#[derive(Debug)]-struct Link {-    /// The ~/system/ path to this machine's config.-    src: PathBuf,-    /// The path to where this config lives on the system.-    dst: PathBuf,-    /// When true, we create a completely new file that is-    /// an identical copy of the source. This is needed in-    /// some cases (like for `/etc/pam.d/system-auth`) where-    /// symlinks are tricky because of permissions. Namely,-    /// the permission of the source file are relevant.-    ///-    /// We can't use hard-links either, because for PAM, files-    /// in `/etc/pam.d` need to be owned by root. But we don't-    /// want this for `~/system`, where files should be owned by-    /// the user. Hardlinks don't allow it.-    ///-    /// So... we copy.-    copy: bool,-}--impl Link {-    /// Write the equivalent Unix command we would execute if we were running-    /// for real.-    fn dry_install<W: Write>(&self, mut wtr: W) -> io::Result<()> {-        let (prog, flags) = if self.copy { ("cp", "") } else { ("ln", " -s") };-        writeln!(-            wtr,-            "{prog}{flags} \"{}\" \"{}\"",-            self.src.display(),-            self.dst.display()-        )-    }--    /// Write the equivalent Unix command we would execute to backup the config-    /// file if we were running for real.-    fn dry_backup<W: Write>(&self, mut wtr: W, dir: &Path) -> io::Result<()> {-        if !self.dst.exists() {-            return Ok(());-        }-        let backup_dst = dir.join(self.relative_dst());-        // OK since self.dst is guaranteed to be non-empty.-        let parent = backup_dst.parent().unwrap();-        writeln!(wtr, "mkdir -p \"{}\"", parent.display(),)?;-        writeln!(-            wtr,-            "cp \"{}\" \"{}\"",-            self.dst.display(),-            backup_dst.display()-        )-    }--    /// Link (or install) the source to the destination, overwriting the-    /// destination if it exists. Callers should ensure that the destination is-    /// backed up before executing.-    fn install<W: Write>(&self, wtr: W) -> anyhow::Result<()> {-        if self.dst.exists() {-            std::fs::remove_file(&self.dst).with_context(|| {-                format!(-                    "failed to delete {} before creating symlink from {}",-                    self.dst.display(),-                    self.src.display()-                )-            })?;-        }-        if self.copy {-            std::fs::copy(&self.src, &self.dst).with_context(|| {-                format!(-                    "failed to copy {} to {}",-                    self.src.display(),-                    self.dst.display()-                )-            })?;-        } else {-            unix::fs::symlink(&self.src, &self.dst).with_context(|| {-                format!(-                    "failed to link {} to {}",-                    self.src.display(),-                    self.dst.display()-                )-            })?;-        }-        self.dry_install(wtr)?;-        Ok(())-    }--    /// Backup the destination to the directory given.-    fn backup<W: Write>(&self, wtr: W, dir: &Path) -> anyhow::Result<()> {-        if !self.dst.exists() {-            return Ok(());-        }--        let backup_dst = dir.join(self.relative_dst());-        // OK since self.dst is guaranteed to be non-empty.-        let parent = backup_dst.parent().unwrap();-        std::fs::create_dir_all(parent).with_context(|| {-            format!(-                "failed to create parent directory {} for backup",-                parent.display()-            )-        })?;-        std::fs::copy(&self.dst, &backup_dst).with_context(|| {-            format!(-                "failed to backup {} to {}",-                self.dst.display(),-                backup_dst.display()-            )-        })?;-        self.dry_backup(wtr, dir)?;-        Ok(())-    }--    /// Returns true if and only if this link already exists in the file-    /// system. That is, when the src and dst refer to the same underlying-    /// file.-    fn exists(&self) -> anyhow::Result<bool> {-        if !self.dst.exists() {-            return Ok(false);-        }-        same_file::is_same_file(&self.src, &self.dst).with_context(|| {-            format!(-                "failed to determine if {} == {}",-                self.src.display(),-                self.dst.display(),-            )-        })-    }--    /// Returns the destination path without its leading /.-    fn relative_dst(&self) -> &Path {-        // OK because dst is always absolute, by construction.-        self.dst.strip_prefix("/").unwrap()-    }-}--#[derive(Debug)]-struct Args {-    /// The directory in which to copy the contents of system configuration-    /// files before removing them and putting a symlink in its place.-    backup: Option<PathBuf>,-    /// When true, make no changes to the file system and instead print what-    /// we would do instead.-    dry_run: bool,-    /// The hostname of this machine (possibly derived from ~/.myhostname).-    hostname: String,-    /// The path to the directory containing this machine's system-    /// configuration. This is currently hard-coded to ~/system.-    system: PathBuf,-}--impl Args {-    fn parse() -> anyhow::Result<Args> {-        use clap::{crate_authors, crate_version, App, Arg};--        let parsed = App::new("Install system configuration symlinks.")-            .author(crate_authors!())-            .version(crate_version!())-            .max_term_width(80)-            .arg(Arg::with_name("no-backup").long("no-backup"))-            .arg(Arg::with_name("dry-run").long("dry-run"))-            .get_matches();--        let home = match env::var_os("HOME") {-            None => anyhow::bail!("HOME environment variable not set"),-            Some(home) => PathBuf::from(home),-        };-        let system = home.join("system");-        if !system.is_dir() {-            anyhow::bail!(-                "{}: system directory does not exist",-                system.display()-            );-        }--        let dry_run = parsed.is_present("dry-run");-        let hostname = runcmd(&["myhostname"])?;-        let backup = if parsed.is_present("no-backup") {-            None-        } else if dry_run {-            let parent = home.join("tmp").join("setup-system-links-backup");-            Some(parent.join("backup-{timestamp}"))-        } else {-            let parent = home.join("tmp").join("setup-system-links-backup");-            Some(-                create_unique_dir_in(parent, "backup")-                    .context("failed to create backup directory")?,-            )-        };-        Ok(Args { backup, dry_run, hostname, system })-    }-}--fn runcmd<A: AsRef<OsStr>>(-    argv: impl IntoIterator<Item = A>,-) -> anyhow::Result<String> {-    let args: Vec<OsString> =-        argv.into_iter().map(|a| a.as_ref().to_os_string()).collect();-    let output = std::process::Command::new(&args[0])-        .args(&args[1..])-        .output()-        .context("failed to run 'myhostname'")?;-    if !output.status.success() {-        let stderr = BString::from(output.stderr.trim().to_vec());-        let args: Vec<Vec<u8>> = args-            .into_iter()-            .map(|a| Vec::from_os_str_lossy(&a).into_owned())-            .collect();-        let mut command = BString::from(bstr::join(" ", args));-        if let Some(code) = output.status.code() {-            command =-                BString::from(format!("`{}` (exit code {})", command, code));-        } else {-            command = BString::from(format!("`{}`", command));-        }-        anyhow::bail!("failed to run {}, stderr:\n\n{}", command, stderr)-    }--    let stdout = output-        .stdout-        .to_str()-        .context("hostname is not valid UTF-8")?-        .trim()-        .to_string();-    Ok(stdout)-}--fn create_unique_dir_in<P: AsRef<Path>, S: AsRef<str>>(-    parent: P,-    prefix: S,-) -> anyhow::Result<PathBuf> {-    use std::time::{Duration, SystemTime};--    let parent = parent.as_ref();-    let prefix = prefix.as_ref();-    std::fs::create_dir_all(&parent).with_context(|| {-        format!("{}: failed to create parent directory", parent.display())-    })?;-    for _ in 0..100 {-        let ts = SystemTime::now()-            .duration_since(SystemTime::UNIX_EPOCH)-            .context("system time is borked")?;-        let name =-            format!("{}-{}-{}", prefix, ts.as_secs(), ts.subsec_nanos());-        let dir = parent.join(name);-        if std::fs::create_dir(&dir).is_err() {-            std::thread::sleep(Duration::from_secs(1));-            continue;-        }-        return Ok(dir);-    }-    anyhow::bail!(-        "failed to create unique directory inside of {}",-        parent.display()-    );-}

bin/sc

@@ -8,4 +8,4 @@ fi d="/tmp/screenshots" mkdir -p "$d" scrot -s "$d/$1"-exec audio-play /usr/share/sounds/freedesktop/stereo/screen-capture.oga+exec pw-play /usr/share/sounds/freedesktop/stereo/screen-capture.oga

bin/startup

@@ -1,6 +1,5 @@ #!/bin/sh -set -x set -e  process-running xconfig-self-healing &

bin/terminal-tmux

@@ -12,4 +12,5 @@ fi exec \   terminal \     -e ssh -AY "$host" -t \-      '. ~/.pathrc ; tmux' -u new -A -s "$tmux_session_name"+      '. ~/.pathrc ; export SSH_AUTH_SOCK="$(relink-ssh-auth-sock)" ; tmux' \+      -u new -A -s "$tmux_session_name"

bin/toggle-headset-mode

@@ -21,7 +21,7 @@ notify_high_fidelity() { }  beep() {-  audio-play /usr/share/sounds/freedesktop/stereo/audio-volume-change.oga+  pw-play /usr/share/sounds/freedesktop/stereo/audio-volume-change.oga }  case "$(myhostname)" in

bin/video

@@ -1,109 +1,88 @@-#!/bin/bash+#!/usr/bin/env bash -. $HOME/bin/x11-go+. "$HOME/bin/x11-go" -function headNum {-  headName="$1"-  gohead tabs | cut -f1-2 | grep "$headName" | cut -f1-}--function placeHead {-  headName="$1"-  geom=$(gohead query "$headName" | cut -f2-5)-  x=$(echo "$geom" | cut -f1)-  y=$(echo "$geom" | cut -f2)-  w=$(echo "$geom" | cut -f3)-  h=$(echo "$geom" | cut -f4)+# Place the pager, status bar and system try on the head given.+#+# The head given should be a name recognized by `gohead`. This also sets the+# "primary" head to be the one given.+place_head() {+  local head_name="$1"+  local x y w h -  place $(headNum "$headName") $x $y $w $h+  IFS=$'\t' read -r x y w h < <(gohead query "$head_name" | cut -f2-5)+  # This is important because stalonetray bases its positioning on the primary+  # RandR monitor.+  gohead primary "$head_name" &> /dev/null+  place_by_head_geometry "$x" "$y" "$w" "$h" } -function array_exists {-  search="$1"-  haystack=($2)+# Places the pager, status bar and system tray based on the head geometry+# given.+#+# This routine doesn't know what head it's being place on, despite the fact+# that dzen2 and stalonetray purportedly support positioning based on RandR+# head number. The problem is that they each use different systems. So we just+# position everything ourselves in the context of the root window geometry.+place_by_head_geometry() {+  local mx="$1"+  local my="$2"+  local mwidth="$3"+  local mheight="$4" -  for needle in ${haystack[@]}; do-    if [ "$search" == "$needle" ]; then-      return 0-    fi-  done+  local height=35+  local font_size=14+  local status_width=450+  local tray_width=100+  if [[ $mheight -gt 1200 ]] || is-media; then+    height=50+    font_size=21+    status_width=650+    tray_width=300+  fi -  return 1+  local extended_status_width=$((status_width + tray_width))+  local pager_width=$((mwidth - extended_status_width))+  local y=$((mheight - height + my))+  dzen-wingo-workspaces "$font_size" "$mx" "$y" "$pager_width" "$height" &+  dzen-laptop-status \+    "$font_size" \+    "$((mx + pager_width))" "$y" "$extended_status_width" "$height" &+  # stalonetray has weird positioning. It's relative to the *primary* RandR+  # head, which we set in `pl`. So we give it -0-0 to indicate the+  # bottom right corner.+  my-stalonetray "0" "0" "$height" & } -function array_key {-  search="$1"-  keys=($2)-  values=($3)+has_arg() {+  local search="$1"+  shift -  for (( i=0; i<${#values[*]}; i++ )); do-    if [ "$search" == "${values[$i]}" ]; then-      echo "${keys[$i]}"+  local arg+  for arg in "$@"; do+    if [ "$search" = "$arg" ]; then+      return 0     fi   done+  return 1 } -if ! gohead set $*; then+# Actually run the necessary `xrandr` command.+if ! gohead set "$@" &> /dev/null; then   exit 1 fi-sleep 1 -case $(myhostname) in-  duff|frink)-    ps aux | grep -v grep | grep dzen-workspaces | awk '{print $2}' | xargs -r kill-    killall trayer-    (sleep 1 && \-      trayer \-      --widthtype pixel --width 150 \-      --heighttype pixel --height 40 \-      --edge bottom --align right --monitor 1 \-      --SetDockType true) &-    (sleep 1 && \-      $HOME/.config/wingo/scripts/dzen-workspaces/dzen-workspaces \-      | dzen2 -dock -xs 3 -h 40 -fn 'Cousine-18' -ta l -bg '#ffffff' -y 1170) &-    ;;-  nas2)-    ps aux | grep -v grep | grep dzen-workspaces | awk '{print $2}' | xargs -r kill-    killall stalonetray-    (-      stalonetray \-        --background '#ffffff' \-        --icon-size 40 --geometry 1x1-0-0 --grow-gravity SE --icon-gravity SE-    ) &-    (-      $HOME/.config/wingo/scripts/dzen-workspaces/dzen-workspaces \-        | dzen2 -dock -xs 3 -h 40 -fn 'Cousine-18' -ta l -bg '#ffffff' -y 1170-    ) &-    ;;-  bart|kang)-    if array_exists laptop "$*"; then-      placeHead laptop-    else-      placeHead "$1"-    fi-    ;;-  flanders|milhouse|wiggum)-    placeHead "$1"-    ;;-  otto)-    pgrep dzen-workspaces | xargs -r kill-    killall trayer-    (sleep 1 && \-      trayer \-      --widthtype pixel --width 150 \-      --heighttype pixel --height 40 \-      --edge bottom --align right \-      --SetDockType true) &+# Kill any pager stuff we had.+killall dzen-workspaces &> /dev/null+killall dzen2 &> /dev/null+killall stalonetray &> /dev/null -    headHeight="$(gohead query tv | cut -f4)"-    y="$((headHeight - 40))"-    (sleep 1 && \-      $HOME/.config/wingo/scripts/dzen-workspaces/dzen-workspaces \-      | dzen2 -dock -xs 2 -h 40 -fn 'Cousine-18' -ta l -bg '#ffffff' -y "$y") &-    ;;-  *)-    echo "Unrecognized machine '$(hostname)'." >&2-    exit 1-    ;;-esac-exit 0+# Look for a special head to place our stuff on first.+for special in main middle; do+  if has_arg "$special" "$@"; then+    place_head "$special"+    exit 0+  fi+done+# Otherwise fall back to the first one given.+place_head "$1"

bin/volume

@@ -30,7 +30,7 @@ notify_volume() {  # Play a little beep. beep() {-  audio-play /usr/share/sounds/freedesktop/stereo/audio-volume-change.oga+  pw-play /usr/share/sounds/freedesktop/stereo/audio-volume-change.oga }  # Gets an icon for the provided volume. The volume provided should be a@@ -51,49 +51,26 @@ get_volume_icon() {   echo "$icon" } -case "$(myhostname)" in-  nas2|duff|kang|bart|flanders|milhouse|otto|wiggum)-    case $1 in-      up)-        pamixer --increase 5-        notify_volume-        ;;-      down)-        pamixer --decrease 5-        notify_volume-        ;;-      mute)-        if ! eval $(pamixer --get-mute); then-          notify_muted-        fi-        pamixer --toggle-mute-        if ! eval $(pamixer --get-mute); then-          notify_volume-        fi-        ;;-      *)-        echo "Unrecognized command: $1" >&2-        exit 1-        ;;-    esac+case $1 in+  up)+    pamixer --increase 5+    notify_volume+    ;;+  down)+    pamixer --decrease 5+    notify_volume+    ;;+  mute)+    if ! eval $(pamixer --get-mute); then+      notify_muted+    fi+    pamixer --toggle-mute+    if ! eval $(pamixer --get-mute); then+      notify_volume+    fi     ;;   *)-    case "$(hostname)" in-      *)      control='Master' mcontrol='Master' ;;-    esac-    case "$(hostname)" in-      monty|Turtle)-        card=0-        ;;-      *)-        card=1-        ;;-    esac--    case $1 in-      up)   amixer -c$card set "$control" 2+ unmute ;;-      down) amixer -c$card set "$control" 2- unmute ;;-      mute) amixer -q set "$mcontrol" toggle ;;-    esac+    echo "Unrecognized command: $1" >&2+    exit 1     ;; esac

bin/x11-init

new file mode 100755@@ -0,0 +1,74 @@+#!/usr/bin/env bash++set -e++. "$HOME/bin/x11-go"++if is-lower-res; then+  sleep 2s+  case "$(myhostname)" in+    duff)+      gohead set left middle right+      ;;+    *)+      gohead set main+      ;;+  esac+fi++run-wingo-environment &+# Wait for Wingo to start before doing other things that generally require+# a WM to be present below. e.g., Starting the bottom dock in `video`.+while ! pgrep '^wingo$'; do+  sleep 0.1s+done++# I'm not sure why this is needed, but when the mode needs to be+# changed, we need to wait a bit more time. Otherwise the mode doesn't+# actually seem to be changed. It ends up being too quick after X11+# starts? I suppose we could change the default mode in a static X11+# configuration, but this is fine in practice.+# if is-lower-res; then+  # sleep 2s+# fi++startup+# Most things jsut want to boot to one main monitor, but duff is my special+# triple monitor setup.+case "$(myhostname)" in+  duff)+    video left middle right &+    ;;+  *)+    video main &+    ;;+esac+variety &+dunst &+ag-blueberry-tray &+(volume mute ; volume mute) &+restart-compositor &+if cmd-exists nm-applet; then+  nm-applet &+fi+if is-laptop; then+  autobright &++  # An agent to make the fingerprint sensor work.+  /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1 &+fi+if is-media; then+  idesk &+fi++# Run the 1password tray.+1password \+  --silent \+  --force-device-scale-factor=1.5 \+  --log=debug &> /tmp/1password.log &++if is-laptop; then+  (ssh-add-keys && relink-ssh-auth-sock && systemctl --user start mount-nas) &+fi++while true; do sleep 86400s; done

system/etc/NetworkManager/dispatcher.d/50-mount-nas/copy:bart

new file mode 120000@@ -0,0 +1 @@+run\ No newline at end of file

system/etc/NetworkManager/dispatcher.d/50-mount-nas/run

new file mode 100755@@ -0,0 +1,39 @@+#!/usr/bin/env bash++target_user=andrew+target_uid="$(id -u "$target_user")"+unit=mount-nas.service+tag=NetworkManager-mount-nas++iface="${1:-}"+action="${2:-}"++case "$action" in+  up|vpn-up)+    ;;+  connectivity-change)+    # Only react when NetworkManager says connectivity is fully online.+    [ "${CONNECTIVITY_STATE:-}" = "FULL" ] || exit 0+    ;;+  *)+    exit 0+    ;;+esac++runtime_dir="/run/user/$target_uid"+bus="unix:path=$runtime_dir/bus"++if [ ! -S "$runtime_dir/bus" ]; then+  logger -t "$tag" \+    "user systemd bus not available for $target_user; not starting $unit"+  exit 0+fi++logger -t "$tag" \+  "NetworkManager action=$action iface=$iface" \+  "connection=${CONNECTION_ID:-unknown}; starting $unit"++runuser -u "$target_user" -- \+  env XDG_RUNTIME_DIR="$runtime_dir" \+      DBUS_SESSION_BUS_ADDRESS="$bus" \+      systemctl --user start --no-block "$unit"