about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.bash_logout13
-rw-r--r--.emacs.d/.gitignore5
-rw-r--r--.emacs.d/README.md1
-rw-r--r--.emacs.d/early-init.el36
-rw-r--r--.emacs.d/init.el8
-rw-r--r--.emacs.d/lisp/init-clojure.el41
-rw-r--r--.emacs.d/lisp/init-completion.el184
-rw-r--r--.emacs.d/lisp/init-crux.el11
-rw-r--r--.emacs.d/lisp/init-dired.el5
-rw-r--r--.emacs.d/lisp/init-editor.el99
-rw-r--r--.emacs.d/lisp/init-emacs-lisp.el14
-rw-r--r--.emacs.d/lisp/init-git.el9
-rw-r--r--.emacs.d/lisp/init-kill.el1
-rw-r--r--.emacs.d/lisp/init-lsp.el34
-rw-r--r--.emacs.d/lisp/init-minibuffer.el276
-rw-r--r--.emacs.d/lisp/init-misc.el26
-rw-r--r--.emacs.d/lisp/init-navigation.el27
-rw-r--r--.emacs.d/lisp/init-nix.el3
-rw-r--r--.emacs.d/lisp/init-packages.el78
-rw-r--r--.emacs.d/lisp/init-project.el15
-rw-r--r--.emacs.d/lisp/init-search.el158
-rw-r--r--.emacs.d/lisp/init-shell.el25
-rw-r--r--.emacs.d/lisp/init-sql.el5
-rw-r--r--.emacs.d/lisp/init-ui.el96
-rw-r--r--.emacs.d/lisp/init-windows.el79
-rw-r--r--.emacs.d/themes/non-modo-theme.el6
-rw-r--r--nix-conf/.sops.yaml18
-rw-r--r--nix-conf/home/config.nix1
-rw-r--r--nix-conf/home/djmuk1.nix6
-rw-r--r--nix-conf/home/djmuk2.nix4
-rw-r--r--nix-conf/home/edrahil.nix4
-rw-r--r--nix-conf/home/egalmoth.nix6
-rw-r--r--nix-conf/home/includes/clojure.nix33
-rw-r--r--nix-conf/home/includes/common.nix218
-rw-r--r--nix-conf/home/includes/darwin.nix111
-rw-r--r--nix-conf/home/includes/dev-common.nix71
-rw-r--r--nix-conf/home/includes/irssi.nix18
-rw-r--r--nix-conf/home/includes/linux-dev.nix19
-rw-r--r--nix-conf/home/includes/linux-server.nix10
-rw-r--r--nix-conf/home/includes/scripts/hm-changes-report.nix3
-rw-r--r--nix-conf/home/includes/scripts/system-changes-report.nix1
-rw-r--r--nix-conf/home/includes/secrets.yaml21
-rw-r--r--nix-conf/home/includes/zsh.nix418
-rw-r--r--nix-conf/home/otm.nix229
-rw-r--r--nix-conf/machines/djmuk1/configuration.nix86
-rw-r--r--nix-conf/machines/djmuk1/hardware-configuration.nix17
-rw-r--r--nix-conf/machines/djmuk2/configuration.nix93
-rw-r--r--nix-conf/machines/djmuk2/hardware-configuration.nix18
-rw-r--r--nix-conf/machines/djmuk2/secrets.yaml21
-rw-r--r--nix-conf/machines/edrahil/configuration.nix191
-rw-r--r--nix-conf/machines/edrahil/hardware-configuration.nix14
-rw-r--r--nix-conf/machines/edrahil/network-configuration.nix19
-rw-r--r--nix-conf/machines/edrahil/secrets.yaml31
-rw-r--r--nix-conf/machines/egalmoth/configuration.nix101
-rw-r--r--nix-conf/machines/egalmoth/hardware-configuration.nix41
-rwxr-xr-xsetup-home.sh6
56 files changed, 2120 insertions, 964 deletions
diff --git a/.bash_logout b/.bash_logout
deleted file mode 100644
index df7ab07..0000000
--- a/.bash_logout
+++ /dev/null
@@ -1,13 +0,0 @@
-# don't leave behind any stray ssh sessions - kill them when bash exits,
-# unless it was running inside a tmux session (but the idea is to kill
-# ssh sessions running inside tmux!)
-if [ -n "${SSH_CONNECTION}" -a -z "${TMUX}" ]; then
-    for ssh_host in $(grep 'Host\b' .ssh/config|cut -f2 -d' ') ; do
-        ssh_pid=$(ps u|awk "/ssh ${ssh_host}/ && !/grep/{print $2}")
-        [ -n "${ssh_pid}" ] && kill ${ssh_pid} 2>/dev/null
-    done
-fi
-
-if [ "$SHLVL" = 1 ]; then
-    [ -x /usr/bin/clear_console ] && /usr/bin/clear_console -q
-fi
diff --git a/.emacs.d/.gitignore b/.emacs.d/.gitignore
index 9168e78..2ce66c1 100644
--- a/.emacs.d/.gitignore
+++ b/.emacs.d/.gitignore
@@ -36,5 +36,8 @@ site-lisp
 straight
 !straight/versions
 elpaca
-elpa
+elpaca.lock
 eln-cache
+forge-database.sqlite
+sql
+ielm
diff --git a/.emacs.d/README.md b/.emacs.d/README.md
index 965a718..0f006b6 100644
--- a/.emacs.d/README.md
+++ b/.emacs.d/README.md
@@ -10,6 +10,7 @@ My Emacs Configuration, inspired by Prelude (https://github.com/bbatsov/prelude)
 - https://gitlab.com/protesilaos/dotfiles
 - https://gitlab.com/buildfunthings/emacs-config
 - https://www.emacswiki.org/emacs
+- https://github.com/jamescherti/minimal-emacs.d
 
 As well as the github READMEs and wikis for corfu, vertico, orderless, consult, embark, marginalia, and other places too.
 
diff --git a/.emacs.d/early-init.el b/.emacs.d/early-init.el
index 9e1f474..e55becc 100644
--- a/.emacs.d/early-init.el
+++ b/.emacs.d/early-init.el
@@ -2,12 +2,18 @@
 ;;; Commentary:
 ;;; Code:
 
+
+(when (eq system-type 'darwin)
+  (setq frame-resize-pixelwise t))
+
 (setq gc-cons-threshold most-positive-fixnum
       gc-cons-percentage 0.6)
 
 (setq load-prefer-newer t
       native-comp-async-report-warnings-errors nil
+      native-comp-warning-on-missing-source nil
       warning-suppress-log-types '((comp) (bytecomp))
+      byte-compile-verbose nil
       byte-compile-warnings '(not free-vars unresolved noruntime lexical make-local obsolete))
 
 (push '(menu-bar-lines . 0) default-frame-alist)
@@ -19,15 +25,28 @@
 
 (setq frame-inhibit-implied-resize t)
 
-(toggle-frame-maximized)
-(add-to-list 'default-frame-alist '(fullscreen . maximized))
+(if (eq system-type 'darwin)
+    (add-to-list 'default-frame-alist '(undecorated-round . t))
+  (toggle-frame-maximized)
+  (add-to-list 'default-frame-alist '(fullscreen . maximized)))
 
 (menu-bar-mode -1)
 (when window-system
   (tool-bar-mode -1)
   (scroll-bar-mode -1)
+  (tooltip-mode -1)
   (horizontal-scroll-bar-mode -1))
 
+;; Copied from https://github.com/jamescherti/minimal-emacs.d
+;; Some features that are not represented as packages can be found in
+;; `features', but this can be inconsistent. The following enforce consistency:
+(if (fboundp #'json-parse-string)
+    (push 'jansson features))
+(if (string-match-p "HARFBUZZ" system-configuration-features) ; no alternative
+    (push 'harfbuzz features))
+(if (bound-and-true-p module-file-suffix)
+    (push 'dynamic-modules features))
+
 (setq package-enable-at-startup nil)
 
 ;; Some optimizations from doom.el (some of these probably don't belong here!)
@@ -42,6 +61,19 @@
 (setq inhibit-compacting-font-caches t)
 (setq redisplay-skip-fontification-on-input t)
 
+;; Copied/modified from https://github.com/jamescherti/minimal-emacs.d
+(setq ad-redefinition-action 'accept
+      warning-suppress-types '((lexical-binding))
+      inhibit-startup-buffer-menu t
+      inhibit-x-resources t
+      use-file-dialog nil
+      use-dialog-box nil)
+(advice-add #'display-startup-screen :override #'ignore)
+(unless (eq system-type 'darwin)
+  (setq command-line-ns-option-alist nil))
+(unless (memq initial-window-system '(x pgtk))
+  (setq command-line-x-option-alist nil))
+
 (define-advice load-file (:override (file) silence)
   (load file nil 'nomessage))
 (define-advice startup--load-user-init-file (:before (&rest _) undo-silence)
diff --git a/.emacs.d/init.el b/.emacs.d/init.el
index 7c8c8d8..2250665 100644
--- a/.emacs.d/init.el
+++ b/.emacs.d/init.el
@@ -26,14 +26,6 @@
                                             gc-cons-percentage 0.1))
           99)
 
-;; Some straight functions need to be able to reload everything, so require won't do
-(defun require! (feature &optional filename noerror)
-  "Like `require', but if `force-reload' is non-nil, `load' instead.
-`FEATURE', `FILENAME' and `NOERROR' have the same meaning as with require"
-  (if (and (boundp 'force-reload) force-reload)
-      (load (prin1-to-string feature) noerror nil nil t)
-    (require feature filename noerror)))
-
 (require 'init-packages)
 (require 'init-ui)
 (require 'init-compile)
diff --git a/.emacs.d/lisp/init-clojure.el b/.emacs.d/lisp/init-clojure.el
index 2bfaf78..7673565 100644
--- a/.emacs.d/lisp/init-clojure.el
+++ b/.emacs.d/lisp/init-clojure.el
@@ -28,7 +28,8 @@
       ("st" "#spy/t")))
 
   (defalias 'cape-clojure (cape-capf-super #'cider-complete-at-point
-                                           #'lsp-completion-at-point))
+                                           #'lsp-completion-at-point
+                                           #'cape-dabbrev))
   (defun set-clojure-capf ()
     (add-hook 'completion-at-point-functions #'cape-clojure -99 t))
 
@@ -101,14 +102,13 @@
   :init
   ;; Always show more of the path in clj buffer names.
   ;; Using setq-local in clojure-mode-hook is not enough, as it runs too late
-  (defun clj-uniquify-get-proposed-name (orig base dirname &optional depth original-dirname)
+  (defun clj-uniquify-get-proposed-name (orig base dirname &optional depth)
     (when (and (> (length base) 4)
                (string= ".clj" (substring base -4))
                (not (string= "project.clj" base)))
       (setq-local uniquify-min-dir-content 3))
-    (funcall orig base dirname depth original-dirname))
+    (funcall orig base dirname depth))
   (advice-add 'uniquify-get-proposed-name :around 'clj-uniquify-get-proposed-name)
-  :bind
   :hook
   (clojure-mode . clojure-mode-hook-fn))
 
@@ -152,10 +152,10 @@
     (cider-jack-in params))
   (defun load-debug-namespaces ()
     (interactive)
-    (cider-interactive-eval "(require 'snitch.core)" nil nil (cider--nrepl-pr-request-map))
-    (cider-interactive-eval "(require 'miracle.save)" nil nil (cider--nrepl-pr-request-map))
-    (cider-interactive-eval "(require 'sc.api)" nil nil (cider--nrepl-pr-request-map))
-    (cider-interactive-eval "(require '[debux.cs.core :refer [dbg dbgn dbgt]])" nil nil (cider--nrepl-pr-request-map)))
+    (cider-interactive-eval "(require 'snitch.core)" nil nil (cider--nrepl-pr-request-plist))
+    (cider-interactive-eval "(require 'miracle.save)" nil nil (cider--nrepl-pr-request-plist))
+    (cider-interactive-eval "(require 'sc.api)" nil nil (cider--nrepl-pr-request-plist))
+    (cider-interactive-eval "(require '[debux.cs.core :refer [dbg dbgn dbgt]])" nil nil (cider--nrepl-pr-request-plist)))
   (defun cider-toggle-boolean ()
     (interactive)
     (let ((opposite (pcase (cider-symbol-at-point)
@@ -196,30 +196,47 @@
         cider-connection-message-fn nil
         cider-show-error-buffer 'except-in-repl
         cider-test-fail-fast nil
+        cider-download-java-sources t
         clojure-toplevel-inside-comment-form t)
   (setq cider-clojure-compilation-error-phases nil)
   (setq-default cider-use-overlays t)
   (unbind-key "C-c C-l" cider-mode-map)
   (unbind-key "C-c C-b" cider-mode-map)
   (unbind-key "C-c C-b" cider-repl-mode-map)
+  (unbind-key "M-." cider-repl-mode-map)
 
   (defun fix-duplicate-windows ()
     "When all windows are the same, delete all of them except the current one."
     (when (apply #'eq (mapcar 'window-buffer (window-list)))
       (delete-other-windows)))
   (advice-add #'cider-close-ancillary-buffers :after #'fix-duplicate-windows)
+
   :bind
   (:map cider-mode-map
         ("C-c M-l" . cider-load-file)
-        ("C-c M-b" . cider-interrupt))
+        ("C-c M-b" . cider-interrupt)
+        ("C-c C-j C-;" . cider-pprint-eval-last-sexp-to-comment)
+        ("C-c C-M-p" . cider-pprint-eval-last-sexp-to-repl)
+        ("C-x M-i e" . cider-inspect-last-sexp)
+        ("C-x M-i f" . cider-inspect-defun-at-point)
+        ("C-x M-i l" . cider-inspect-last-result)
+        ("C-x M-i v" . cider-inspect-expr))
   (:map cider-repl-mode-map
-        ("C-c M-b" . cider-interrupt))
+        ("C-c M-b" . cider-interrupt)
+        ;; sp commands sometimes behave strangely in the cider repl buffer
+        ("M-d" . paredit-forward-kill-word)
+        ("M-DEL" . paredit-backward-kill-word)
+        ("C-k" . paredit-kill)
+        ("C-x M-i e" . cider-inspect-last-sexp)
+        ("C-x M-i f" . cider-inspect-defun-at-point)
+        ("C-x M-i l" . cider-inspect-last-result)
+        ("C-x M-i v" . cider-inspect-expr))
   (:map cider-start-map
-        ("C-c M-i" . cider-jack-in-and-run-main))
+        ("C-c C-M-j" . cider-jack-in-and-run-main))
   (:map clojure-mode-map
         ("C-c C-r C-m" . run-main)
         ("C-c C-r C-d" . load-debug-namespaces)
-        ("C-c M-i" . cider-jack-in-and-run-main)
+        ("C-c C-M-j" . cider-jack-in-and-run-main)
         ("C-x p q" . project-clojure-test-switch)
         ("C-c C-M-c" . (lambda () (interactive) (cider-clear-compilation-highlights t)))
         ("C-c C->" . cider-find-dwim)
diff --git a/.emacs.d/lisp/init-completion.el b/.emacs.d/lisp/init-completion.el
index cac9c0a..16eca6b 100644
--- a/.emacs.d/lisp/init-completion.el
+++ b/.emacs.d/lisp/init-completion.el
@@ -4,75 +4,103 @@
 ;; Most of it is taken from the READMEs and wikis of those packages
 ;;; Code:
 
+(defun +elpaca-unload-dabbrev (e)
+  (and (featurep 'dabbrev) (unload-feature 'dabbrev t))
+  (elpaca--continue-build e))
+
+(defun +elpaca-dabbrev-build-steps ()
+  (append (butlast (if (file-exists-p (expand-file-name "dabbrev" elpaca-builds-directory))
+                       elpaca--pre-built-steps elpaca-build-steps))
+          (list '+elpaca-unload-dabbrev 'elpaca--activate-package)))
+
 (use-feature dabbrev
-  :diminish
   :custom
   (dabbrev-case-distinction nil)
   (dabbrev-case-fold-search t)
   (dabbrev-case-replace nil))
 
-(use-feature hippie-expand
+(use-package mono-complete
   :config
-  (setq hippie-expand-try-functions-list
-        '(;yas-hippie-try-expand
-          try-expand-dabbrev
-          try-expand-all-abbrevs
-          try-expand-dabbrev-all-buffers
-          try-expand-dabbrev-from-kill
-          try-complete-file-name-partially
-          try-complete-file-name
-          try-expand-list
-          try-expand-line
-          try-complete-lisp-symbol-partially
-          try-complete-lisp-symbol))
-  ;; https://www.emacswiki.org/emacs/HippieExpand#h5o-9
-  (defadvice he-substitute-string (after he-paredit-fix)
-    "Remove extra paren when expanding line in paredit."
-    (if (and (or smartparens-mode paredit-mode) (equal (substring str -1) ")"))
-        (progn (backward-delete-char 1) (forward-char))))
+  (setq mono-complete-preview-delay 0.15
+        mono-complete-backends '(dabbrev filesystem whole-line)
+        mono-complete-project-root 'persp-current-project-root)
+  (append-to-list* 'mono-complete-self-insert-commands 'sp-backward-delete-char 'sp-delete-char 'delete-indentation 'backward-delete-char 'delete-char)
+  (defun mono-complete-expand-or-complete ()
+    (interactive)
+    (if (eq 'mono-complete-expand-or-complete real-last-command)
+        (let ((corfu-preselect 'prompt))
+          (progn
+            (primitive-undo 2 buffer-undo-list)
+            (completion-at-point)))
+      (mono-complete-expand-or-fallback)))
+  :hook ((text-mode prog-mode) . mono-complete-mode)
   :bind
-  ("C-M-/" . hippie-expand))
+  (:map mono-complete-mode-map ("M-/" . mono-complete-expand-or-complete)))
 
-(use-package fancy-dabbrev
-  :diminish
-  :config
-  (global-fancy-dabbrev-mode)
-  (defun fancy-dabbrev-popup-advice (_next)
-    (local-set-key (kbd "C-M-/") #'fancy-dabbrev-backward))
-  (defun fancy-dabbrev-popup-exit-advice ()
-    (local-unset-key (kbd "C-M-/")))
-  (advice-add #'fancy-dabbrev--expand-again :before #'fancy-dabbrev-popup-advice)
-  (advice-add #'fancy-dabbrev--on-exit :after #'fancy-dabbrev-popup-exit-advice)
-  :bind ("M-/" . fancy-dabbrev-expand))
+(use-feature hippie-expand
+  :custom
+  (hippie-expand-try-functions-list '(try-expand-dabbrev
+                                      try-expand-all-abbrevs
+                                      try-expand-dabbrev-all-buffers
+                                      try-expand-dabbrev-from-kill
+                                      try-complete-file-name-partially
+                                      try-complete-file-name
+                                      try-expand-list
+                                      try-expand-line
+                                      try-complete-lisp-symbol-partially
+                                      try-complete-lisp-symbol))
+  :hook (elpaca-after-init . (lambda ()
+                               ;; Modified from https://www.emacswiki.org/emacs/HippieExpand#h5o-9
+                               (define-advice he-substitute-string (:after (str &optional trans-case) he-paredit-fix)
+                                 "Remove extra bracket when expanding line in paredit/smartparents mode."
+                                 (if (and (or smartparens-mode paredit-mode) (string-match "[]})]$" str))
+                                     (progn (backward-delete-char 1) (forward-char))))))
+  :bind
+  ("C-M-/" . hippie-expand))
 
 (use-feature emacs
   :config
-  (setq completion-cycle-threshold 3)
+  (setq completion-cycle-threshold 2)
   (setq tab-always-indent 'complete)
   (setq read-buffer-completion-ignore-case t
         read-file-name-completion-ignore-case t
         completion-ignore-case t))
 
 (use-package orderless
-  :defer 2
-  :bind (:map minibuffer-local-map
-              ("C-l" . my/orderless-match-components-literally))
+  :bind
+  (:map minibuffer-local-map
+        ("C-l" . orderless-toggle-literal-matching))
+  (:map corfu-map
+        ("C-l" . orderless-toggle-literal-matching))
   :custom
   (orderless-component-separator 'orderless-escapable-split-on-space)
   (completion-styles '(orderless partial-completion basic))
   (completion-category-defaults nil)
-  (completion-category-overrides '((file (styles . (partial-completion orderless)))))
   (orderless-matching-styles '(orderless-literal orderless-regexp orderless-strict-initialism))
-  (orderless-style-dispatchers '(+orderless-dispatch))
+  (orderless-style-dispatchers (list #'+orderless-consult-dispatch
+                                     #'orderless-affix-dispatch))
   :config
-  (defun my/orderless-match-components-literally ()
-    "Components match literally for the rest of the session."
+  ;; Inspired by https://github.com/oantolin/orderless/blob/ac4aeb66f331f4c4a430d5556071e33177304c37/README.org#interactively-changing-the-configuration
+  (defun orderless-toggle-literal-matching ()
+    "Toggle matching components literally for the rest of the session."
     (interactive)
-    (setq-local orderless-matching-styles '(orderless-literal)
-                orderless-style-dispatchers nil))
+    (if orderless-style-dispatchers
+        (progn
+          (setq-local +saved-orderless-matching-styles orderless-matching-styles
+                      +saved-orderless-style-dispatchers orderless-style-dispatchers)
+          (setq-local orderless-matching-styles '(orderless-literal)
+                      orderless-style-dispatchers nil))
+      (setq-local orderless-matching-styles +saved-orderless-matching-styles
+                  orderless-style-dispatchers +saved-orderless-style-dispatchers))
+    (when vertico--input
+      (setq vertico--input t)
+      (vertico--update))
+    (when corfu--input
+      (setq corfu--input t)
+      (corfu--update)))
 
   (defun orderless-strict-initialism (component &optional leading)
-    "Match a component as a strict leading initialism.
+    "Match a component as a strict initialism.
 This means the characters in COMPONENT must occur in the
 candidate, in that order, at the beginning of words, with
 no words in between. If LEADING is non-nil, anchor to the
@@ -88,68 +116,84 @@ candidate, in that order, at the beginning of words, with
 no words in between, beginning with the first word."
     (orderless-strict-initialism component t))
 
-  ;; based on https://github.com/minad/consult/wiki#minads-orderless-configuration
-  (defvar +orderless-dispatch-alist
-    '((?% . char-fold-to-regexp)
-      (?! . orderless-without-literal)
-      (?` . orderless-strict-leading-initialism)
-      (?= . orderless-literal)
-      (?_ . orderless-prefix)
-      (?~ . orderless-flex)))
+  ;; Replace initialism (,) with strict-leading-initialism, and also add strict initialism
+  (setf (alist-get ?, orderless-affix-dispatch-alist) #'orderless-strict-leading-initialism)
+  (add-to-list 'orderless-affix-dispatch-alist '(?` . orderless-strict-initialism) t)
 
-  (defun +orderless--suffix-regexp ()
+  ;; Copied from https://github.com/minad/consult/wiki#minads-orderless-configuration
+  (defun +orderless--consult-suffix ()
+    "Regexp which matches the end of string with Consult tofu support."
     (if (and (boundp 'consult--tofu-char) (boundp 'consult--tofu-range))
         (format "[%c-%c]*$"
                 consult--tofu-char
                 (+ consult--tofu-char consult--tofu-range -1))
       "$"))
 
+  ;; Copied from https://github.com/minad/consult/wiki#minads-orderless-configuration
   ;; Recognizes the following patterns:
-  ;; * ~flex flex~
-  ;; * =literal literal=
-  ;; * _prefix prefix_
-  ;; * %char-fold char-fold%
-  ;; * `strict-leading-initialism strict-leading-initialism`
-  ;; * !without-literal without-literal!
   ;; * .ext (file extension)
   ;; * regexp$ (regexp matching at end)
-  (defun +orderless-dispatch (word _index _total)
+  (defun +orderless-consult-dispatch (word _index _total)
     (cond
      ;; Ensure that $ works with Consult commands, which add disambiguation suffixes
      ((string-suffix-p "$" word)
-      `(orderless-regexp . ,(concat (substring word 0 -1) (+orderless--suffix-regexp))))
+      `(orderless-regexp . ,(concat (substring word 0 -1) (+orderless--consult-suffix))))
      ;; File extensions
      ((and (or minibuffer-completing-file-name
                (derived-mode-p 'eshell-mode))
            (string-match-p "\\`\\.." word))
-      `(orderless-regexp . ,(concat "\\." (substring word 1) (+orderless--suffix-regexp))))
-     ;; Ignore single !
-     ((equal "!" word) `(orderless-literal . ""))
-     ;; Prefix and suffix
-     ((if-let (x (assq (aref word 0) +orderless-dispatch-alist))
-          (cons (cdr x) (substring word 1))
-        (when-let (x (assq (aref word (1- (length word))) +orderless-dispatch-alist))
-          (cons (cdr x) (substring word 0 -1))))))))
+      `(orderless-regexp . ,(concat "\\." (substring word 1) (+orderless--consult-suffix))))))
+
+  ;; Based on https://github.com/minad/consult/wiki#minads-orderless-configuration
+  (orderless-define-completion-style +orderless-with-strict-leading-initialism
+    (orderless-matching-styles '(orderless-literal orderless-regexp orderless-strict-leading-initialism)))
+
+  (setopt completion-category-overrides '((file (styles partial-completion orderless))
+                                          (command (styles +orderless-with-strict-leading-initialism))
+                                          (variable (styles +orderless-with-strict-leading-initialism))
+                                          (symbol (styles +orderless-with-strict-leading-initialism)))))
 
 ;; code completion - corfu
 (use-package corfu
   :ensure (corfu :files (:defaults "extensions/*"))
   :custom
   (corfu-cycle t)
+  (corfu-preselect 'first)
   :bind (:map corfu-map
+              ("SPC" . corfu-insert)
               ("TAB" . corfu-next)
               ([tab] . corfu-next)
+              ("M-/" . corfu-next)
               ("S-TAB" . corfu-previous)
-              ([backtab] . corfu-previous))
+              ([backtab] . corfu-previous)
+              ("C-M-/" . corfu-previous))
   :hook (elpaca-after-init . global-corfu-mode))
 
 (use-extension corfu corfu-indexed
-  :config (corfu-indexed-mode 1))
+  :config
+  (defmacro define-corfu-complete (n)
+    `(defun ,(intern (format "corfu-indexed-complete-%s" n)) ()
+       ,(format "Complete with candidate %s." n)
+       (interactive)
+       (let ((corfu--index ,n))
+         (funcall-interactively 'corfu-complete))))
+  (defmacro define-corfu-insert (n)
+    `(defun ,(intern (format "corfu-indexed-insert-%s" n)) ()
+       ,(format "Insert candidate %s." n)
+       (interactive)
+       (let ((corfu--index ,n))
+         (funcall-interactively 'corfu-insert))))
+  (dotimes (n 10)
+    (eval `(define-corfu-complete ,n))
+    (eval `(define-corfu-insert ,n))
+    (define-key corfu-map (kbd (format "C-%s" n)) (intern (format "corfu-indexed-complete-%s" n)))
+    (define-key corfu-map (kbd (format "M-%s" n)) (intern (format "corfu-indexed-insert-%s" n))))
+  (corfu-indexed-mode 1))
 
 (use-extension corfu corfu-quick
   :bind (:map corfu-map
               ("M-;" . corfu-quick-insert)
-              ("M-'" . corfu-quick-exit)))
+              ("M-'" . corfu-quick-complete)))
 
 (use-extension corfu corfu-history
   :after savehist
diff --git a/.emacs.d/lisp/init-crux.el b/.emacs.d/lisp/init-crux.el
index 0739bd3..5f7b836 100644
--- a/.emacs.d/lisp/init-crux.el
+++ b/.emacs.d/lisp/init-crux.el
@@ -6,16 +6,6 @@
   :defer 5
   :commands crux-start-or-switch-to
   :config
-  (defmacro crux-with-region-or-sexp-or-line (func)
-    "When called with no active region, call FUNC on current sexp."
-    `(defadvice ,func (before with-region-or-sexp-or-line activate compile)
-       (interactive
-        (cond
-         (mark-active (list (region-beginning) (region-end)))
-         ((in-string-p) (flatten-list (bounds-of-thing-at-point 'string)))
-         ((thing-at-point 'list) (flatten-list (bounds-of-thing-at-point 'list)))
-         (t (list (line-beginning-position) (line-beginning-position 2)))))))
-
   (crux-with-region-or-sexp-or-line sp-kill-region)
   (crux-with-region-or-buffer shell-command-on-region)
   (crux-with-region-or-buffer indent-region)
@@ -25,6 +15,7 @@
   ("C-^" . crux-top-join-line)
   ("C-<backspace>" . crux-kill-line-backwards)
   ([remap kill-whole-line] . crux-kill-whole-line)
+  ([remap keyboard-quit] . crux-keyboard-quit-dwim)
   ;; TODO don't need all of these
   ("C-<return>" . crux-smart-open-line)
   ("S-<return>" . crux-smart-open-line)
diff --git a/.emacs.d/lisp/init-dired.el b/.emacs.d/lisp/init-dired.el
index 5ec6531..bd01329 100644
--- a/.emacs.d/lisp/init-dired.el
+++ b/.emacs.d/lisp/init-dired.el
@@ -13,6 +13,8 @@
   (dired-kill-when-opening-new-dired-buffer t)
   (wdired-use-dired-vertical-movement 'sometimes)
   (dired-vc-rename-file t)
+  (dired-clean-confirm-killing-deleted-buffers nil)
+  (dired-create-destination-dirs 'ask)
   :bind (:map dired-mode-map
               ("M-o" . dired-omit-mode)
               ("E" . wdired-change-to-wdired-mode)))
@@ -25,7 +27,8 @@
              ("i" . dired-subtree-insert)
              (";" . dired-subtree-remove)))
 
-(use-package casual-dired
+(use-feature casual-dired
+  :after dired
   :bind (:map dired-mode-map
               ("C-o" . casual-dired-tmenu)
               ("s" . casual-dired-sort-by-tmenu)))
diff --git a/.emacs.d/lisp/init-editor.el b/.emacs.d/lisp/init-editor.el
index 5cb0cc7..fc575c4 100644
--- a/.emacs.d/lisp/init-editor.el
+++ b/.emacs.d/lisp/init-editor.el
@@ -33,14 +33,24 @@
   (setq comment-auto-fill-only-comments t)
   (setq large-file-warning-threshold 100000000)
   (setq create-lockfiles nil)
-  (setq global-auto-revert-non-file-buffers t)
-  (setq backup-by-copying t)
+  (setq global-auto-revert-non-file-buffers t
+        revert-without-query (list ".")
+        auto-revert-stop-on-user-input nil)
+  (setq backup-by-copying t
+        delete-old-versions t
+        version-control t
+        kept-new-versions 5
+        kept-old-versions 5)
   (setq backup-directory-alist
         `((".*" . ,temporary-file-directory))
         auto-save-file-name-transforms
-        `((".*" ,temporary-file-directory t)))
+        `((".*" ,temporary-file-directory t))
+        auto-save-include-big-deletions t)
+  (setq comment-multi-line t
+        comment-empty-lines t)
 
-  (setq save-place-file (expand-file-name "saveplace" save-dir))
+  (setq save-place-file (expand-file-name "saveplace" save-dir)
+        save-place-limit 800)
   ;; https://git.sr.ht/~technomancy/better-defaults/tree/master/item/better-defaults.el
   (setq save-interprogram-paste-before-kill t
         apropos-do-all t
@@ -50,11 +60,12 @@
   (setq ffap-machine-p-local 'accept
         ffap-machine-p-known 'reject
         ffap-machine-p-unkown 'reject)
-  ;; https://github.com/natecox/dotfiles/blob/master/workspaces/shared/symlinks/emacs/.emacs.d/nathancox.org
+
   (setq sentence-end-double-space nil)
-  (set-charset-priority 'unicode)
-  (setq locale-coding-system 'utf-8)
-  (setq default-process-coding-system '(utf-8-unix . utf-8-unix))
+  (set-language-environment "UTF-8")
+  ;; The previous line sets this to "rfc1345"
+  (setq default-input-method nil)
+
   (set-default 'imenu-auto-rescan t))
 
 (use-package move-text
@@ -72,6 +83,7 @@
   (savehist-additional-variables '(search-ring regexp-search-ring))
   (savehist-autosave-interval 60)
   (savehist-file (expand-file-name "savehist" save-dir))
+  (history-length 300)
   :hook (elpaca-after-init . savehist-mode))
 
 (use-package super-save
@@ -99,13 +111,21 @@
   :config
   (global-flycheck-mode))
 
-;(use-package flyspell
-;  :custom
-;  (ispell-program-name "aspell")
-;  (ispell-extra-args '("--sug-mode=ultra"))
-;  :hook
-;  (text-mode . (lambda () (flyspell-mode +1)))
-;  (prog-mode . (lambda () (flyspell-prog-mode))))
+(use-feature flyspell
+  :diminish
+  :config
+  (when (string-suffix-p "aspell" ispell-program-name)
+    (setq ispell-extra-args '("--sug-mode=ultra")))
+  (unbind-key "C-," flyspell-mode-map)
+  (unbind-key "C-." flyspell-mode-map)
+  ;;(unbind-key "C-;" flyspell-mode-map)
+  :custom (flyspell-auto-correct-binding (kbd "C-x C-M-;"))
+  :bind (:map flyspell-mode-map
+              ("C-x C-," . flyspell-goto-next-error)
+              ("C-x C-." . flyspell-correct-word))
+  :hook
+  (text-mode . flyspell-mode)
+  (prog-mode . flyspell-prog-mode))
 
 (use-feature bookmark
   :custom
@@ -125,7 +145,6 @@
   (undo-tree-auto-save-history t))
 
 (use-feature abbrev
-  :defer 5
   :diminish
   :hook
   (text-mode . abbrev-mode)
@@ -146,28 +165,48 @@
 
 (use-package operate-on-number
   :bind
-  ("C-c ." . operate-on-number-at-point))
+  ("C-c +" . operate-on-number-at-point))
 
 (defun +elpaca-unload-xref (e)
   (and (featurep 'xref) (unload-feature 'xref t))
+  ;; Make sure these aren't overwritten
+  (setq xref-search-program 'ripgrep
+        xref-show-xrefs-function #'consult-xref
+        xref-show-definitions-function #'consult-xref)
   (elpaca--continue-build e))
 
 (defun +elpaca-xref-build-steps ()
   (append (butlast (if (file-exists-p (expand-file-name "xref" elpaca-builds-directory))
                        elpaca--pre-built-steps elpaca-build-steps))
-          (list '+elpaca-unload-xref'elpaca--activate-package)))
+          (list '+elpaca-unload-xref 'elpaca--activate-package)))
 
-;;(elpaca `(xref :build ,(+elpaca-xref-build-steps)))
 (use-package xref
-  :ensure `(xref :build ,(+elpaca-xref-build-steps))
-  :custom (xref-search-program 'ripgrep)
+  :ensure `(xref :build ,(+elpaca-xref-build-steps) :ref "87db670d045bea2d90139b1f741eea8db7c193ea" :pin t)
+  :custom
+  (xref-search-program 'ripgrep)
+  (xref-show-xrefs-function #'consult-xref)
+  (xref-show-definitions-function #'consult-xref)
   :config
+  (defun xref-find-references-current-defun ()
+    "`xref-find-references' for the enclosing defun."
+    (interactive)
+    (xref-backend-identifier-completion-table (xref-find-backend))
+    (xref-find-references (which-function)))
+  (defun xref-find-definitions-current-list-function ()
+    "`xref-find-definitions' for the function at the beginning of the current list.
+With a prefix argument, moves up `current-prefix-arg' sexps first."
+    (interactive)
+    (let ((fn-name (save-excursion
+                     (when current-prefix-arg
+                       (sp-backward-up-sexp current-prefix-arg))
+                     (sp-beginning-of-sexp) (thing-at-point 'symbol))))
+      (xref-find-definitions fn-name)))
   (defun xref-find-references-other-window (identifier)
-    "Like `xref-find-references' but switch to the other window"
+    "Like `xref-find-references' but switch to the other window."
     (interactive (list (xref--read-identifier "Find references of: ")))
     (xref--find-xrefs identifier 'references identifier 'window))
   (defun xref-find-references-other-frame (identifier)
-    "Like `xref-find-references' but switch to the other frame"
+    "Like `xref-find-references' but switch to the other frame."
     (interactive (list (xref--read-identifier "Find references of: ")))
     (xref--find-xrefs identifier 'references identifier 'frame))
   (define-key ctl-x-4-map (kbd "M-?") 'xref-find-references-other-window)
@@ -177,7 +216,10 @@
   ;; there is no value at point that can be used
   (add-to-list 'xref-prompt-for-identifier 'xref-find-references t)
   (add-to-list 'xref-prompt-for-identifier 'xref-find-references-other-window t)
-  (add-to-list 'xref-prompt-for-identifier 'xref-find-references-other-frame t))
+  (add-to-list 'xref-prompt-for-identifier 'xref-find-references-other-frame t)
+  :bind
+  ("C-c q" . xref-find-references-current-defun)
+  ("C-c C-M-." . xref-find-definitions-current-list-function))
 
 (use-package ws-butler
   :diminish
@@ -222,8 +264,9 @@
   :hook (elpaca-after-init . editorconfig-mode))
 
 (use-package titlecase
-  ;; TODO find a better binding
-  :bind ("C-c c t t" . titlecase-dwim))
+  :bind (("C-c c t t" . titlecase-dwim)
+         (:map embark-heading-map ("T" . titlecase-line))
+         (:map embark-region-map ("T" . titlecase-region))))
 
 (use-package caser
   :ensure (caser :host github :repo "emacsmirror/caser")
@@ -266,5 +309,9 @@
               ("x" . cider-eval-region)
               ("r" . cider-insert-region-in-repl)))
 
+(use-package repeat-fu
+  :bind ("M-+" . repeat-fu-execute)
+  :hook ((prog-mode text-mode) . repeat-fu-mode))
+
 (provide 'init-editor)
 ;;; init-editor.el ends here
diff --git a/.emacs.d/lisp/init-emacs-lisp.el b/.emacs.d/lisp/init-emacs-lisp.el
index 4ca010a..0cf1f44 100644
--- a/.emacs.d/lisp/init-emacs-lisp.el
+++ b/.emacs.d/lisp/init-emacs-lisp.el
@@ -10,6 +10,10 @@
   :diminish)
 
 (use-feature emacs
+  :custom
+  ;; These don't really belong here, but do affect ielm
+  (comint-prompt-read-only t)
+  (comint-buffer-maximum-size 2048)
   :config
   ;; Based on prelude-emacs-lisp.el
   (defun recompile-init-lisp ()
@@ -61,6 +65,16 @@ Start `ielm' if it's not already running."
 
 
 (use-package eros
+  :config
+  ;; https://xenodium.com/inline-previous-result-and-why-you-should-edebug/
+  (defun adviced:edebug-previous-result (_ &rest r)
+    "Adviced `edebug-previous-result'."
+    (eros--make-result-overlay edebug-previous-result
+      :where (point)
+      :duration eros-eval-result-duration))
+  (advice-add #'edebug-previous-result
+              :around
+              #'adviced:edebug-previous-result)
   :hook
   (emacs-lisp-mode . eros-mode))
 
diff --git a/.emacs.d/lisp/init-git.el b/.emacs.d/lisp/init-git.el
index 1d7183e..10c9c78 100644
--- a/.emacs.d/lisp/init-git.el
+++ b/.emacs.d/lisp/init-git.el
@@ -77,6 +77,8 @@
   ("C-c g r" . my/magit-refresh-state)
   ("C-c g m" . my/magit-update-master)
   ("C-c g C-c" . my/magit-stage-and-commit-file)
+  ;; Used by eshell-prompt-function (see init-shell.el)
+  :commands (magit-get-shortname magit-file-status)
   :config
   ;; Requires the following gitconfig:
   ;; [alias]
@@ -338,8 +340,13 @@ GitHub/Bitbucket/GitLab/... The URL will be added to the kill ring.  If
   ("C-c g c" . git-link-commit)
   ("C-c g b" . git-link-branch))
 
+(use-feature git-link-transient
+  :bind ("C-c g d" . git-link-dispatch))
+
 (use-feature git-related
-  :defer 10)
+  :bind
+  ("C-c g #" . git-related-find-file)
+  ("C-c g ~" . git-related-update))
 
 (provide 'init-git)
 ;;; init-git.el ends here
diff --git a/.emacs.d/lisp/init-kill.el b/.emacs.d/lisp/init-kill.el
index 37103e3..d72f776 100644
--- a/.emacs.d/lisp/init-kill.el
+++ b/.emacs.d/lisp/init-kill.el
@@ -16,6 +16,7 @@
   (:map easy-kill-base-map ("C-=" . easy-kill-expand)))
 
 (use-feature emacs
+  :custom (kill-do-not-save-duplicates t)
   :hook
   (elpaca-after-init . (lambda ()
                          ;; Based on code in prelude-editor.el
diff --git a/.emacs.d/lisp/init-lsp.el b/.emacs.d/lisp/init-lsp.el
index e496813..9a0b4d5 100644
--- a/.emacs.d/lisp/init-lsp.el
+++ b/.emacs.d/lisp/init-lsp.el
@@ -26,6 +26,7 @@
   (lsp-lens-mode . really-diminish-lsp-lens-mode)
   (lsp-completion-mode . my/lsp-mode-setup-completion)
   (sql-mode . lsp)
+  (lsp-after-apply-edits . save-buffer)
   :config
   (defun really-diminish-lsp-lens-mode ()
     (diminish 'lsp-lens-mode)
@@ -55,7 +56,38 @@
         lsp-references-exclude-definition t
         ;; user cider for indendation and eldoc
         lsp-enable-indentation nil
-        lsp-eldoc-enable-hover nil))
+        lsp-eldoc-enable-hover nil)
+
+  ;; Copied from https://github.com/blahgeek/emacs-lsp-booster/blob/4200ed6ae0cd83b8e3fd1dbefb09121480951a22/README.md#configure-lsp-mode
+  (defun lsp-booster--advice-json-parse (old-fn &rest args)
+    "Try to parse bytecode instead of json."
+    (or
+     (when (equal (following-char) ?#)
+       (let ((bytecode (read (current-buffer))))
+         (when (byte-code-function-p bytecode)
+           (funcall bytecode))))
+     (apply old-fn args)))
+  (advice-add (if (progn (require 'json)
+                         (fboundp 'json-parse-buffer))
+                  'json-parse-buffer
+                'json-read)
+              :around
+              #'lsp-booster--advice-json-parse)
+  (defun lsp-booster--advice-final-command (old-fn cmd &optional test?)
+    "Prepend emacs-lsp-booster command to lsp CMD."
+    (let ((orig-result (funcall old-fn cmd test?)))
+      (if (and (not test?)                             ;; for check lsp-server-present?
+               (not (file-remote-p default-directory)) ;; see lsp-resolve-final-command, it would add extra shell wrapper
+               lsp-use-plists
+               (not (functionp 'json-rpc-connection))  ;; native json-rpc
+               (executable-find "emacs-lsp-booster"))
+          (progn
+            (when-let ((command-from-exec-path (executable-find (car orig-result))))  ;; resolve command from exec-path (in case not found in $PATH)
+              (setcar orig-result command-from-exec-path))
+            (message "Using emacs-lsp-booster for %s!" orig-result)
+            (cons "emacs-lsp-booster" orig-result))
+        orig-result)))
+  (advice-add 'lsp-resolve-final-command :around #'lsp-booster--advice-final-command))
 
 (provide 'init-lsp)
 ;;; init-lsp.el ends here
diff --git a/.emacs.d/lisp/init-minibuffer.el b/.emacs.d/lisp/init-minibuffer.el
index 9fbcf0c..118f730 100644
--- a/.emacs.d/lisp/init-minibuffer.el
+++ b/.emacs.d/lisp/init-minibuffer.el
@@ -12,7 +12,7 @@
   :config
   ;; Do not allow the cursor in the minibuffer prompt
   (setq minibuffer-prompt-properties
-        '(read-only t cursor-intangible t face minibuffer-prompt))
+        '(read-only t cursor-intangible t intangible t face minibuffer-prompt))
   (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)
   (setq enable-recursive-minibuffers t)
   (minibuffer-depth-indicate-mode t)
@@ -42,24 +42,31 @@
                    "  ")
                  cand)))
 
+  ;; https://github.com/minad/vertico/wiki#ding-when-wrapping-around
+  (advice-add #'vertico-next
+              :around
+              #'(lambda (origin &rest args)
+                  (let ((beg-index vertico--index))
+                    (apply origin args)
+                    (if (not (eq 1 (abs (- beg-index vertico--index))))
+                        (ding)))))
+
+  ;; https://github.com/minad/vertico/wiki#useful-commands-from-outside-minibuffer
   (defun down-from-outside ()
     "Move to next candidate in minibuffer, even when minibuffer isn't selected."
     (interactive)
     (with-selected-window (active-minibuffer-window)
       (execute-kbd-macro [down])))
-
   (defun up-from-outside ()
     "Move to previous candidate in minibuffer, even when minibuffer isn't selected."
     (interactive)
     (with-selected-window (active-minibuffer-window)
       (execute-kbd-macro [up])))
-
   (defun preview-from-outside ()
     "Preview the selected candidate, even when minibuffer isn't selected."
     (interactive)
     (with-selected-window (active-minibuffer-window)
       (execute-kbd-macro (kbd "M-."))))
-
   (defun to-and-fro-minibuffer ()
     "Go back and forth between minibuffer and other window."
     (interactive)
@@ -67,18 +74,73 @@
         (select-window (minibuffer-selected-window))
       (select-window (active-minibuffer-window))))
 
-  (defun minibuffer-really-quit ()
-    "Quit minibuffer session, even if it is not the selected window."
+  ;; Modified from https://github.com/minad/vertico/wiki#update-minibuffer-history-with-candidate-insertions
+  (defadvice vertico-insert
+      (after vertico-insert-add-history activate)
+    "Make vertico-insert add to the minibuffer history."
+    (if (and (not (eq minibuffer-history-variable t))
+             (eq 'file (vertico--metadata-get 'category)))
+        (add-to-history minibuffer-history-variable (minibuffer-contents))))
+
+  (defun toggle-sort-directories-first ()
     (interactive)
-    (with-selected-window (active-minibuffer-window)
-      (minibuffer-keyboard-quit)))
+    (if (eq vertico-sort-function 'vertico-sort-directories-first)
+        (set (make-local-variable 'vertico-sort-function) 'vertico-sort-history-length-alpha)
+      (set (make-local-variable 'vertico-sort-function) 'vertico-sort-directories-first))
+    (setq vertico--input t)
+    (vertico--update))
 
   :bind (("C-M-<" . up-from-outside)
          ("C-M->" . down-from-outside)
          ("C-M-+" . preview-from-outside)
          ("M-X" . to-and-fro-minibuffer)
-         ("C-M-S-g" . minibuffer-really-quit)
-         (:map vertico-map ("M-RET" . minibuffer-force-complete-and-exit))))
+         (:map vertico-map
+               ("M-RET" . minibuffer-force-complete-and-exit)
+               ("M-D" . toggle-sort-directories-first))))
+
+(use-extension vertico vertico-multiform
+  :config
+  (vertico-multiform-mode +1)
+
+  (defun vertico-multiform-buffer-grid ()
+    "Toggle displaying Vertico as a grid in a large window (like a regular buffer).)"
+    (interactive)
+    (if (equal '(vertico-buffer-mode vertico-grid-mode) (car vertico-multiform--stack))
+        (vertico-multiform-vertical)
+      (setcar vertico-multiform--stack '(vertico-buffer-mode vertico-grid-mode))
+      (vertico-multiform--toggle 1)))
+
+  ;; https://github.com/minad/vertico/wiki#candidate-display-transformations-custom-candidate-highlighting
+  (defvar +vertico-transform-functions nil)
+  (cl-defmethod vertico--format-candidate :around
+    (cand prefix suffix index start &context ((not +vertico-transform-functions) null))
+    (dolist (fun (ensure-list +vertico-transform-functions))
+      (setq cand (funcall fun cand)))
+    (cl-call-next-method cand prefix suffix index start))
+  (defun +vertico-highlight-directory (file)
+    "If FILE ends with a slash, highlight it as a directory."
+    (if (string-suffix-p "/" file)
+        (propertize file 'face 'marginalia-file-priv-dir)
+      file))
+  (defun +vertico-highlight-enabled-mode (cmd)
+    "If MODE is enabled, highlight it as font-lock-doc-face."
+    (let ((sym (intern cmd)))
+      (if (or (eq sym major-mode)
+              (and
+               (memq sym minor-mode-list)
+               (boundp sym)))
+          (propertize cmd 'face 'font-lock-doc-face)
+        cmd)))
+
+  (setq vertico-multiform-commands
+        '((execute-extended-command
+           (+vertico-transform-functions . +vertico-highlight-enabled-mode))))
+  (setq vertico-multiform-categories
+        '((file (+vertico-transform-functions . +vertico-highlight-directory)
+                (:keymap . vertico-directory-map))
+          (imenu grid)))
+  :bind (:map vertico-multiform-map
+              ("M-H" . vertico-multiform-buffer-grid)))
 
 (use-extension vertico vertico-directory
   :config
@@ -115,28 +177,28 @@
 DEFS is a plist associating completion categories to commands."
     (let ((default-command (lookup-key vertico-map (kbd key))))
       (define-key vertico-map (kbd key)
-        (list 'menu-item nil defs :filter
-              (lambda (d)
-                (or (plist-get d (completion-metadata-get
-                                  (completion-metadata (minibuffer-contents)
-                                                       minibuffer-completion-table
-                                                       minibuffer-completion-predicate)
-                                  'category))
-                    default-command))))))
+                  (list 'menu-item nil defs :filter
+                        (lambda (d)
+                          (or (plist-get d (completion-metadata-get
+                                            (completion-metadata (minibuffer-contents)
+                                                                 minibuffer-completion-table
+                                                                 minibuffer-completion-predicate)
+                                            'category))
+                              default-command))))))
   (define-vertico-key "/"
-    'file #'vertico-directory-slash
-    'project-file #'vertico-directory-slash)
+                      'file #'vertico-directory-slash
+                      'project-file #'vertico-directory-slash)
   (define-vertico-key "RET"
-    'file #'vertico-directory-enter-or-select-project
-    'project-file #'vertico-directory-enter)
+                      'file #'vertico-directory-enter-or-select-project
+                      'project-file #'vertico-directory-enter)
   (define-vertico-key "~"
-    'file #'vertico-directory-home)
+                      'file #'vertico-directory-home)
   (define-vertico-key "DEL"
-    'file #'vertico-directory-delete-char
-    'project-file #'vertico-directory-delete-char)
+                      'file #'vertico-directory-delete-char
+                      'project-file #'vertico-directory-delete-char)
   (define-vertico-key "M-DEL"
-    'file #'vertico-directory-delete-word
-    'project-file #'vertico-directory-delete-word)
+                      'file #'vertico-directory-delete-word
+                      'project-file #'vertico-directory-delete-word)
   :commands (vertico-directory-enter vertico-directory-delete-word vertico-directory-delete-char)
   ;; Tidy shadowed file names
   :hook (rfn-eshadow-update-overlay . vertico-directory-tidy))
@@ -152,21 +214,21 @@ DEFS is a plist associating completion categories to commands."
 
 (use-extension vertico vertico-indexed
   :config
-  (defmacro define-choose (n)
+  (defmacro define-vertico-choose (n)
     `(defun ,(intern (format "vertico-indexed-choose-%s" n)) ()
        ,(format "Exit minibuffer with candidate %s." n)
        (interactive)
-       (let ((current-prefix-arg ,n))
+       (let ((vertico--index ,n))
          (funcall-interactively 'vertico-exit))))
-  (defmacro define-insert (n)
+  (defmacro define-vertico-insert (n)
     `(defun ,(intern (format "vertico-indexed-insert-%s" n)) ()
        ,(format "Insert candidate %s in minibuffer." n)
        (interactive)
-       (let ((current-prefix-arg ,n))
+       (let ((vertico--index ,n))
          (funcall-interactively 'vertico-insert))))
   (dotimes (n 10)
-    (eval `(define-choose ,n))
-    (eval `(define-insert ,n))
+    (eval `(define-vertico-choose ,n))
+    (eval `(define-vertico-insert ,n))
     (define-key vertico-map (kbd (format "C-%s" n)) (intern (format "vertico-indexed-choose-%s" n)))
     (define-key vertico-map (kbd (format "M-%s" n)) (intern (format "vertico-indexed-insert-%s" n))))
   (vertico-indexed-mode 1))
@@ -179,32 +241,36 @@ DEFS is a plist associating completion categories to commands."
 (use-package consult
   :bind (;; C-c bindings (mode-specific-map)
          ("C-c h" . consult-history)
-         ("C-c m" . consult-mode-command)
+         ("C-c M-x" . consult-mode-command)
          ("C-c b" . consult-bookmark)
          ("C-c k" . consult-kmacro)
+         ("C-c m" . consult-man)
+         ("C-c i" . consult-info)
+         ([remap Info-search] . consult-info)
          ;; C-x bindings (ctl-x-map)
          ("C-x M-:" . consult-complex-command) ;; orig. repeat-complex-command
          ("C-x b" . consult-buffer) ;; orig. switch-to-buffer
-         ("C-x B" . consult-buffer-no-preview) ;; orig. switch-to-buffer
+         ("C-x B" . consult-buffer-no-preview)
          ("C-x 4 b" . consult-buffer-other-window) ;; orig. switch-to-buffer-other-window
          ("C-x 5 b" . consult-buffer-other-frame) ;; orig. switch-to-buffer-other-frame
+         ("C-x t b" . consult-buffer-other-tab) ;; orig. switch-to-buffer-other-tab
+         ("C-x r b" . consult-bookmark) ;; orig. bookmark-jump
+         ("C-x p b" . consult-project-buffer) ;; orig. project-switch-to-buffer
          ;; Custom M-# bindings for fast register access
          ("M-#" . consult-register-load)
          ("M-'" . consult-register-store) ;; orig. abbrev-prefix-mark (unrelated)
          ("C-M-#" . consult-register)
          ;; Other custom bindings
+         ("C-," . consult-line)
          ("C-S-s" . consult-line)
-         ("M-*" . consult-line-thing-at-point)
          ("C-c f" . consult-recent-file)
          ("C-c r" . consult-ripgrep)
+         ("C-c ." . consult-ripgrep) ;; convenient for using with embark-act (C-. C-c . to search for thing at point)
          ;; TODO find an alternative to C-c c?
          ("C-c c r" . consult-ripgrep-auto-preview)
          ("C-c c s" . consult-ripgrep-case-sensitive)
-         ("C-c c z" . consult-z-ripgrep)
-         ("C-c C-*" . consult-ripgrep-thing-at-point)
          ("C-c C-^" . consult-ripgrep-parent)
          ("M-y" . consult-yank-pop)     ;; orig. yank-pop
-         ("<help> a" . consult-apropos) ;; orig. apropos-command
          ;; M-g bindings (goto-map)
          ("M-g e" . consult-compile-error)
          ("M-g f" . consult-flycheck)
@@ -227,41 +293,42 @@ DEFS is a plist associating completion categories to commands."
                ("G" . consult-git-grep)
                ("r" . consult-ripgrep)
                ("R" . consult-ripgrep) ;; can't use r in isearch-mode, so add R too
-               ("M-r" . consult-ripgrep-unrestricted)
-               ("*" . consult-ripgrep-thing-at-point)
-               ("z" . consult-z-ripgrep)
+               ("u" . consult-ripgrep-unrestricted)
                ("^" . consult-ripgrep-parent)
                ("l" . consult-line)
                ("L" . consult-line-multi)
                ("m" . consult-multi-occur)
                ("k" . consult-keep-lines)
-               ("u" . consult-focus-lines)
-               ("e" . consult-isearch))
+               ("C-f" . consult-focus-lines)
+               ("e" . consult-isearch-history))
          (:map vertico-map
                ;; These are used for previewing with some consult commands (see consult-customize call below)
                ("C-S-p" . vertico-previous)
                ("C-S-n" . vertico-next)
                ;; Toggle preview on/off without changing preview-key
-               ("M-P" . consult-toggle-preview)))
+               ("M-P" . consult-toggle-preview)
+               ("C-x C-M-x" . remove-leading-hash)))
 
   :config
 
-  ;; Optionally configure the register formatting. This improves the register
+  ;; Configure register formatting. This improves the register
   ;; preview for `consult-register', `consult-register-load',
   ;; `consult-register-store' and the Emacs built-ins.
   (setq register-preview-delay 0
         register-preview-function #'consult-register-format)
 
-  ;; Optionally tweak the register preview window.
+  ;; Tweak the register preview window.
   ;; This adds thin lines, sorting and hides the mode line of the window.
   (advice-add #'register-preview :override #'consult-register-window)
 
-  ;; Use Consult to select xref locations with preview
-  (setq xref-show-xrefs-function #'consult-xref
-        xref-show-definitions-function #'consult-xref)
-
   (add-to-list 'consult-mode-histories '(cider-repl-mode cider-repl-input-history))
 
+  (defvar consult-line-map
+    (let ((map (make-sparse-keymap)))
+      (define-key map "\C-s" #'previous-history-element)
+      map))
+  (consult-customize consult-line :keymap consult-line-map)
+
   (defun consult-ripgrep-auto-preview (&optional dir initial)
     (interactive "P")
     (consult-ripgrep dir initial))
@@ -269,10 +336,6 @@ DEFS is a plist associating completion categories to commands."
     (interactive "P")
     (let ((consult-ripgrep-args (replace-regexp-in-string "\\." "-uu ." consult-ripgrep-args)))
       (consult-ripgrep dir initial)))
-  (defun consult-z-ripgrep (&optional dir initial)
-    (interactive "P")
-    (let ((consult-ripgrep-args (replace-regexp-in-string "\\." "-z ." consult-ripgrep-args)))
-      (consult-ripgrep dir initial)))
   (defun consult-ripgrep-case-sensitive (&optional dir initial)
     (interactive "P")
     (let ((consult-ripgrep-args (replace-regexp-in-string "\\." "-s ." consult-ripgrep-args)))
@@ -282,25 +345,19 @@ DEFS is a plist associating completion categories to commands."
     (consult-buffer))
   (defun consult-ripgrep-parent (&optional initial)
     (interactive "P")
-    (consult-ripgrep (file-name-directory (directory-file-name (persp-current-project-root))) initial))
-
-  (defalias 'consult-line-thing-at-point 'consult-line)
-  (defalias 'consult-ripgrep-thing-at-point 'consult-ripgrep)
+    (consult-ripgrep (file-name-directory
+                      (directory-file-name (or (persp-current-project-root) default-directory)))
+                     initial))
 
   (consult-customize
    consult-theme
    :preview-key '(:debounce 0.2 any)
    ;; For these commands we can use C-N/C-P to scroll and preview, or M-. to preview
    consult-git-grep consult-grep
-   consult-ripgrep-parent consult-ripgrep consult-ripgrep-case-sensitive
-   consult-ripgrep-unrestricted consult-z-ripgrep consult-ripgrep-thing-at-point
+   consult-ripgrep-parent consult-ripgrep consult-ripgrep-case-sensitive consult-ripgrep-unrestricted
    consult-bookmark consult-recent-file consult-xref consult-buffer-no-preview
-   consult--source-recent-file consult--source-project-recent-file consult--source-bookmark
-   :preview-key '("M-." :debounce 0.2 "C-S-n" :debounce 0.2 "C-S-p")
-   consult-ripgrep-thing-at-point
-   :initial (concat "#" (thing-at-point 'symbol))
-   consult-line-thing-at-point
-   :initial (thing-at-point 'symbol))
+   consult--source-recent-file consult--source-project-recent-file consult--source-bookmark;
+   :preview-key '("M-." :debounce 0.2 "C-S-n" :debounce 0.2 "C-S-p"))
 
   (defvar-local consult-toggle-preview-orig nil)
   (defun consult-toggle-preview ()
@@ -398,12 +455,46 @@ DEFS is a plist associating completion categories to commands."
                                          (format  "fd --color never -t f -0 . %s" root))
                                         "\0" t))))))))
 
+  (defun remove-leading-hash ()
+    "Remove a # character from the beginning of the current line.
+
+Designed to be used for consult commands that automatically add a # at the beginning of the minibuffer.
+See `+become' and the functions that call it (e.g. `+become-consult-line')."
+    (interactive)
+    (save-excursion
+      (beginning-of-line)
+      (when (= ?# (char-after))
+        (delete-forward-char 1))))
+
   (defun consult-project-buffer ()
+    "Version of `consult-buffer' that only uses project-related sources."
     (interactive)
     (let ((consult-buffer-sources '(consult--project-source-project-buffer
                                     consult--project-source-project-file-recentf
                                     consult--project-source-project-file-all)))
-      (consult-buffer))))
+      (consult-buffer)))
+
+  ;; https://takeonrules.com/2024/06/08/adding-a-consult-function-for-visualizing-xref/
+  (defvar consult--xref-history nil
+    "History for the `consult-recent-xref' results.")
+  (defun consult-recent-xref (&optional markers)
+    "Jump to a marker in MARKERS list (defaults to `xref--history'.
+
+The command supports preview of the currently selected marker position.
+The symbol at point is added to the future history."
+    (interactive)
+    (consult--read
+     (consult--global-mark-candidates
+      (or markers (flatten-list xref--history)))
+     :prompt "Go to Xref: "
+     :annotate (consult--line-prefix)
+     :category 'consult-location
+     :sort nil
+     :require-match t
+     :lookup #'consult--lookup-location
+     :history '(:input consult--xref-history)
+     :add-history (thing-at-point 'symbol)
+     :state (consult--jump-state))))
 
 (use-package consult-flycheck)
 
@@ -424,6 +515,9 @@ DEFS is a plist associating completion categories to commands."
 (use-package consult-ls-git
   :bind ("C-c g f" . consult-ls-git))
 
+(use-package consult-vc-modified-files
+  :bind (:map vc-prefix-map ("f" . consult-vc-modified-files)))
+
 (use-package consult-project-extra)
 
 (use-package marginalia
@@ -435,19 +529,38 @@ DEFS is a plist associating completion categories to commands."
 (use-package embark
   :bind
   (("C-." . embark-act)
-   ("M-." . embark-dwim)
+   ([remap xref-find-definitions-current-list-function] . embark-dwim-beginning-of-list)
+   ([remap xref-find-definitions] . embark-dwim)
+   ([remap xref-find-definitions-other-window] . embark-dwim-other-window)
    ("C-c C-o" . embark-export)
    ("C-h b" . embark-bindings)
    ("C-h B" . describe-bindings)
    (:map minibuffer-local-map
          ("M-." . embark-preview)
-         ("C-," . embark-become))
+         ("C-," . embark-become)
+         ("C-^" . embark-become-ripgrep-parent)
+         ("C-S-SPC" . embark-select))
    (:map embark-become-file+buffer-map
          ("e" . consult-project-extra-find)
          ("E" . project-switch-consult-project-extra-find)))
   :custom
   (prefix-help-command 'embark-prefix-help-command)
   :config
+  (defun embark-dwim-other-window ()
+    "Like `embark-dwim' but switch to the other window."
+    (interactive)
+    (other-window-prefix)
+    (embark-dwim))
+  (defun embark-dwim-beginning-of-list ()
+    "`embark-dwim' at the beginning of the current list.
+With a prefix argument, moves up `current-prefix-arg' sexps first."
+    (interactive)
+    (progn
+      (when current-prefix-arg
+        (sp-backward-up-sexp current-prefix-arg))
+      (sp-beginning-of-sexp)
+      (embark-dwim)))
+  (defalias 'embark-become-ripgrep-parent (kmacro "C-, ^"))
   (defun embark-preview ()
     "Previews candidate in vertico buffer, unless it's a consult command"
     (interactive)
@@ -467,10 +580,31 @@ DEFS is a plist associating completion categories to commands."
   ;; demand, combined with after means that this will load after embark and consult
   ;; See https://github.com/oantolin/embark/commit/47daded610b245caf01a97d74c940aff91fe14e2#r46010972
   :demand t
+  :config
+  (defun +become (fn)
+    "Remove the leading # from the minibuffer, and call `FN'.
+Useful with embark-become, when changing from a command that uses # as a separator, to one that doesn't."
+    (interactive)
+    (setq unread-command-events (listify-key-sequence "\C-x\C-\M-x"))
+    (funcall-interactively fn))
+  (defun +become-consult-line ()
+    "A version of `consult-line', designed for use with `embark-become'.
+The leading # added by other consult commands is removed."
+    (interactive)
+    (+become #'consult-line))
+  (defun +become-consult-line ()
+    "A version of `consult-imenu', designed for use with `embark-become'.
+The leading # added by other consult commands is removed."
+    (interactive)
+    (+become #'consult-imenu))
   :bind
   (:map embark-consult-async-search-map
+        ("l" . +become-consult-line)
+        ("f" . consult-focus-lines)
+        ("i" . +become-consult-imenu)
         ("^" . consult-ripgrep-parent)
-        ("R" . consult-ripgrep-unrestricted))
+        ("u" . consult-ripgrep-unrestricted)
+        ("c" . consult-ripgrep-case-sensitive))
   :hook
   (embark-collect-mode . consult-preview-at-point-mode))
 
diff --git a/.emacs.d/lisp/init-misc.el b/.emacs.d/lisp/init-misc.el
index d71c646..82c4264 100644
--- a/.emacs.d/lisp/init-misc.el
+++ b/.emacs.d/lisp/init-misc.el
@@ -44,8 +44,27 @@
                       (cdar restclient-saved-requests)
                     (alist-get (completing-read "Call: " (map-keys restclient-saved-requests)) restclient-saved-requests nil nil 'string-equal))))
         (apply 'restclient-http-do args))))
+
+  ;; https://github.com/pashky/restclient.el/issues/288#issuecomment-1775770753
+  (defun my/restclient-copy-curl-command ()
+    "Formats the request as a curl command and copies the command to the clipboard."
+    (interactive)
+    (restclient-http-parse-current-and-do
+     '(lambda (method url headers entity)
+        (let* ((header-args
+                (apply 'append
+                       (mapcar (lambda (header)
+                                 (list "-H" (format "\"%s: %s\"" (car header) (cdr header))))
+                               headers)))
+               (header-parsed (mapconcat 'identity header-args " "))
+               (method-arg (concat "-X" " " method))
+               (entity-arg (if (> 0 (string-width entity)) ""
+                             (format "-d \x27%s\x27" entity)))
+               (curl-command (format "curl %s %s %s %s" header-parsed method-arg url entity-arg)))
+          (kill-new curl-command)
+          (message "curl command copied to clipboard.")))))
   :bind
-  ("C-c C-h" . restclient-call-saved-request)
+  ("C-c M-h" . restclient-call-saved-request)
   (:map restclient-mode-map ("C-c h" . restclient-save-current))
   :mode (("\\.http\\'" . restclient-mode)))
 
@@ -61,8 +80,9 @@
 
 (use-package csv-mode
   :bind (:map csv-mode-map
-              ("M-]" . csv-forward-field)
-              ("M-[" . csv-backward-field)))
+              ;; TODO find something less awkward
+              ("C-M-)" . csv-forward-field)
+              ("C-M-(" . csv-backward-field)))
 
 (use-package yaml-mode
   :diminish
diff --git a/.emacs.d/lisp/init-navigation.el b/.emacs.d/lisp/init-navigation.el
index 513d580..b407e7b 100644
--- a/.emacs.d/lisp/init-navigation.el
+++ b/.emacs.d/lisp/init-navigation.el
@@ -7,6 +7,13 @@
   (avy-all-windows nil)
   (avy-all-windows-alt t)
   (avy-timeout-seconds 0.3)
+  :init
+  ;; Allow C-[ to be bound (instead of being equivalent to ESC), but only in GUI Emacs
+  ;; https://emacs.stackexchange.com/a/52334
+  (let ((frame (framep (selected-frame))))
+    (or (eq  t  frame)
+        (eq 'pc frame)
+        (define-key input-decode-map (kbd "C-[") [control-bracketleft])))
   :config
   ;; https://karthinks.com/software/avy-can-do-anything/#avy-plus-embark-any-action-anywhere
   (defun avy-action-embark (pt)
@@ -17,14 +24,14 @@
       (select-window
        (cdr (ring-ref avy-ring 0))))
     t)
-  (add-to-list 'avy-dispatch-alist '(111 . avy-action-embark))
+  (add-to-list 'avy-dispatch-alist '(?o . avy-action-embark))
   (defun avy-copy-as-kill ()
     (interactive)
     (avy-goto-char-timer)
     (let ((beg (point)))
       (avy-goto-char-timer)
       (copy-region-as-kill beg (point))))
-  (defun avy-kill-in-line ()
+  (defun avy-copy-as-kill-in-line ()
     (interactive)
     (avy-goto-char-timer)
     (let ((beg (point)))
@@ -33,12 +40,12 @@
   :bind
   ("C-'" . avy-goto-char-timer)
   ("C-;" . avy-goto-char-in-line)
+  ("C-#" . avy-goto-word-1)
   ("C-c C-'" . avy-copy-as-kill)
   ("C-c C-;" . avy-copy-as-kill-in-line))
 
 (use-package casual-avy
-  :ensure t
-  :bind ("C-M-;" . casual-avy-tmenu))
+  :bind ([control-bracketleft] . casual-avy-tmenu))
 
 (use-package smartscan
   :custom (smartscan-symbol-selector "symbol")
@@ -72,7 +79,8 @@ Or remove all highlighted symbols in the current buffer (with`ARG')."
   :bind
   ("C-c o" . symbol-overlay-put-or-clear)
   ("M-N" . symbol-overlay-switch-forward)
-  ("M-P" . symbol-overlay-switch-backward))
+  ("M-P" . symbol-overlay-switch-backward)
+  (:map symbol-overlay-map ("o" . symbol-overlay-put-or-clear)))
 
 (use-package gumshoe
   :after perspective
@@ -85,16 +93,7 @@ Or remove all highlighted symbols in the current buffer (with`ARG')."
   (gumshoe-slot-schema '(perspective time buffer position line))
   :config
   (global-gumshoe-mode +1)
-  (defvar gumshoe-repeat-map
-    (let ((map (make-sparse-keymap)))
-      (define-key map (kbd "}") #'global-gumshoe-backtracking-mode-forward)
-      (define-key map (kbd "{") #'global-gumshoe-backtracking-mode-back)
-      map))
-  (dolist (cmd '(global-gumshoe-backtracking-mode-forward global-gumshoe-backtracking-mode-back))
-    (put cmd 'repeat-map 'gumshoe-repeat-map))
   :bind
-  ("C-c }" . global-gumshoe-backtracking-mode-forward)
-  ("C-c {" . global-gumshoe-backtracking-mode-back)
   ("C-c '" . gumshoe-peruse-in-persp))
 
 (use-package goto-chg
diff --git a/.emacs.d/lisp/init-nix.el b/.emacs.d/lisp/init-nix.el
index b4cf0fe..c92ef0b 100644
--- a/.emacs.d/lisp/init-nix.el
+++ b/.emacs.d/lisp/init-nix.el
@@ -5,6 +5,9 @@
 (use-package nix-mode
   :commands nix-repl-show)
 
+(use-package nixfmt
+  :hook (nix-mode . nixfmt-on-save-mode))
+
 (use-package nix-update
   :commands nix-update-fetch)
 
diff --git a/.emacs.d/lisp/init-packages.el b/.emacs.d/lisp/init-packages.el
index 48080d8..55b3eb5 100644
--- a/.emacs.d/lisp/init-packages.el
+++ b/.emacs.d/lisp/init-packages.el
@@ -3,12 +3,12 @@
 ;;; Code:
 
 ;; Elpaca installer block
-(defvar elpaca-installer-version 0.7)
+(defvar elpaca-installer-version 0.11)
 (defvar elpaca-directory (expand-file-name "elpaca/" user-emacs-directory))
 (defvar elpaca-builds-directory (expand-file-name "builds/" elpaca-directory))
 (defvar elpaca-repos-directory (expand-file-name "repos/" elpaca-directory))
 (defvar elpaca-order '(elpaca :repo "https://github.com/progfolio/elpaca.git"
-                              :ref nil :depth 1
+                              :ref nil :depth 1 :inherit ignore
                               :files (:defaults "elpaca-test.el" (:exclude "extensions"))
                               :build (:not elpaca--activate-package)))
 (let* ((repo  (expand-file-name "elpaca/" elpaca-repos-directory))
@@ -18,27 +18,27 @@
   (add-to-list 'load-path (if (file-exists-p build) build repo))
   (unless (file-exists-p repo)
     (make-directory repo t)
-    (when (< emacs-major-version 28) (require 'subr-x))
+    (when (<= emacs-major-version 28) (require 'subr-x))
     (condition-case-unless-debug err
-        (if-let ((buffer (pop-to-buffer-same-window "*elpaca-bootstrap*"))
-                 ((zerop (apply #'call-process `("git" nil ,buffer t "clone"
-                                                 ,@(when-let ((depth (plist-get order :depth)))
-                                                     (list (format "--depth=%d" depth) "--no-single-branch"))
-                                                 ,(plist-get order :repo) ,repo))))
-                 ((zerop (call-process "git" nil buffer t "checkout"
-                                       (or (plist-get order :ref) "--"))))
-                 (emacs (concat invocation-directory invocation-name))
-                 ((zerop (call-process emacs nil buffer nil "-Q" "-L" "." "--batch"
-                                       "--eval" "(byte-recompile-directory \".\" 0 'force)")))
-                 ((require 'elpaca))
-                 ((elpaca-generate-autoloads "elpaca" repo)))
+        (if-let* ((buffer (pop-to-buffer-same-window "*elpaca-bootstrap*"))
+                  ((zerop (apply #'call-process `("git" nil ,buffer t "clone"
+                                                  ,@(when-let* ((depth (plist-get order :depth)))
+                                                      (list (format "--depth=%d" depth) "--no-single-branch"))
+                                                  ,(plist-get order :repo) ,repo))))
+                  ((zerop (call-process "git" nil buffer t "checkout"
+                                        (or (plist-get order :ref) "--"))))
+                  (emacs (concat invocation-directory invocation-name))
+                  ((zerop (call-process emacs nil buffer nil "-Q" "-L" "." "--batch"
+                                        "--eval" "(byte-recompile-directory \".\" 0 'force)")))
+                  ((require 'elpaca))
+                  ((elpaca-generate-autoloads "elpaca" repo)))
             (progn (message "%s" (buffer-string)) (kill-buffer buffer))
           (error "%s" (with-current-buffer buffer (buffer-string))))
       ((error) (warn "%s" err) (delete-directory repo 'recursive))))
   (unless (require 'elpaca-autoloads nil t)
     (require 'elpaca)
     (elpaca-generate-autoloads "elpaca" repo)
-    (load "./elpaca-autoloads")))
+    (let ((load-source-file-function nil)) (load "./elpaca-autoloads"))))
 (add-hook 'after-init-hook #'elpaca-process-queues)
 (elpaca `(,@elpaca-order))
 ;; End of elpaca installer block
@@ -118,6 +118,52 @@
 ;; https://github.com/progfolio/elpaca/wiki/Logging#customizing-the-position-of-the-elpaca-log-buffer
 (add-to-list 'display-buffer-alist '("\\*elpaca-log\\*" (display-buffer-reuse-window display-buffer-at-bottom)))
 
+;; https://github.com/progfolio/elpaca/wiki/Reloading-a-package%E2%80%99s-features-after-updating-a-package
+(defun +elpaca-reload-package (package &optional allp)
+  "Reload PACKAGE's features.
+If ALLP is non-nil (interactively, with prefix), load all of its
+features; otherwise only load ones that were already loaded.
+
+This is useful to reload a package after upgrading it.  Since a
+package may provide multiple features, to reload it properly
+would require either restarting Emacs or manually unloading and
+reloading each loaded feature.  This automates that process.
+
+Note that this unloads all of the package's symbols before
+reloading.  Any data stored in those symbols will be lost, so if
+the package would normally save that data, e.g. when a mode is
+deactivated or when Emacs exits, the user should do so before
+using this command."
+  (interactive
+   (list (let ((elpaca-overriding-prompt "Reload package: "))
+           (elpaca--read-queued))
+         current-prefix-arg))
+  ;; This finds features in the currently installed version of PACKAGE, so if
+  ;; it provided other features in an older version, those are not unloaded.
+  (when (yes-or-no-p (format "Unload all of %s's symbols and reload its features? " package))
+    (let* ((package-name (symbol-name package))
+           (package-dir (file-name-directory
+                         (locate-file package-name load-path (get-load-suffixes))))
+           (package-files (directory-files package-dir 'full (rx ".el" eos)))
+           (package-features
+            (cl-loop for file in package-files
+                     when (with-temp-buffer
+                            (insert-file-contents file)
+                            (when (re-search-forward (rx bol "(provide" (1+ space)) nil t)
+                              (goto-char (match-beginning 0))
+                              (cadadr (read (current-buffer)))))
+                     collect it)))
+      (unless allp
+        (setf package-features (seq-intersection package-features features)))
+      (dolist (feature package-features)
+        (ignore-errors
+          ;; Ignore error in case it's not loaded.
+          (unload-feature feature 'force)))
+      (dolist (feature package-features)
+        (require feature))
+      (when package-features
+        (message "Reloaded: %s" (mapconcat #'symbol-name package-features " "))))))
+
 ;; https://github.com/radian-software/radian/blob/e3aad124c8e0cc870ed09da8b3a4905d01e49769/emacs/radian.el#L352
 (defmacro use-feature (name &rest args)
   "Like `use-package', but without elpaca integration.
diff --git a/.emacs.d/lisp/init-project.el b/.emacs.d/lisp/init-project.el
index 3e847da..e66a4cd 100644
--- a/.emacs.d/lisp/init-project.el
+++ b/.emacs.d/lisp/init-project.el
@@ -5,16 +5,17 @@
 (require 'subr-x)
 
 (use-package project
+  :ensure (:ref "87db670d045bea2d90139b1f741eea8db7c193ea" :pin t)
   :config
   (defun project--clojure-switch-to-test (filename project-root)
     (let* ((project-src-file (string-remove-prefix project-root filename))
            (project-test-file (replace-regexp-in-string "\.clj$" "_test.clj"
-                                                (replace-regexp-in-string "^src/" "test/" project-src-file))))
+                                                        (replace-regexp-in-string "^src/" "test/" project-src-file))))
       (find-file (expand-file-name project-test-file project-root))))
   (defun project--clojure-switch-to-src (test-filename project-root)
     (let* ((project-test-file (string-remove-prefix project-root test-filename))
            (project-src-file (replace-regexp-in-string "_test\.clj$" ".clj"
-                                                        (replace-regexp-in-string "^test/" "src/" project-test-file))))
+                                                       (replace-regexp-in-string "^test/" "src/" project-test-file))))
       (find-file (expand-file-name project-src-file project-root))))
   (defun project-clojure-test-switch ()
     (interactive)
@@ -28,14 +29,18 @@
     "Show a list of recently visited files in a project."
     (interactive)
     (if (boundp 'recentf-list)
+        ;; Use expand-file-name for project-root and later recentf-list to ensure consistency
         (let* ((project-root (expand-file-name (project-root (project-current))))
                (project-recentf-files (mapcar
                                        (lambda (f) (file-relative-name f project-root))
-                                       (seq-filter (apply-partially 'string-prefix-p project-root) recentf-list))))
+                                       (seq-filter (apply-partially 'string-prefix-p project-root)
+                                                   (mapcar 'expand-file-name recentf-list)))))
           (find-file (expand-file-name
                       (funcall project-read-file-name-function
                                "Find recent project files"
-                               project-recentf-files)
+                               project-recentf-files
+                               nil
+                               'file-name-history)
                       project-root)))
       (message "recentf is not enabled")))
   (defun project-switch-src-project ()
@@ -136,7 +141,7 @@ mode as the current buffer (or do nothing)."
 
   :bind
   ("C-x p p" . switch-project)
-  ("C-c C-M-j" . persp-switch-to-previous-buffer)
+  ("C-c C-M-b" . persp-switch-to-previous-buffer)
   ("C-x C-b" . persp-previous-buffer-same-mode)
   ("C-x 4 C-b" . persp-previous-buffer-same-mode-other-window)
   ("C-x C-S-b" . persp-switch-buffer-same-mode)
diff --git a/.emacs.d/lisp/init-search.el b/.emacs.d/lisp/init-search.el
index cdb8d4d..f82d527 100644
--- a/.emacs.d/lisp/init-search.el
+++ b/.emacs.d/lisp/init-search.el
@@ -15,95 +15,6 @@
     (goto-char (point-min)))
   (advice-add 'isearch-occur :after 'isearch-occur-advice)
 
-  ;; Modified from http://yummymelon.com/devnull/improving-emacs-isearch-usability-with-transient.html
-  (transient-define-prefix isearch-menu ()
-    "isearch Menu"
-    [["Edit Search String"
-      ("e"
-       "Edit the search string (recursive)"
-       isearch-edit-string
-       :transient nil)
-      ("w"
-       "Pull next word or character word from buffer"
-       isearch-yank-word-or-char
-       :transient nil)
-      ("s"
-       "Pull next symbol or character from buffer"
-       isearch-yank-symbol-or-char
-       :transient nil)
-      ("l"
-       "Pull rest of line from buffer"
-       isearch-yank-line
-       :transient nil)
-      ("y"
-       "Pull string from kill ring"
-       isearch-yank-kill
-       :transient nil)
-      ("t"
-       "Pull thing from buffer"
-       isearch-forward-thing-at-point
-       :transient nil)]
-
-     ["Replace"
-      ("q"
-       "Start ‘query-replace’"
-       anzu-isearch-query-replace
-       :if-nil buffer-read-only
-       :transient nil)
-      ("x"
-       "Start ‘query-replace-regexp’"
-       anzu-isearch-query-replace-regexp
-       :if-nil buffer-read-only
-       :transient nil)]]
-
-    [["Toggle"
-      ("X"
-       "Toggle regexp searching"
-       isearch-toggle-regexp
-       :transient nil)
-      ("S"
-       "Toggle symbol searching"
-       isearch-toggle-symbol
-       :transient nil)
-      ("W"
-       "Toggle word searching"
-       isearch-toggle-word
-       :transient nil)
-      ("F"
-       "Toggle case fold"
-       isearch-toggle-case-fold
-       :transient nil)
-      ("L"
-       "Toggle lax whitespace"
-       isearch-toggle-lax-whitespace
-       :transient nil)]
-
-     ["Misc"
-      ("o"
-       "occur"
-       isearch-occur
-       :transient nil)
-      ("h"
-       "highlight"
-       isearch-highlight-regexp
-       :transient nil)
-      ("H"
-       "highlight lines"
-       isearch-highlight-lines-matching-regexp
-       :transient nil)
-      ("l"
-       "consult-line"
-       consult-line
-       :transient nil)
-      ("<"
-       "isearch-beginning-of-buffer"
-       isearch-beginning-of-buffer
-       :transient nil)
-      (">"
-       "isearch-end-of-buffer"
-       isearch-end-of-buffer
-       :transient nil)]])
-
   :custom
   (search-whitespace-regexp ".*\\b")
   (isearch-lax-whitespace t)
@@ -112,16 +23,25 @@
   (isearch-lazy-count t)
   (lazy-count-prefix-format nil)
   (lazy-count-suffix-format "   (%s/%s)")
+  (lazy-highlight-initial-delay 0)
   (isearch-message-properties '(read-only t cursor-intangible t face isearch-prompt))
   :bind-keymap ("C-c s" . search-map) ;; M-s clashes with paredit/smartparens bindings
   :bind
-  ("C-*" . isearch-forward-symbol-at-point)
-  (:map isearch-mode-map ("<f2>" . isearch-menu))
   (:map search-map
-        ("M-s M-<" . isearch-beginning-of-buffer)
-        ("M-s M->" . isearch-end-of-buffer)
-        ("C-c s M-<" . isearch-beginning-of-buffer)
-        ("C-c s M->" . isearch-end-of-buffer)))
+        ("<" . isearch-beginning-of-buffer)
+        (">" . isearch-end-of-buffer)))
+
+(use-feature casual-isearch
+  :after isearch
+  :bind (:map isearch-mode-map ("C-o" . casual-isearch-tmenu))
+  :config
+  ;; Replace isearch-query-replace functions with their aznu equivalents
+  (transient-replace-suffix 'casual-isearch-tmenu "r" '("r" "Start ‘anzu-query-replace’" anzu-isearch-query-replace
+                                                        :if-nil buffer-read-only))
+  (transient-replace-suffix 'casual-isearch-tmenu "x" '("x" "Start ‘anzu-query-replace-regexp’" anzu-isearch-query-replace-regexp
+                                                        :if-nil buffer-read-only))
+  ;; Add consult-line to Misc section
+  (transient-append-suffix 'casual-isearch-tmenu "u" '("l" "consult-line" consult-line)))
 
 (use-package isearch-dabbrev
   :after isearch
@@ -153,63 +73,57 @@
 
 (use-package deadgrep
   :config
-  (defun deadgrep-symbol-at-point ()
-    (interactive)
-    (deadgrep (thing-at-point 'symbol)))
   (defun deadgrep-current-directory (search-term)
     (interactive (list (deadgrep--read-search-term)))
     (deadgrep search-term (file-name-directory buffer-file-name)))
-  (defvar include-all nil)
+  (defvar deadgrep--include-all nil)
   (defun deadgrep--include-all-advice (rg-args)
-    (if include-all
+    (if deadgrep--include-all
         (push "-uuuLz" rg-args)
       rg-args))
   (advice-add 'deadgrep--arguments :filter-return #'deadgrep--include-all-advice)
   (defun deadgrep-all (search-term)
     (interactive (list (deadgrep--read-search-term)))
-    (let ((include-all t))
+    (let ((deadgrep--include-all t))
       (deadgrep search-term)))
+  :hook (deadgrep-mode . next-error-follow-minor-mode)
   :bind
   ("C-c c d" . deadgrep)
   ("C-c c M-d" . deadgrep-all)
-  ("C-S-z" . deadgrep-symbol-at-point)
   ("C-c c C-d" . deadgrep-current-directory)
+  (:map deadgrep-mode-map
+        ("e" . deadgrep-edit-mode)
+        ("{" . deadgrep-backward-filename)
+        ("}" . deadgrep-forward-filename))
   (:map search-map
         ("d" . deadgrep)
         ("M-d" . deadgrep-all)
-        ("C-d" . deadgrep-current-directory)
-        ("D" . deadgrep-symbol-at-point)))
+        ("C-d" . deadgrep-current-directory)))
 
 (use-package affe
   :config
   (setq affe-grep-command (replace-regexp-in-string "rg" "rg -Suu" affe-grep-command))
   ;; Configure Orderless
   (defun affe-orderless-regexp-compiler (input _type _ignorecase)
-    (setq input (orderless-pattern-compiler input))
+    (setq input (cdr (orderless-compile input)))
     (cons input (apply-partially #'orderless--highlight input t)))
   (setq affe-regexp-compiler #'affe-orderless-regexp-compiler)
-  ;; Manual preview key for `affe-grep'
-  (consult-customize affe-grep :preview-key (kbd "M-."))
-  (defun my/affe-grep-symbol-at-point (&optional dir initial)
-    (interactive
-     (list prefix-arg (when-let ((s (symbol-at-point)))
-                        (symbol-name s))))
-    (affe-grep dir initial))
-  (defun my/affe-find-symbol-at-point (&optional dir initial)
-    (interactive
-     (list prefix-arg (when-let ((s (symbol-at-point)))
-                        (symbol-name s))))
-    (affe-find dir initial))
+  (defalias 'affe-grep-symbol-at-point 'affe-grep)
+  (defalias 'affe-find-symbol-at-point 'affe-find)
+  (consult-customize
+   affe-grep :preview-key "M-."
+   affe-grep-symbol-at-point :initial (thing-at-point 'symbol) :preview-key "M-."
+   affe-find-symbol-at-point :initial (thing-at-point 'symbol) :preview-key "M-.")
   :bind
-  ("C-#" . affe-grep)
+  ("C-c C-#" . affe-grep)
   ("C-c z" . affe-find)
-  ("C-c Z" . my/affe-find-symbol-at-point)
-  ("C-~" . my/affe-grep-symbol-at-point)
+  ("C-c Z" . affe-find-symbol-at-point)
+  ("C-c C-~" . affe-grep-symbol-at-point)
   (:map search-map
         ("#" . affe-grep)
-        ("~" . my/affe-grep-symbol-at-point)
+        ("~" . affe-grep-symbol-at-point)
         ("a" . affe-find)
-        ("A" . my/affe-find-symbol-at-point)))
+        ("A" . affe-find-symbol-at-point)))
 
 (provide 'init-search)
 ;;; init-search.el ends here
diff --git a/.emacs.d/lisp/init-shell.el b/.emacs.d/lisp/init-shell.el
index 4a03b27..bf737cb 100644
--- a/.emacs.d/lisp/init-shell.el
+++ b/.emacs.d/lisp/init-shell.el
@@ -9,6 +9,31 @@
   (eshell-mode-hook . (lambda () (setenv "TERM" "xterm-256color")))
   :custom
   (eshell-directory-name (expand-file-name "eshell" save-dir))
+  ;; https://lambdaland.org/posts/2024-08-19_fancy_eshell_prompt/#eshell-prompt
+  (eshell-highlight-prompt nil)
+  (eshell-prompt-regexp "^[^#$\n]* [$#] ")
+  (eshell-prompt-function
+   (lambda ()
+     (let* ((cwd (abbreviate-file-name (eshell/pwd)))
+            (ref (magit-get-shortname "HEAD"))
+            (stat (magit-file-status))
+            (x-stat eshell-last-command-status)
+            (git-chunk
+             (if ref
+                 (format "%s%s%s "
+                         (propertize (if stat "[" "(") 'font-lock-face (list :foreground (if stat "red" "green")))
+                         (propertize ref 'font-lock-face '(:foreground "yellow"))
+                         (propertize (if stat "]" ")") 'font-lock-face (list :foreground (if stat "red" "green"))))
+               "")))
+       (propertize
+        (format "%s %s %s$ "
+                (if (< 0 x-stat) (format (propertize "!%s" 'font-lock-face '(:foreground "red")) x-stat)
+                  (propertize "➤" 'font-lock-face (list :foreground (if (< 0 x-stat) "red" "green"))))
+                (propertize cwd 'font-lock-face '(:foreground "#45babf"))
+                git-chunk)
+        'read-only t
+        'front-sticky   '(font-lock-face read-only)
+        'rear-nonsticky '(font-lock-face read-only)))))
   :config
   (setenv "PAGER" "cat"))
 
diff --git a/.emacs.d/lisp/init-sql.el b/.emacs.d/lisp/init-sql.el
index d71e73d..cf3a89a 100644
--- a/.emacs.d/lisp/init-sql.el
+++ b/.emacs.d/lisp/init-sql.el
@@ -37,7 +37,7 @@
          (format "SQL history will not be saved because %s is nil"
                  (symbol-name rval))))))
 
-  (add-hook 'sql-interactive-mode-hook 'my-sql-save-history-hook))
+  :hook (sql-interactive-mode . my-sql-save-history-hook))
 
 
 (use-package sqlup-mode
@@ -46,9 +46,10 @@
   :hook
   (sql-mode . sqlup-mode)
   (sql-interactive-mode . sqlup-mode)
-  :bind ("C-c c u" . sqlup-capitalize-keywords-in-region))
+  :bind ("C-c c C-u" . sqlup-capitalize-keywords-in-region))
 
 (use-package sql-indent
+  :diminish sqlind-minor-mode
   :commands sqlind-minor-mode)
 
 (use-package sqlformat
diff --git a/.emacs.d/lisp/init-ui.el b/.emacs.d/lisp/init-ui.el
index 0c12934..50e9cdb 100644
--- a/.emacs.d/lisp/init-ui.el
+++ b/.emacs.d/lisp/init-ui.el
@@ -8,6 +8,8 @@
   :hook
   (elpaca-after-init . (lambda ()
                          (cond
+                          ((find-font (font-spec :name "aporetic serif mono"))
+                           (set-face-attribute 'default nil :font "aporetic serif mono"))
                           ((find-font (font-spec :name "iosevka comfy"))
                            (set-face-attribute 'default nil :font "iosevka comfy"))
                           ((find-font (font-spec :name "iosevka"))
@@ -16,37 +18,11 @@
                          (global-display-line-numbers-mode)
                          (global-hl-line-mode +1)
 
-                         (global-set-key (kbd "C-x C-S-k") 'kill-this-buffer)
-
                          (add-to-list 'custom-theme-load-path "~/.emacs.d/themes")
                          (load-theme 'non-modo t)))
-  ;; http://yummymelon.com/devnull/enhancing-navigation-in-emacs-view-mode.html
-  (view-mode . (lambda ()
-                 (cond ((derived-mode-p 'org-mode)
-                        (define-key view-mode-map (kbd "p") 'org-previous-visible-heading)
-                        (define-key view-mode-map (kbd "n") 'org-next-visible-heading))
-                       ((derived-mode-p 'markdown-mode)
-                        (define-key view-mode-map (kbd "p") 'markdown-outline-previous)
-                        (define-key view-mode-map (kbd "n") 'markdown-outline-next))
-                       ((derived-mode-p 'html-mode)
-                        (define-key view-mode-map (kbd "p") 'sgml-skip-tag-backward)
-                        (define-key view-mode-map (kbd "n") 'sgml-skip-tag-forward))
-                       ((derived-mode-p 'emacs-lisp-mode)
-                        (define-key view-mode-map (kbd "p") 'backward-sexp)
-                        (define-key view-mode-map (kbd "n") 'forward-sexp))
-                       ((derived-mode-p 'clojure-mode)
-                        (define-key view-mode-map (kbd "p") 'backward-sexp)
-                        (define-key view-mode-map (kbd "n") 'forward-sexp))
-                       ((derived-mode-p 'makefile-mode)
-                        (define-key view-mode-map (kbd "p") 'makefile-previous-dependency)
-                        (define-key view-mode-map (kbd "n") 'makefile-next-dependency))
-                       ((derived-mode-p 'c-mode)
-                        (define-key view-mode-map (kbd "p") 'c-beginning-of-defun)
-                        (define-key view-mode-map (kbd "n") 'c-end-of-defun))
-                       (t
-                        (define-key view-mode-map (kbd "p") 'scroll-down-command)
-                        (define-key view-mode-map (kbd "n") 'scroll-up-command)))))
+  :bind ("C-x C-S-k" . kill-buffer)
   :config
+  (setq-default display-line-numbers-widen t)
   ;; https://github.com/rougier/elegant-emacs/blob/master/sanity.el
   (setq inhibit-startup-screen t
         inhibit-startup-echo-area-message t
@@ -65,13 +41,22 @@
                              (run-with-timer 0.05 nil 'invert-face 'mode-line))
         visible-bell t)
 
-  ;; TODO do we want these? (copied from prelude)
+  ;; A combination of settings from prelude and minimal-emacs.d, may need tweaking
   (setq scroll-margin 0
         scroll-conservatively 100000
-        scroll-preserve-screen-position 1)
+        scroll-preserve-screen-position 1
+        hscroll-margin 2
+        hscroll-step 1
+        auto-window-vscroll nil)
   (setq pixel-scroll-precision-mode t)
+  (setq-default word-wrap t
+                truncate-lines t
+                fill-column 80)
+  ;; If enabled and `truncate-lines' is disabled, soft wrapping will not occur
+  ;; when the window is narrower than `truncate-partial-width-windows' characters.
+  (setq truncate-partial-width-windows nil)
 
-  (fset 'yes-or-no-p 'y-or-n-p)
+  (setq use-short-answers t)
 
   (setq frame-title-format
         '("Emacs: " (:eval (if (buffer-file-name)
@@ -79,9 +64,41 @@
                              "%b"))))
 
   (when (eq system-type 'darwin)
-    (setq mac-option-modifier 'meta)
-    (setq mac-right-option-modifier 'none)
-    (setq mac-command-modifier 'super)))
+    (setq mac-option-modifier 'meta
+          mac-right-option-modifier 'none)
+    ;; After swapping control and command, this works nicely,
+    ;; otherwise use (setq mac-command-modifier 'super)
+    (setq mac-command-modifier 'control
+          mac-control-modifier 'super)))
+
+(use-feature view
+  ;; http://yummymelon.com/devnull/enhancing-navigation-in-emacs-view-mode.html
+  :hook
+  (view-mode . (lambda ()
+                 (cond ((derived-mode-p 'org-mode)
+                        (define-key view-mode-map (kbd "p") 'org-previous-visible-heading)
+                        (define-key view-mode-map (kbd "n") 'org-next-visible-heading))
+                       ((derived-mode-p 'markdown-mode)
+                        (define-key view-mode-map (kbd "p") 'markdown-outline-previous)
+                        (define-key view-mode-map (kbd "n") 'markdown-outline-next))
+                       ((derived-mode-p 'html-mode)
+                        (define-key view-mode-map (kbd "p") 'sgml-skip-tag-backward)
+                        (define-key view-mode-map (kbd "n") 'sgml-skip-tag-forward))
+                       ((derived-mode-p 'emacs-lisp-mode)
+                        (define-key view-mode-map (kbd "p") 'backward-sexp)
+                        (define-key view-mode-map (kbd "n") 'forward-sexp))
+                       ((derived-mode-p 'clojure-mode)
+                        (define-key view-mode-map (kbd "p") 'backward-sexp)
+                        (define-key view-mode-map (kbd "n") 'forward-sexp))
+                       ((derived-mode-p 'makefile-mode)
+                        (define-key view-mode-map (kbd "p") 'makefile-previous-dependency)
+                        (define-key view-mode-map (kbd "n") 'makefile-next-dependency))
+                       ((derived-mode-p 'c-mode)
+                        (define-key view-mode-map (kbd "p") 'c-beginning-of-defun)
+                        (define-key view-mode-map (kbd "n") 'c-end-of-defun))
+                       (t
+                        (define-key view-mode-map (kbd "p") 'scroll-down-command)
+                        (define-key view-mode-map (kbd "n") 'scroll-up-command))))))
 
 (use-package hl-todo
   :bind
@@ -116,7 +133,7 @@
   (dolist (mode whitespace-disabled-modes)
     (add-hook (intern (concat (symbol-name mode) "-hook")) #'turn-off-whitespace-mode))
   :hook
-  ((text-mode prog-mode) . (lambda () (whitespace-mode +1))))
+  ((text-mode prog-mode) . whitespace-mode))
 
 (use-package goggles
   :diminish
@@ -131,6 +148,9 @@
   :hook (prog-mode . idle-highlight-mode))
 
 (use-feature paren
+  :custom
+  (show-paren-when-point-inside-paren t)
+  (show-paren-when-point-in-periphery t)
   :config
   (show-paren-mode +1))
 
@@ -194,6 +214,9 @@
     ;; Run setup hook for `eval-expression' (calls `eldoc--eval-expression-setup')
     (run-hooks 'eval-expression-minibuffer-setup-hook)
 
+    (mono-complete-mode +1)
+    (setq-local mono-complete-fallback-command 'dabbrev-expand)
+
     ;; smartparens, but don't insert pairs of '
     (smartparens-strict-mode)
     (setq-local sp-pair-list (assoc-delete-all "'" sp-pair-list))
@@ -217,5 +240,8 @@
   (set-face-foreground 'highlight-indent-guides-character-face "grey25")
   :hook ((prog-mode text-mode conf-mode) . highlight-indent-guides-mode))
 
+;; Main causal collection package; the individual packages are configured in the appropriate places
+(use-package casual)
+
 (provide 'init-ui)
 ;;; init-ui.el ends here
diff --git a/.emacs.d/lisp/init-windows.el b/.emacs.d/lisp/init-windows.el
index 2bf54a3..15ec232 100644
--- a/.emacs.d/lisp/init-windows.el
+++ b/.emacs.d/lisp/init-windows.el
@@ -5,38 +5,30 @@
 (use-feature emacs
   :custom
   (switch-to-buffer-obey-display-actions t)
+  (split-height-threshold nil)
+  (split-width-threshold 200))
+
+(use-feature ibuffer
   :bind
-  ("C-x C-M-b" . ibuffer)
-  :config
-  ;; From EmacsWiki
-  (defun toggle-window-split ()
-    (interactive)
-    (if (= (count-windows) 2)
-        (let* ((this-win-buffer (window-buffer))
-               (next-win-buffer (window-buffer (next-window)))
-               (this-win-edges (window-edges (selected-window)))
-               (next-win-edges (window-edges (next-window)))
-               (this-win-2nd (not (and (<= (car this-win-edges)
-                                           (car next-win-edges))
-                                       (<= (cadr this-win-edges)
-                                           (cadr next-win-edges)))))
-               (splitter
-                (if (= (car this-win-edges)
-                       (car (window-edges (next-window))))
-                    'split-window-horizontally
-                  'split-window-vertically)))
-          (delete-other-windows)
-          (let ((first-win (selected-window)))
-            (funcall splitter)
-            (if this-win-2nd (other-window 1))
-            (set-window-buffer (selected-window) this-win-buffer)
-            (set-window-buffer (next-window) next-win-buffer)
-            (select-window first-win)
-            (if this-win-2nd (other-window 1))))))
-  (define-key ctl-x-4-map "t" 'toggle-window-split))
+  ("C-x M-b" . ibuffer)
+  (:map ibuffer-mode-map
+        ("{" . ibuffer-backwards-next-marked)
+        ("}" . ibuffer-forward-next-marked)
+        ("[" . ibuffer-backward-filter-group)
+        ("]" . ibuffer-forward-filter-group)
+        ("$" . ibuffer-toggle-filter-group))
+  :hook (ibuffer-mode . ibuffer-auto-mode))
+
+(use-feature casual-ibuffer
+  :after ibuffer
+  :bind (:map ibuffer-mode-map
+              ("C-o" . casual-ibuffer-tmenu)
+              ("F" . casual-ibuffer-filter-tmenu)
+              ("s" . casual-ibuffer-sortby-tmenu)))
 
 (use-feature winner
   :defer 5
+  :custom (winner-boring-buffers-regexp "\*Minibuf-[0-9]+")
   :config
   (winner-mode +1)
   (defvar winner-repeat-map
@@ -49,7 +41,11 @@
 
 (use-feature windmove
   :defer 5
-  :config (windmove-default-keybindings))
+  :config (windmove-default-keybindings)
+  :bind (("C-c M-<up>" . windmove-delete-up)
+         ("C-c M-<down>" . windmove-delete-down)
+         ("C-c M-<left>" . windmove-delete-left)
+         ("C-c M-<right>" . windmove-delete-right)))
 
 (use-package buffer-move
   :bind (("C-S-<up>" . buf-move-up)
@@ -68,6 +64,9 @@
   (fullframe vc-annotate quit-window)
   (fullframe elpaca-fetch-all quit-window))
 
+(use-package transpose-frame
+  :bind (:map ctl-x-4-map ("t" . transpose-frame)))
+
 (use-package ace-window
   :diminish
   :config
@@ -91,6 +90,7 @@
          ("M-¬" . popper-cycle)
          ("C-`" . popper-toggle-type))
   :custom
+  (popper-group-function #'popper-group-by-perspective)
   (popper-reference-buffers
    '("\\*Messages\\*"
      "\\*HTTP Response\\*"
@@ -142,7 +142,7 @@
                                           ("P" "[project]" frog-jump-buffer-filter-same-project)
                                           ("S" "[similar]" frog-jump-buffer-filter-similar-name)))
   :bind
-  ("C-," . frog-jump-buffer)
+  ("C-c C-," . frog-jump-buffer)
   ("C-x 4 C-," . frog-jump-buffer-other-window))
 
 (use-package buffer-ring
@@ -180,9 +180,6 @@
     (or (apply orig args)
         (persp-buffer-filter (car args))))
   (advice-add 'buffer-flip-skip-buffer :around 'persp-buffer-flip-skip-buffer)
-  ;; (defun persp-buffer-flip-skip-buffer (orig-val &rest args)
-  ;;   (or orig-val (persp-buffer-filter (car args))))
-  ;; (advice-add 'buffer-flip-skip-buffer :filter-return 'persp-buffer-flip-skip-buffer)
   :bind  (("C-c C-<left>" . buffer-flip)
           (:map buffer-flip-map
                 ( "C-<left>" .   buffer-flip-forward)
@@ -194,15 +191,25 @@
   (defun iflipb-persp-buffer-list ()
     "Buffer list for iflipb."
     (seq-filter 'buffer-live-p (persp-current-buffers* t)))
-  (dolist (cmd '(iflipb-previous-buffer iflipb-next-buffer))
-    (put cmd 'repeat-map 'iflipb-repeat-map))
   (defun iflibp-abort ()
     "Abort buffer flipping and return to the original buffer."
     (interactive)
     (iflipb-restore-buffers))
+  (defun iflipb-kill-current-buffer ()
+    "Same as `kill-current-buffer' but keep the iflipb buffer list state.
+
+Modified from `iflipb-kill-buffer'."
+    (interactive)
+    (call-interactively #'kill-current-buffer)
+    (if (iflipb-first-iflipb-buffer-switch-command)
+        (setq last-command 'kill-current-buffer)
+      (if (< iflipb-current-buffer-index (length (iflipb-interesting-buffers)))
+          (iflipb-select-buffer iflipb-current-buffer-index)
+        (iflipb-select-buffer (1- iflipb-current-buffer-index)))
+      (setq last-command 'iflipb-kill-current-buffer)))
   :custom (iflipb-buffer-list-function 'iflipb-persp-buffer-list)
   :bind
-  ("C-x k" . iflipb-kill-buffer) ;; TODO replace with a kill currently selected buffer command
+  ("C-x k" . iflipb-kill-current-buffer)
   ("<f12>" . iflipb-previous-buffer)
   ("<f11>" . iflipb-next-buffer)
   ("<f10>" . iflibp-abort))
diff --git a/.emacs.d/themes/non-modo-theme.el b/.emacs.d/themes/non-modo-theme.el
index d6778c7..4ef4a54 100644
--- a/.emacs.d/themes/non-modo-theme.el
+++ b/.emacs.d/themes/non-modo-theme.el
@@ -296,6 +296,8 @@
 ;;;;; corfu-quick
    `(corfu-quick1 ((,class :inherit bold :background "#0050af")))
    `(corfu-quick2 ((,class :inherit bold :background "#7f1f7f")))
+;;;;; corfu-candidate-overlay
+   `(corfu-candidate-overlay-face ((t :inherit shadow)))
 ;;;;; cov
    `(cov-coverage-not-run-face ((,class :foreground "red")))
    `(cov-coverage-run-face ((,class :foreground "#4fe42f")))
@@ -1165,6 +1167,8 @@
    `(mode-line-emphasis ((,class :inherit bold :foreground "#d5b1ff")))
    `(mode-line-highlight ((,class :inherit 'highlight)))
    `(mode-line-inactive ((,class :background "#1e1e1e" :foreground "#bfc0c4" :box "#3c3c3c")))
+;;;;; mono-complete
+   `(mono-complete-preview-face ((t :inherit shadow)))
 ;;;;; mpdel
    `(mpdel-browser-directory-face ((,class :foreground "#2fafff")))
    `(mpdel-playlist-current-song-face ((,class :inherit bold :foreground "#00bcff")))
@@ -1475,7 +1479,7 @@
    `(shortdoc-section (())) ; remove the default's variable-pitch style
 ;;;;; show-paren-mode
    `(show-paren-match ((,class :inherit bold :background "#7416b5" :foreground "#ffffff" :underline t)))
-   `(show-paren-match-expression ((,class :background "#221044")))
+   `(show-paren-match-expression ((,class :background "grey12")))
    `(show-paren-mismatch ((,class :background "#a4202a" :foreground "#ffffff")))
 ;;;;; shr
    `(shr-abbreviation ((,class :foreground "#9d9def" :underline (:color "#5f6fff" :style wave))))
diff --git a/nix-conf/.sops.yaml b/nix-conf/.sops.yaml
new file mode 100644
index 0000000..2cb1a1e
--- /dev/null
+++ b/nix-conf/.sops.yaml
@@ -0,0 +1,18 @@
+keys:
+  - &admin_djm age1w7kjp0qdgfyg9cyj5w4qc4fc9qz3w65xw2veazesfgdenqrd3ucqsc5ejv
+  - &server_edrahil age1tjfctwnwldmyxnu6qmeufgr9l79vyzmrs7fy58v3d0qj4x4nhqhq2gjmlp
+  - &server_djmuk2 age17j56andser5ddtlfunm35m25xueua4djh9glxlscfcet8865yv9s5aqvla
+creation_rules:
+  - path_regex: edrahil/secrets\.(json|yaml)$
+    key_groups:
+    - age:
+      - *server_edrahil
+      - *admin_djm
+  - path_regex: djmuk2/secrets\.(json|yaml)$
+    key_groups:
+    - age:
+      - *server_djmuk2
+  - path_regex: secrets\.(json|yaml)$
+    key_groups:
+    - age:
+      - *admin_djm
diff --git a/nix-conf/home/config.nix b/nix-conf/home/config.nix
deleted file mode 100644
index 69baf10..0000000
--- a/nix-conf/home/config.nix
+++ /dev/null
@@ -1 +0,0 @@
-{ allowUnfree = true; }
diff --git a/nix-conf/home/djmuk1.nix b/nix-conf/home/djmuk1.nix
index 54b1171..d11499e 100644
--- a/nix-conf/home/djmuk1.nix
+++ b/nix-conf/home/djmuk1.nix
@@ -1,8 +1,6 @@
 { config, pkgs, ... }:
 {
-  imports = [ 
-    ./includes/linux-server.nix
-  ];
+  imports = [ ./includes/linux-server.nix ];
 
   # Let Home Manager install and manage itself.
   programs.home-manager.enable = true;
@@ -11,7 +9,7 @@
   # paths it should manage.
   home.username = "djm";
   home.homeDirectory = "/home/djm";
-  
+
   # This value determines the Home Manager release that your
   # configuration is compatible with. This helps avoid breakage
   # when a new Home Manager release introduces backwards
diff --git a/nix-conf/home/djmuk2.nix b/nix-conf/home/djmuk2.nix
index b871056..576ed39 100644
--- a/nix-conf/home/djmuk2.nix
+++ b/nix-conf/home/djmuk2.nix
@@ -1,8 +1,6 @@
 { config, pkgs, ... }:
 {
-  imports = [ 
-    ./includes/linux-server.nix
-  ];
+  imports = [ ./includes/linux-server.nix ];
 
   # Let Home Manager install and manage itself.
   programs.home-manager.enable = true;
diff --git a/nix-conf/home/edrahil.nix b/nix-conf/home/edrahil.nix
index 1db7be3..9b7f2fb 100644
--- a/nix-conf/home/edrahil.nix
+++ b/nix-conf/home/edrahil.nix
@@ -1,6 +1,6 @@
 { config, pkgs, ... }:
 {
-  imports = [ 
+  imports = [
     ./includes/linux-server.nix
     ./includes/irssi.nix
   ];
@@ -14,7 +14,7 @@
   home.homeDirectory = "/home/djm";
 
   home.sessionVariables = {
-    TMUX_AUTO_ATTACH=1;
+    TMUX_AUTO_ATTACH = 1;
   };
 
   # This value determines the Home Manager release that your
diff --git a/nix-conf/home/egalmoth.nix b/nix-conf/home/egalmoth.nix
index 6e41320..79d29d0 100644
--- a/nix-conf/home/egalmoth.nix
+++ b/nix-conf/home/egalmoth.nix
@@ -1,8 +1,6 @@
 { config, pkgs, ... }:
 {
-  imports = [ 
-    ./includes/linux-dev.nix
-  ];
+  imports = [ ./includes/linux-dev.nix ];
 
   # Let Home Manager install and manage itself.
   programs.home-manager.enable = true;
@@ -11,7 +9,7 @@
   # paths it should manage.
   home.username = "djm";
   home.homeDirectory = "/home/djm";
-  
+
   # This value determines the Home Manager release that your
   # configuration is compatible with. This helps avoid breakage
   # when a new Home Manager release introduces backwards
diff --git a/nix-conf/home/includes/clojure.nix b/nix-conf/home/includes/clojure.nix
index 3681e8b..3915ab3 100644
--- a/nix-conf/home/includes/clojure.nix
+++ b/nix-conf/home/includes/clojure.nix
@@ -1,18 +1,25 @@
-{ config, pkgs, lib, isDarwin, isLinux, ... }:
+{
+  config,
+  pkgs,
+  lib,
+  ...
+}:
 let
   inherit (lib) optionals;
 in
 {
-  home.packages = with pkgs; [
-    babashka
-    clj-kondo
-    clojure
-    clojure-lsp
-    jet
-    maven
-    neil
-  ]
-  ++ optionals stdenv.isDarwin [ (leiningen.override { jdk = jdk8; }) ]
-  ++ optionals stdenv.isLinux [ leiningen ];
+  home.packages =
+    with pkgs;
+    [
+      babashka
+      clj-kondo
+      clojure-lsp
+      emacs-lsp-booster
+      jet
+      maven
+      neil
+    ]
+    # TODO these are here because of the custom versions in otm.nix
+    # but there should be a better way
+    ++ optionals stdenv.isLinux [ leiningen clojure ];
 }
-
diff --git a/nix-conf/home/includes/common.nix b/nix-conf/home/includes/common.nix
index f43168a..7f88087 100644
--- a/nix-conf/home/includes/common.nix
+++ b/nix-conf/home/includes/common.nix
@@ -1,14 +1,47 @@
-{ config, pkgs, ... }:
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
 let
   hcr = pkgs.callPackage ./scripts/hm-changes-report.nix { inherit config pkgs; };
   scr = pkgs.callPackage ./scripts/system-changes-report.nix { inherit config pkgs; };
-  email = builtins.readFile "${config.home.homeDirectory}/email.txt";
+  unstable = import <unstable> { };
 in
 {
   imports = [
     ./zsh.nix
+    <sops-nix/modules/home-manager/sops.nix>
   ];
 
+  nixpkgs.config.allowUnfreePredicate =
+    pkg: builtins.elem (lib.getName pkg) [ "aspell-dict-en-science" ];
+
+  nix = {
+    package = pkgs.nix;
+    settings = {
+      extra-experimental-features = [
+        "nix-command"
+        "flakes"
+      ];
+    };
+  };
+
+  sops = {
+    age.keyFile = "${config.xdg.configHome}/sops/age/keys.txt";
+    defaultSopsFile = builtins.path {
+      path = ./secrets.yaml;
+      name = "home-secrets.yaml";
+    };
+    secrets."ssh_config/oci" = { };
+    secrets."git_email_config/default" = { };
+  };
+
+  home.sessionVariables = {
+    LSP_USE_PLISTS = "true";
+  };
+
   home.packages = with pkgs; [
     hcr
     scr
@@ -17,14 +50,10 @@ in
     aspellDicts.en
     aspellDicts.en-computers
     aspellDicts.en-science
-    bat
-    bat-extras.batdiff
-    bat-extras.batgrep
-    bat-extras.batman
-    bat-extras.batwatch
-    bat-extras.batpipe
+    bandwhich
     bottom
     broot
+    comma
     curl
     diff-so-fancy
     difftastic
@@ -33,7 +62,6 @@ in
     ea
     elinks
     entr
-    eza
     fd
     file
     fzf
@@ -44,37 +72,83 @@ in
     inetutils
     ispell
     isync
+    jd-diff-patch
     jq
     libqalculate
     lscolors
-    lsd
     lynx
     mercurial
-    nixfmt-classic
     nix-info
     nix-prefetch-git
     nix-prefetch-github
+    nix-search
     nixpkgs-review
     nvd
     pass
+    procs
+    pstree
+    rage
     ripgrep
     rlwrap
     sd
+    shfmt
+    sops
+    ssh-to-age
     tealdeer
     tre-command
+    tree
     ugrep
     uni
+    unzip
     w3m
+    xan
+    yq
+    zip
     zstd
+
+    unstable.nixfmt-rfc-style
+    unstable.wcurl
   ];
 
-  nix = {
-    package = pkgs.nix;
-    settings = {
-      extra-experimental-features = [ "nix-command" "flakes" ];
+  programs.bat = {
+    enable = true;
+    extraPackages = with pkgs.bat-extras; [
+      batdiff
+      batgrep
+      batman
+      batwatch
+      batpipe
+    ];
+    config = {
+      style = "full";
+      pager = "less -RXF";
+      map-syntax = [
+        ".ignore:Git Ignore"
+        "*.jenkinsfile:Groovy"
+      ];
     };
   };
 
+  programs.eza = {
+    enable = true;
+    git = true;
+    icons = "auto";
+    enableBashIntegration = false;
+    enableZshIntegration = false;
+    enableFishIntegration = false;
+    enableIonIntegration = false;
+    extraOptions = [
+      "--colour=auto"
+      "--long"
+      "--group-directories-first"
+      "--classify"
+      "--no-user"
+      "--no-time"
+      "--no-filesize"
+      "--no-permissions"
+    ];
+  };
+
   programs.gpg.enable = true;
   programs.nix-index = {
     enable = true;
@@ -94,20 +168,29 @@ in
       set pastetoggle=<F2>
       nmap <silent> <F3> :silent nohlsearch<CR>
       imap <silent> <F3> <C-o>:silent nohlsearch<CR>
+      nmap <silent> <F4> :silent setlocal spell spelllang=en_gb<CR>
+      imap <silent> <F4> <C-o>:silent setlocal spell spelllang=en_gb<CR>
+      nmap <silent> <F5> :silent setlocal nospell<CR>
+      imap <silent> <F5> <C-o>:silent setlocal nospell<CR>
+      nmap <silent> <F6> :silent set diffopt+=iwhite<CR>
+      imap <silent> <F6> <C-o>:silent set diffopt+=iwhite<CR>
+      nmap <silent> <F7> :silent set diffopt-=iwhite<CR>
+      imap <silent> <F7> <C-o>:silent set diffopt-=iwhite<CR>
     '';
-    plugins = [ pkgs.vimPlugins.sensible
-                pkgs.vimPlugins.auto-pairs
-                pkgs.vimPlugins.ctrlp
-                pkgs.vimPlugins.editorconfig-vim
-                pkgs.vimPlugins.inkpot
-                pkgs.vimPlugins.molokai
-                pkgs.vimPlugins.surround
-                pkgs.vimPlugins.vim-lastplace
-                pkgs.vimPlugins.vim-nix
-                pkgs.vimPlugins.vim-pasta
-                pkgs.vimPlugins.vim-repeat
-                pkgs.vimPlugins.vim-sexp-mappings-for-regular-people
-                pkgs.vimPlugins.vim-sleuth
+    plugins = [
+      pkgs.vimPlugins.sensible
+      pkgs.vimPlugins.auto-pairs
+      pkgs.vimPlugins.ctrlp
+      pkgs.vimPlugins.editorconfig-vim
+      pkgs.vimPlugins.inkpot
+      pkgs.vimPlugins.molokai
+      pkgs.vimPlugins.surround
+      pkgs.vimPlugins.vim-lastplace
+      pkgs.vimPlugins.vim-nix
+      pkgs.vimPlugins.vim-pasta
+      pkgs.vimPlugins.vim-repeat
+      pkgs.vimPlugins.vim-sexp-mappings-for-regular-people
+      pkgs.vimPlugins.vim-sleuth
     ];
     settings = {
       background = "dark";
@@ -130,17 +213,19 @@ in
         UseKeychain yes
         User djm
     '';
-    includes = [ "~/.ssh/config_local" ];
+    includes = [
+      "~/.ssh/config_local"
+      config.sops.secrets."ssh_config/oci".path
+    ];
     matchBlocks = {
       "djm.ovh" = {
         hostname = "v.djm.ovh";
         port = 2222;
-        dynamicForwards = [ { port = 8889; } ];
       };
       "devio" = {
-         hostname = "devio.us";
-         user = "deejayem";
-         port = 2222;
+        hostname = "devio.us";
+        user = "deejayem";
+        port = 2222;
       };
       "sdf" = {
         hostname = "sdf.org";
@@ -155,18 +240,12 @@ in
         user = "deejayem";
       };
       "blinkenshell" = {
-         hostname = "ssh.blinkenshell.org";
-         port = 2222;
+        hostname = "ssh.blinkenshell.org";
+        port = 2222;
       };
       "hashbang" = {
         hostname = "de1.hashbang.sh";
       };
-      "o1" = {
-        hostname = "130.162.163.108";
-      };
-      "o2" = {
-        hostname = "143.47.239.39";
-      };
       "tilde.institute" = {
         hostname = "tilde.institute";
       };
@@ -182,13 +261,29 @@ in
         identityFile = "~/.ssh/id_ed25519";
         identitiesOnly = true;
       };
+      "hb-backup" = {
+        hostname = "de1.hashbang.sh";
+        identityFile = "~/.ssh/hb_backup_key";
+        identitiesOnly = true;
+      };
+      "bs-backup" = {
+        hostname = "ssh.blinkenshell.org";
+        port = 2222;
+        identityFile = "~/.ssh/bs_backup_key";
+        identitiesOnly = true;
+      };
+      "tt-backup" = {
+        hostname = "tilde.team";
+        identityFile = "~/.ssh/tt_backup_key";
+        identitiesOnly = true;
+      };
     };
   };
 
   programs.git = {
     enable = true;
     userName = "David Morgan";
-    userEmail = email;
+    includes = [ { path = config.sops.secrets."git_email_config/default".path; } ];
     aliases = {
       # difftastic
       logt = "!sh -c 'GIT_EXTERNAL_DIFF=\"difft --background=dark\" git log -p --ext-diff'";
@@ -203,9 +298,9 @@ in
       cshow = "!git -c delta.line-numbers=false show";
       cdiff = "!git -c delta.line-numbers=false diff";
       # diff-so-fancy
-      flog = "!git -c core.pager=\"diff-so-fancy | less\" log"; # usually used with -p
-      fshow = "!git -c core.pager=\"diff-so-fancy | less\" show";
-      fdiff = "!git -c core.pager=\"diff-so-fancy | less\" diff";
+      flog = ''!git -c core.pager="diff-so-fancy | less" log''; # usually used with -p
+      fshow = ''!git -c core.pager="diff-so-fancy | less" show'';
+      fdiff = ''!git -c core.pager="diff-so-fancy | less" diff'';
 
       upstream = "!git push -u origin HEAD";
       update-master = "!git fetch origin master:master";
@@ -220,13 +315,20 @@ in
       diff = {
         tool = "difftastic";
         colorMoved = "default";
-        elisp = { xfuncname = "^\\((((def\\S+)|use-package)\\s+\\S+)"; };
-        clojure = { xfuncname = "^\\((def\\S+\\s+\\S+)"; };
+        elisp = {
+          xfuncname = "^\\((((def\\S+)|use-package)\\s+\\S+)";
+        };
+        clojure = {
+          xfuncname = "^\\((def\\S+\\s+\\S+)";
+        };
       };
       difftool = {
         prompt = false;
-        difftastic = { cmd = ''difft "$LOCAL" "$REMOTE"''; };
+        difftastic = {
+          cmd = ''difft "$LOCAL" "$REMOTE"'';
+        };
       };
+      merge.conflictstyle = "diff3";
       pull = {
         ff = "only";
         rebase = false;
@@ -261,5 +363,23 @@ in
       signByDefault = true;
     };
   };
-}
 
+  programs.lsd = {
+    enable = true;
+    enableZshIntegration = false; # don't set aliases
+    settings = {
+      indicators = true;
+      #layout = "oneline";
+      sorting.dir-grouping = "first";
+      blocks = [
+        "git"
+        "permission"
+        "user"
+        "group"
+        "size"
+        "date"
+        "name"
+      ];
+    };
+  };
+}
diff --git a/nix-conf/home/includes/darwin.nix b/nix-conf/home/includes/darwin.nix
index 3d81678..e85f3f9 100644
--- a/nix-conf/home/includes/darwin.nix
+++ b/nix-conf/home/includes/darwin.nix
@@ -1,4 +1,9 @@
-{ config, lib, pkgs, ... }:
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
 
 with lib;
 let
@@ -20,56 +25,120 @@ let
       makeWrapper ${pkgs.mopidy}/bin/mopidy $out/bin/mopidy \
         --prefix PYTHONPATH : $out/${pkgs.mopidyPackages.python.sitePackages}
     '';
-    };
+  };
+
+  # https://github.com/NixOS/nixpkgs/issues/395169
+  patched-pkgs = pkgs.extend (
+    _final: prev: {
+      ld64 = prev.ld64.overrideAttrs (old: {
+        patches = old.patches ++ [ ./Dedupe-RPATH-entries.patch  ];
+      });
+      libuv = prev.libuv.overrideAttrs (old: {
+        doCheck = false;
+      });
+      dbus = prev.dbus.overrideAttrs (old: {
+        doCheck = false;
+      });
+      python313 = prev.python313.override {
+        packageOverrides = self: super: {
+          execnet = super.execnet.overridePythonAttrs (old: {
+            doCheck = false;
+          });
+          pytest-xdist = super.pytest-xdist.overridePythonAttrs (old: {
+            doCheck = false;
+          });
+          requests = super.requests.overridePythonAttrs (old: {
+            doCheck = false;
+          });
+          sphinx = super.sphinx.overridePythonAttrs (old: {
+            doCheck = false;
+          });
+        };
+      };
+    }
+  );
+
+  # Use the patches from emacs-plus
+  emacs-plus = (patched-pkgs.emacs30-pgtk.overrideAttrs (old: {
+        patches =
+          (old.patches or [])
+          ++ [
+            (pkgs.fetchpatch {
+              url = "https://raw.githubusercontent.com/d12frosted/homebrew-emacs-plus/master/patches/emacs-30/fix-window-role.patch";
+              sha256 = "0c41rgpi19vr9ai740g09lka3nkjk48ppqyqdnncjrkfgvm2710z";
+            })
+            (pkgs.fetchpatch {
+              url = "https://raw.githubusercontent.com/d12frosted/homebrew-emacs-plus/master/patches/emacs-30/round-undecorated-frame.patch";
+              sha256 = "uYIxNTyfbprx5mCqMNFVrBcLeo+8e21qmBE3lpcnd+4=";
+            })
+            (pkgs.fetchpatch {
+              url = "https://raw.githubusercontent.com/d12frosted/homebrew-emacs-plus/master/patches/emacs-30/system-appearance.patch";
+              sha256 = "3QLq91AQ6E921/W9nfDjdOUWR8YVsqBAT/W9c1woqAw=";
+            })
+          ];
+      })).override{ withNativeCompilation = true; };
+
+  emacs-plus-with-packages = (pkgs.emacsPackagesFor emacs-plus).emacsWithPackages (ps: [
+    ps.vterm
+    ps.multi-vterm
+  ]);
+
 in
 {
-  imports = [
-    ./dev-common.nix
-  ];
+  imports = [ ./dev-common.nix ];
 
   home.packages = with pkgs; [
     awscli2
     cacert
+    caddy
     coreutils
     curl
     diffutils
-    ((emacsPackagesFor emacs29-macport).emacsWithPackages(ps: [ ps.vterm ps.multi-vterm ]))
+    emacs-plus-with-packages
     findutils
     gh
     gh-dash
     #gnused
-    mopidy-with-extensions
-    mpdscribble
-    mpc-cli
-    mpd
-    mpv
-    ncmpcpp
+    #mopidy-with-extensions
+    #mpdscribble
+    #mpc-cli
+    #mpd
+    #ncmpcpp
     nix # on darwin we are not using nixos (duh)
     nodejs
-    nodePackages.eslint
-    openvpn
     pam-reattach
     pinentry_mac
     pgcli
     pgformatter
-    pms
+    #pms
+    poetry
     postgresql
     podman
     #python310Packages.sqlparse
+    redis
     sqls
-    vimpc
+    #vimpc
     wget
   ];
 
+  nixpkgs.config.permittedInsecurePackages = [
+    "emacs-mac-macport-29.1"
+    "emacs-mac-macport-with-packages-29.1"
+  ];
+
   nix.settings = {
     sandbox = true;
     keep-outputs = true;
     keep-derivations = true;
   };
 
-  programs.java = {
-    enable = true;
-    #package = (pkgs.jdk8.overrideAttrs (_: { postPatch = "ln -nsf ../zulu-8.jdk/Contents/Home/man man"; }));
-  };
-}
+  programs.bat.extraPackages = with pkgs.bat-extras; [
+    (prettybat.override {
+      withClangTools = false;
+      withRustFmt = false;
+    })
+  ];
 
+  # TODO is this a good idea?
+  #programs.zsh.shellAliases = { emacs = "${emacs-plus-with-packages}/Applications/Emacs.app/Contents/MacOS/Emacs"; };
+}
diff --git a/nix-conf/home/includes/dev-common.nix b/nix-conf/home/includes/dev-common.nix
index a76eb72..92b286e 100644
--- a/nix-conf/home/includes/dev-common.nix
+++ b/nix-conf/home/includes/dev-common.nix
@@ -1,21 +1,33 @@
-{ config, pkgs, lib, ... }:
 {
-
+  config,
+  pkgs,
+  lib,
+  ...
+}:
+let
+  inherit (lib) optionals;
+in
+{
   imports = [
     ./common.nix
     ./clojure.nix
   ];
 
-  home.packages = with pkgs; [
-    docker
-    docker-compose
-    docker-credential-helpers
-    ffmpeg
-    gopass-jsonapi
-    mpv
-    multimarkdown
-    neovim
-  ];
+  home.packages =
+    with pkgs;
+    [
+      docker
+      docker-compose
+      docker-credential-helpers
+      gopass-jsonapi
+      multimarkdown
+      neovim
+      ripgrep-all
+    ]
+    ++ optionals (!stdenv.isDarwin) [
+      ffmpeg
+      mpv
+    ];
 
   programs.tmux = {
     enable = true;
@@ -47,28 +59,27 @@
       }
     ];
     extraConfig = ''
-     unbind-key R
-     bind-key R run-shell ' \
-       tmux source-file ~/.config/tmux/tmux.conf > /dev/null; \
-       tmux display-message "Sourced .config/tmux/tmux.conf!"'
+      unbind-key R
+      bind-key R run-shell ' \
+        tmux source-file ~/.config/tmux/tmux.conf > /dev/null; \
+        tmux display-message "Sourced .config/tmux/tmux.conf!"'
 
-      bind-key £ split-window -h
+       bind-key £ split-window -h
 
-      set-option -g status-bg '#666666'
-      set-option -g status-fg '#aaaaaa'
-      set-option -g status-left-length 50
-      set-option -g status-right " %a, %b %d - %H:%M "
+       set-option -g status-bg '#666666'
+       set-option -g status-fg '#aaaaaa'
+       set-option -g status-left-length 50
+       set-option -g status-right " %a, %b %d - %H:%M "
 
-      ${lib.optionalString pkgs.stdenv.isLinux ''
-        bind-key -T copy-mode y send-keys -X copy-pipe-and-cancel "xsel -i -p && xsel -o -p | xsel -i -b"
-        bind-key C-y run "xsel -o | tmux load-buffer - ; tmux paste-buffer"
-      ''}
-      ${lib.optionalString pkgs.stdenv.isDarwin ''
-        bind-key -T copy-mode y send-keys -X copy-pipe-and-cancel "reattach-to-user-namespace pbcopy"
-        bind-key C-y run "reattach-to-user-namespace pbpaste | tmux load-buffer - ; tmux paste-buffer"
-      ''}
+       ${lib.optionalString pkgs.stdenv.isLinux ''
+         bind-key -T copy-mode y send-keys -X copy-pipe-and-cancel "xsel -i -p && xsel -o -p | xsel -i -b"
+         bind-key C-y run "xsel -o | tmux load-buffer - ; tmux paste-buffer"
+       ''}
+       ${lib.optionalString pkgs.stdenv.isDarwin ''
+         bind-key -T copy-mode y send-keys -X copy-pipe-and-cancel "reattach-to-user-namespace pbcopy"
+         bind-key C-y run "reattach-to-user-namespace pbpaste | tmux load-buffer - ; tmux paste-buffer"
+       ''}
     '';
   };
 
 }
-
diff --git a/nix-conf/home/includes/irssi.nix b/nix-conf/home/includes/irssi.nix
index e7cd92c..54bdc9b 100644
--- a/nix-conf/home/includes/irssi.nix
+++ b/nix-conf/home/includes/irssi.nix
@@ -1,4 +1,9 @@
-{ config, pkgs, lib, ... }:
+{
+  config,
+  pkgs,
+  lib,
+  ...
+}:
 
 {
   programs.irssi = {
@@ -41,7 +46,7 @@
     extraConfig = ''
       settings = {
         core = {
-          real_name = "David Morgan";
+          real_name = "djm";
           user_name = "djm";
           nick = "djm";
           timestamp_format = "%H:%M:%S";
@@ -85,6 +90,7 @@
         11 = { items = ( { type = "CHANNEL"; chat_type = "IRC"; name = "#!"; tag = "hashbang"; }); };
         12 = { items = ( { type = "CHANNEL"; chat_type = "IRC"; name = "#nixos"; tag = "libera"; }); };
         13 = { items = ( { type = "CHANNEL"; chat_type = "IRC"; name = "#home-manager"; tag = "oftc"; }); };
+        14 = { items = ( { type = "CHANNEL"; chat_type = "IRC"; name = "#blinkenshell.log"; tag = "blinkenirc"; }); };
       };
     '';
     networks = {
@@ -123,7 +129,6 @@
         };
         channels = {
           ctrl-c.autoJoin = true;
-          emacs.autoJoin = true;
           institute.autoJoin = true;
           meta.autoJoin = true;
           team.autoJoin = true;
@@ -147,7 +152,11 @@
       };
       blinkenirc = {
         nick = "djm";
-        autoCommands = [ "msg chanserv invite #blinkenshell.op" "wait 2000" ];
+        autoCommands = [
+          "msg chanserv invite #blinkenshell.op"
+          "msg chanserv invite #blinkenshell.log"
+          "wait 2000"
+        ];
         saslExternal = true;
         server = {
           address = "irc.blinkenshell.org";
@@ -162,6 +171,7 @@
         channels = {
           blinkenshell.autoJoin = true;
           "blinkenshell.op".autoJoin = true;
+          "blinkenshell.log".autoJoin = true;
         };
       };
       hashbang = {
diff --git a/nix-conf/home/includes/linux-dev.nix b/nix-conf/home/includes/linux-dev.nix
index cfd3760..473118e 100644
--- a/nix-conf/home/includes/linux-dev.nix
+++ b/nix-conf/home/includes/linux-dev.nix
@@ -1,23 +1,26 @@
 { config, pkgs, ... }:
 {
-  imports = [
-    ./dev-common.nix
-  ];
+  imports = [ ./dev-common.nix ];
 
   programs.emacs = {
     enable = true;
-    package = pkgs.emacs29;
-    extraPackages = (epkgs: [ epkgs.vterm epkgs.multi-vterm ] );
+    extraPackages = (
+      epkgs: [
+        epkgs.vterm
+        epkgs.multi-vterm
+      ]
+    );
   };
 
   home.packages = with pkgs; [
+    lame
     libtree
-    mpv
+    mp3cat
     mu
     pinentry
     protonvpn-cli
-    youtube-dl
+    sword
+    yt-dlp
   ];
 
 }
-
diff --git a/nix-conf/home/includes/linux-server.nix b/nix-conf/home/includes/linux-server.nix
index 12164b4..62233f0 100644
--- a/nix-conf/home/includes/linux-server.nix
+++ b/nix-conf/home/includes/linux-server.nix
@@ -1,22 +1,21 @@
 { config, pkgs, ... }:
 {
-  imports = [
-    ./common.nix
-  ];
+  imports = [ ./common.nix ];
 
   home.packages = with pkgs; [
     emacs-nox
     irssi
     libtree
     msmtp
-    neomutt
     pinentry
     restic
+    sword
+    yt-dlp
   ];
 
   services.gpg-agent = {
     enable = true;
-    pinentryPackage = pkgs.pinentry-curses;
+    pinentry.package = pkgs.pinentry-curses;
     defaultCacheTtl = 34560000;
     maxCacheTtl = 34560000;
   };
@@ -51,4 +50,3 @@
 
   programs.vim.packageConfigurable = pkgs.vim;
 }
-
diff --git a/nix-conf/home/includes/scripts/hm-changes-report.nix b/nix-conf/home/includes/scripts/hm-changes-report.nix
index 303d482..65e7fe8 100644
--- a/nix-conf/home/includes/scripts/hm-changes-report.nix
+++ b/nix-conf/home/includes/scripts/hm-changes-report.nix
@@ -2,7 +2,7 @@
 
 # https://github.com/gvolpe/nix-config/blob/e28a220d0087064e6bad6b992b4914a65eb545e5/home/scripts/changes-report.nix
 let
-  hm-profiles = "/nix/var/nix/profiles/per-user/${config.home.username}/profile-*-link";
+  hm-profiles = "${config.home.homeDirectory}/.local/state/nix/profiles/home-manager-*-link";
 in
 pkgs.writeShellScriptBin "hm-changes-report" ''
   # Disable nvd if there are less than 2 hm profiles.
@@ -12,4 +12,3 @@ pkgs.writeShellScriptBin "hm-changes-report" ''
     ${pkgs.nvd}/bin/nvd diff $(ls -d1v ${hm-profiles} | tail -2)
   fi
 ''
-
diff --git a/nix-conf/home/includes/scripts/system-changes-report.nix b/nix-conf/home/includes/scripts/system-changes-report.nix
index 56166cb..b94498c 100644
--- a/nix-conf/home/includes/scripts/system-changes-report.nix
+++ b/nix-conf/home/includes/scripts/system-changes-report.nix
@@ -12,4 +12,3 @@ pkgs.writeShellScriptBin "system-changes-report" ''
     ${pkgs.nvd}/bin/nvd diff $(ls -d1v ${system-profiles} | tail -2)
   fi
 ''
-
diff --git a/nix-conf/home/includes/secrets.yaml b/nix-conf/home/includes/secrets.yaml
new file mode 100644
index 0000000..2fc9d8c
--- /dev/null
+++ b/nix-conf/home/includes/secrets.yaml
@@ -0,0 +1,21 @@
+ssh_config:
+    oci: ENC[AES256_GCM,data:l1GZ6mszgDhGztWmMdkNY2wRGfLIOGfHou7m0p8NkvaZZ3oKhblyu9C2Y2uEZArC8aCysxmU0QDfeIxDAzBdszUY,iv:HD8xdaiF9s0XZAuHNjAQfEtMgKaM0R12FCv5rTq19+Y=,tag:bfa48iOXhASXc+JhmYy/EQ==,type:str]
+    otm: ""
+git_email_config:
+    default: ENC[AES256_GCM,data:ADmbGuV+E5wvGdbdC12BDi2TvHeoIRWjerKxnvDV7dENCxFyy+3P01IyCA==,iv:Nik4YiC8WhWmAnM7g1ER5HU0pg88l9uFiHQNtou5jas=,tag:RtK0XKKcHHR39p3mSl5YRw==,type:str]
+    otm: ENC[AES256_GCM,data:dFrxmxFRU5MThUSdqWuL3ZmBCJfMUVYWQTnWQF25Cnn6lMflau5vHNEFZZDZxyFBk7A=,iv:EOv1xgxXuN3LuiO1eorazgQHBkWY9GKUjFBaYnfkLRI=,tag:Mg6SwdQSGjtlR5iiOU/q7g==,type:str]
+sops:
+    age:
+        - recipient: age1w7kjp0qdgfyg9cyj5w4qc4fc9qz3w65xw2veazesfgdenqrd3ucqsc5ejv
+          enc: |
+            -----BEGIN AGE ENCRYPTED FILE-----
+            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAybC93bWJ1d05pSWdyOHkv
+            eXBUa1dUcWFnNTRKZXpxckhKNXlLMVBoR0dFCnNKcVhmcWFaQkllc05iVmtub2E2
+            YkRTbnNNSnF2WWlET2N4MExYNFAzZFEKLS0tIHhwbTE3bEJlTEpXOXprSTBRckF0
+            cjlWWTNQR3lLLzBqTHhld05VblFJdHcKihceil9ge+IKG2GZcLpGWUncvRvmyJ7w
+            YiWtb/ApF4T27wsmmFyLSnG8OWkLCKzaeU4QOVIGYQcfzzcQD5nUGg==
+            -----END AGE ENCRYPTED FILE-----
+    lastmodified: "2025-04-29T13:45:42Z"
+    mac: ENC[AES256_GCM,data:Z4jJujUvl8jmJQbquV9NeczVZF17HI01sDpUoSSS7cM89WUOZzAOcT3zxR4hzyCfq4je++YkBPPFl4hcqbuVyBu8zCxY4z9673sFSLYrUA8THVeLYjMOTo0Hu0tIy8vy+uZZcAuRFaXyS+GrQHAdSSm4zVkwLG9R1nod+ys6LvQ=,iv:WfubcgFSY94cyfrlAlND6CQudoISuGL5fBhm9E3O4Q4=,tag:IeKYzou3EDwGyA23FGHtdA==,type:str]
+    unencrypted_suffix: _unencrypted
+    version: 3.10.2
diff --git a/nix-conf/home/includes/zsh.nix b/nix-conf/home/includes/zsh.nix
index 9729386..650f559 100644
--- a/nix-conf/home/includes/zsh.nix
+++ b/nix-conf/home/includes/zsh.nix
@@ -1,17 +1,33 @@
-{ config, pkgs, lib, isDarwin, ... }:
+{
+  config,
+  pkgs,
+  lib,
+  ...
+}:
 let
-  inherit (lib)
-    optionalAttrs
-    optionals;
+  inherit (lib) optionalAttrs optionals;
+  show_file_or_dir_preview = "if [ -d {} ]; then eza --tree --color=always {} | head -200; else bat -n --color=always --line-range :500 {}; fi";
 in
 {
-  home.packages = with pkgs; [
-    zsh-completions
-  ];
+  home.packages = with pkgs; [ zsh-completions ];
 
   programs.fzf = {
     enable = true;
     enableZshIntegration = true;
+    changeDirWidgetCommand = "fd --type=d --hidden --strip-cwd-prefix --exclude .git --exclude node_modules"; # FZF_ALT_C_COMMAND
+    changeDirWidgetOptions = [ "--preview 'eza --tree --color=always {} | head -200'" ]; # FZF_ALT_C_OPTS
+    defaultCommand = "fd --hidden --strip-cwd-prefix --exclude .git --exclude node_modules"; # FZF_DEFAULT_COMMAND
+    defaultOptions = [
+      "--bind=ctrl-t:toggle-all"
+      "--bind=ctrl-j:jump"
+    ]; # FZF_DEFAULT_OPTS
+    fileWidgetCommand = config.programs.fzf.defaultCommand; # FZF_CTRL_T_COMMAND
+    fileWidgetOptions = [ "--preview '${show_file_or_dir_preview}'" ]; # FZF_CTRL_T_OPTS
+    historyWidgetOptions = [
+      "--preview 'echo {}'"
+      "--preview-window down:3:hidden:wrap"
+      "--bind 'ctrl-t:toggle-preview'"
+    ]; # FZF_CTRL_R_OPTS
   };
   programs.zoxide = {
     enable = true;
@@ -23,20 +39,41 @@ in
   };
   programs.keychain = {
     enable = lib.mkIf pkgs.stdenv.isLinux true;
-    agents = [ "ssh" "gpg"];
-    keys = [ "id_rsa" "id_ed25519" "C171251002C200F2" ];
-  #  extraFlags = [ "--quiet" "--ignore-missing" ];
+    agents = [
+      "ssh"
+      "gpg"
+    ];
+    keys = [
+      "id_rsa"
+      "id_ed25519"
+      "C171251002C200F2"
+    ];
+    #  extraFlags = [ "--quiet" "--ignore-missing" ];
   };
   programs.command-not-found.enable = true;
   programs.zsh = {
     enable = true;
     enableCompletion = true;
-    autosuggestion.enable = true;
+    autosuggestion = {
+      enable = true;
+      highlight = "fg=#808080";
+      strategy = [
+        "history"
+        "completion"
+      ];
+    };
     defaultKeymap = "emacs";
     history = {
-      size = 100000;
-      save = 100000;
       expireDuplicatesFirst = true;
+      ignoreSpace = true;
+      save = 100000;
+      share = true;
+      size = 100000;
+    };
+    historySubstringSearch = {
+      enable = true;
+      searchUpKey = "$terminfo[kcuu1]";
+      searchDownKey = "$terminfo[kcud1]";
     };
 
     profileExtra = ''
@@ -50,11 +87,23 @@ in
       cp = "cp -iv";
       mv = "mv -iv";
       mkdir = "mkdir -v";
-      tree = "eza --tree";
-
-      pp = "pushbullet push \"Pixel\" link \"\${1}\" \"\${1}\"";
-
-      upgrade_emacs = "cp ~/.emacs.d/straight/versions/default.el ~/straight-versions-default-`date \"+%Y-%m-%d-%H%M%S\"`.el && emacs --batch -l \"~/.emacs.d/init.el\" -f \"my/upgrade-packages\"";
+      cat = "bat -p";
+      c = "bat -p";
+      l = "eza";
+      la = "eza -a";
+      lg = "eza -G";
+      lga = "eza -aG";
+      ll = "\\eza --icons --git --colour --long --group-directories-first --classify";
+      lla = "ll -a";
+      t = "eza --tree";
+      p = "lsd";
+      pa = "lsd -a";
+      pll = "lsd -l";
+      pla = "lsd -la";
+
+      pp = ''pushbullet push "Pixel" link "''${1}" "''${1}"'';
+
+      upgrade_emacs = ''cp ~/.emacs.d/straight/versions/default.el ~/straight-versions-default-`date "+%Y-%m-%d-%H%M%S"`.el && emacs --batch -l "~/.emacs.d/init.el" -f "my/upgrade-packages"'';
       diff_emacs = "difft --color always --context 0 $(ls -d1v ~/straight-versions-default-*.el | tail -1) ~/.emacs.d/straight/versions/default.el | grep '\\[9[12]' | egrep -v '(gnu-elpa-mirror|nongnu-elpa|melpa|emacsmirror-mirror)'";
 
       nix-up = "git -C ~/dotfiles pull && doas nix-channel --update && doas nixos-rebuild switch && nix-channel --update && home-manager switch && system-changes-report && hm-changes-report && df -h && date";
@@ -65,13 +114,14 @@ in
 
       fb = "fzf --preview 'bat --color=always --style=numbers --line-range=:500 {}'";
 
+      # Restrict matches to subdirs of the current one (https://github.com/skywind3000/z.lua/blob/ef9a49d73d2b4f262c6fbb23262253dcda7c19a7/README.md#tips)
       zz = "z $PWD";
 
       els = "ea run linear ls -- -1";
       erg = "ea run grouped rg --";
       fd = "ea run linear fd --";
 
-      git-reset-branch = "git fetch && git reset --hard origin/\$(git branch --show-current)";
+      git-reset-branch = "git fetch && git reset --hard origin/$(git branch --show-current)";
 
       # Git log aliases from the omz git plugin
       gl = "git pull";
@@ -81,165 +131,211 @@ in
       glgga = "git log --graph --decorate --all";
       glgm = "git log --graph --max-count=10";
       glo_ = "git log --oneline --decorate";
-      glol = "git log --graph --pretty=\"%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ar) %C(bold blue)<%an>%Creset\"";
-      glols = "git log --graph --pretty=\"%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ar) %C(bold blue)<%an>%Creset\" --stat";
-      glod = "git log --graph --pretty=\"%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ad) %C(bold blue)<%an>%Creset\"";
-      glods = "git log --graph --pretty=\"%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ad) %C(bold blue)<%an>%Creset\" --date=short";
-      glola = "git log --graph --pretty=\"%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ar) %C(bold blue)<%an>%Creset\" --all";
+      glol = ''git log --graph --pretty="%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ar) %C(bold blue)<%an>%Creset"'';
+      glols = ''git log --graph --pretty="%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ar) %C(bold blue)<%an>%Creset" --stat'';
+      glod = ''git log --graph --pretty="%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ad) %C(bold blue)<%an>%Creset"'';
+      glods = ''git log --graph --pretty="%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ad) %C(bold blue)<%an>%Creset" --date=short'';
+      glola = ''git log --graph --pretty="%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ar) %C(bold blue)<%an>%Creset" --all'';
       glog = "git log --oneline --decorate --graph";
       gloga = "git log --oneline --decorate --graph --all";
-    }
-    // optionalAttrs pkgs.stdenv.isDarwin {
-      oemacs = "open -a /Applications/Emacs.app";
-    };
+    } // optionalAttrs pkgs.stdenv.isDarwin { oemacs = "open -a /Applications/Emacs.app"; };
 
     localVariables = {
       PER_DIRECTORY_HISTORY_TOGGLE = "^\\\\"; # ^\\ is ^#
       HISTORY_START_WITH_GLOBAL = true;
-
-      ZSH_AUTOSUGGEST_STRATEGY = ["history" "completion"];
-
-      NVM_AUTO_USE = true;
-      NVM_LAZY_LOAD = true;
-
-      LSP_USE_PLISTS = true;
       LESS = "-iRXF";
     };
-    initExtraFirst = ''
-      [[ $TERM == "tramp" ]] && unsetopt zle && PS1='$ ' && return
-    '';
-    initExtra = ''
-      # Based on prezto tmux plugin
-      if [[ -z "$TMUX" && -z "$EMACS" && -z "$VIM" && -z "$INSIDE_EMACS" && (-z "$SSH_TTY" || -n "$TMUX_AUTO_ATTACH") ]]; then
-        tmux start-server
+    initContent = lib.mkMerge [
+      (lib.mkBefore ''
+        [[ $TERM == "tramp" ]] && unsetopt zle && PS1='$ ' && return
+      '')
+      ''
+        # Based on prezto tmux plugin
+        if [[ -z "$TMUX" && -z "$EMACS" && -z "$VIM" && -z "$INSIDE_EMACS" && (-z "$SSH_TTY" || -n "$TMUX_AUTO_ATTACH") ]]; then
+          tmux start-server
+
+          if ! tmux has-session 2> /dev/null; then
+            tmux new-session -d -s "0" \; set-option -t "0" destroy-unattached off &> /dev/null
+          fi
 
-        if ! tmux has-session 2> /dev/null; then
-          tmux new-session -d -s "0" \; set-option -t "0" destroy-unattached off &> /dev/null
+          if [[ -n "$SSH_TTY" ]]; then
+            exec tmux -u attach-session
+          else
+            exec tmux -u attach-session -d
+          fi
         fi
 
-        if [[ -n "$SSH_TTY" ]]; then
-          exec tmux -u attach-session
-        else
-          exec tmux -u attach-session -d
-        fi
-      fi
-
-      # Keep these in initExtra, rather than localVariables, because the order matters
-      export FZF_DEFAULT_COMMAND='rg --files --no-ignore --hidden --follow -g "!{.git,node_modules}/*" 2> /dev/null'
-      export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
-      export FZF_ALT_C_COMMAND='rg --hidden --files --sort-files --null -g "!{.git,node_modules}/*" | xargs -0 dirname | sort -u'
-      export FZF_ALT_C_OPTS="--preview 'eza --tree {} | head -200'"
-      export FZF_CTRL_R_OPTS="--preview 'echo {}' --preview-window down:3:hidden:wrap --bind 'ctrl-t:toggle-preview'"
-      export FZF_DEFAULT_OPTS="--bind=ctrl-t:toggle-all --bind=ctrl-j:jump"
-
-      eval "$(batpipe)"
-      autopair-init
-      enable-fzf-tab
-      bindkey '^[[A' history-substring-search-up
-      bindkey '^[[B' history-substring-search-down
-
-      # make home and end work
-      [[ -z "$terminfo[khome]" ]] || bindkey -M emacs "$terminfo[khome]" beginning-of-line
-      [[ -z "$terminfo[kend]" ]] || bindkey -M emacs "$terminfo[kend]" end-of-line
-
-      # disable sort when completing `git checkout`
-      zstyle ''\':completion:*:git-checkout:*''\' sort false
-      # set descriptions format to enable group support
-      zstyle ''\':completion:*:descriptions''\' format ''\'[%d]''\'
-      # Allow tab to expand aliases
-      zstyle ':completion:*' completer _expand_alias _complete _ignored
-      # set list-colors to enable filename colorizing
-      #zstyle ''\':completion:*''\' list-colors ''${(s.:.)LS_COLORS}
-      # preview directory''\'s content with eza when completing cd
-      zstyle ''\':fzf-tab:complete:cd:*''\' fzf-preview ''\'eza -1 --color=always ''$realpath''\'
-      # switch group using `,` and `.`
-      zstyle ''\':fzf-tab:*''\' switch-group ''\',''\' ''\'.''\'
-
-      set -o noclobber append_history share_history
-
-      # disable flow control (so that fzf-git.sh's ^g^s can work)
-      stty -ixon
-
-      function generate () { gopass generate -s -p $1 $((RANDOM % 14 + 45)) }
-      function fcd { cd $(fd -L --max-depth=''${1:-4} --type=d 2>/dev/null | fzf-tmux) }
-
-      fif() {
-        if [ ! "$#" -gt 0  ]; then
-          echo "usage: fif <SEARCH_TERM>"
-          return 1;
-        fi
-        rg --files-with-matches --no-messages "$1" | fzf $FZF_PREVIEW_WINDOW --preview "rg --ignore-case --pretty --context 10 '$1' {}"
-      }
+        autopair-init
+        enable-fzf-tab
+
+        # make home and end work
+        [[ -z "$terminfo[khome]" ]] || bindkey -M emacs "$terminfo[khome]" beginning-of-line
+        [[ -z "$terminfo[kend]" ]] || bindkey -M emacs "$terminfo[kend]" end-of-line
+
+        # disable sort when completing `git checkout`
+        zstyle ':completion:*:git-checkout:*' sort false
+        # set descriptions format to enable group support
+        zstyle ':completion:*:descriptions' format '[%d]'
+        # Allow tab to expand aliases
+        zstyle ':completion:*' completer _expand_alias _complete _ignored
+        # set list-colors to enable filename colorizing
+        #zstyle ':completion:*' list-colors ''${(s.:.)LS_COLORS}
+        # preview directory's content with eza when completing cd
+        zstyle ':fzf-tab:complete:cd:*' fzf-preview 'eza -1 --color=always $realpath'
+        # switch group using `,` and `.`
+        zstyle ':fzf-tab:*' switch-group ',' '.'
+
+        # functions modified from https://www.josean.com/posts/7-amazing-cli-tools
+        _fzf_compgen_path() {
+          fd --hidden --exclude .git --exclude node_modules . "$1"
+        }
+        _fzf_compgen_dir() {
+          fd --type=d --hidden --exclude .git --exclude node_modules . "$1"
+        }
+        _fzf_comprun() {
+          local command=$1
+          shift
+
+          case "$command" in
+            cd)           fzf --preview 'eza --tree --color=always {} | head -200' "$@" ;;
+            export|unset) fzf --preview "eval 'echo $'{}"         "$@" ;;
+            ssh)          fzf --preview 'dig {}'                   "$@" ;;
+            *)            fzf --preview "${show_file_or_dir_preview}" "$@" ;;
+          esac
+        }
 
-      fe() {
-        IFS=$'\n' files=($(fzf-tmux --query="$1" --multi --select-1 --exit-0))
-        [[ -n "$files" ]] && ''${EDITOR:-vim} "''${files[@]}"
-      }
+        set -o noclobber
+
+        # disable flow control (so that fzf-git.sh's ^g^s can work)
+        stty -ixon
+
+        # These functions are called as follows, after using ea (using vip as an example):
+        # vip  # edits the first result from ea (roughly equivalent to vi `ea p 1`)
+        # vip <n> # edits the nth result from ea (vi `ea p <n>`)
+        # vip <n> foo # if the nth result from ea is a directory, edit foo in that directory (vi `ea p <n>`/foo)
+        # Will add +<line-number>, where the line number is available
+        function _vip () {
+          local cmd=(''${=1}) # zsh only, not portable; something like CMD=($(echo $1)) is more portable but is ugly
+          local idx=''${2:-1}
+          local base_path=$(ea p $idx)
+          local line=$(ea p $idx "{line}")
+          local ea_format="'{path}'"
+
+          if [ -z "$base_path" ]; then
+            echo "No file path found for index $2"
+            return 1
+          fi
 
-      # TODO is there a way to do this in shellAliases
-      alias ..="cd .."
-      alias -- -="cd -"
+          if [ $# -gt 2 -a ! -d "$base_path" ]; then
+            echo "$base_path is not a directory"
+            return 2
+          fi
 
-      ...() {
-        local declare dirs=()
-        get_parent_dirs() {
-          if [[ -d "''${1}" ]]; then dirs+=("$1"); else return; fi
-          if [[ "''${1}" == '/' ]]; then
-            for _dir in "''${dirs[@]}"; do echo $_dir; done
-          else
-            get_parent_dirs $(dirname "$1")
+          if [ $# -lt 3 -a $line -ne 1 ]; then
+            ea_format+=" +{line}"
           fi
+
+          eval $(ea p $idx "$cmd ''${ea_format}$3")
         }
-        local DIR=$(get_parent_dirs $(realpath "$PWD/..") | fzf-tmux)
-        cd "$DIR"
-      }
 
-      # From omz
-      function mkcd () {
-        mkdir -p $@ && cd ''${@:$#}
-      }
+        function vip () {
+          _vip $EDITOR ''${@}
+        }
+        function bp () {
+          _vip bat ''${@}
+        }
+        function bpp () {
+          # this will be split into an array in _vip
+          CMD="bat -p"
+          _vip $CMD ''${@}
+        }
 
-      tre () { command tre "$@" -e && source "/tmp/tre_aliases_$USER" 2>/dev/null; }
+        function ecd () {
+          cd $(ea p ''${1:-1})
+        }
 
-      function gcd () {
-        if [ $# -eq 0 ] ; then
-          echo "Number of days must be specified" >&2
-          return 1
-        fi
-        if ! [[ $1 =~ '^[0-9]+$' ]] ; then
-          echo "Number of days must be a number" >&2
-          return 2
-        fi
+        function generate () { gopass generate -s -p $1 $((RANDOM % 14 + 45)) }
+        function fcd { cd $(fd -L --max-depth=''${1:-4} --type=d 2>/dev/null | fzf-tmux) }
 
-        if [ $1 -eq 0 ] ; then
-         GC_ARGS=(-d)
-        else
-          GC_ARGS=(--delete-older-than ''${1}d)
-        fi
+        fif() {
+          if [ ! "$#" -gt 0  ]; then
+            echo "usage: fif <SEARCH_TERM>"
+            return 1;
+          fi
+          rg --files-with-matches --no-messages "$1" | fzf $FZF_PREVIEW_WINDOW --preview "rg --ignore-case --pretty --context 10 '$1' {}"
+        }
 
-        DOAS=$(command -v doas)
+        fe() {
+          IFS=$'\n' files=($(fzf-tmux --query="$1" --multi --select-1 --exit-0))
+          [[ -n "$files" ]] && ''${EDITOR:-vim} "''${files[@]}"
+        }
 
-        # Run as the current user (as well as root) to clean up hm generations
-        nix-collect-garbage ''${GC_ARGS[@]}
-        if [ -n $DOAS ] ; then
-          $DOAS nix-collect-garbage ''${GC_ARGS[@]}
-        fi
+        # TODO is there a way to do this in shellAliases
+        alias ..="cd .."
+        alias ...="cd ../.."
+        alias -- -="cd -"
+
+        .,() {
+          local declare dirs=()
+          get_parent_dirs() {
+            if [[ -d "''${1}" ]]; then dirs+=("$1"); else return; fi
+            if [[ "''${1}" == '/' ]]; then
+              for _dir in "''${dirs[@]}"; do echo $_dir; done
+            else
+              get_parent_dirs $(dirname "$1")
+            fi
+          }
+          local DIR=$(get_parent_dirs $(realpath "$PWD/..") | fzf-tmux)
+          cd "$DIR"
+        }
 
-        df -h
-        date
-      }
+        # From omz
+        function mkcd () {
+          mkdir -p $@ && cd ''${@:$#}
+        }
 
-      function checkout-pr () {
-        git fetch ''${2:-upstream} pull/''${1}/head:pr-''${1}
-        git switch pr-''${1}
-      }
+        tre () { command tre "$@" -e && source "/tmp/tre_aliases_$USER" 2>/dev/null; }
 
-      [[ ! -f ~/.zsh.local ]] || source ~/.zsh.local
+        function gcd () {
+          if [ $# -eq 0 ] ; then
+            echo "Number of days must be specified" >&2
+            return 1
+          fi
+          if ! [[ $1 =~ '^[0-9]+$' ]] ; then
+            echo "Number of days must be a number" >&2
+            return 2
+          fi
 
-      [[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh
-    '';
+          if [ $1 -eq 0 ] ; then
+           GC_ARGS=(-d)
+          else
+            GC_ARGS=(--delete-older-than ''${1}d)
+          fi
 
-   plugins = with pkgs; [
+          DOAS=$(command -v doas)
+
+          # Run as the current user (as well as root) to clean up hm generations
+          nix-collect-garbage ''${GC_ARGS[@]}
+          if [ -n $DOAS ] ; then
+            $DOAS nix-collect-garbage ''${GC_ARGS[@]}
+          fi
+
+          df -h
+          date
+        }
+
+        function checkout-pr () {
+          git fetch ''${2:-upstream} pull/''${1}/head:pr-''${1}
+          git switch pr-''${1}
+        }
+
+        [[ ! -f ~/.zsh.local ]] || source ~/.zsh.local
+
+        [[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh
+      ''
+    ];
+
+    plugins = with pkgs; [
       {
         name = "zsh-autopair";
         src = zsh-autopair;
@@ -266,11 +362,6 @@ in
         file = "share/zsh-powerlevel10k/powerlevel10k.zsh-theme";
       }
       {
-        name = "zsh-history-substring-search";
-        src = zsh-history-substring-search;
-        file = "share/zsh-history-substring-search/zsh-history-substring-search.zsh";
-      }
-      {
         name = "zsh-forgit";
         src = zsh-forgit;
         file = "share/zsh/zsh-forgit/forgit.plugin.zsh";
@@ -283,7 +374,7 @@ in
       {
         name = "fzf-git.sh";
         src = fzf-git-sh;
-        file = "share/zsh/fzf-git-sh/fzf-git.sh";
+        file = "share/fzf-git-sh/fzf-git.sh";
       }
       {
         name = "per-directory-history";
@@ -295,19 +386,6 @@ in
         };
         file = "per-directory-history.zsh";
       }
-    ]
-    ++ optionals stdenv.isDarwin [
-      {
-        name = "zsh-nvm";
-        src = fetchFromGitHub {
-          owner = "lukechilds";
-          repo = "zsh-nvm";
-          rev = "23067bd9bb6eb6f4737a3ea90cb0cb5e85f61ba2";
-          sha256 = "Zwdi7bezMFKaIKYwsSftu3mJSFvadEWmY2hYnU1Kpu4=";
-        };
-        file = "zsh-nvm.plugin.zsh";
-      }
     ];
   };
 }
-
diff --git a/nix-conf/home/otm.nix b/nix-conf/home/otm.nix
index cf9a219..2055780 100644
--- a/nix-conf/home/otm.nix
+++ b/nix-conf/home/otm.nix
@@ -1,12 +1,123 @@
-{ config, lib, pkgs, ... }:
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
 let
-  email = builtins.readFile "${config.home.homeDirectory}/email.txt";
-  otmEmail = builtins.readFile "${config.home.homeDirectory}/otm_email.txt";
+  zscaler-cert = ''
+    -----BEGIN CERTIFICATE-----
+    MIIE0zCCA7ugAwIBAgIJANu+mC2Jt3uTMA0GCSqGSIb3DQEBCwUAMIGhMQswCQYD
+    VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTERMA8GA1UEBxMIU2FuIEpvc2Ux
+    FTATBgNVBAoTDFpzY2FsZXIgSW5jLjEVMBMGA1UECxMMWnNjYWxlciBJbmMuMRgw
+    FgYDVQQDEw9ac2NhbGVyIFJvb3QgQ0ExIjAgBgkqhkiG9w0BCQEWE3N1cHBvcnRA
+    enNjYWxlci5jb20wHhcNMTQxMjE5MDAyNzU1WhcNNDIwNTA2MDAyNzU1WjCBoTEL
+    MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExETAPBgNVBAcTCFNhbiBK
+    b3NlMRUwEwYDVQQKEwxac2NhbGVyIEluYy4xFTATBgNVBAsTDFpzY2FsZXIgSW5j
+    LjEYMBYGA1UEAxMPWnNjYWxlciBSb290IENBMSIwIAYJKoZIhvcNAQkBFhNzdXBw
+    b3J0QHpzY2FsZXIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+    qT7STSxZRTgEFFf6doHajSc1vk5jmzmM6BWuOo044EsaTc9eVEV/HjH/1DWzZtcr
+    fTj+ni205apMTlKBW3UYR+lyLHQ9FoZiDXYXK8poKSV5+Tm0Vls/5Kb8mkhVVqv7
+    LgYEmvEY7HPY+i1nEGZCa46ZXCOohJ0mBEtB9JVlpDIO+nN0hUMAYYdZ1KZWCMNf
+    5J/aTZiShsorN2A38iSOhdd+mcRM4iNL3gsLu99XhKnRqKoHeH83lVdfu1XBeoQz
+    z5V6gA3kbRvhDwoIlTBeMa5l4yRdJAfdpkbFzqiwSgNdhbxTHnYYorDzKfr2rEFM
+    dsMU0DHdeAZf711+1CunuQIDAQABo4IBCjCCAQYwHQYDVR0OBBYEFLm33UrNww4M
+    hp1d3+wcBGnFTpjfMIHWBgNVHSMEgc4wgcuAFLm33UrNww4Mhp1d3+wcBGnFTpjf
+    oYGnpIGkMIGhMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTERMA8G
+    A1UEBxMIU2FuIEpvc2UxFTATBgNVBAoTDFpzY2FsZXIgSW5jLjEVMBMGA1UECxMM
+    WnNjYWxlciBJbmMuMRgwFgYDVQQDEw9ac2NhbGVyIFJvb3QgQ0ExIjAgBgkqhkiG
+    9w0BCQEWE3N1cHBvcnRAenNjYWxlci5jb22CCQDbvpgtibd7kzAMBgNVHRMEBTAD
+    AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAw0NdJh8w3NsJu4KHuVZUrmZgIohnTm0j+
+    RTmYQ9IKA/pvxAcA6K1i/LO+Bt+tCX+C0yxqB8qzuo+4vAzoY5JEBhyhBhf1uK+P
+    /WVWFZN/+hTgpSbZgzUEnWQG2gOVd24msex+0Sr7hyr9vn6OueH+jj+vCMiAm5+u
+    kd7lLvJsBu3AO3jGWVLyPkS3i6Gf+rwAp1OsRrv3WnbkYcFf9xjuaf4z0hRCrLN2
+    xFNjavxrHmsH8jPHVvgc1VD0Opja0l/BRVauTrUaoW6tE+wFG5rEcPGS80jjHK4S
+    pB5iDj2mUZH1T8lzYtuZy0ZPirxmtsk3135+CKNa2OCAhhFjE0xd
+    -----END CERTIFICATE-----
+  '';
+
+  internal-cert = ''
+    -----BEGIN CERTIFICATE-----
+    MIIDpzCCAo+gAwIBAgIRAPimIVPUvFeeWdKoTVr/KaowDQYJKoZIhvcNAQELBQAw
+    bTELMAkGA1UEBhMCR0IxGDAWBgNVBAoMD29udGhlbWFya2V0LmNvbTELMAkGA1UE
+    CwwCSVQxDzANBgNVBAgMBkxvbmRvbjEVMBMGA1UEAwwMaW50ZXJuYWwub3RtMQ8w
+    DQYDVQQHDAZMb25kb24wHhcNMjQwNTIxMTIyNTUzWhcNMzQwNTIxMTMyNTQ1WjBt
+    MQswCQYDVQQGEwJHQjEYMBYGA1UECgwPb250aGVtYXJrZXQuY29tMQswCQYDVQQL
+    DAJJVDEPMA0GA1UECAwGTG9uZG9uMRUwEwYDVQQDDAxpbnRlcm5hbC5vdG0xDzAN
+    BgNVBAcMBkxvbmRvbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMb
+    +jxbONNYRWXFYLHOVsKkTBVY/SkPo9VYv63Xzp8YF5CC3GMNKVvtdfwDLxdB4yDE
+    71kOngybxIRTeX+UdZCfhmcgpmu6trT8RB27SzpOVkrVz+wCzYx/3qE4xSQok474
+    komOtHkuwoL1MMqTH1WOPqUL3RaNkK3YSq2M8JPfjG9w6eboT0i+c7GG9OEk9BwW
+    35M+tdiI9fjAK95yMU9DjVI7PqTfqBVT5pUoyzAKhTikZlC6O8X8U98NJojwhaT4
+    RJcbbd1bdNqcxdpshIiP1kWAE4CKp2+tMzzz9yqwgQ1igbsm2j37TxI74JoEV9+k
+    95tFwgXLT7Bih3MFuI0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
+    FgQUuA22mh+yV3W8D3mpiouhO2Y/2c4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3
+    DQEBCwUAA4IBAQCJgkhzAWW0Rh4EWyAdVIoUnjHIr032Qu61cXiNqvvRS9GIF5gs
+    oynXjIIj2scBeNlkG3oSy0G3wWyFzng6ixwuol2fRhDsllvm2bgeaObdbccbLbWx
+    8OTobCqWTfZvEn8dYs7Qbx/9l4yBH6pYptnOmDt+Ze2hOVZyTuiVq91CEn+on9FG
+    2V6Bjuu8dNpz2CC8na7H4wsqUNRfBVSTSKgdeeiLj1zdueWgOtA1PNOZp5wi452U
+    mpb61I1k/Xfe6ECUn8QEh9oEB4MprNvlvLVmmnstcBmqU9SvONtmSrn8ekI2OO69
+    R7pRciveNTEVrJRPqOfL4fjfQbjtpKx6Gk5m
+    -----END CERTIFICATE-----
+  '';
+
+  internal-staging-cert = ''
+    -----BEGIN CERTIFICATE-----
+    MIIDpzCCAo+gAwIBAgIRANXYUsUWHHGL/LgpcIY3zlUwDQYJKoZIhvcNAQELBQAw
+    bTELMAkGA1UEBhMCR0IxGDAWBgNVBAoMD29udGhlbWFya2V0LmNvbTELMAkGA1UE
+    CwwCSVQxDzANBgNVBAgMBkxvbmRvbjEVMBMGA1UEAwwMaW50ZXJuYWwub3RtMQ8w
+    DQYDVQQHDAZMb25kb24wHhcNMjQwNTI0MDc1MDQ3WhcNMzQwNTI0MDg1MDQ0WjBt
+    MQswCQYDVQQGEwJHQjEYMBYGA1UECgwPb250aGVtYXJrZXQuY29tMQswCQYDVQQL
+    DAJJVDEPMA0GA1UECAwGTG9uZG9uMRUwEwYDVQQDDAxpbnRlcm5hbC5vdG0xDzAN
+    BgNVBAcMBkxvbmRvbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAImM
+    FljdqdQ4M0tRYAiRU6WPWiIKFHwZSTsdLohNXikjnSar1xnN1LQLLH1mzPpztnpA
+    eCnADo9Dc1Nsm+dt6WREL6n57oQSG3d5eM+br6MIm2qWIXQhtJtpKFcbSuOlaB4z
+    uWNmk3R09+3GaGNhpYBmEbh3Nvc5it0/p6EUOVWigF3ghr0NO2JSOhPtGhNSPyyS
+    9Q7DZSwdaGeix9yKWKDh3X4ikZvjm4xqkogFFdyFHKA1qmsaCsT+NP1iH6HNb6pB
+    xOb1ZyzR1EcFKAP+8uOgoI3bF0iJswNtkSc2kqf0vNQ+K/qoNL8OH7VyKCfeQqNL
+    2b8lV+FwHIBD2ZwhsuUCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
+    FgQUxbubUk2Kf9k68OUOSwrdgGuAtJYwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3
+    DQEBCwUAA4IBAQBNRKzWrQQBh1RAU023u0pgNjfk0OV5mTlb2024jCLmqG7U/eSC
+    X8LoO/Gm3yVuj5RsoH8H5ftNU1j71c+dHg7+UVvQZRCOfgVIMnPCxuMvcaljRnLG
+    qHaRCra5G3IOLrBtJDg9DgKg6/gUbg5DvZwiO5J21yzJWxy6wmoRBmy43DZBa2RV
+    /rO3WOM0uuMp4DHqBIYx55d+4mdtshikZoys3TCiFH3C11xrUwkIdNEqvMcjl/Pr
+    5WrzfbTpDzvo/GCkPhA0thVgUBx4LXB8HizVmDZgGbSuh7ic7LHyh1ahE0fqGX9C
+    ZvHif3XTcAZlNkilVHvF3pM4EIosFEc6dHFy
+    -----END CERTIFICATE-----
+  '';
+
+  aws-cert =
+    (builtins.readFile "${pkgs.awscli2}/lib/python${pkgs.awscli2.python.pythonVersion}/site-packages/awscli/botocore/cacert.pem")
+    + zscaler-cert;
+
+  full-cert =
+    (builtins.readFile /etc/ssl/cert.pem) + aws-cert + internal-cert + internal-staging-cert;
+
+  zscaler-cert-file = pkgs.writeText "zscaler-cert.pem" zscaler-cert;
+  aws-cert-file = pkgs.writeText "aws-cert.pem" aws-cert;
+  full-cert-file = pkgs.writeText "full-cert.pem" full-cert;
+
+  zscaler-jdk = pkgs.jdk.overrideAttrs (old: {
+    # passthru.home must be set to ensure JAVA_HOME is set correctly
+    # See https://github.com/nix-community/home-manager/blob/086f619dd991a4d355c07837448244029fc2d9ab/modules/programs/java.nix#L39-L41
+    # and https://github.com/NixOS/nixpkgs/blob/4877ea239f4d02410c3516101faf35a81af0c30e/pkgs/development/compilers/openjdk/jre.nix#L32
+    passthru.home = "${zscaler-jdk}"; # make sure JAVA_HOME is set
+    installPhase =
+      # This is probably equivalent to
+      # $out/bin/keytool -import -noprompt -trustcacerts -alias zscalerrootca -keystore $out/lib/security/cacerts <<< "${zscaler-cert}"
+      # but follow the zscaler instructions just in case
+      old.installPhase
+      + ''
+        ${pkgs.openssl}/bin/openssl x509 -inform pem -outform der <<< "${zscaler-cert}" | $out/bin/keytool -import -noprompt -trustcacerts -alias zscalerrootca -keystore $out/lib/security/cacerts
+      '';
+  });
+
+  zscaler-lein = pkgs.leiningen.override { jdk = zscaler-jdk; };
+  zscaler-clojure = pkgs.clojure.override { jdk = zscaler-jdk; };
+
 in
 {
-  imports = [ 
-    ./includes/darwin.nix
-  ];
+  imports = [ ./includes/darwin.nix ];
 
   # Let Home Manager install and manage itself.
   programs.home-manager.enable = true;
@@ -16,15 +127,84 @@ in
   home.username = "dmorgan";
   home.homeDirectory = "/Users/dmorgan";
 
+  home.sessionPath = [ "$HOME/.costar/auth2aws" ];
+
+  home.sessionVariables = {
+    AWS_DEFAULT_REGION = "eu-west-1";
+    AWS_PROFILE = "aws_otm_dev_developers";
+    AM_PROFILE = "staging";
+    AWS_CA_BUNDLE = "${aws-cert-file}";
+    CURL_CA_BUNDLE = "${full-cert-file}";
+    NIX_SSL_CERT_FILE = "${full-cert-file}";
+    NODE_EXTRA_CA_CERTS = "${zscaler-cert-file}";
+    JVM_OPTS = "-Dcom.amazonaws.sdk.disableCertChecking";
+    LEIN_JVM_OPTS = "-Dcom.amazonaws.sdk.disableCertChecking";
+  };
+
+  home.shellAliases = {
+    notify_success = ''( osascript -e 'display notification "The command finished" with title "Success"' && afplay /System/Library/Sounds/Ping.aiff && say done  )'';
+    notify_failure = ''( osascript -e 'display notification "The command failed" with title "Failure"' && afplay /System/Library/Sounds/Sosumi.aiff && say failed  )'';
+    notify = "notify_success || notify_failure";
+    ltn = "lein test && notify";
+    yb = "aws codeartifact login --tool npm --repository otm-js --domain otm --domain-owner 103567893073 --region eu-west-1 --profile aws_otm_dev_developers && yarn && yarn build && notify";
+    auth = "auth2aws login -r aws_otm_dev_developers,aws_otm_prd_developers && osascript -e 'tell app \"iTerm\" to activate'";
+  };
+
+  home.packages = with pkgs; [
+    zscaler-clojure
+    zscaler-lein
+  ];
+
+  home.file = {
+    "certs/zscaler-cert.pem".source = zscaler-cert-file;
+    "certs/aws-cert.pem".source = aws-cert-file;
+    "certs/full-cert.pem".source = full-cert-file;
+    "certs/internal-ca.pem".text = internal-cert;
+    "certs/staging-internal-ca.pem".text = internal-staging-cert;
+    ".docker/certs.d/zcaler-cert.pem".source = zscaler-cert-file;
+    ".wgetrc".text = "ca_certificate=${full-cert-file}";
+  };
+
+  sops.secrets = {
+    "git_email_config/otm" = { };
+    "ssh_config/otm" = { };
+  };
+
+  programs.java = {
+    enable = true;
+    package = zscaler-jdk;
+  };
+
   programs.git = {
     signing.signByDefault = lib.mkForce false;
-    userEmail = lib.mkForce otmEmail;
-    includes = [
-      { contents = { commit.gpgSign = true; user.email = email; }; condition = "gitdir:~/src/personal/"; }
-      { contents = { commit.gpgSign = true; user.email = email; }; condition = "gitdir:~/dotfiles/"; }
+    includes = lib.mkForce [
+      { path = config.sops.secrets."git_email_config/otm".path; }
+      {
+        path = config.sops.secrets."git_email_config/default".path;
+        condition = "gitdir:~/src/personal/";
+      }
+      {
+        path = config.sops.secrets."git_email_config/default".path;
+        condition = "gitdir:~/dotfiles/";
+      }
+      {
+        contents = {
+          commit.gpgSign = true;
+          tag.gpgSign = true;
+        };
+        condition = "gitdir:~/src/personal/";
+      }
+      {
+        contents = {
+          commit.gpgSign = true;
+          tag.gpgSign = true;
+        };
+        condition = "gitdir:~/dotfiles/";
+      }
     ];
     extraConfig = {
       github.user = "david-morgan-otm";
+      http.sslcainfo = "${full-cert-file}";
     };
     ignores = [
       ".envrc"
@@ -37,21 +217,24 @@ in
       "resources/next/package-lock.json"
     ];
   };
-  programs.ssh.matchBlocks = {
-    "github.com" = lib.mkForce {
-      hostname = "github.com";
-      user = "git";
-      identityFile = "~/.ssh/id_rsa";
-      identitiesOnly = true;
-    };
-    "github.com-personal" = {
-      hostname = "github.com";
-      user = "git";
-      identityFile = "~/.ssh/id_ed25519";
-      identitiesOnly = true;
+  programs.ssh = {
+    includes = [ config.sops.secrets."ssh_config/otm".path ];
+    matchBlocks = {
+      "github.com" = lib.mkForce {
+        hostname = "github.com";
+        user = "git";
+        identityFile = "~/.ssh/id_rsa";
+        identitiesOnly = true;
+      };
+      "github.com-personal" = {
+        hostname = "github.com";
+        user = "git";
+        identityFile = "~/.ssh/id_ed25519";
+        identitiesOnly = true;
+      };
     };
   };
-  
+
   # This value determines the Home Manager release that your
   # configuration is compatible with. This helps avoid breakage
   # when a new Home Manager release introduces backwards
diff --git a/nix-conf/machines/djmuk1/configuration.nix b/nix-conf/machines/djmuk1/configuration.nix
index 8c7187e..7405e9a 100644
--- a/nix-conf/machines/djmuk1/configuration.nix
+++ b/nix-conf/machines/djmuk1/configuration.nix
@@ -1,7 +1,6 @@
-{ config, pkgs, ... }: {
-  imports = [
-    ./hardware-configuration.nix
-  ];
+{ config, pkgs, ... }:
+{
+  imports = [ ./hardware-configuration.nix ];
 
   boot.tmp.cleanOnBoot = true;
 
@@ -30,45 +29,60 @@
   services.sshguard.enable = true;
   services.oidentd.enable = true;
 
-  services.locate = {
-    enable = true;
-    package = pkgs.plocate;
-    localuser = null;
-  };
+  services.locate.enable = true;
 
-  users.users.djm =
-   { isNormalUser = true;
-     home = "/home/djm";
-     description = "David Morgan";
-     extraGroups = [ "wheel" "plocate" ];
-     shell = pkgs.zsh;
-     openssh.authorizedKeys.keys = [
-      "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCurCpxZCHtByB5wXzsjTXwMyDSB4+B8rq5XY6EGss58NwD8jc5cII4i+QUbCOGTiAggSZUSC9YIP24hjpOeNT/IYs5m7Qn1B9MtBAiUSrIYew8eDwnMLlPzN+k2x9zCrJeCHIvGJaFHPXTh1Lf5Jt2fPVGW9lksE/XUVOe6ht4N/b+nqqszXFhc8Ug6le2bC1YeTCVEf8pjlh/I7DkDBl6IB8uEXc3X2vxxbV0Z4vlBrFkkAywcD3j5VlS/QYfBr4BICNmq/sO3fMkbMbtAPwuFxeL4+h6426AARQZiSS0qVEc8OoFRBVx3GEH5fqVAWfB1geyLzei22HbjUcT9+xN davidmo@gendros" 
+  users.users.djm = {
+    isNormalUser = true;
+    home = "/home/djm";
+    description = "David Morgan";
+    extraGroups = [
+      "wheel"
+      "plocate"
+    ];
+    shell = pkgs.zsh;
+    openssh.authorizedKeys.keys = [
+      "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCurCpxZCHtByB5wXzsjTXwMyDSB4+B8rq5XY6EGss58NwD8jc5cII4i+QUbCOGTiAggSZUSC9YIP24hjpOeNT/IYs5m7Qn1B9MtBAiUSrIYew8eDwnMLlPzN+k2x9zCrJeCHIvGJaFHPXTh1Lf5Jt2fPVGW9lksE/XUVOe6ht4N/b+nqqszXFhc8Ug6le2bC1YeTCVEf8pjlh/I7DkDBl6IB8uEXc3X2vxxbV0Z4vlBrFkkAywcD3j5VlS/QYfBr4BICNmq/sO3fMkbMbtAPwuFxeL4+h6426AARQZiSS0qVEc8OoFRBVx3GEH5fqVAWfB1geyLzei22HbjUcT9+xN davidmo@gendros"
       "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK9UDTaVnUOU/JknrNdihlhhGOk53LmHq9I1ASri3aga djm@gaius"
-     ];
-   };
+    ];
+  };
 
-   security.sudo.extraConfig = ''
-     djm ALL=(ALL) NOPASSWD: ALL
-   '';
-   security.doas = {
-     enable = true;
-     extraRules = [ { users = [ "djm" ]; noPass = true; keepEnv = true; } ];
-   };
+  security.sudo.extraConfig = ''
+    djm ALL=(ALL) NOPASSWD: ALL
+  '';
+  security.doas = {
+    enable = true;
+    extraRules = [
+      {
+        users = [ "djm" ];
+        noPass = true;
+        keepEnv = true;
+      }
+    ];
+  };
 
-   programs.zsh.enable = true;
+  programs.zsh.enable = true;
 
-   programs.vim.defaultEditor = true;
+  programs.vim = {
+    enable = true;
+    defaultEditor = true;
+  };
 
-   environment.systemPackages = with pkgs; [
-     #procmail
-     vim
-     wget
-   ];
+  environment.systemPackages = with pkgs; [
+    #procmail
+    wget
+  ];
+
+  nix.settings.trusted-users = [
+    "root"
+    "djm"
+  ];
 
-   nix.settings.trusted-users = [ "root" "djm" ];
+  i18n.defaultLocale = "en_GB.UTF-8";
 
-   i18n.defaultLocale = "en_GB.UTF-8";
+  swapDevices = [ {
+    device = "/var/lib/swapfile";
+    size = 2*1024;
+  } ];
 
-   system.stateVersion = "22.05";
+  system.stateVersion = "23.11";
 }
diff --git a/nix-conf/machines/djmuk1/hardware-configuration.nix b/nix-conf/machines/djmuk1/hardware-configuration.nix
index 4d5ccf9..894b817 100644
--- a/nix-conf/machines/djmuk1/hardware-configuration.nix
+++ b/nix-conf/machines/djmuk1/hardware-configuration.nix
@@ -6,7 +6,20 @@
     efiInstallAsRemovable = true;
     device = "nodev";
   };
-  fileSystems."/boot" = { device = "/dev/disk/by-uuid/C149-C30B"; fsType = "vfat"; };
+  fileSystems."/boot" = {
+    device = "/dev/disk/by-uuid/0D60-CDE2";
+    fsType = "vfat";
+  };
+  boot.initrd.availableKernelModules = [
+    "ata_piix"
+    "uhci_hcd"
+    "xen_blkfront"
+    "vmw_pvscsi"
+  ];
   boot.initrd.kernelModules = [ "nvme" ];
-  fileSystems."/" = { device = "/dev/sda1"; fsType = "ext4"; };
+  fileSystems."/" = {
+    device = "/dev/sda1";
+    fsType = "ext4";
+  };
+
 }
diff --git a/nix-conf/machines/djmuk2/configuration.nix b/nix-conf/machines/djmuk2/configuration.nix
index 8d679ac..0b31a01 100644
--- a/nix-conf/machines/djmuk2/configuration.nix
+++ b/nix-conf/machines/djmuk2/configuration.nix
@@ -1,7 +1,6 @@
-{ config, pkgs, ... }: {
-  imports = [
-    ./hardware-configuration.nix
-  ];
+{ config, pkgs, ... }:
+{
+  imports = [ ./hardware-configuration.nix ];
 
   boot.tmp.cleanOnBoot = true;
   zramSwap.enable = true;
@@ -31,49 +30,65 @@
   services.sshguard.enable = true;
   services.oidentd.enable = true;
 
-  services.locate = {
-    enable = true;
-    package = pkgs.plocate;
-    localuser = null;
-  };
+  services.locate.enable = true;
 
-  users.users.djm =
-   { isNormalUser = true;
-     home = "/home/djm";
-     description = "David Morgan";
-     extraGroups = [ "wheel" "plocate" ];
-     shell = pkgs.zsh;
-     openssh.authorizedKeys.keys = [
+  # Emulate nix-sops. Technically an anti-pattern, but this isn't a real secret, and this has to be embedded here, as we cannot set a file path to read it from.
+  # Populate/update with:
+  # SOPS_AGE_KEY=$(doas ssh-to-age -private-key -i /etc/ssh/ssh_host_ed25519_key) sops -d --extract '["openiscsi_name"]' machines/djmuk2/secrets.yaml | doas tee /root/.config/secrets/openiscsi_name
+  services.openiscsi.enable = true;
+  services.openiscsi.name = builtins.readFile "/root/.config/secrets/openiscsi_name";
+  #services.openiscsi.enableAutoLoginOut = true;
+
+  users.users.djm = {
+    isNormalUser = true;
+    home = "/home/djm";
+    description = "David Morgan";
+    extraGroups = [
+      "wheel"
+      "plocate"
+    ];
+    shell = pkgs.zsh;
+    openssh.authorizedKeys.keys = [
       "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCurCpxZCHtByB5wXzsjTXwMyDSB4+B8rq5XY6EGss58NwD8jc5cII4i+QUbCOGTiAggSZUSC9YIP24hjpOeNT/IYs5m7Qn1B9MtBAiUSrIYew8eDwnMLlPzN+k2x9zCrJeCHIvGJaFHPXTh1Lf5Jt2fPVGW9lksE/XUVOe6ht4N/b+nqqszXFhc8Ug6le2bC1YeTCVEf8pjlh/I7DkDBl6IB8uEXc3X2vxxbV0Z4vlBrFkkAywcD3j5VlS/QYfBr4BICNmq/sO3fMkbMbtAPwuFxeL4+h6426AARQZiSS0qVEc8OoFRBVx3GEH5fqVAWfB1geyLzei22HbjUcT9+xN davidmo@gendros"
       "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK9UDTaVnUOU/JknrNdihlhhGOk53LmHq9I1ASri3aga djm@gaius"
-     ];
-   };
+    ];
+  };
 
-   security.sudo.extraConfig = ''
-     djm ALL=(ALL) NOPASSWD: ALL
-   '';
-   security.doas = {
-     enable = true;
-     extraRules = [ { users = [ "djm" ]; noPass = true; keepEnv = true; } ];
-   };
+  security.sudo.extraConfig = ''
+    djm ALL=(ALL) NOPASSWD: ALL
+  '';
+  security.doas = {
+    enable = true;
+    extraRules = [
+      {
+        users = [ "djm" ];
+        noPass = true;
+        keepEnv = true;
+      }
+    ];
+  };
 
-   programs.zsh.enable = true;
+  programs.zsh.enable = true;
 
-   programs.vim.defaultEditor = true;
+  programs.vim = {
+    enable = true;
+    defaultEditor = true;
+  };
 
-   environment.systemPackages = with pkgs; [
-     #procmail
-     git
-     vim
-     wget
-   ];
+  environment.systemPackages = with pkgs; [
+    #procmail
+    git
+    wget
+  ];
 
-   nix.settings.trusted-users = [ "root" "djm" ];
-   nix.optimise.automatic = true;
-   nix.optimise.dates = [ "03:00" ];
+  nix.settings.trusted-users = [
+    "root"
+    "djm"
+  ];
+  nix.optimise.automatic = true;
+  nix.optimise.dates = [ "03:00" ];
 
-   i18n.defaultLocale = "en_GB.UTF-8";
+  i18n.defaultLocale = "en_GB.UTF-8";
 
-   system.stateVersion = "22.05";
+  system.stateVersion = "22.05";
 }
-
diff --git a/nix-conf/machines/djmuk2/hardware-configuration.nix b/nix-conf/machines/djmuk2/hardware-configuration.nix
index e27e899..5c421f9 100644
--- a/nix-conf/machines/djmuk2/hardware-configuration.nix
+++ b/nix-conf/machines/djmuk2/hardware-configuration.nix
@@ -6,9 +6,19 @@
     efiInstallAsRemovable = true;
     device = "nodev";
   };
-  fileSystems."/boot" = { device = "/dev/disk/by-uuid/4875-017B"; fsType = "vfat"; };
-  boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "xen_blkfront" ];
+  fileSystems."/boot" = {
+    device = "/dev/disk/by-uuid/4875-017B";
+    fsType = "vfat";
+  };
+  boot.initrd.availableKernelModules = [
+    "ata_piix"
+    "uhci_hcd"
+    "xen_blkfront"
+  ];
   boot.initrd.kernelModules = [ "nvme" ];
-  fileSystems."/" = { device = "/dev/mapper/ocivolume-root"; fsType = "xfs"; };
-  
+  fileSystems."/" = {
+    device = "/dev/mapper/ocivolume-root";
+    fsType = "xfs";
+  };
+
 }
diff --git a/nix-conf/machines/djmuk2/secrets.yaml b/nix-conf/machines/djmuk2/secrets.yaml
new file mode 100644
index 0000000..3216fd3
--- /dev/null
+++ b/nix-conf/machines/djmuk2/secrets.yaml
@@ -0,0 +1,21 @@
+openiscsi_name: ENC[AES256_GCM,data:RZtrRGCnYgiAwq1bVnyK8fiYCxCKbtNs5diV3nUmNWAhU8CYRxau6SIAhB9t3f7p1fKgVC1V0fxV0nko6tdK,iv:M7qSnfBdxdTaCIb2/QZfrTUOZGX19IJY69IncTEk68w=,tag:eIo0fSKZTMEakGHh2zi5oQ==,type:str]
+sops:
+    kms: []
+    gcp_kms: []
+    azure_kv: []
+    hc_vault: []
+    age:
+        - recipient: age17j56andser5ddtlfunm35m25xueua4djh9glxlscfcet8865yv9s5aqvla
+          enc: |
+            -----BEGIN AGE ENCRYPTED FILE-----
+            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5UFgxckhMa1RWL3hGQkZw
+            M25XN1JkT2dnQk9iVXdyaFJsa3hMM0pVam04CmZSWFdJbnl4RzFpUUpYK2JmRXFO
+            L3ZZbXZ3aHA4NjBuRCtnYlpsNG94ZVkKLS0tIFNIUTVjOUxhS00zZFlyODVuQ1lB
+            bC9sLzdObkpFNTJRcmk3N3Y0TG1xakkKvFbr1YlLFS7c0BfK1MYczTXgjwcaNjxH
+            tHCQWzVyx1VzLID1TCQDGXWApkaaQYxa2d/afTTRxk98w6xJIvLj2g==
+            -----END AGE ENCRYPTED FILE-----
+    lastmodified: "2024-09-03T15:08:24Z"
+    mac: ENC[AES256_GCM,data:CtMDdk/tY52HLDuTHIUWF8qV3wdyykWnEKJk0bGMT+feWd/+PAzJRzCOVDuL6AxT1FmtZGx2lFZz6A9vzFbGsn1fawXVo40q+6TWpdcv80tRaicfyh1FTppWGNOJn/bh7DILuX41HRTEP2ngpMHwSr3cbCUfhxrV+r7giguj1do=,iv:uGe15h57SyQr8yi19sqDRPwtC/4WmBAwqvsHI5g5pAc=,tag:2Lv+QZf0CsgusJMay9MyQQ==,type:str]
+    pgp: []
+    unencrypted_suffix: _unencrypted
+    version: 3.8.1
diff --git a/nix-conf/machines/edrahil/configuration.nix b/nix-conf/machines/edrahil/configuration.nix
index 2ef4c9d..a1bc8e3 100644
--- a/nix-conf/machines/edrahil/configuration.nix
+++ b/nix-conf/machines/edrahil/configuration.nix
@@ -1,7 +1,9 @@
-{ config, pkgs,... }: {
+{ config, pkgs, ... }:
+{
   imports = [
     ./hardware-configuration.nix
     ./network-configuration.nix
+    <sops-nix/modules/sops>
   ];
 
   boot.tmp.cleanOnBoot = true;
@@ -10,7 +12,20 @@
   networking.hostName = "edrahil";
   networking.firewall = {
     enable = true;
-    allowedTCPPorts = [ 113 2222 ];
+    allowedTCPPorts = [
+      113
+      2222
+    ];
+  };
+
+  sops = {
+    defaultSopsFile = builtins.path {
+      path = /etc/nixos/secrets.yaml;
+      name = "edrahil-secrets.yaml";
+    };
+    secrets.restic_password = {
+      owner = config.users.users.djm.name;
+    };
   };
 
   services.openssh = {
@@ -34,50 +49,158 @@
   services.sshguard.enable = true;
   services.oidentd.enable = true;
 
-  services.locate = {
-    enable = true;
-    package = pkgs.plocate;
-    localuser = null;
+  services.locate.enable = true;
+
+  services.restic = {
+    backups = {
+      hb = {
+        paths = [ "${config.users.users.djm.home}" ];
+        repository = "sftp:djm@hb-backup:/home/djm/backup/edrahil";
+        initialize = true;
+        user = "djm";
+        environmentFile = "/etc/restic-environment";
+        passwordFile = config.sops.secrets.restic_password.path;
+        timerConfig = {
+          OnCalendar = "02:25";
+          RandomizedDelaySec = "20min";
+        };
+        exclude = [
+          "irclogs"
+          ".cache"
+          ".config"
+          ".directory_history"
+          ".local"
+          "nixpkgs"
+        ];
+        extraBackupArgs = [
+          "--compression=max"
+        ];
+        pruneOpts = [
+          "--keep-daily 5"
+          "--keep-weekly 2"
+          "--keep-monthly 3"
+        ];
+      };
+      bs = {
+        paths = [ "${config.users.users.djm.home}" ];
+        repository = "sftp:djm@bs-backup:/home/djm/backup/edrahil";
+        initialize = true;
+        user = "djm";
+        environmentFile = "/etc/restic-environment";
+        passwordFile = config.sops.secrets.restic_password.path;
+        timerConfig = {
+          OnCalendar = "03:15";
+          RandomizedDelaySec = "20min";
+        };
+        exclude = [
+          "irclogs"
+          ".cache"
+          ".config"
+          ".directory_history"
+          ".local"
+          "nixpkgs"
+        ];
+        extraBackupArgs = [
+          "--compression=max"
+        ];
+        pruneOpts = [
+          "--keep-daily 5"
+          "--keep-weekly 2"
+          "--keep-monthly 3"
+        ];
+      };
+      tt = {
+        paths = [ "${config.users.users.djm.home}" ];
+        repository = "sftp:djm@tt-backup:/home/djm/backup/edrahil";
+        initialize = true;
+        user = "djm";
+        environmentFile = "/etc/restic-environment";
+        passwordFile = config.sops.secrets.restic_password.path;
+        timerConfig = {
+          OnCalendar = "04:05";
+          RandomizedDelaySec = "20min";
+        };
+        exclude = [
+          "irclogs"
+          ".cache"
+          ".config"
+          ".directory_history"
+          ".local"
+          "nixpkgs"
+        ];
+        extraBackupArgs = [
+          "--compression=max"
+        ];
+        pruneOpts = [
+          "--keep-daily 5"
+          "--keep-weekly 2"
+          "--keep-monthly 3"
+        ];
+      };
+    };
   };
 
   time.timeZone = "Europe/London";
 
-  users.users.djm =
-   { isNormalUser = true;
-     home = "/home/djm";
-     description = "David Morgan";
-     extraGroups = [ "wheel" "plocate" ];
-     shell = pkgs.zsh;
-     openssh.authorizedKeys.keys = [
+  users.users.djm = {
+    isNormalUser = true;
+    home = "/home/djm";
+    description = "David Morgan";
+    extraGroups = [
+      "wheel"
+      "plocate"
+    ];
+    shell = pkgs.zsh;
+    openssh.authorizedKeys.keys = [
       "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCurCpxZCHtByB5wXzsjTXwMyDSB4+B8rq5XY6EGss58NwD8jc5cII4i+QUbCOGTiAggSZUSC9YIP24hjpOeNT/IYs5m7Qn1B9MtBAiUSrIYew8eDwnMLlPzN+k2x9zCrJeCHIvGJaFHPXTh1Lf5Jt2fPVGW9lksE/XUVOe6ht4N/b+nqqszXFhc8Ug6le2bC1YeTCVEf8pjlh/I7DkDBl6IB8uEXc3X2vxxbV0Z4vlBrFkkAywcD3j5VlS/QYfBr4BICNmq/sO3fMkbMbtAPwuFxeL4+h6426AARQZiSS0qVEc8OoFRBVx3GEH5fqVAWfB1geyLzei22HbjUcT9+xN davidmo@gendros"
       "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK9UDTaVnUOU/JknrNdihlhhGOk53LmHq9I1ASri3aga djm@gaius"
-     ];
-   };
+    ];
+  };
 
-   security.sudo.extraConfig = ''
-     djm ALL=(ALL) NOPASSWD: ALL
-   '';
-   security.doas = {
-     enable = true;
-     extraRules = [ { users = [ "djm" ]; noPass = true; keepEnv = true; } ];
-   };
+  security.sudo.extraConfig = ''
+    djm ALL=(ALL) NOPASSWD: ALL
+  '';
+  security.doas = {
+    enable = true;
+    extraRules = [
+      {
+        users = [ "djm" ];
+        noPass = true;
+        keepEnv = true;
+      }
+    ];
+  };
+
+  programs.zsh.enable = true;
 
-   programs.zsh.enable = true;
+  programs.vim = {
+    enable = true;
+    defaultEditor = true;
+  };
 
-   programs.vim.defaultEditor = true;
+  environment.etc = {
+    "restic-environment" = {
+      text = ''
+        RESTIC_COMPRESSION=max
+      '';
+    };
+  };
 
-   environment.systemPackages = with pkgs; [
-     #procmail
-     git
-     wget
-   ];
+  environment.systemPackages = with pkgs; [
+    #procmail
+    git
+    wget
+  ];
 
-   nix.settings.trusted-users = [ "root" "djm" ];
-   nix.optimise.automatic = true;
-   nix.optimise.dates = [ "03:00" ];
+  nix.settings.trusted-users = [
+    "root"
+    "djm"
+  ];
+  nix.optimise.automatic = true;
+  nix.optimise.dates = [ "03:00" ];
 
-   i18n.defaultLocale = "en_GB.UTF-8";
+  i18n.defaultLocale = "en_GB.UTF-8";
 
-   system.stateVersion = "22.05";
+  system.stateVersion = "22.05";
 
 }
diff --git a/nix-conf/machines/edrahil/hardware-configuration.nix b/nix-conf/machines/edrahil/hardware-configuration.nix
index f67b9f4..c8ee3f5 100644
--- a/nix-conf/machines/edrahil/hardware-configuration.nix
+++ b/nix-conf/machines/edrahil/hardware-configuration.nix
@@ -2,8 +2,16 @@
 {
   imports = [ (modulesPath + "/profiles/qemu-guest.nix") ];
   boot.loader.grub.device = "/dev/sda";
-  boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "vmw_pvscsi" "xen_blkfront" ];
+  boot.initrd.availableKernelModules = [
+    "ata_piix"
+    "uhci_hcd"
+    "vmw_pvscsi"
+    "xen_blkfront"
+  ];
   boot.initrd.kernelModules = [ "nvme" ];
-  fileSystems."/" = { device = "/dev/sda1"; fsType = "ext4"; };
-  
+  fileSystems."/" = {
+    device = "/dev/sda1";
+    fsType = "ext4";
+  };
+
 }
diff --git a/nix-conf/machines/edrahil/network-configuration.nix b/nix-conf/machines/edrahil/network-configuration.nix
new file mode 100644
index 0000000..4b85912
--- /dev/null
+++ b/nix-conf/machines/edrahil/network-configuration.nix
@@ -0,0 +1,19 @@
+{ ... }:
+{
+  networking = {
+    interfaces.ens3.ipv6.addresses = [
+      {
+        # Emulate nix-sops. Technically an anti-pattern, but IP addresses aren't real secrets, and this has to be embedded here,
+        # as we cannot set a file path to read it from.
+        # Populate/update with:
+        # SOPS_AGE_KEY=$(doas ssh-to-age -private-key -i /etc/ssh/ssh_host_ed25519_key) sops -d --extract '["ipv6_address"]' machines/edrahil/secrets.yaml | doas tee /root/.config/secrets/ipv6_address
+        address = builtins.readFile "/root/.config/secrets/ipv6_address";
+        prefixLength = 64;
+      }
+    ];
+    defaultGateway6 = {
+      address = "fe80::1";
+      interface = "ens3";
+    };
+  };
+}
diff --git a/nix-conf/machines/edrahil/secrets.yaml b/nix-conf/machines/edrahil/secrets.yaml
new file mode 100644
index 0000000..1f4c31e
--- /dev/null
+++ b/nix-conf/machines/edrahil/secrets.yaml
@@ -0,0 +1,31 @@
+ipv6_address: ENC[AES256_GCM,data:CGQWUSuwmucIEwtlLK0FodXOWjM=,iv:ZLPiACwjOmes+FbezZKjjwUETujhTbT++4zCuoptpkY=,tag:VjMtetJhRDlJXdKAmJlOxQ==,type:str]
+restic_password: ENC[AES256_GCM,data:2sxeUDRdh9cPv0ACY9EIP7JcmPFo/w==,iv:bkA/FW82l5gSEOZPtVhSNoATmoJf07kC0FJLAcXFkZU=,tag:PbDY039oBas7CvK8RaFRkA==,type:str]
+sops:
+    kms: []
+    gcp_kms: []
+    azure_kv: []
+    hc_vault: []
+    age:
+        - recipient: age1tjfctwnwldmyxnu6qmeufgr9l79vyzmrs7fy58v3d0qj4x4nhqhq2gjmlp
+          enc: |
+            -----BEGIN AGE ENCRYPTED FILE-----
+            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvUGNnbm1Jemc5dVZYM01h
+            R0h4RlRpVnU1ZGxyL3V2TXdYS2JUSHFNTm1BCjJxdlFFbURjdXBaNjNUdldXNkJy
+            blZYRkhkZUgxR0lST2MxM3hENUhiQkEKLS0tIDhvYjhpRnpIVnVmV0VoTDFNOXIx
+            RlB1dXVsdEhETTNUdTRIbGxIMGNFSEkKqeafOyRg3F9dtENNnH5DhJzJU+AEEqrV
+            nfndOlVQe0G/e8SUzUYjVtD6V6Hj/x8OxN6FSOfZnNFNFHQgJ42jFg==
+            -----END AGE ENCRYPTED FILE-----
+        - recipient: age1w7kjp0qdgfyg9cyj5w4qc4fc9qz3w65xw2veazesfgdenqrd3ucqsc5ejv
+          enc: |
+            -----BEGIN AGE ENCRYPTED FILE-----
+            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwSGIvTjdEZGdGWm9ldnow
+            RGxnZ3RhMmthQXFhOCtaNUk1UGhYSytQdkFnCmY3UUxWVFJKeDE5eG9GNktobndz
+            UjBLOFBNWmFXSmJ2akRDYitsZm9TcmMKLS0tIDZoTGFxSWpwWmFTNjN6b2Q2T2Y3
+            Qm1PWnAvZGcxWGZjcnE4QWJZaDE2cGMKOAfTIipI68eJnOnvpQyLCOyH1KAWd/d/
+            SLnJQ+rmh9onA/znahB7Pn3LQtfKLNBADVtwLIuPID0FcgUW7nlOiw==
+            -----END AGE ENCRYPTED FILE-----
+    lastmodified: "2024-09-17T13:43:53Z"
+    mac: ENC[AES256_GCM,data:lVMbjnDvwlw72CiixJkEXCO7a20DYi4zKA8JTf0kSVQR/xjr9WbLpyllNq9Ex+eca/X0yaHBYjyOnBBpgz1h5o4i5iq738VXOEqD9v5BMdOrVmmDNnVcTAXqmWZGE7/pGmkiKef/iXOyJT2vsrrYR0vhgrvo/0WXce1YLUA4NTs=,iv:Y1w/llSNDry+PWz4oA/0MBJ+Ra6ceC1ZHMKb+CPCvE0=,tag:r2RR6ZfGL9TYwHtV9auL3A==,type:str]
+    pgp: []
+    unencrypted_suffix: _unencrypted
+    version: 3.8.1
diff --git a/nix-conf/machines/egalmoth/configuration.nix b/nix-conf/machines/egalmoth/configuration.nix
index 66382dd..e47cd45 100644
--- a/nix-conf/machines/egalmoth/configuration.nix
+++ b/nix-conf/machines/egalmoth/configuration.nix
@@ -1,14 +1,18 @@
-{ config, pkgs, lib, ... }:
-
 {
-  imports =
-    [
-      ./hardware-configuration.nix
-    ];
+  config,
+  pkgs,
+  lib,
+  ...
+}:
+let
+  unstable = import <unstable> { };
+in
+{
+  imports = [ ./hardware-configuration.nix ];
 
   boot.loader.systemd-boot.enable = true;
   boot.loader.efi.canTouchEfiVariables = true;
-  boot.kernelParams = ["intel_pstate=enable"];
+  boot.kernelParams = [ "intel_pstate=enable" ];
   powerManagement = {
     enable = true;
     #cpuFreqGovernor = "powersave";
@@ -50,8 +54,7 @@
     };
   };
 
-  hardware.opengl.enable = true;
-  hardware.opengl.driSupport = true;
+  hardware.graphics.enable = true;
 
   networking.hostName = "egalmoth"; # Define your hostname.
   networking.networkmanager.enable = true;
@@ -80,13 +83,19 @@
   services.xserver.xkb.layout = "gb";
 
   services.printing.enable = true;
-  services.printing.drivers = [ pkgs.gutenprint pkgs.hplipWithPlugin ];
-
-  nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
-     "hplip"
-     "corefonts"
+  services.printing.drivers = [
+    pkgs.gutenprint
+    pkgs.hplipWithPlugin
   ];
 
+  nixpkgs.config.allowUnfreePredicate =
+    pkg:
+    builtins.elem (lib.getName pkg) [
+      "corefonts"
+      "hplip"
+      "zoom"
+    ];
+
   hardware.sane.enable = true;
 
   services.udev.packages = [
@@ -118,31 +127,40 @@
     pulse.enable = true;
   };
 
-  users.users.djm =
-   { isNormalUser = true;
-     description = "David Morgan";
-     extraGroups = [ "wheel" "networkmanager" "scanner" "lp" "plocate" "cdrom" ];
-     shell = pkgs.zsh;
-     openssh.authorizedKeys.keys = [
+  users.users.djm = {
+    isNormalUser = true;
+    description = "David Morgan";
+    extraGroups = [
+      "wheel"
+      "networkmanager"
+      "scanner"
+      "lp"
+      "plocate"
+      "cdrom"
+      "disk"
+    ];
+    shell = pkgs.zsh;
+    openssh.authorizedKeys.keys = [
       "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCurCpxZCHtByB5wXzsjTXwMyDSB4+B8rq5XY6EGss58NwD8jc5cII4i+QUbCOGTiAggSZUSC9YIP24hjpOeNT/IYs5m7Qn1B9MtBAiUSrIYew8eDwnMLlPzN+k2x9zCrJeCHIvGJaFHPXTh1Lf5Jt2fPVGW9lksE/XUVOe6ht4N/b+nqqszXFhc8Ug6le2bC1YeTCVEf8pjlh/I7DkDBl6IB8uEXc3X2vxxbV0Z4vlBrFkkAywcD3j5VlS/QYfBr4BICNmq/sO3fMkbMbtAPwuFxeL4+h6426AARQZiSS0qVEc8OoFRBVx3GEH5fqVAWfB1geyLzei22HbjUcT9+xN davidmo@gendros"
       "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK9UDTaVnUOU/JknrNdihlhhGOk53LmHq9I1ASri3aga djm@gaius"
-     ];
-   };
-   security.sudo.extraConfig = ''
-     djm ALL=(ALL) NOPASSWD: ALL
-   '';
-   security.doas = {
-     enable = true;
-     extraRules = [ { users = [ "djm" ]; noPass = true; keepEnv = true; } ];
-   };
-
-
-  services.locate = {
+    ];
+  };
+  security.sudo.extraConfig = ''
+    djm ALL=(ALL) NOPASSWD: ALL
+  '';
+  security.doas = {
     enable = true;
-    package = pkgs.plocate;
-    localuser = null;
+    extraRules = [
+      {
+        users = [ "djm" ];
+        noPass = true;
+        keepEnv = true;
+      }
+    ];
   };
 
+  services.locate.enable = true;
+
   environment.systemPackages = with pkgs; [
     acpi
     acpitool
@@ -160,19 +178,25 @@
     rofi
     st
     sway
-    vim
+    vdhcoapp
+    ungoogled-chromium
     wayland
     wayst
     wezterm
     wl-clipboard
     wget
     xclip
+    xorg.xkill
     xurls
     xst
+    zoom-us
 
     libreoffice
     onlyoffice-bin
   ];
+  programs.nix-ld.enable = true;
+
+  programs.nix-ld.libraries = with pkgs; [ xorg.libxcb ];
 
   fonts.packages = with pkgs; [
     corefonts
@@ -180,6 +204,7 @@
     iosevka-bin
     jetbrains-mono
     meslo-lgs-nf
+    unstable.aporetic
   ];
 
   programs.zsh.enable = true;
@@ -194,7 +219,10 @@
     enableSSHSupport = true;
   };
 
-  programs.vim.defaultEditor = true;
+  programs.vim = {
+    enable = true;
+    defaultEditor = true;
+  };
 
   services.openssh.enable = true;
 
@@ -203,4 +231,3 @@
   system.stateVersion = "21.05"; # Did you read the comment?
 
 }
-
diff --git a/nix-conf/machines/egalmoth/hardware-configuration.nix b/nix-conf/machines/egalmoth/hardware-configuration.nix
index 4a5ae74..e5cb5f7 100644
--- a/nix-conf/machines/egalmoth/hardware-configuration.nix
+++ b/nix-conf/machines/egalmoth/hardware-configuration.nix
@@ -1,31 +1,40 @@
 # Do not modify this file!  It was generated by ‘nixos-generate-config’
 # and may be overwritten by future invocations.  Please make changes
 # to /etc/nixos/configuration.nix instead.
-{ config, lib, pkgs, modulesPath, ... }:
+{
+  config,
+  lib,
+  pkgs,
+  modulesPath,
+  ...
+}:
 
 {
-  imports =
-    [ (modulesPath + "/installer/scan/not-detected.nix")
-    ];
+  imports = [ (modulesPath + "/installer/scan/not-detected.nix") ];
 
-  boot.initrd.availableKernelModules = [ "xhci_pci" "thunderbolt" "nvme" "usb_storage" "sd_mod" "rtsx_pci_sdmmc" ];
+  boot.initrd.availableKernelModules = [
+    "xhci_pci"
+    "thunderbolt"
+    "nvme"
+    "usb_storage"
+    "sd_mod"
+    "rtsx_pci_sdmmc"
+  ];
   boot.initrd.kernelModules = [ ];
   boot.kernelModules = [ "kvm-intel" ];
   boot.extraModulePackages = [ ];
 
-  fileSystems."/" =
-    { device = "/dev/disk/by-uuid/b2189909-19fe-4f58-a8ff-4de288199843";
-      fsType = "ext4";
-    };
+  fileSystems."/" = {
+    device = "/dev/disk/by-uuid/b2189909-19fe-4f58-a8ff-4de288199843";
+    fsType = "ext4";
+  };
 
-  fileSystems."/boot" =
-    { device = "/dev/disk/by-uuid/6ED1-F330";
-      fsType = "vfat";
-    };
+  fileSystems."/boot" = {
+    device = "/dev/disk/by-uuid/6ED1-F330";
+    fsType = "vfat";
+  };
 
-  swapDevices =
-    [ { device = "/dev/disk/by-uuid/a130cacb-d7e0-4fb8-a312-a34d19f00796"; }
-    ];
+  swapDevices = [ { device = "/dev/disk/by-uuid/a130cacb-d7e0-4fb8-a312-a34d19f00796"; } ];
 
   powerManagement.cpuFreqGovernor = lib.mkDefault "powersave";
 }
diff --git a/setup-home.sh b/setup-home.sh
index 4a417e6..f44f0d5 100755
--- a/setup-home.sh
+++ b/setup-home.sh
@@ -1,5 +1,7 @@
 #!/bin/sh
 
+[ -f ~/.config/sops/age/keys.txt ] || ( echo "Age key not present, aborting." ; exit 1 )
+
 [ -e ~/dotfiles ] || git clone git@codeberg.org:djm/dotfiles.git
 
 if [ -x "$(command -v nixos-version)" ]; then
@@ -12,6 +14,8 @@ else
   nix-channel --add https://nixos.org/channels/nixpkgs-unstable unstable
 fi
 
+nix-channel --add https://github.com/Mic92/sops-nix/archive/master.tar.gz sops-nix
+
 nix-channel --update
 
 export NIX_PATH=$HOME/.nix-defexpr/channels:/nix/var/nix/profiles/per-user/root/channels${NIX_PATH:+:$NIX_PATH}
@@ -22,8 +26,6 @@ HOME_CONF="$HOME/dotfiles/nix-conf/home/${CONF:-${HOST}}.nix"
 ln -sf ~/dotfiles/.p10k.zsh ~/
 ln -sf ~/dotfiles/.emacs.d ~/
 
-echo -n $EMAIL > ~/email.txt
-
 home-manager switch
 
 if [ "$(uname 2> /dev/null)" = "Darwin"  ]; then