summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.dir-locals.el20
-rw-r--r--.editorconfig2
-rw-r--r--.envrc7
-rw-r--r--.gitignore1
-rw-r--r--.gitmodules3
l---------bin/darwin-rebuild1
-rwxr-xr-xbin/home-manager152
l---------bin/nixos-rebuild1
-rw-r--r--flake.lock619
-rw-r--r--flake.nix186
-rw-r--r--lib/caddy.nix47
-rw-r--r--lib/default.nix25
-rw-r--r--npins/default.nix80
-rw-r--r--npins/sources.json95
-rw-r--r--overlays/emacs.nix1
-rw-r--r--overlays/extra-packages.nix10
-rw-r--r--overlays/htmlformat/default.nix3
-rw-r--r--overlays/htmlformat/htmlformat.nix28
-rw-r--r--overlays/node-packages/default.nix4
-rw-r--r--overlays/node-packages/prettier-plugin-go-template/default.nix17
-rw-r--r--overlays/node-packages/prettier-plugin-go-template/node-env.nix689
-rw-r--r--overlays/node-packages/prettier-plugin-go-template/node-packages.json3
-rw-r--r--overlays/node-packages/prettier-plugin-go-template/node-packages.nix50
-rw-r--r--overlays/nur.nix3
-rw-r--r--overlays/personal.nix13
m---------packages0
-rw-r--r--pin.nix11
m---------private0
-rw-r--r--secrets/dex.agebin0 -> 509 bytes
-rw-r--r--secrets/golink.age8
-rw-r--r--secrets/secrets.nix2
-rw-r--r--shell.nix21
-rw-r--r--sources.nix15
-rw-r--r--system/.gitignore6
-rwxr-xr-xsystem/autorandr/docked-close/block3
-rw-r--r--system/autorandr/docked-close/config23
-rw-r--r--system/autorandr/docked-close/setup3
-rwxr-xr-xsystem/autorandr/docked-open/block3
-rw-r--r--system/autorandr/docked-open/config25
-rw-r--r--system/autorandr/docked-open/setup3
-rw-r--r--system/autorandr/laptop/config19
-rw-r--r--system/autorandr/laptop/setup1
-rwxr-xr-xsystem/autorandr/postswitch2
-rw-r--r--system/autorandr/work-1/config21
-rw-r--r--system/autorandr/work-1/setup2
-rw-r--r--system/linde.nix871
-rw-r--r--system/mba.nix1
-rwxr-xr-xsystem/nanopi.nix320
-rw-r--r--system/prefect.nix82
-rw-r--r--system/settings/base.nix5
-rw-r--r--system/settings/configuration/networking.nix11
-rw-r--r--system/settings/configuration/nix-linux.nix1
-rw-r--r--system/settings/configuration/nix.nix13
-rw-r--r--system/settings/configuration/user.nix1
-rw-r--r--system/settings/darwin.nix10
-rw-r--r--system/settings/gaming.nix4
-rw-r--r--system/settings/hardware/intel-gpu.nix18
-rw-r--r--system/settings/hardware/laptop.nix79
-rw-r--r--system/settings/hardware/mouse.nix2
-rw-r--r--system/settings/hardware/network-manager.nix16
-rw-r--r--system/settings/hardware/personal-computer.nix8
-rw-r--r--system/settings/hardware/thinkpad.nix23
-rw-r--r--system/settings/hardware/trackball.nix15
-rw-r--r--system/settings/hardware/trezor.nix6
-rw-r--r--system/settings/machines/t470s.nix57
-rw-r--r--system/settings/pin.nix12
-rw-r--r--system/settings/programs/barrier.nix10
-rw-r--r--system/settings/programs/base.nix17
-rw-r--r--system/settings/programs/gnome.nix26
-rw-r--r--system/settings/programs/gnupg.nix12
-rw-r--r--system/settings/programs/kde.nix7
-rw-r--r--system/settings/programs/shell.nix1
-rw-r--r--system/settings/programs/tor.nix27
-rw-r--r--system/settings/programs/window-manager.nix57
-rw-r--r--system/settings/programs/xfce.nix8
-rw-r--r--system/settings/services/git-server.nix279
-rw-r--r--system/settings/services/virtualisation.nix4
-rw-r--r--system/settings/services/xserver.nix15
-rw-r--r--system/settings/user-interface.nix13
-rw-r--r--user/.gitignore2
-rw-r--r--user/config.nix17
-rw-r--r--user/emacs/early-init.el7
-rw-r--r--user/emacs/init.el150
-rw-r--r--user/gnupg/dirmngr.conf3
-rw-r--r--user/gnupg/gpa.conf2
-rw-r--r--user/gnupg/gpg-agent.conf3
-rw-r--r--user/gnupg/gpg.conf83
-rw-r--r--user/gnupg/trezor/dirmngr.conf1
-rw-r--r--user/mba.nix5
-rw-r--r--user/nanopi.nix1
-rw-r--r--user/nvim/init.lua1
-rw-r--r--user/prefect.nix10
-rw-r--r--user/server.nix1
-rw-r--r--user/settings/base.nix18
-rw-r--r--user/settings/development/base.nix16
-rw-r--r--user/settings/development/golang.nix6
-rw-r--r--user/settings/development/javascript.nix4
-rw-r--r--user/settings/development/web.nix81
-rw-r--r--user/settings/emacs.nix52
-rw-r--r--user/settings/fish/functions/dired.fish2
-rw-r--r--user/settings/fish/functions/magit.fish2
-rw-r--r--user/settings/fish/functions/newest.fish10
-rw-r--r--user/settings/fish/functions/oldest.fish10
-rw-r--r--user/settings/git.nix5
-rw-r--r--user/settings/gnupg.nix16
-rw-r--r--user/settings/i3.nix3
-rw-r--r--user/settings/neovim.nix18
-rw-r--r--user/settings/nix.nix12
-rw-r--r--user/settings/nixos.nix4
-rw-r--r--user/settings/shell.nix11
-rw-r--r--user/settings/ssh.nix4
-rw-r--r--user/settings/tabnine.nix13
-rw-r--r--user/settings/trezor.nix10
-rw-r--r--user/settings/user-interface.nix11
114 files changed, 1950 insertions, 2903 deletions
diff --git a/.dir-locals.el b/.dir-locals.el
index 5535ffa9..d9b7c0ae 100644
--- a/.dir-locals.el
+++ b/.dir-locals.el
@@ -1,4 +1,22 @@
 ;;; Directory Local Variables
 ;;; For more information see (info "(emacs) Directory Variables")
 
-((nil . ((compile-command . "home-manager switch"))))
+((nil . ((compile-command . "./bin/home-manager switch")))
+ ("system/nanopi.nix" .
+  ((nil . ((ssh-deploy-root-local . "/home/alan/projects/alanpearce.eu/nixfiles/")
+           (ssh-deploy-root-remote . "/sshx:nanopi:projects/alanpearce.eu/nixfiles/")
+           (ssh-deploy-on-explicit-save . 1)))))
+ ("user/nanopi.nix" .
+  ((nil . ((ssh-deploy-root-local . "/home/alan/projects/alanpearce.eu/nixfiles/")
+           (ssh-deploy-root-remote . "/sshx:nanopi:projects/alanpearce.eu/nixfiles/")
+           (ssh-deploy-on-explicit-save . 1)))))
+ ("system/linde.nix" .
+  ((nil . ((ssh-deploy-root-local . "/home/alan/projects/alanpearce.eu/nixfiles/")
+           (ssh-deploy-root-remote . "/sshx:linde:projects/alanpearce.eu/nixfiles/")
+           (ssh-deploy-on-explicit-save . 1)
+           (ssh-deploy-async . 1)))))
+ ("system/linde-hardware.nix" .
+  ((nil . ((ssh-deploy-root-local . "/home/alan/projects/alanpearce.eu/nixfiles/")
+           (ssh-deploy-root-remote . "/sshx:linde:projects/alanpearce.eu/nixfiles/")
+           (ssh-deploy-on-explicit-save . 1)
+           (ssh-deploy-async . 1))))))
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..78030d65
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,2 @@
+[nix/sources.json]
+indent_size = 4
diff --git a/.envrc b/.envrc
index fa82cfc4..e7aaa238 100644
--- a/.envrc
+++ b/.envrc
@@ -1,7 +1,8 @@
 if type -P lorri &>/dev/null; then
-	eval "$(lorri direnv)"
+  eval "$(lorri direnv)"
 else
-	echo 'while direnv evaluated .envrc, could not find the command "lorri" [https://github.com/nix-community/lorri]'
-	use flake
+  echo 'while direnv evaluated .envrc, could not find the command "lorri" [https://github.com/nix-community/lorri]'
+  use nix
 fi
+PATH_add bin
 FLAKE=$PWD
diff --git a/.gitignore b/.gitignore
index eaa6f762..c4a847d9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1 @@
 /result
-/override.nix
diff --git a/.gitmodules b/.gitmodules
index cd7b321f..e6975e13 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,6 @@
 [submodule "private"]
 	path = private
 	url = ssh://alanpearce.eu/nixfiles-private
+[submodule "packages"]
+	path = packages
+	url = ssh://git.alanpearce.eu/nix-packages
diff --git a/bin/darwin-rebuild b/bin/darwin-rebuild
new file mode 120000
index 00000000..2eaccdef
--- /dev/null
+++ b/bin/darwin-rebuild
@@ -0,0 +1 @@
+home-manager
\ No newline at end of file
diff --git a/bin/home-manager b/bin/home-manager
new file mode 100755
index 00000000..cf434738
--- /dev/null
+++ b/bin/home-manager
@@ -0,0 +1,152 @@
+#!/usr/bin/env fish
+
+if not set --query HOME
+    echo "error: no HOME"
+    exit 1
+end
+
+set --query XDG_STATE_HOME || set --function XDG_STATE_HOME $HOME/.local/state
+set --function nix_state_home $XDG_STATE_HOME/nix
+set --function channel_root $nix_state_home/channels
+set --function user_nixpkgs $HOME/.config/nixpkgs
+set --function nixfiles (path resolve (status dirname)/..)
+
+if set --query XDG_CONFIG_HOME
+    set --function hm_config_dir $XDG_CONFIG_HOME/home-manager
+else
+    set --function hm_config_dir $HOME/.config/home-manager
+end
+
+for i in 1
+    if test ! -d $user_nixpkgs
+        if test ! -e $user_nixpkgs && test -L $user_nixpkgs
+            echo "~/.config/nixpkgs is a broken symlink, deleting it"
+            rm $channel_root
+        end
+        echo "user nixpkgs folder $user_nixpkgs does not exist, creating it"
+        mkdir -p $user_nixpkgs
+    end
+    if test -L $hm_config_dir -a (path resolve $hm_config_dir) = (path resolve (status basename))
+        if test y = (read --nchars=1 --prompt-str="$hm_config_dir already exists as a symlink to the current directory. Remove it [yN]? ")
+            unlink $hm_config_dir
+        end
+    end
+    if test ! -d $hm_config_dir
+        echo "user home-manager configuration folder $hm_config_dir does not exist, creating it"
+        mkdir -p $hm_config_dir
+    end
+    if test ! -e $channel_root
+        if test -L $channel_root
+            # broken symlink
+            rm $channel_root
+        end
+        echo "channel root $channel_root does not exist, creating it"
+        mkdir -p $channel_root
+    else
+        if test ! -d $channel_root
+            echo "error: $channel_root is not a directory"
+        else if test -L $channel_root
+            echo "error: $channel_root is a symlink, please remove it"
+        else if path is --invert --perm write $channel_root
+            echo "error: $channel_root is not writable; might be a symlink"
+        else if path is --invert --type link $channel_root/*
+            for p in (path filter --invert --type link $channel_root/*)
+                # might be a broken symlink
+                if test -L $p -a ! -e $p
+                    rm $p
+                else
+                    echo "error: channel $p is not a broken symlink, don't know what to do with it"
+                end
+            end
+            # check again and fail if there are still unknown files
+            if path is --invert --type link $channel_root/*
+                echo "error: non-link(s) in $channel_root"
+                path filter --invert --type link $channel_root/*
+            else
+                continue
+            end
+        else
+            continue # with script
+        end
+        exit 1
+    end
+end
+
+set --function current_script_name (status basename)
+switch $current_script_name
+    case darwin-rebuild
+        set --local darwin_config_source $nixfiles/system/$hostname.nix
+        set --local darwin_config_target $user_nixpkgs/darwin-configuration.nix
+        if test ! -e $darwin_config_target
+            ln -s $darwin_config_source $darwin_config_target
+        end
+        set --append argv -I darwin-config=$darwin_config_source
+    case nixos-rebuild
+        set --local nixos_config_source $nixfiles/system/$hostname.nix
+        set --local nixos_config_target $user_nixpkgs/configuration.nix
+        if test ! -e $nixos_config_target
+            ln -s $nixos_config_source $nixos_config_target
+        end
+        if ! fish_is_root_user
+            set --append argv --use-remote-sudo
+        end
+        set --append argv -I nixos-config=$nixos_config_source
+    case home-manager
+        set --local hm_config_source $nixfiles/user/$hostname.nix
+        set --local hm_config_target $hm_config_dir/home.nix
+        if test ! -e $hm_config_target
+            ln -s $hm_config_source $hm_config_target
+        end
+        set --export HOME_MANAGER_CONFIG $hm_config_source
+end
+
+set --function old_channels (path basename $channel_root/*)
+
+function update_link --argument-names new_src target
+    set --function current_src (path resolve $target)
+    if test -e $current_src
+        if test $current_src = $new_src
+            # no need to re-link it
+            return
+        else
+            unlink $target
+        end
+    end
+    ln -s $new_src $target
+end
+
+for np in $NIX_PATH
+    if string match --quiet --entire "=" $np
+        echo $np | read --function --delimiter "=" channel new_src_rel
+        set --local new_src (path resolve $new_src_rel)
+
+        if test $channel = darwin-config
+            continue
+        end
+        if set --local i (contains --index $channel $old_channels)
+            set --erase old_channels[$i]
+        end
+
+        update_link $new_src $channel_root/$channel
+    end
+end
+
+for old in $channel_root/$old_channels
+    rm $old
+end
+
+if set --function i (contains --index (status dirname) $PATH) && test -n $i
+    set --erase PATH[$i]
+end
+
+set --function cmd $current_script_name $argv
+
+if contains -- -n $argv
+    set --prepend cmd echo
+end
+
+if set --query IN_NIX_SHELL
+    eval $cmd
+else
+    nix-shell --run "$cmd"
+end
diff --git a/bin/nixos-rebuild b/bin/nixos-rebuild
new file mode 120000
index 00000000..2eaccdef
--- /dev/null
+++ b/bin/nixos-rebuild
@@ -0,0 +1 @@
+home-manager
\ No newline at end of file
diff --git a/flake.lock b/flake.lock
deleted file mode 100644
index e968f6f6..00000000
--- a/flake.lock
+++ /dev/null
@@ -1,619 +0,0 @@
-{
-  "nodes": {
-    "agenix": {
-      "inputs": {
-        "darwin": "darwin",
-        "home-manager": "home-manager",
-        "nixpkgs": [
-          "nixpkgs"
-        ],
-        "systems": "systems"
-      },
-      "locked": {
-        "lastModified": 1715290355,
-        "narHash": "sha256-2T7CHTqBXJJ3ZC6R/4TXTcKoXWHcvubKNj9SfomURnw=",
-        "owner": "ryantm",
-        "repo": "agenix",
-        "rev": "8d37c5bdeade12b6479c85acd133063ab53187a0",
-        "type": "github"
-      },
-      "original": {
-        "owner": "ryantm",
-        "repo": "agenix",
-        "type": "github"
-      }
-    },
-    "darwin": {
-      "inputs": {
-        "nixpkgs": [
-          "agenix",
-          "nixpkgs"
-        ]
-      },
-      "locked": {
-        "lastModified": 1700795494,
-        "narHash": "sha256-gzGLZSiOhf155FW7262kdHo2YDeugp3VuIFb4/GGng0=",
-        "owner": "lnl7",
-        "repo": "nix-darwin",
-        "rev": "4b9b83d5a92e8c1fbfd8eb27eda375908c11ec4d",
-        "type": "github"
-      },
-      "original": {
-        "owner": "lnl7",
-        "ref": "master",
-        "repo": "nix-darwin",
-        "type": "github"
-      }
-    },
-    "darwin_2": {
-      "inputs": {
-        "nixpkgs": [
-          "nixpkgs"
-        ]
-      },
-      "locked": {
-        "lastModified": 1713946171,
-        "narHash": "sha256-lc75rgRQLdp4Dzogv5cfqOg6qYc5Rp83oedF2t0kDp8=",
-        "owner": "lnl7",
-        "repo": "nix-darwin",
-        "rev": "230a197063de9287128e2c68a7a4b0cd7d0b50a7",
-        "type": "github"
-      },
-      "original": {
-        "owner": "lnl7",
-        "ref": "master",
-        "repo": "nix-darwin",
-        "type": "github"
-      }
-    },
-    "deploy-rs": {
-      "inputs": {
-        "flake-compat": "flake-compat",
-        "nixpkgs": "nixpkgs",
-        "utils": "utils"
-      },
-      "locked": {
-        "lastModified": 1711973905,
-        "narHash": "sha256-UFKME/N1pbUtn+2Aqnk+agUt8CekbpuqwzljivfIme8=",
-        "owner": "serokell",
-        "repo": "deploy-rs",
-        "rev": "88b3059b020da69cbe16526b8d639bd5e0b51c8b",
-        "type": "github"
-      },
-      "original": {
-        "owner": "serokell",
-        "repo": "deploy-rs",
-        "type": "github"
-      }
-    },
-    "emacs-overlay": {
-      "inputs": {
-        "flake-utils": "flake-utils",
-        "nixpkgs": [
-          "nixpkgs"
-        ],
-        "nixpkgs-stable": "nixpkgs-stable"
-      },
-      "locked": {
-        "lastModified": 1715418343,
-        "narHash": "sha256-DieMydUZ8oZkH2jdjPv02FXTujJoJ8u0cLXQIYApX5o=",
-        "owner": "nix-community",
-        "repo": "emacs-overlay",
-        "rev": "e41288bc8adbd180681d12eb4b9274a7bae7f974",
-        "type": "github"
-      },
-      "original": {
-        "owner": "nix-community",
-        "repo": "emacs-overlay",
-        "type": "github"
-      }
-    },
-    "flake-compat": {
-      "flake": false,
-      "locked": {
-        "lastModified": 1696426674,
-        "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
-        "owner": "edolstra",
-        "repo": "flake-compat",
-        "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
-        "type": "github"
-      },
-      "original": {
-        "owner": "edolstra",
-        "repo": "flake-compat",
-        "type": "github"
-      }
-    },
-    "flake-compat_2": {
-      "flake": false,
-      "locked": {
-        "lastModified": 1696426674,
-        "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
-        "owner": "edolstra",
-        "repo": "flake-compat",
-        "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
-        "type": "github"
-      },
-      "original": {
-        "owner": "edolstra",
-        "repo": "flake-compat",
-        "type": "github"
-      }
-    },
-    "flake-utils": {
-      "inputs": {
-        "systems": "systems_3"
-      },
-      "locked": {
-        "lastModified": 1710146030,
-        "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
-        "owner": "numtide",
-        "repo": "flake-utils",
-        "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
-        "type": "github"
-      },
-      "original": {
-        "owner": "numtide",
-        "repo": "flake-utils",
-        "type": "github"
-      }
-    },
-    "flake-utils_2": {
-      "inputs": {
-        "systems": "systems_4"
-      },
-      "locked": {
-        "lastModified": 1710146030,
-        "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
-        "owner": "numtide",
-        "repo": "flake-utils",
-        "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
-        "type": "github"
-      },
-      "original": {
-        "owner": "numtide",
-        "repo": "flake-utils",
-        "type": "github"
-      }
-    },
-    "gitignore": {
-      "inputs": {
-        "nixpkgs": [
-          "searchix",
-          "pre-commit-hooks",
-          "nixpkgs"
-        ]
-      },
-      "locked": {
-        "lastModified": 1709087332,
-        "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
-        "owner": "hercules-ci",
-        "repo": "gitignore.nix",
-        "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
-        "type": "github"
-      },
-      "original": {
-        "owner": "hercules-ci",
-        "repo": "gitignore.nix",
-        "type": "github"
-      }
-    },
-    "gomod2nix": {
-      "inputs": {
-        "flake-utils": [
-          "searchix",
-          "flake-utils"
-        ],
-        "nixpkgs": [
-          "searchix",
-          "nixpkgs"
-        ]
-      },
-      "locked": {
-        "lastModified": 1710154385,
-        "narHash": "sha256-4c3zQ2YY4BZOufaBJB4v9VBBeN2dH7iVdoJw8SDNCfI=",
-        "owner": "nix-community",
-        "repo": "gomod2nix",
-        "rev": "872b63ddd28f318489c929d25f1f0a3c6039c971",
-        "type": "github"
-      },
-      "original": {
-        "owner": "nix-community",
-        "repo": "gomod2nix",
-        "type": "github"
-      }
-    },
-    "home-manager": {
-      "inputs": {
-        "nixpkgs": [
-          "agenix",
-          "nixpkgs"
-        ]
-      },
-      "locked": {
-        "lastModified": 1703113217,
-        "narHash": "sha256-7ulcXOk63TIT2lVDSExj7XzFx09LpdSAPtvgtM7yQPE=",
-        "owner": "nix-community",
-        "repo": "home-manager",
-        "rev": "3bfaacf46133c037bb356193bd2f1765d9dc82c1",
-        "type": "github"
-      },
-      "original": {
-        "owner": "nix-community",
-        "repo": "home-manager",
-        "type": "github"
-      }
-    },
-    "home-manager_2": {
-      "inputs": {
-        "nixpkgs": [
-          "nixpkgs"
-        ]
-      },
-      "locked": {
-        "lastModified": 1715380449,
-        "narHash": "sha256-716+f9Rj3wjSyD1xitCv2FcYbgPz1WIVDj+ZBclH99Y=",
-        "owner": "nix-community",
-        "repo": "home-manager",
-        "rev": "d7682620185f213df384c363288093b486b2883f",
-        "type": "github"
-      },
-      "original": {
-        "owner": "nix-community",
-        "repo": "home-manager",
-        "type": "github"
-      }
-    },
-    "nix-index-database": {
-      "inputs": {
-        "nixpkgs": [
-          "nixpkgs"
-        ]
-      },
-      "locked": {
-        "lastModified": 1714878592,
-        "narHash": "sha256-E68C03sYRsYFsK7wiGHUIJm8IsyPRALOrFoTL0glXnI=",
-        "owner": "Mic92",
-        "repo": "nix-index-database",
-        "rev": "a362555e9dbd4ecff3bb98969bbdb8f79fe87f10",
-        "type": "github"
-      },
-      "original": {
-        "owner": "Mic92",
-        "repo": "nix-index-database",
-        "type": "github"
-      }
-    },
-    "nixos-hardware": {
-      "locked": {
-        "lastModified": 1715148395,
-        "narHash": "sha256-lRxjTxY3103LGMjWdVqntKZHhlmMX12QUjeFrQMmGaE=",
-        "owner": "NixOS",
-        "repo": "nixos-hardware",
-        "rev": "a4e2b7909fc1bdf30c30ef21d388fde0b5cdde4a",
-        "type": "github"
-      },
-      "original": {
-        "owner": "NixOS",
-        "repo": "nixos-hardware",
-        "type": "github"
-      }
-    },
-    "nixpkgs": {
-      "locked": {
-        "lastModified": 1702272962,
-        "narHash": "sha256-D+zHwkwPc6oYQ4G3A1HuadopqRwUY/JkMwHz1YF7j4Q=",
-        "owner": "NixOS",
-        "repo": "nixpkgs",
-        "rev": "e97b3e4186bcadf0ef1b6be22b8558eab1cdeb5d",
-        "type": "github"
-      },
-      "original": {
-        "owner": "NixOS",
-        "ref": "nixpkgs-unstable",
-        "repo": "nixpkgs",
-        "type": "github"
-      }
-    },
-    "nixpkgs-small": {
-      "locked": {
-        "lastModified": 1715910673,
-        "narHash": "sha256-D7gtcOKMK0PFLrQHCndErNi2m5n6ddgtJdbQukL1QZE=",
-        "owner": "NixOS",
-        "repo": "nixpkgs",
-        "rev": "22b25d36f84f5449e2799af1def209278ef75147",
-        "type": "github"
-      },
-      "original": {
-        "owner": "NixOS",
-        "ref": "nixos-unstable-small",
-        "repo": "nixpkgs",
-        "type": "github"
-      }
-    },
-    "nixpkgs-stable": {
-      "locked": {
-        "lastModified": 1715218190,
-        "narHash": "sha256-R98WOBHkk8wIi103JUVQF3ei3oui4HvoZcz9tYOAwlk=",
-        "owner": "NixOS",
-        "repo": "nixpkgs",
-        "rev": "9a9960b98418f8c385f52de3b09a63f9c561427a",
-        "type": "github"
-      },
-      "original": {
-        "owner": "NixOS",
-        "ref": "nixos-23.11",
-        "repo": "nixpkgs",
-        "type": "github"
-      }
-    },
-    "nixpkgs-stable_2": {
-      "locked": {
-        "lastModified": 1710695816,
-        "narHash": "sha256-3Eh7fhEID17pv9ZxrPwCLfqXnYP006RKzSs0JptsN84=",
-        "owner": "NixOS",
-        "repo": "nixpkgs",
-        "rev": "614b4613980a522ba49f0d194531beddbb7220d3",
-        "type": "github"
-      },
-      "original": {
-        "owner": "NixOS",
-        "ref": "nixos-23.11",
-        "repo": "nixpkgs",
-        "type": "github"
-      }
-    },
-    "nixpkgs_2": {
-      "locked": {
-        "lastModified": 1715266358,
-        "narHash": "sha256-doPgfj+7FFe9rfzWo1siAV2mVCasW+Bh8I1cToAXEE4=",
-        "owner": "NixOS",
-        "repo": "nixpkgs",
-        "rev": "f1010e0469db743d14519a1efd37e23f8513d714",
-        "type": "github"
-      },
-      "original": {
-        "owner": "NixOS",
-        "ref": "nixos-unstable",
-        "repo": "nixpkgs",
-        "type": "github"
-      }
-    },
-    "nixpkgs_3": {
-      "locked": {
-        "lastModified": 1715447595,
-        "narHash": "sha256-VsVAUQOj/cS1LCOmMjAGeRksXIAdPnFIjCQ0XLkCsT0=",
-        "owner": "NixOS",
-        "repo": "nixpkgs",
-        "rev": "062ca2a9370a27a35c524dc82d540e6e9824b652",
-        "type": "github"
-      },
-      "original": {
-        "owner": "NixOS",
-        "ref": "nixos-unstable",
-        "repo": "nixpkgs",
-        "type": "github"
-      }
-    },
-    "nixpkgs_4": {
-      "locked": {
-        "lastModified": 1710765496,
-        "narHash": "sha256-p7ryWEeQfMwTB6E0wIUd5V2cFTgq+DRRBz2hYGnJZyA=",
-        "owner": "NixOS",
-        "repo": "nixpkgs",
-        "rev": "e367f7a1fb93137af22a3908f00b9a35e2d286a7",
-        "type": "github"
-      },
-      "original": {
-        "owner": "NixOS",
-        "ref": "nixpkgs-unstable",
-        "repo": "nixpkgs",
-        "type": "github"
-      }
-    },
-    "pre-commit-hooks": {
-      "inputs": {
-        "flake-compat": "flake-compat_2",
-        "flake-utils": [
-          "searchix",
-          "flake-utils"
-        ],
-        "gitignore": "gitignore",
-        "nixpkgs": "nixpkgs_4",
-        "nixpkgs-stable": "nixpkgs-stable_2"
-      },
-      "locked": {
-        "lastModified": 1714478972,
-        "narHash": "sha256-q//cgb52vv81uOuwz1LaXElp3XAe1TqrABXODAEF6Sk=",
-        "owner": "cachix",
-        "repo": "pre-commit-hooks.nix",
-        "rev": "2849da033884f54822af194400f8dff435ada242",
-        "type": "github"
-      },
-      "original": {
-        "owner": "cachix",
-        "repo": "pre-commit-hooks.nix",
-        "type": "github"
-      }
-    },
-    "root": {
-      "inputs": {
-        "agenix": "agenix",
-        "darwin": "darwin_2",
-        "deploy-rs": "deploy-rs",
-        "emacs-overlay": "emacs-overlay",
-        "home-manager": "home-manager_2",
-        "nix-index-database": "nix-index-database",
-        "nixos-hardware": "nixos-hardware",
-        "nixpkgs": "nixpkgs_2",
-        "nixpkgs-small": "nixpkgs-small",
-        "searchix": "searchix",
-        "secrets": "secrets",
-        "utils": "utils_2"
-      }
-    },
-    "searchix": {
-      "inputs": {
-        "flake-utils": "flake-utils_2",
-        "gomod2nix": "gomod2nix",
-        "nixpkgs": "nixpkgs_3",
-        "pre-commit-hooks": "pre-commit-hooks",
-        "simple-css": "simple-css"
-      },
-      "locked": {
-        "lastModified": 1715960277,
-        "narHash": "sha256-b/iq6O1Y4DkiPBWjiu+20lGzDVGYKVpx7SL85UKm3Fw=",
-        "ref": "refs/heads/main",
-        "rev": "8df293c0ad8b8be9f43f2d5dc1334b8546d6f470",
-        "revCount": 168,
-        "type": "git",
-        "url": "https://git.alanpearce.eu/searchix"
-      },
-      "original": {
-        "type": "git",
-        "url": "https://git.alanpearce.eu/searchix"
-      }
-    },
-    "secrets": {
-      "flake": false,
-      "locked": {
-        "lastModified": 1712637001,
-        "narHash": "sha256-0JUbvcWQRAZKubLIn0TTA83v1g/QyjxUiTckVdU189k=",
-        "ref": "refs/heads/main",
-        "rev": "5264068d4f5b85bc3b08e4b86ca46e49d8a411eb",
-        "revCount": 62,
-        "type": "git",
-        "url": "file:///home/alan/projects/alanpearce.eu/nixfiles/private"
-      },
-      "original": {
-        "id": "secrets",
-        "type": "indirect"
-      }
-    },
-    "simple-css": {
-      "flake": false,
-      "locked": {
-        "narHash": "sha256-AAA6fucfxB8R7z66UjOps79XMxbK/gZAB+JDEpZeXGo=",
-        "type": "file",
-        "url": "https://raw.githubusercontent.com/kevquirk/simple.css/v2.3.0/simple.css"
-      },
-      "original": {
-        "type": "file",
-        "url": "https://raw.githubusercontent.com/kevquirk/simple.css/v2.3.0/simple.css"
-      }
-    },
-    "systems": {
-      "locked": {
-        "lastModified": 1681028828,
-        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
-        "owner": "nix-systems",
-        "repo": "default",
-        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
-        "type": "github"
-      },
-      "original": {
-        "owner": "nix-systems",
-        "repo": "default",
-        "type": "github"
-      }
-    },
-    "systems_2": {
-      "locked": {
-        "lastModified": 1681028828,
-        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
-        "owner": "nix-systems",
-        "repo": "default",
-        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
-        "type": "github"
-      },
-      "original": {
-        "owner": "nix-systems",
-        "repo": "default",
-        "type": "github"
-      }
-    },
-    "systems_3": {
-      "locked": {
-        "lastModified": 1681028828,
-        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
-        "owner": "nix-systems",
-        "repo": "default",
-        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
-        "type": "github"
-      },
-      "original": {
-        "owner": "nix-systems",
-        "repo": "default",
-        "type": "github"
-      }
-    },
-    "systems_4": {
-      "locked": {
-        "lastModified": 1681028828,
-        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
-        "owner": "nix-systems",
-        "repo": "default",
-        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
-        "type": "github"
-      },
-      "original": {
-        "owner": "nix-systems",
-        "repo": "default",
-        "type": "github"
-      }
-    },
-    "systems_5": {
-      "locked": {
-        "lastModified": 1681028828,
-        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
-        "owner": "nix-systems",
-        "repo": "default",
-        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
-        "type": "github"
-      },
-      "original": {
-        "owner": "nix-systems",
-        "repo": "default",
-        "type": "github"
-      }
-    },
-    "utils": {
-      "inputs": {
-        "systems": "systems_2"
-      },
-      "locked": {
-        "lastModified": 1701680307,
-        "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
-        "owner": "numtide",
-        "repo": "flake-utils",
-        "rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
-        "type": "github"
-      },
-      "original": {
-        "owner": "numtide",
-        "repo": "flake-utils",
-        "type": "github"
-      }
-    },
-    "utils_2": {
-      "inputs": {
-        "systems": "systems_5"
-      },
-      "locked": {
-        "lastModified": 1710146030,
-        "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
-        "owner": "numtide",
-        "repo": "flake-utils",
-        "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
-        "type": "github"
-      },
-      "original": {
-        "owner": "numtide",
-        "repo": "flake-utils",
-        "type": "github"
-      }
-    }
-  },
-  "root": "root",
-  "version": 7
-}
diff --git a/flake.nix b/flake.nix
deleted file mode 100644
index 459d2202..00000000
--- a/flake.nix
+++ /dev/null
@@ -1,186 +0,0 @@
-{
-  inputs = {
-    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
-    nixpkgs-small.url = "github:NixOS/nixpkgs/nixos-unstable-small";
-    nixos-hardware.url = "github:NixOS/nixos-hardware";
-    nix-index-database.url = "github:Mic92/nix-index-database";
-    nix-index-database.inputs.nixpkgs.follows = "nixpkgs";
-    darwin.url = "github:lnl7/nix-darwin/master";
-    darwin.inputs.nixpkgs.follows = "nixpkgs";
-    home-manager.url = "github:nix-community/home-manager";
-    home-manager.inputs.nixpkgs.follows = "nixpkgs";
-    emacs-overlay.url = "github:nix-community/emacs-overlay";
-    emacs-overlay.inputs.nixpkgs.follows = "nixpkgs";
-    secrets = {
-      flake = false;
-    };
-    utils.url = "github:numtide/flake-utils";
-    agenix.url = "github:ryantm/agenix";
-    agenix.inputs.nixpkgs.follows = "nixpkgs";
-    deploy-rs.url = "github:serokell/deploy-rs";
-    searchix.url = "git+https://git.alanpearce.eu/searchix";
-  };
-
-  outputs =
-    inputs@
-    { self
-    , utils
-    , nixpkgs
-    , nixpkgs-small
-    , nixos-hardware
-    , home-manager
-    , darwin
-    , nix-index-database
-    , secrets
-    , emacs-overlay
-    , agenix
-    , deploy-rs
-    , searchix
-    , ...
-    }:
-    let
-      readOverlays = path:
-        let content = builtins.readDir path; in
-        map (n: import (path + ("/" + n)))
-          (builtins.filter
-            (n:
-              (builtins.match ".*\\.nix" n != null &&
-              # ignore Emacs lock files (.#foo.nix)
-              builtins.match "\\.#.*" n == null) ||
-              builtins.pathExists (path + ("/" + n + "/default.nix")))
-            (builtins.attrNames content));
-
-      mkHomeConfiguration = { modules, system }: home-manager.lib.homeManagerConfiguration {
-        pkgs = import nixpkgs {
-          inherit system;
-          overlays = readOverlays (toString ./overlays) ++ [ emacs-overlay.overlay ];
-        };
-
-        inherit modules;
-        extraSpecialArgs = {
-          inherit inputs system;
-        };
-      };
-    in
-    {
-      nixosConfigurations.prefect = nixpkgs.lib.nixosSystem {
-        system = utils.lib.system.x86_64-linux;
-        specialArgs = { inherit inputs; };
-        modules = [
-          ./system/prefect.nix
-        ] ++ (with nixos-hardware.nixosModules; [
-          common-cpu-amd
-          common-cpu-amd-pstate
-          common-pc-ssd
-          common-pc
-          common-gpu-nvidia-nonprime
-        ]);
-      };
-      nixosConfigurations.nanopi = nixpkgs-small.lib.nixosSystem {
-        system = utils.lib.system.aarch64-linux;
-        specialArgs = { inherit inputs; };
-        modules = [
-          agenix.nixosModules.default
-          ./system/nanopi.nix
-        ];
-      };
-      nixosConfigurations.linde = nixpkgs-small.lib.nixosSystem {
-        system = utils.lib.system.aarch64-linux;
-        specialArgs = { inherit inputs; };
-        modules = [
-          agenix.nixosModules.default
-          searchix.nixosModules.default
-          ./system/linde.nix
-        ];
-      };
-      darwinConfigurations.mba = darwin.lib.darwinSystem {
-        system = utils.lib.system.aarch64-darwin;
-        specialArgs = { inherit inputs; };
-        modules = [ ./system/mba.nix ];
-      };
-      homeConfigurations."alan@mba" = mkHomeConfiguration {
-        system = utils.lib.system.aarch64-darwin;
-        modules = [
-          ./user/mba.nix
-          nix-index-database.hmModules.nix-index
-          (secrets + "/default.nix")
-          (secrets + "/ssh.nix")
-        ];
-      };
-      homeConfigurations."alan@prefect" = mkHomeConfiguration {
-        system = utils.lib.system.x86_64-linux;
-        modules = [
-          ./user/prefect.nix
-          nix-index-database.hmModules.nix-index
-          (secrets + "/default.nix")
-          (secrets + "/ssh.nix")
-        ];
-      };
-      homeConfigurations."alan@nanopi" = mkHomeConfiguration {
-        system = utils.lib.system.aarch64-linux;
-        modules = [
-          ./user/nanopi.nix
-          nix-index-database.hmModules.nix-index
-          (secrets + "/default.nix")
-        ];
-      };
-      homeConfigurations."alan@linde" = mkHomeConfiguration {
-        system = utils.lib.system.aarch64-linux;
-        modules = [
-          ./user/server.nix
-          nix-index-database.hmModules.nix-index
-          (secrets + "/default.nix")
-        ];
-      };
-
-      checks = builtins.mapAttrs
-        (system: deployLib:
-          deployLib.deployChecks self.deploy)
-        deploy-rs.lib;
-
-      deploy = {
-        remoteBuild = true;
-        nodes.linde = {
-          hostname = "linde";
-          profiles.system = {
-            user = "root";
-            interactiveSudo = true;
-            path = deploy-rs.lib.${utils.lib.system.aarch64-linux}.activate.nixos
-              self.nixosConfigurations.linde;
-          };
-          profiles.alan = {
-            user = "alan";
-            path = deploy-rs.lib.${utils.lib.system.aarch64-linux}.activate.home-manager
-              self.homeConfigurations."alan@linde";
-          };
-        };
-        nodes.nanopi = {
-          hostname = "nanopi";
-          profiles.system = {
-            user = "root";
-            interactiveSudo = true;
-            path = deploy-rs.lib.${utils.lib.system.aarch64-linux}.activate.nixos
-              self.nixosConfigurations.nanopi;
-          };
-          profiles.alan = {
-            user = "alan";
-            path = deploy-rs.lib.${utils.lib.system.aarch64-linux}.activate.home-manager
-              self.homeConfigurations."alan@nanopi";
-          };
-        };
-      };
-    } // utils.lib.eachDefaultSystem (system:
-    let
-      pkgs = import nixpkgs { inherit system; };
-    in
-    {
-      devShells = {
-        default = pkgs.mkShell {
-          packages = [
-            deploy-rs.packages.${system}.default
-            agenix.packages.${system}.default
-          ];
-        };
-      };
-    });
-}
diff --git a/lib/caddy.nix b/lib/caddy.nix
new file mode 100644
index 00000000..42777eeb
--- /dev/null
+++ b/lib/caddy.nix
@@ -0,0 +1,47 @@
+{ lib
+, ...
+}:
+rec {
+  subValue = v:
+    if builtins.isList v
+    then
+      builtins.concatStringsSep " "
+        (builtins.map
+          (v:
+            (if lib.strings.hasPrefix "http" v
+            then v
+            else "'${v}'"))
+          v)
+    else toString v;
+
+  headerValue = sep: val:
+    if builtins.isAttrs val
+    then
+      builtins.concatStringsSep "; "
+        (lib.attrsets.mapAttrsToList
+          (k: v:
+            if builtins.isBool v then k else
+            "${k}${sep}${subValue v}"
+          )
+          val)
+    else toString val;
+  genHeader = header:
+    let
+      sep = if header == "content-security-policy" then " " else "=";
+    in
+    value: "${header} \"${headerValue sep value}\"";
+
+  headers = matcher: headers: ''
+    header ${matcher} {
+      ${builtins.concatStringsSep "\n"
+        (lib.attrsets.mapAttrsToList genHeader headers)}
+    }
+  '';
+  security-headers = { matcher ? "", overrides ? { } }: headers matcher ({
+    strict-transport-security = {
+      max-age = 2 * 365 * 24 * 60 * 60;
+    };
+    x-content-type-options = "nosniff";
+    x-frame-options = "DENY";
+  } // overrides);
+}
diff --git a/lib/default.nix b/lib/default.nix
new file mode 100644
index 00000000..2cbaf55c
--- /dev/null
+++ b/lib/default.nix
@@ -0,0 +1,25 @@
+{ pkgs }:
+with builtins;
+let
+  inherit (pkgs) lib;
+in
+rec {
+  importPathStore = p: "${append ../. "/${p}"}";
+
+  mkPathable = s: toString (
+    if lib.path.subpath.isValid s
+    then (lib.path.append ../. s)
+    else s
+  );
+
+  kvPath = k: v: "${k}=${v}";
+
+  fromSources = sources:
+    lib.attrsets.mapAttrs
+      (k: v: v.outPath)
+      sources;
+
+  mkNixPath = sources: lib.attrsets.mapAttrsToList
+    (k: v: kvPath k (mkPathable v))
+    sources;
+}
diff --git a/npins/default.nix b/npins/default.nix
new file mode 100644
index 00000000..5e7d086e
--- /dev/null
+++ b/npins/default.nix
@@ -0,0 +1,80 @@
+# Generated by npins. Do not modify; will be overwritten regularly
+let
+  data = builtins.fromJSON (builtins.readFile ./sources.json);
+  version = data.version;
+
+  mkSource =
+    spec:
+    assert spec ? type;
+    let
+      path =
+        if spec.type == "Git" then
+          mkGitSource spec
+        else if spec.type == "GitRelease" then
+          mkGitSource spec
+        else if spec.type == "PyPi" then
+          mkPyPiSource spec
+        else if spec.type == "Channel" then
+          mkChannelSource spec
+        else
+          builtins.throw "Unknown source type ${spec.type}";
+    in
+    spec // { outPath = path; };
+
+  mkGitSource =
+    {
+      repository,
+      revision,
+      url ? null,
+      hash,
+      branch ? null,
+      ...
+    }:
+    assert repository ? type;
+    # At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository
+    # In the latter case, there we will always be an url to the tarball
+    if url != null then
+      (builtins.fetchTarball {
+        inherit url;
+        sha256 = hash; # FIXME: check nix version & use SRI hashes
+      })
+    else
+      assert repository.type == "Git";
+      let
+        urlToName =
+          url: rev:
+          let
+            matched = builtins.match "^.*/([^/]*)(\\.git)?$" repository.url;
+
+            short = builtins.substring 0 7 rev;
+
+            appendShort = if (builtins.match "[a-f0-9]*" rev) != null then "-${short}" else "";
+          in
+          "${if matched == null then "source" else builtins.head matched}${appendShort}";
+        name = urlToName repository.url revision;
+      in
+      builtins.fetchGit {
+        url = repository.url;
+        rev = revision;
+        inherit name;
+        # hash = hash;
+      };
+
+  mkPyPiSource =
+    { url, hash, ... }:
+    builtins.fetchurl {
+      inherit url;
+      sha256 = hash;
+    };
+
+  mkChannelSource =
+    { url, hash, ... }:
+    builtins.fetchTarball {
+      inherit url;
+      sha256 = hash;
+    };
+in
+if version == 3 then
+  builtins.mapAttrs (_: mkSource) data.pins
+else
+  throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`"
diff --git a/npins/sources.json b/npins/sources.json
new file mode 100644
index 00000000..d7b43684
--- /dev/null
+++ b/npins/sources.json
@@ -0,0 +1,95 @@
+{
+  "pins": {
+    "agenix": {
+      "type": "Git",
+      "repository": {
+        "type": "GitHub",
+        "owner": "ryantm",
+        "repo": "agenix"
+      },
+      "branch": "main",
+      "revision": "3a56735779db467538fb2e577eda28a9daacaca6",
+      "url": "https://github.com/ryantm/agenix/archive/3a56735779db467538fb2e577eda28a9daacaca6.tar.gz",
+      "hash": "1h66zapc6im07k3kcgvhy3lhzahb70vd6m2ijhz4i0v6mn5l3fk9"
+    },
+    "darwin": {
+      "type": "Git",
+      "repository": {
+        "type": "GitHub",
+        "owner": "lnl7",
+        "repo": "nix-darwin"
+      },
+      "branch": "master",
+      "revision": "50581970f37f06a4719001735828519925ef8310",
+      "url": "https://github.com/lnl7/nix-darwin/archive/50581970f37f06a4719001735828519925ef8310.tar.gz",
+      "hash": "1c2zihl124j7xz5fyhkjvcpabyrvs1qgix1fzr0fc002mnkcrf13"
+    },
+    "emacs-overlay": {
+      "type": "Git",
+      "repository": {
+        "type": "GitHub",
+        "owner": "nix-community",
+        "repo": "emacs-overlay"
+      },
+      "branch": "master",
+      "revision": "dc376600483aae0272de58ea9b2d06c9f4e132eb",
+      "url": "https://github.com/nix-community/emacs-overlay/archive/dc376600483aae0272de58ea9b2d06c9f4e132eb.tar.gz",
+      "hash": "15b1w9vg1g7zih56lh198yi8si6m7b4yxxfsn4dxffdzal415vbl"
+    },
+    "home-manager": {
+      "type": "Git",
+      "repository": {
+        "type": "GitHub",
+        "owner": "nix-community",
+        "repo": "home-manager"
+      },
+      "branch": "master",
+      "revision": "cd886711998fe5d9ff7979fdd4b4cbd17b1f1511",
+      "url": "https://github.com/nix-community/home-manager/archive/cd886711998fe5d9ff7979fdd4b4cbd17b1f1511.tar.gz",
+      "hash": "1kvww9d28nlz2gawbrasvgpk172vzxlxdbhh1b8c41m1x7rrvqk8"
+    },
+    "nix-index-database": {
+      "type": "Git",
+      "repository": {
+        "type": "GitHub",
+        "owner": "Mic92",
+        "repo": "nix-index-database"
+      },
+      "branch": "main",
+      "revision": "afc8f5a6a2c00a89a6d6bdcaf4157797960f10f7",
+      "url": "https://github.com/Mic92/nix-index-database/archive/afc8f5a6a2c00a89a6d6bdcaf4157797960f10f7.tar.gz",
+      "hash": "0kh8nyxp44wv5i37jwylf6vvjcwnr6zfqavxajb3zglhkrdfija6"
+    },
+    "nixos-hardware": {
+      "type": "Git",
+      "repository": {
+        "type": "GitHub",
+        "owner": "NixOS",
+        "repo": "nixos-hardware"
+      },
+      "branch": "master",
+      "revision": "e8232c132a95ddc62df9d404120ad4ff53862910",
+      "url": "https://github.com/NixOS/nixos-hardware/archive/e8232c132a95ddc62df9d404120ad4ff53862910.tar.gz",
+      "hash": "0w6d2nk498i0hqiimfxhxj7i9zhija9sybnhbyknwl7pkc4b7lkp"
+    },
+    "nixpkgs": {
+      "type": "Channel",
+      "name": "nixos-unstable",
+      "url": "https://releases.nixos.org/nixos/unstable/nixos-24.11pre642660.a71e967ef369/nixexprs.tar.xz",
+      "hash": "0km1smh73aqa9syc3pd6f8l8rz6jb87x8a4qx7d6x1b8932z3is2"
+    },
+    "nur": {
+      "type": "Git",
+      "repository": {
+        "type": "GitHub",
+        "owner": "nix-community",
+        "repo": "NUR"
+      },
+      "branch": "master",
+      "revision": "88407857c90e39f2654a0ef347c2c920c25f453c",
+      "url": "https://github.com/nix-community/NUR/archive/88407857c90e39f2654a0ef347c2c920c25f453c.tar.gz",
+      "hash": "1gn8lbgrcqx4i13p2jjqqp3n2pkrmxn8rliz53x0vdmlg4vvzrjk"
+    }
+  },
+  "version": 3
+}
\ No newline at end of file
diff --git a/overlays/emacs.nix b/overlays/emacs.nix
new file mode 100644
index 00000000..ded36707
--- /dev/null
+++ b/overlays/emacs.nix
@@ -0,0 +1 @@
+import <emacs-overlay>
diff --git a/overlays/extra-packages.nix b/overlays/extra-packages.nix
index 0e0451d0..6c64c24b 100644
--- a/overlays/extra-packages.nix
+++ b/overlays/extra-packages.nix
@@ -13,14 +13,4 @@ self: super: {
         '';
       };
   });
-  enchant = super.enchant.overrideAttrs (old: {
-    configureFlags = old.configureFlags ++ [ "--without-hspell" ]
-      # builtins.filter (c: c != "--with-hspell") old.configureFlags
-      ++ self.lib.optional super.stdenv.isDarwin "--with-applespell"
-    ;
-    buildInputs =
-      builtins.filter (c: c.name != "hspell") old.buildInputs
-      ++ self.lib.optionals super.stdenv.isDarwin (with super.darwin.apple_sdk.frameworks; [ Cocoa ]);
-    propagatedBuildInputs = builtins.filter (c: c.name != "hspell") old.propagatedBuildInputs;
-  });
 }
diff --git a/overlays/htmlformat/default.nix b/overlays/htmlformat/default.nix
deleted file mode 100644
index 93292c76..00000000
--- a/overlays/htmlformat/default.nix
+++ /dev/null
@@ -1,3 +0,0 @@
-self: super: {
-  htmlformat = super.callPackage ./htmlformat.nix { };
-}
diff --git a/overlays/htmlformat/htmlformat.nix b/overlays/htmlformat/htmlformat.nix
deleted file mode 100644
index aa124ee5..00000000
--- a/overlays/htmlformat/htmlformat.nix
+++ /dev/null
@@ -1,28 +0,0 @@
-{ lib
-, buildGoModule
-, fetchFromGitHub
-}:
-
-buildGoModule rec {
-  pname = "htmlformat";
-  version = "unstable-2023-11-08";
-
-  src = fetchFromGitHub {
-    owner = "a-h";
-    repo = "htmlformat";
-    rev = "5bd994fe268e4d505a9793143fa85414c7d50887";
-    hash = "sha256-YSl9GsXhc0L2oKGZLwwjUtpe5W6ra6kk74zvQdsDCMU=";
-  };
-
-  vendorHash = "sha256-uVfh1pPhfj6AyQDqFd1EDWshuyDRvbMDZj3SN5tCS2w=";
-
-  ldflags = [ "-s" "-w" ];
-
-  meta = with lib; {
-    description = "Htmlformat";
-    homepage = "https://github.com/a-h/htmlformat";
-    license = licenses.mit;
-    maintainers = with maintainers; [ alanpearce ];
-    mainProgram = "htmlformat";
-  };
-}
diff --git a/overlays/node-packages/default.nix b/overlays/node-packages/default.nix
index b89a10a3..9c8720ff 100644
--- a/overlays/node-packages/default.nix
+++ b/overlays/node-packages/default.nix
@@ -1,8 +1,4 @@
 self: super: {
-  nodePackages = super.nodePackages.extend
-    (final: prev:
-      super.callPackage ./prettier-plugin-go-template { } // { }
-    );
   prettierd = super.prettierd.overrideAttrs (old: {
     patches = [ ./prettierd.patch ];
   });
diff --git a/overlays/node-packages/prettier-plugin-go-template/default.nix b/overlays/node-packages/prettier-plugin-go-template/default.nix
deleted file mode 100644
index d9c0daef..00000000
--- a/overlays/node-packages/prettier-plugin-go-template/default.nix
+++ /dev/null
@@ -1,17 +0,0 @@
-# This file has been generated by node2nix 1.11.1. Do not edit!
-
-{pkgs ? import <nixpkgs> {
-    inherit system;
-  }, system ? builtins.currentSystem, nodejs ? pkgs."nodejs_18"}:
-
-let
-  nodeEnv = import ./node-env.nix {
-    inherit (pkgs) stdenv lib python2 runCommand writeTextFile writeShellScript;
-    inherit pkgs nodejs;
-    libtool = if pkgs.stdenv.isDarwin then pkgs.darwin.cctools else null;
-  };
-in
-import ./node-packages.nix {
-  inherit (pkgs) fetchurl nix-gitignore stdenv lib fetchgit;
-  inherit nodeEnv;
-}
diff --git a/overlays/node-packages/prettier-plugin-go-template/node-env.nix b/overlays/node-packages/prettier-plugin-go-template/node-env.nix
deleted file mode 100644
index bc1e3662..00000000
--- a/overlays/node-packages/prettier-plugin-go-template/node-env.nix
+++ /dev/null
@@ -1,689 +0,0 @@
-# This file originates from node2nix
-
-{lib, stdenv, nodejs, python2, pkgs, libtool, runCommand, writeTextFile, writeShellScript}:
-
-let
-  # Workaround to cope with utillinux in Nixpkgs 20.09 and util-linux in Nixpkgs master
-  utillinux = if pkgs ? utillinux then pkgs.utillinux else pkgs.util-linux;
-
-  python = if nodejs ? python then nodejs.python else python2;
-
-  # Create a tar wrapper that filters all the 'Ignoring unknown extended header keyword' noise
-  tarWrapper = runCommand "tarWrapper" {} ''
-    mkdir -p $out/bin
-
-    cat > $out/bin/tar <<EOF
-    #! ${stdenv.shell} -e
-    $(type -p tar) "\$@" --warning=no-unknown-keyword --delay-directory-restore
-    EOF
-
-    chmod +x $out/bin/tar
-  '';
-
-  # Function that generates a TGZ file from a NPM project
-  buildNodeSourceDist =
-    { name, version, src, ... }:
-
-    stdenv.mkDerivation {
-      name = "node-tarball-${name}-${version}";
-      inherit src;
-      buildInputs = [ nodejs ];
-      buildPhase = ''
-        export HOME=$TMPDIR
-        tgzFile=$(npm pack | tail -n 1) # Hooks to the pack command will add output (https://docs.npmjs.com/misc/scripts)
-      '';
-      installPhase = ''
-        mkdir -p $out/tarballs
-        mv $tgzFile $out/tarballs
-        mkdir -p $out/nix-support
-        echo "file source-dist $out/tarballs/$tgzFile" >> $out/nix-support/hydra-build-products
-      '';
-    };
-
-  # Common shell logic
-  installPackage = writeShellScript "install-package" ''
-    installPackage() {
-      local packageName=$1 src=$2
-
-      local strippedName
-
-      local DIR=$PWD
-      cd $TMPDIR
-
-      unpackFile $src
-
-      # Make the base dir in which the target dependency resides first
-      mkdir -p "$(dirname "$DIR/$packageName")"
-
-      if [ -f "$src" ]
-      then
-          # Figure out what directory has been unpacked
-          packageDir="$(find . -maxdepth 1 -type d | tail -1)"
-
-          # Restore write permissions to make building work
-          find "$packageDir" -type d -exec chmod u+x {} \;
-          chmod -R u+w "$packageDir"
-
-          # Move the extracted tarball into the output folder
-          mv "$packageDir" "$DIR/$packageName"
-      elif [ -d "$src" ]
-      then
-          # Get a stripped name (without hash) of the source directory.
-          # On old nixpkgs it's already set internally.
-          if [ -z "$strippedName" ]
-          then
-              strippedName="$(stripHash $src)"
-          fi
-
-          # Restore write permissions to make building work
-          chmod -R u+w "$strippedName"
-
-          # Move the extracted directory into the output folder
-          mv "$strippedName" "$DIR/$packageName"
-      fi
-
-      # Change to the package directory to install dependencies
-      cd "$DIR/$packageName"
-    }
-  '';
-
-  # Bundle the dependencies of the package
-  #
-  # Only include dependencies if they don't exist. They may also be bundled in the package.
-  includeDependencies = {dependencies}:
-    lib.optionalString (dependencies != []) (
-      ''
-        mkdir -p node_modules
-        cd node_modules
-      ''
-      + (lib.concatMapStrings (dependency:
-        ''
-          if [ ! -e "${dependency.packageName}" ]; then
-              ${composePackage dependency}
-          fi
-        ''
-      ) dependencies)
-      + ''
-        cd ..
-      ''
-    );
-
-  # Recursively composes the dependencies of a package
-  composePackage = { name, packageName, src, dependencies ? [], ... }@args:
-    builtins.addErrorContext "while evaluating node package '${packageName}'" ''
-      installPackage "${packageName}" "${src}"
-      ${includeDependencies { inherit dependencies; }}
-      cd ..
-      ${lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."}
-    '';
-
-  pinpointDependencies = {dependencies, production}:
-    let
-      pinpointDependenciesFromPackageJSON = writeTextFile {
-        name = "pinpointDependencies.js";
-        text = ''
-          var fs = require('fs');
-          var path = require('path');
-
-          function resolveDependencyVersion(location, name) {
-              if(location == process.env['NIX_STORE']) {
-                  return null;
-              } else {
-                  var dependencyPackageJSON = path.join(location, "node_modules", name, "package.json");
-
-                  if(fs.existsSync(dependencyPackageJSON)) {
-                      var dependencyPackageObj = JSON.parse(fs.readFileSync(dependencyPackageJSON));
-
-                      if(dependencyPackageObj.name == name) {
-                          return dependencyPackageObj.version;
-                      }
-                  } else {
-                      return resolveDependencyVersion(path.resolve(location, ".."), name);
-                  }
-              }
-          }
-
-          function replaceDependencies(dependencies) {
-              if(typeof dependencies == "object" && dependencies !== null) {
-                  for(var dependency in dependencies) {
-                      var resolvedVersion = resolveDependencyVersion(process.cwd(), dependency);
-
-                      if(resolvedVersion === null) {
-                          process.stderr.write("WARNING: cannot pinpoint dependency: "+dependency+", context: "+process.cwd()+"\n");
-                      } else {
-                          dependencies[dependency] = resolvedVersion;
-                      }
-                  }
-              }
-          }
-
-          /* Read the package.json configuration */
-          var packageObj = JSON.parse(fs.readFileSync('./package.json'));
-
-          /* Pinpoint all dependencies */
-          replaceDependencies(packageObj.dependencies);
-          if(process.argv[2] == "development") {
-              replaceDependencies(packageObj.devDependencies);
-          }
-          else {
-              packageObj.devDependencies = {};
-          }
-          replaceDependencies(packageObj.optionalDependencies);
-          replaceDependencies(packageObj.peerDependencies);
-
-          /* Write the fixed package.json file */
-          fs.writeFileSync("package.json", JSON.stringify(packageObj, null, 2));
-        '';
-      };
-    in
-    ''
-      node ${pinpointDependenciesFromPackageJSON} ${if production then "production" else "development"}
-
-      ${lib.optionalString (dependencies != [])
-        ''
-          if [ -d node_modules ]
-          then
-              cd node_modules
-              ${lib.concatMapStrings (dependency: pinpointDependenciesOfPackage dependency) dependencies}
-              cd ..
-          fi
-        ''}
-    '';
-
-  # Recursively traverses all dependencies of a package and pinpoints all
-  # dependencies in the package.json file to the versions that are actually
-  # being used.
-
-  pinpointDependenciesOfPackage = { packageName, dependencies ? [], production ? true, ... }@args:
-    ''
-      if [ -d "${packageName}" ]
-      then
-          cd "${packageName}"
-          ${pinpointDependencies { inherit dependencies production; }}
-          cd ..
-          ${lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."}
-      fi
-    '';
-
-  # Extract the Node.js source code which is used to compile packages with
-  # native bindings
-  nodeSources = runCommand "node-sources" {} ''
-    tar --no-same-owner --no-same-permissions -xf ${nodejs.src}
-    mv node-* $out
-  '';
-
-  # Script that adds _integrity fields to all package.json files to prevent NPM from consulting the cache (that is empty)
-  addIntegrityFieldsScript = writeTextFile {
-    name = "addintegrityfields.js";
-    text = ''
-      var fs = require('fs');
-      var path = require('path');
-
-      function augmentDependencies(baseDir, dependencies) {
-          for(var dependencyName in dependencies) {
-              var dependency = dependencies[dependencyName];
-
-              // Open package.json and augment metadata fields
-              var packageJSONDir = path.join(baseDir, "node_modules", dependencyName);
-              var packageJSONPath = path.join(packageJSONDir, "package.json");
-
-              if(fs.existsSync(packageJSONPath)) { // Only augment packages that exist. Sometimes we may have production installs in which development dependencies can be ignored
-                  console.log("Adding metadata fields to: "+packageJSONPath);
-                  var packageObj = JSON.parse(fs.readFileSync(packageJSONPath));
-
-                  if(dependency.integrity) {
-                      packageObj["_integrity"] = dependency.integrity;
-                  } else {
-                      packageObj["_integrity"] = "sha1-000000000000000000000000000="; // When no _integrity string has been provided (e.g. by Git dependencies), add a dummy one. It does not seem to harm and it bypasses downloads.
-                  }
-
-                  if(dependency.resolved) {
-                      packageObj["_resolved"] = dependency.resolved; // Adopt the resolved property if one has been provided
-                  } else {
-                      packageObj["_resolved"] = dependency.version; // Set the resolved version to the version identifier. This prevents NPM from cloning Git repositories.
-                  }
-
-                  if(dependency.from !== undefined) { // Adopt from property if one has been provided
-                      packageObj["_from"] = dependency.from;
-                  }
-
-                  fs.writeFileSync(packageJSONPath, JSON.stringify(packageObj, null, 2));
-              }
-
-              // Augment transitive dependencies
-              if(dependency.dependencies !== undefined) {
-                  augmentDependencies(packageJSONDir, dependency.dependencies);
-              }
-          }
-      }
-
-      if(fs.existsSync("./package-lock.json")) {
-          var packageLock = JSON.parse(fs.readFileSync("./package-lock.json"));
-
-          if(![1, 2].includes(packageLock.lockfileVersion)) {
-            process.stderr.write("Sorry, I only understand lock file versions 1 and 2!\n");
-            process.exit(1);
-          }
-
-          if(packageLock.dependencies !== undefined) {
-              augmentDependencies(".", packageLock.dependencies);
-          }
-      }
-    '';
-  };
-
-  # Reconstructs a package-lock file from the node_modules/ folder structure and package.json files with dummy sha1 hashes
-  reconstructPackageLock = writeTextFile {
-    name = "reconstructpackagelock.js";
-    text = ''
-      var fs = require('fs');
-      var path = require('path');
-
-      var packageObj = JSON.parse(fs.readFileSync("package.json"));
-
-      var lockObj = {
-          name: packageObj.name,
-          version: packageObj.version,
-          lockfileVersion: 2,
-          requires: true,
-          packages: {
-              "": {
-                  name: packageObj.name,
-                  version: packageObj.version,
-                  license: packageObj.license,
-                  bin: packageObj.bin,
-                  dependencies: packageObj.dependencies,
-                  engines: packageObj.engines,
-                  optionalDependencies: packageObj.optionalDependencies
-              }
-          },
-          dependencies: {}
-      };
-
-      function augmentPackageJSON(filePath, packages, dependencies) {
-          var packageJSON = path.join(filePath, "package.json");
-          if(fs.existsSync(packageJSON)) {
-              var packageObj = JSON.parse(fs.readFileSync(packageJSON));
-              packages[filePath] = {
-                  version: packageObj.version,
-                  integrity: "sha1-000000000000000000000000000=",
-                  dependencies: packageObj.dependencies,
-                  engines: packageObj.engines,
-                  optionalDependencies: packageObj.optionalDependencies
-              };
-              dependencies[packageObj.name] = {
-                  version: packageObj.version,
-                  integrity: "sha1-000000000000000000000000000=",
-                  dependencies: {}
-              };
-              processDependencies(path.join(filePath, "node_modules"), packages, dependencies[packageObj.name].dependencies);
-          }
-      }
-
-      function processDependencies(dir, packages, dependencies) {
-          if(fs.existsSync(dir)) {
-              var files = fs.readdirSync(dir);
-
-              files.forEach(function(entry) {
-                  var filePath = path.join(dir, entry);
-                  var stats = fs.statSync(filePath);
-
-                  if(stats.isDirectory()) {
-                      if(entry.substr(0, 1) == "@") {
-                          // When we encounter a namespace folder, augment all packages belonging to the scope
-                          var pkgFiles = fs.readdirSync(filePath);
-
-                          pkgFiles.forEach(function(entry) {
-                              if(stats.isDirectory()) {
-                                  var pkgFilePath = path.join(filePath, entry);
-                                  augmentPackageJSON(pkgFilePath, packages, dependencies);
-                              }
-                          });
-                      } else {
-                          augmentPackageJSON(filePath, packages, dependencies);
-                      }
-                  }
-              });
-          }
-      }
-
-      processDependencies("node_modules", lockObj.packages, lockObj.dependencies);
-
-      fs.writeFileSync("package-lock.json", JSON.stringify(lockObj, null, 2));
-    '';
-  };
-
-  # Script that links bins defined in package.json to the node_modules bin directory
-  # NPM does not do this for top-level packages itself anymore as of v7
-  linkBinsScript = writeTextFile {
-    name = "linkbins.js";
-    text = ''
-      var fs = require('fs');
-      var path = require('path');
-
-      var packageObj = JSON.parse(fs.readFileSync("package.json"));
-
-      var nodeModules = Array(packageObj.name.split("/").length).fill("..").join(path.sep);
-
-      if(packageObj.bin !== undefined) {
-          fs.mkdirSync(path.join(nodeModules, ".bin"))
-
-          if(typeof packageObj.bin == "object") {
-              Object.keys(packageObj.bin).forEach(function(exe) {
-                  if(fs.existsSync(packageObj.bin[exe])) {
-                      console.log("linking bin '" + exe + "'");
-                      fs.symlinkSync(
-                          path.join("..", packageObj.name, packageObj.bin[exe]),
-                          path.join(nodeModules, ".bin", exe)
-                      );
-                  }
-                  else {
-                      console.log("skipping non-existent bin '" + exe + "'");
-                  }
-              })
-          }
-          else {
-              if(fs.existsSync(packageObj.bin)) {
-                  console.log("linking bin '" + packageObj.bin + "'");
-                  fs.symlinkSync(
-                      path.join("..", packageObj.name, packageObj.bin),
-                      path.join(nodeModules, ".bin", packageObj.name.split("/").pop())
-                  );
-              }
-              else {
-                  console.log("skipping non-existent bin '" + packageObj.bin + "'");
-              }
-          }
-      }
-      else if(packageObj.directories !== undefined && packageObj.directories.bin !== undefined) {
-          fs.mkdirSync(path.join(nodeModules, ".bin"))
-
-          fs.readdirSync(packageObj.directories.bin).forEach(function(exe) {
-              if(fs.existsSync(path.join(packageObj.directories.bin, exe))) {
-                  console.log("linking bin '" + exe + "'");
-                  fs.symlinkSync(
-                      path.join("..", packageObj.name, packageObj.directories.bin, exe),
-                      path.join(nodeModules, ".bin", exe)
-                  );
-              }
-              else {
-                  console.log("skipping non-existent bin '" + exe + "'");
-              }
-          })
-      }
-    '';
-  };
-
-  prepareAndInvokeNPM = {packageName, bypassCache, reconstructLock, npmFlags, production}:
-    let
-      forceOfflineFlag = if bypassCache then "--offline" else "--registry http://www.example.com";
-    in
-    ''
-        # Pinpoint the versions of all dependencies to the ones that are actually being used
-        echo "pinpointing versions of dependencies..."
-        source $pinpointDependenciesScriptPath
-
-        # Patch the shebangs of the bundled modules to prevent them from
-        # calling executables outside the Nix store as much as possible
-        patchShebangs .
-
-        # Deploy the Node.js package by running npm install. Since the
-        # dependencies have been provided already by ourselves, it should not
-        # attempt to install them again, which is good, because we want to make
-        # it Nix's responsibility. If it needs to install any dependencies
-        # anyway (e.g. because the dependency parameters are
-        # incomplete/incorrect), it fails.
-        #
-        # The other responsibilities of NPM are kept -- version checks, build
-        # steps, postprocessing etc.
-
-        export HOME=$TMPDIR
-        cd "${packageName}"
-        runHook preRebuild
-
-        ${lib.optionalString bypassCache ''
-          ${lib.optionalString reconstructLock ''
-            if [ -f package-lock.json ]
-            then
-                echo "WARNING: Reconstruct lock option enabled, but a lock file already exists!"
-                echo "This will most likely result in version mismatches! We will remove the lock file and regenerate it!"
-                rm package-lock.json
-            else
-                echo "No package-lock.json file found, reconstructing..."
-            fi
-
-            node ${reconstructPackageLock}
-          ''}
-
-          node ${addIntegrityFieldsScript}
-        ''}
-
-        npm ${forceOfflineFlag} --nodedir=${nodeSources} ${npmFlags} ${lib.optionalString production "--production"} rebuild
-
-        runHook postRebuild
-
-        if [ "''${dontNpmInstall-}" != "1" ]
-        then
-            # NPM tries to download packages even when they already exist if npm-shrinkwrap is used.
-            rm -f npm-shrinkwrap.json
-
-            npm ${forceOfflineFlag} --nodedir=${nodeSources} --no-bin-links --ignore-scripts ${npmFlags} ${lib.optionalString production "--production"} install
-        fi
-
-        # Link executables defined in package.json
-        node ${linkBinsScript}
-    '';
-
-  # Builds and composes an NPM package including all its dependencies
-  buildNodePackage =
-    { name
-    , packageName
-    , version ? null
-    , dependencies ? []
-    , buildInputs ? []
-    , production ? true
-    , npmFlags ? ""
-    , dontNpmInstall ? false
-    , bypassCache ? false
-    , reconstructLock ? false
-    , preRebuild ? ""
-    , dontStrip ? true
-    , unpackPhase ? "true"
-    , buildPhase ? "true"
-    , meta ? {}
-    , ... }@args:
-
-    let
-      extraArgs = removeAttrs args [ "name" "dependencies" "buildInputs" "dontStrip" "dontNpmInstall" "preRebuild" "unpackPhase" "buildPhase" "meta" ];
-    in
-    stdenv.mkDerivation ({
-      name = "${name}${if version == null then "" else "-${version}"}";
-      buildInputs = [ tarWrapper python nodejs ]
-        ++ lib.optional (stdenv.isLinux) utillinux
-        ++ lib.optional (stdenv.isDarwin) libtool
-        ++ buildInputs;
-
-      inherit nodejs;
-
-      inherit dontStrip; # Stripping may fail a build for some package deployments
-      inherit dontNpmInstall preRebuild unpackPhase buildPhase;
-
-      compositionScript = composePackage args;
-      pinpointDependenciesScript = pinpointDependenciesOfPackage args;
-
-      passAsFile = [ "compositionScript" "pinpointDependenciesScript" ];
-
-      installPhase = ''
-        source ${installPackage}
-
-        # Create and enter a root node_modules/ folder
-        mkdir -p $out/lib/node_modules
-        cd $out/lib/node_modules
-
-        # Compose the package and all its dependencies
-        source $compositionScriptPath
-
-        ${prepareAndInvokeNPM { inherit packageName bypassCache reconstructLock npmFlags production; }}
-
-        # Create symlink to the deployed executable folder, if applicable
-        if [ -d "$out/lib/node_modules/.bin" ]
-        then
-            ln -s $out/lib/node_modules/.bin $out/bin
-
-            # Fixup all executables
-            ls $out/bin/* | while read i
-            do
-                file="$(readlink -f "$i")"
-                chmod u+rwx "$file"
-                if isScript "$file"
-                then
-                    sed -i 's/\r$//' "$file"  # convert crlf to lf
-                fi
-            done
-        fi
-
-        # Create symlinks to the deployed manual page folders, if applicable
-        if [ -d "$out/lib/node_modules/${packageName}/man" ]
-        then
-            mkdir -p $out/share
-            for dir in "$out/lib/node_modules/${packageName}/man/"*
-            do
-                mkdir -p $out/share/man/$(basename "$dir")
-                for page in "$dir"/*
-                do
-                    ln -s $page $out/share/man/$(basename "$dir")
-                done
-            done
-        fi
-
-        # Run post install hook, if provided
-        runHook postInstall
-      '';
-
-      meta = {
-        # default to Node.js' platforms
-        platforms = nodejs.meta.platforms;
-      } // meta;
-    } // extraArgs);
-
-  # Builds a node environment (a node_modules folder and a set of binaries)
-  buildNodeDependencies =
-    { name
-    , packageName
-    , version ? null
-    , src
-    , dependencies ? []
-    , buildInputs ? []
-    , production ? true
-    , npmFlags ? ""
-    , dontNpmInstall ? false
-    , bypassCache ? false
-    , reconstructLock ? false
-    , dontStrip ? true
-    , unpackPhase ? "true"
-    , buildPhase ? "true"
-    , ... }@args:
-
-    let
-      extraArgs = removeAttrs args [ "name" "dependencies" "buildInputs" ];
-    in
-      stdenv.mkDerivation ({
-        name = "node-dependencies-${name}${if version == null then "" else "-${version}"}";
-
-        buildInputs = [ tarWrapper python nodejs ]
-          ++ lib.optional (stdenv.isLinux) utillinux
-          ++ lib.optional (stdenv.isDarwin) libtool
-          ++ buildInputs;
-
-        inherit dontStrip; # Stripping may fail a build for some package deployments
-        inherit dontNpmInstall unpackPhase buildPhase;
-
-        includeScript = includeDependencies { inherit dependencies; };
-        pinpointDependenciesScript = pinpointDependenciesOfPackage args;
-
-        passAsFile = [ "includeScript" "pinpointDependenciesScript" ];
-
-        installPhase = ''
-          source ${installPackage}
-
-          mkdir -p $out/${packageName}
-          cd $out/${packageName}
-
-          source $includeScriptPath
-
-          # Create fake package.json to make the npm commands work properly
-          cp ${src}/package.json .
-          chmod 644 package.json
-          ${lib.optionalString bypassCache ''
-            if [ -f ${src}/package-lock.json ]
-            then
-                cp ${src}/package-lock.json .
-                chmod 644 package-lock.json
-            fi
-          ''}
-
-          # Go to the parent folder to make sure that all packages are pinpointed
-          cd ..
-          ${lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."}
-
-          ${prepareAndInvokeNPM { inherit packageName bypassCache reconstructLock npmFlags production; }}
-
-          # Expose the executables that were installed
-          cd ..
-          ${lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."}
-
-          mv ${packageName} lib
-          ln -s $out/lib/node_modules/.bin $out/bin
-        '';
-      } // extraArgs);
-
-  # Builds a development shell
-  buildNodeShell =
-    { name
-    , packageName
-    , version ? null
-    , src
-    , dependencies ? []
-    , buildInputs ? []
-    , production ? true
-    , npmFlags ? ""
-    , dontNpmInstall ? false
-    , bypassCache ? false
-    , reconstructLock ? false
-    , dontStrip ? true
-    , unpackPhase ? "true"
-    , buildPhase ? "true"
-    , ... }@args:
-
-    let
-      nodeDependencies = buildNodeDependencies args;
-      extraArgs = removeAttrs args [ "name" "dependencies" "buildInputs" "dontStrip" "dontNpmInstall" "unpackPhase" "buildPhase" ];
-    in
-    stdenv.mkDerivation ({
-      name = "node-shell-${name}${if version == null then "" else "-${version}"}";
-
-      buildInputs = [ python nodejs ] ++ lib.optional (stdenv.isLinux) utillinux ++ buildInputs;
-      buildCommand = ''
-        mkdir -p $out/bin
-        cat > $out/bin/shell <<EOF
-        #! ${stdenv.shell} -e
-        $shellHook
-        exec ${stdenv.shell}
-        EOF
-        chmod +x $out/bin/shell
-      '';
-
-      # Provide the dependencies in a development shell through the NODE_PATH environment variable
-      inherit nodeDependencies;
-      shellHook = lib.optionalString (dependencies != []) ''
-        export NODE_PATH=${nodeDependencies}/lib/node_modules
-        export PATH="${nodeDependencies}/bin:$PATH"
-      '';
-    } // extraArgs);
-in
-{
-  buildNodeSourceDist = lib.makeOverridable buildNodeSourceDist;
-  buildNodePackage = lib.makeOverridable buildNodePackage;
-  buildNodeDependencies = lib.makeOverridable buildNodeDependencies;
-  buildNodeShell = lib.makeOverridable buildNodeShell;
-}
diff --git a/overlays/node-packages/prettier-plugin-go-template/node-packages.json b/overlays/node-packages/prettier-plugin-go-template/node-packages.json
deleted file mode 100644
index 64857b1e..00000000
--- a/overlays/node-packages/prettier-plugin-go-template/node-packages.json
+++ /dev/null
@@ -1,3 +0,0 @@
-[
-  "prettier-plugin-go-template"
-]
diff --git a/overlays/node-packages/prettier-plugin-go-template/node-packages.nix b/overlays/node-packages/prettier-plugin-go-template/node-packages.nix
deleted file mode 100644
index 449c93c8..00000000
--- a/overlays/node-packages/prettier-plugin-go-template/node-packages.nix
+++ /dev/null
@@ -1,50 +0,0 @@
-# This file has been generated by node2nix 1.11.1. Do not edit!
-
-{nodeEnv, fetchurl, fetchgit, nix-gitignore, stdenv, lib, globalBuildInputs ? []}:
-
-let
-  sources = {
-    "prettier-3.2.5" = {
-      name = "prettier";
-      packageName = "prettier";
-      version = "3.2.5";
-      src = fetchurl {
-        url = "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz";
-        sha512 = "3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==";
-      };
-    };
-    "ulid-2.3.0" = {
-      name = "ulid";
-      packageName = "ulid";
-      version = "2.3.0";
-      src = fetchurl {
-        url = "https://registry.npmjs.org/ulid/-/ulid-2.3.0.tgz";
-        sha512 = "keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==";
-      };
-    };
-  };
-in
-{
-  prettier-plugin-go-template = nodeEnv.buildNodePackage {
-    name = "prettier-plugin-go-template";
-    packageName = "prettier-plugin-go-template";
-    version = "0.0.15";
-    src = fetchurl {
-      url = "https://registry.npmjs.org/prettier-plugin-go-template/-/prettier-plugin-go-template-0.0.15.tgz";
-      sha512 = "WqU92E1NokWYNZ9mLE6ijoRg6LtIGdLMePt2C7UBDjXeDH9okcRI3zRqtnWR4s5AloiqyvZ66jNBAa9tmRY5EQ==";
-    };
-    dependencies = [
-      sources."prettier-3.2.5"
-      sources."ulid-2.3.0"
-    ];
-    buildInputs = globalBuildInputs;
-    meta = {
-      description = "Prettier plugin for formatting Go & GoHugo templates.";
-      homepage = "https://github.com/NiklasPor/prettier-plugin-go-template#readme";
-      license = "MIT";
-    };
-    production = true;
-    bypassCache = true;
-    reconstructLock = true;
-  };
-}
diff --git a/overlays/nur.nix b/overlays/nur.nix
new file mode 100644
index 00000000..6f34f2aa
--- /dev/null
+++ b/overlays/nur.nix
@@ -0,0 +1,3 @@
+self: super: {
+  nur = import <nur> { pkgs = self; };
+}
diff --git a/overlays/personal.nix b/overlays/personal.nix
new file mode 100644
index 00000000..9eae64c4
--- /dev/null
+++ b/overlays/personal.nix
@@ -0,0 +1,13 @@
+self: super:
+let
+  personal = import <personal> {
+    pkgs = self;
+  };
+in
+{
+  inherit personal;
+  enchant = personal.enchant-configurable.override {
+    withHspell = false;
+    withAspell = false;
+  };
+}
diff --git a/packages b/packages
new file mode 160000
+Subproject 1155711309d6e54ec6d6cfdbaee2160bab94000
diff --git a/pin.nix b/pin.nix
deleted file mode 100644
index ab7c8b3e..00000000
--- a/pin.nix
+++ /dev/null
@@ -1,11 +0,0 @@
-{ inputs, ... }:
-let
-  inherit (inputs) nixpkgs;
-in
-{
-  nix = {
-    registry = {
-      nixpkgs.flake = nixpkgs;
-    };
-  };
-}
diff --git a/private b/private
-Subproject 5264068d4f5b85bc3b08e4b86ca46e49d8a411e
+Subproject 442aa5d4590153b95817b1f102510e87639d60a
diff --git a/secrets/dex.age b/secrets/dex.age
new file mode 100644
index 00000000..99d463b4
--- /dev/null
+++ b/secrets/dex.age
Binary files differdiff --git a/secrets/golink.age b/secrets/golink.age
new file mode 100644
index 00000000..c7039771
--- /dev/null
+++ b/secrets/golink.age
@@ -0,0 +1,8 @@
+age-encryption.org/v1
+-> ssh-ed25519 cvV2sw Afv1D+MaopWkuWEKI0t0zp4qlcam7bBUtWHq7CwABg8
+T49GUjm0yIB8L93giMNNQm56goIlyUKw81Awem7LGBE
+-> piv-p256 u9NeZg Aym6b0XVHJFxEaH1bi82HjDGpbId6LjDzeANPlP1q75N
+euudxSXIVs2mTeP8DKe6+8ixQb5doTwp3HR7eyfCsCk
+--- c0wvkDM428LPfxbK7xL22xMmUh9OaEXM+gEImi6FVJg
+
¢
+h׃Uß…?•“Í/3
;!Ç»¤îP‰Ù'.‚¾ÕrÄÁætæ±\Üì‹©:¤	¶uèƒÌ9ùY‚y˜_xº€9	Œ.ÇO˜£#פö=%#ìû£,MP?®Ù£
\ No newline at end of file
diff --git a/secrets/secrets.nix b/secrets/secrets.nix
index 86d1062c..75c174d1 100644
--- a/secrets/secrets.nix
+++ b/secrets/secrets.nix
@@ -16,6 +16,8 @@ let
     binarycache = [ linde ];
     paperless = [ linde ];
     powerdns = [ linde ];
+    dex = [ linde ];
+    golink = [ linde ];
 
     dyndns = [ nanopi ];
     syncthing = [ nanopi ];
diff --git a/shell.nix b/shell.nix
index d2c4c458..b7d05d66 100644
--- a/shell.nix
+++ b/shell.nix
@@ -1,3 +1,20 @@
-{ system ? builtins.currentSystem }:
+let
+  inherit (import ./sources.nix) nixPath sources;
 
-(builtins.getFlake (toString ./.)).devShells.${system}.default
+  pkgs = import sources.nixpkgs { };
+in
+pkgs.mkShell
+{
+
+  name = "nixfiles-shell";
+
+  buildInputs = with pkgs; [
+    npins
+    (pkgs.callPackage "${sources.agenix}/pkgs/agenix.nix" { })
+    (import sources.home-manager { inherit pkgs; }).home-manager
+  ];
+
+  shellHook = ''
+    export NIX_PATH="${builtins.concatStringsSep ":" nixPath}";
+  '';
+}
diff --git a/sources.nix b/sources.nix
new file mode 100644
index 00000000..8bcc5a4f
--- /dev/null
+++ b/sources.nix
@@ -0,0 +1,15 @@
+let
+  sources = import ./npins;
+  pkgs = import sources.nixpkgs { };
+  inherit (import ./lib { inherit pkgs; }) mkNixPath fromSources;
+
+  allSources = {
+    personal = ./packages;
+    nixpkgs-overlays = ./overlays;
+    private = ./private;
+  } // (fromSources sources);
+in
+{
+  sources = allSources;
+  nixPath = mkNixPath allSources;
+}
diff --git a/system/.gitignore b/system/.gitignore
deleted file mode 100644
index 9e7db1a3..00000000
--- a/system/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-/private/
-/hardware-configuration.nix
-/configuration.nix
-/darwin-configuration.nix
-/cachix
-/result
diff --git a/system/autorandr/docked-close/block b/system/autorandr/docked-close/block
deleted file mode 100755
index 782c3a74..00000000
--- a/system/autorandr/docked-close/block
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-
-exec grep -vq close /proc/acpi/button/lid/LID/state
\ No newline at end of file
diff --git a/system/autorandr/docked-close/config b/system/autorandr/docked-close/config
deleted file mode 100644
index d8ce5031..00000000
--- a/system/autorandr/docked-close/config
+++ /dev/null
@@ -1,23 +0,0 @@
-output DP1
-off
-output DP2
-off
-output DP2-3
-off
-output HDMI1
-off
-output HDMI2
-off
-output VIRTUAL1
-off
-output eDP1
-off
-output DP2-1
-mode 2560x1440
-pos 0x0
-primary
-rate 59.95
-output DP2-2
-mode 2560x1440
-pos 2560x0
-rate 59.95
diff --git a/system/autorandr/docked-close/setup b/system/autorandr/docked-close/setup
deleted file mode 100644
index 607eacbc..00000000
--- a/system/autorandr/docked-close/setup
+++ /dev/null
@@ -1,3 +0,0 @@
-DP2-1 00ffffffffffff0030aeaf61010101011c1c0104a53c22783e9325a9544d9e250c5054a1080081809500b300d1c0d100a9c001010101565e00a0a0a029503020350055502100001a023a801871382d40582c450055502100001e000000fd00324c1e7822000a202020202020000000fc004c454e20503237682d31300a20010d02031df14a01020304051413901f12230907078301000065030c001000011d007251d01e206e28550055502100001e8c0ad08a20e02d10103e96005550210000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bd
-DP2-2 00ffffffffffff0030aeaf61010101010a1c0104a53c22783e9325a9544d9e250c5054a1080081809500b300d1c0d100a9c001010101565e00a0a0a029503020350055502100001a023a801871382d40582c450055502100001e000000fd00324c1e7822000a202020202020000000fc004c454e20503237682d31300a20011f02031df14a01020304051413901f12230907078301000065030c001000011d007251d01e206e28550055502100001e8c0ad08a20e02d10103e96005550210000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bd
-eDP1 00ffffffffffff0006af3d2400000000001a0104951f117802a2b591575894281c505400000001010101010101010101010101010101843a8034713828403064310035ad10000018d02e8034713828403064310035ad10000018000000fe0041554f0a202020202020202020000000fe004231343048414e30322e34200a00e4
diff --git a/system/autorandr/docked-open/block b/system/autorandr/docked-open/block
deleted file mode 100755
index 4c82692a..00000000
--- a/system/autorandr/docked-open/block
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-
-exec grep -vq open /proc/acpi/button/lid/LID/state
\ No newline at end of file
diff --git a/system/autorandr/docked-open/config b/system/autorandr/docked-open/config
deleted file mode 100644
index 231a3942..00000000
--- a/system/autorandr/docked-open/config
+++ /dev/null
@@ -1,25 +0,0 @@
-output DP1
-off
-output DP2
-off
-output DP2-3
-off
-output HDMI1
-off
-output HDMI2
-off
-output VIRTUAL1
-off
-output eDP1
-mode 1920x1080
-pos 1600x1440
-rate 60.03
-output DP2-1
-mode 2560x1440
-pos 0x0
-primary
-rate 59.95
-output DP2-2
-mode 2560x1440
-pos 2560x0
-rate 59.95
diff --git a/system/autorandr/docked-open/setup b/system/autorandr/docked-open/setup
deleted file mode 100644
index 607eacbc..00000000
--- a/system/autorandr/docked-open/setup
+++ /dev/null
@@ -1,3 +0,0 @@
-DP2-1 00ffffffffffff0030aeaf61010101011c1c0104a53c22783e9325a9544d9e250c5054a1080081809500b300d1c0d100a9c001010101565e00a0a0a029503020350055502100001a023a801871382d40582c450055502100001e000000fd00324c1e7822000a202020202020000000fc004c454e20503237682d31300a20010d02031df14a01020304051413901f12230907078301000065030c001000011d007251d01e206e28550055502100001e8c0ad08a20e02d10103e96005550210000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bd
-DP2-2 00ffffffffffff0030aeaf61010101010a1c0104a53c22783e9325a9544d9e250c5054a1080081809500b300d1c0d100a9c001010101565e00a0a0a029503020350055502100001a023a801871382d40582c450055502100001e000000fd00324c1e7822000a202020202020000000fc004c454e20503237682d31300a20011f02031df14a01020304051413901f12230907078301000065030c001000011d007251d01e206e28550055502100001e8c0ad08a20e02d10103e96005550210000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bd
-eDP1 00ffffffffffff0006af3d2400000000001a0104951f117802a2b591575894281c505400000001010101010101010101010101010101843a8034713828403064310035ad10000018d02e8034713828403064310035ad10000018000000fe0041554f0a202020202020202020000000fe004231343048414e30322e34200a00e4
diff --git a/system/autorandr/laptop/config b/system/autorandr/laptop/config
deleted file mode 100644
index 94f4e43d..00000000
--- a/system/autorandr/laptop/config
+++ /dev/null
@@ -1,19 +0,0 @@
-output DP1
-off
-output HDMI1
-off
-output DP2
-off
-output HDMI2
-off
-output DP2-1
-off
-output DP2-2
-off
-output DP2-3
-off
-output eDP1
-mode 1920x1080
-pos 0x0
-primary
-rate 60.03
diff --git a/system/autorandr/laptop/setup b/system/autorandr/laptop/setup
deleted file mode 100644
index dc17ff13..00000000
--- a/system/autorandr/laptop/setup
+++ /dev/null
@@ -1 +0,0 @@
-eDP1 00ffffffffffff0006af3d2400000000001a0104951f117802a2b591575894281c505400000001010101010101010101010101010101843a8034713828403064310035ad10000018d02e8034713828403064310035ad10000018000000fe0041554f0a202020202020202020000000fe004231343048414e30322e34200a00e4
diff --git a/system/autorandr/postswitch b/system/autorandr/postswitch
deleted file mode 100755
index 25b068d2..00000000
--- a/system/autorandr/postswitch
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-notify-send --expire-time=5000 "Display profile: '$AUTORANDR_CURRENT_PROFILE'"
diff --git a/system/autorandr/work-1/config b/system/autorandr/work-1/config
deleted file mode 100644
index 34fda0db..00000000
--- a/system/autorandr/work-1/config
+++ /dev/null
@@ -1,21 +0,0 @@
-output DP1
-off
-output HDMI1
-off
-output DP2
-off
-output HDMI2
-mode 1920x1200
-pos 0x0
-primary
-rate 59.95
-output DP2-1
-off
-output DP2-2
-off
-output DP2-3
-off
-output eDP1
-mode 1920x1080
-pos 1920x0
-rate 60.03
diff --git a/system/autorandr/work-1/setup b/system/autorandr/work-1/setup
deleted file mode 100644
index 12a4c698..00000000
--- a/system/autorandr/work-1/setup
+++ /dev/null
@@ -1,2 +0,0 @@
-HDMI2 00ffffffffffff0015c33425b1a21403041b010380342178ea0495a9554d9d26105054a10800a9408180b300a9c081c0810001010101283c80a070b023403020360007442100001a023a801871382d40582c450007442100001e000000fd00313d0f4c11000a202020202020000000fc004556323435350a202020202020011e020325f14e901f051404130312021107160615230907078301000066030c00100080e2007b011d8018711c1620582c250007442100009e011d80d0721c1620102c258007442100009e8c0ad08a20e02d10103e96000744210000188c0ad090204031200c4055000744210000180000000000000000000000000000000000008e
-eDP1 00ffffffffffff0006af3d2400000000001a0104951f117802a2b591575894281c505400000001010101010101010101010101010101843a8034713828403064310035ad10000018d02e8034713828403064310035ad10000018000000fe0041554f0a202020202020202020000000fe004231343048414e30322e34200a00e4
diff --git a/system/linde.nix b/system/linde.nix
index 91fc0c3a..067d4dad 100644
--- a/system/linde.nix
+++ b/system/linde.nix
@@ -15,12 +15,22 @@ let
   net-rdnsip = "2a01:4f8:c012:23a4::53";
   net-mask6 = "64";
   net-gw6 = "fe80::1";
+  ts-domain = "hydra-pinecone.ts.net";
+  golink = (builtins.getFlake (toString <golink>)).nixosModules.default;
 in
 {
   imports =
     [
+      <personal/modules/laminar.nix>
+      <home-manager/nixos>
+      <agenix/modules/age.nix>
+      <searchix/nix/modules>
+      golink
       # Include the results of the hardware scan.
       ./linde-hardware.nix
+
+      ./settings/pin.nix
+      ./settings/services/git-server.nix
     ];
   age.secrets = {
     paperless =
@@ -36,7 +46,16 @@ in
       };
     acme.file = ../secrets/acme.age;
     binarycache.file = ../secrets/binarycache.age;
+    dex.file = ../secrets/dex.age;
     powerdns.file = ../secrets/powerdns.age;
+    golink = let golink = config.services.golink; in {
+      # hope this doesn't collide...
+      path = "${golink.dataDir}/.config/tsnet-golink/auth.key";
+      owner = golink.user;
+      mode = "400";
+      symlink = false;
+      file = ../secrets/golink.age;
+    };
   };
 
   # Use the systemd-boot EFI boot loader.
@@ -54,7 +73,6 @@ in
   environment.systemPackages = with pkgs; [
     htop
     lsof
-    gitMinimal
     powerdns
     sqlite-interactive
     knot-dns
@@ -63,51 +81,6 @@ in
     nix-output-monitor
   ];
 
-  programs.ssh = with pkgs; {
-    knownHostsFiles = [
-      (writeText "github.keys" ''
-        # github.com:22 SSH-2.0-babeld-05989c77
-        # github.com:22 SSH-2.0-babeld-05989c77
-        # github.com:22 SSH-2.0-babeld-05989c77
-        # github.com:22 SSH-2.0-babeld-05989c77
-        # github.com:22 SSH-2.0-babeld-05989c77
-        github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=
-        github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=
-        github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl
-      '')
-      (writeText "gitlab.keys" ''
-        # gitlab.com:22 SSH-2.0-GitLab-SSHD
-        # gitlab.com:22 SSH-2.0-GitLab-SSHD
-        # gitlab.com:22 SSH-2.0-GitLab-SSHD
-        # gitlab.com:22 SSH-2.0-GitLab-SSHD
-        # gitlab.com:22 SSH-2.0-GitLab-SSHD
-        gitlab.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9
-        gitlab.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY=
-        gitlab.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf
-      '')
-      (writeText "codeberg.keys" ''
-        # codeberg.org:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2
-        # codeberg.org:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2
-        # codeberg.org:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2
-        # codeberg.org:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2
-        # codeberg.org:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2
-        codeberg.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8hZi7K1/2E2uBX8gwPRJAHvRAob+3Sn+y2hxiEhN0buv1igjYFTgFO2qQD8vLfU/HT/P/rqvEeTvaDfY1y/vcvQ8+YuUYyTwE2UaVU5aJv89y6PEZBYycaJCPdGIfZlLMmjilh/Sk8IWSEK6dQr+g686lu5cSWrFW60ixWpHpEVB26eRWin3lKYWSQGMwwKv4LwmW3ouqqs4Z4vsqRFqXJ/eCi3yhpT+nOjljXvZKiYTpYajqUC48IHAxTWugrKe1vXWOPxVXXMQEPsaIRc2hpK+v1LmfB7GnEGvF1UAKnEZbUuiD9PBEeD5a1MZQIzcoPWCrTxipEpuXQ5Tni4mN
-        codeberg.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL2pDxWr18SoiDJCGZ5LmxPygTlPu+cCKSkpqkvCyQzl5xmIMeKNdfdBpfbCGDPoZQghePzFZkKJNR/v9Win3Sc=
-        codeberg.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIVIC02vnjFyL+I4RHfvIGNtOgJMe769VTF1VR4EB3ZB
-      '')
-      (writeText "sr.ht.keys" ''
-        # git.sr.ht:22 SSH-2.0-OpenSSH_9.6
-        # git.sr.ht:22 SSH-2.0-OpenSSH_9.6
-        # git.sr.ht:22 SSH-2.0-OpenSSH_9.6
-        # git.sr.ht:22 SSH-2.0-OpenSSH_9.6
-        # git.sr.ht:22 SSH-2.0-OpenSSH_9.6
-        git.sr.ht ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDZ+l/lvYmaeOAPeijHL8d4794Am0MOvmXPyvHTtrqvgmvCJB8pen/qkQX2S1fgl9VkMGSNxbp7NF7HmKgs5ajTGV9mB5A5zq+161lcp5+f1qmn3Dp1MWKp/AzejWXKW+dwPBd3kkudDBA1fa3uK6g1gK5nLw3qcuv/V4emX9zv3P2ZNlq9XRvBxGY2KzaCyCXVkL48RVTTJJnYbVdRuq8/jQkDRA8lHvGvKI+jqnljmZi2aIrK9OGT2gkCtfyTw2GvNDV6aZ0bEza7nDLU/I+xmByAOO79R1Uk4EYCvSc1WXDZqhiuO2sZRmVxa0pQSBDn1DB3rpvqPYW+UvKB3SOz
-        git.sr.ht ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCj6y+cJlqK3BHZRLZuM+KP2zGPrh4H66DacfliU1E2DHAd1GGwF4g1jwu3L8gOZUTIvUptqWTkmglpYhFp4Iy4=
-        git.sr.ht ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMZvRd4EtM7R+IHVMWmDkVU3VLQTSwQDSAvW0t2Tkj60
-      '')
-    ];
-  };
-
   # Initial empty root password for easy login:
   users.users.root.initialHashedPassword = "";
   services.openssh = {
@@ -125,18 +98,18 @@ in
   programs.mosh.enable = true;
 
   system.autoUpgrade = {
-    enable = true;
+    enable = false;
     dates = "02:10";
     randomizedDelaySec = "59 min";
     allowReboot = true;
     flake = "git+file://${config.services.gitolite.dataDir}/repositories/nixfiles.git";
     flags = [
       "--no-write-lock-file"
+      "--impure"
       "--update-input"
       "nixpkgs-small"
       "--update-input"
       "searchix"
-      "git+https://git.alanpearce.eu/searchix"
     ];
   };
 
@@ -169,6 +142,7 @@ in
 
   services.nix-serve = {
     enable = true;
+    package = pkgs.nix-serve-ng;
     secretKeyFile = config.age.secrets.binarycache.path;
   };
 
@@ -210,51 +184,111 @@ in
       ];
       allowedUDPPorts = [
         53
+        443 # HTTP/3 (QUIC)
         3478
         6885 # DHT
         6922
       ];
+      trustedInterfaces = [ "tailscale0" ];
     };
     resolvconf = {
-      enable = true;
+      enable = false;
       useLocalResolver = false;
     };
   };
-  services.resolved.enable = false;
+  services.resolved = {
+    enable = true;
+    llmnr = "false";
+    dnssec = "true";
+  };
   systemd.network = {
     enable = true;
     networks.${netif} =
       {
         name = netif;
-        gateway = [ net-gw ];
-        routes = [{
-          routeConfig = {
+        routes = [
+          {
             Gateway = net-gw6;
             PreferredSource = net-ip6;
-          };
-        }];
+            QuickAck = true;
+            InitialCongestionWindow = 30;
+            InitialAdvertisedReceiveWindow = 30;
+          }
+          {
+            Gateway = net-gw;
+            QuickAck = true;
+            InitialCongestionWindow = 30;
+            InitialAdvertisedReceiveWindow = 30;
+          }
+        ];
         address = [
           "${net-ip6}/${net-mask6}"
           "${net-rdnsip}/${net-mask6}"
         ];
         addresses = [{
-          addressConfig = {
-            Address = "${net-ip4}/${net-mask4}";
-            Peer = "${net-gw}/32";
-          };
+          Address = "${net-ip4}/${net-mask4}";
+          Peer = "${net-gw}/32";
         }];
       };
+    wait-online = {
+      extraArgs = [ "--interface=${netif}" ];
+    };
+  };
+
+  services.tailscale = {
+    enable = true;
+    extraUpFlags = [ "--accept-routes" ];
+    useRoutingFeatures = "client";
+  };
+  services.golink = {
+    enable = true;
+    tailscaleAuthKeyFile = config.age.secrets.golink.path;
   };
 
   services.journald.extraConfig = ''
     MaxRetentionSec=1 month
   '';
 
-  boot.kernel.sysctl = {
-    "net.ipv4.tcp_allowed_congestion_control" = "bbr illinois reno";
-    "net.ipv4.tcp_congestion_control" = "bbr";
-    "net.core.default_qdisc" = "fq";
-  };
+  zramSwap = {
+    enable = true;
+    algorithm = "zstd";
+  };
+
+  boot.kernel.sysctl =
+    let
+      buffer_size = 16 * 1024 * 1024;
+      server_count = 2;
+      max_clients = 100;
+      page_size = 4096;
+      # This server might have 100 clients simultaneously, so:
+      #   max(tcp_wmem) * 2 * 100 / 4096
+      mem = toString (buffer_size * server_count * max_clients / page_size);
+    in
+    {
+      "net.ipv4.tcp_allowed_congestion_control" = "bbr illinois reno";
+      "net.ipv4.tcp_congestion_control" = "bbr";
+      "net.core.default_qdisc" = "fq";
+
+      # Provide adequate buffer memory.
+      # rmem_max and wmem_max are TCP max buffer size
+      # settable with setsockopt(), in bytes
+      # tcp_rmem and tcp_wmem are per socket in bytes.
+      # tcp_mem is for all TCP streams, in 4096-byte pages.
+      # The following are suggested on IBM's
+      # High Performance Computing page
+      "net.core.rmem_max" = buffer_size;
+      "net.core.wmem_max" = buffer_size;
+      "net.core.rmem_default" = buffer_size;
+      "net.core.wmem_default" = buffer_size;
+      "net.ipv4.tcp_rmem" = "4096 87380 ${toString buffer_size}";
+      "net.ipv4.tcp_wmem" = "4096 87380 ${toString buffer_size}";
+      "net.ipv4.tcp_mem" = "${mem} ${mem} ${mem}";
+
+      "net.ipv4.tcp_sack" = false;
+      "net.ipv4.tcp_dsack" = false;
+
+      "net.ipv4.tcp_slow_start_after_idle" = false;
+    };
 
   security.sudo.execWheelOnly = true;
   security.sudo.extraConfig = ''
@@ -275,7 +309,7 @@ in
   users.users.root.shell = "${pkgs.fish}/bin/fish";
   users.users.alan = {
     shell = "${pkgs.fish}/bin/fish";
-    extraGroups = [ "wheel" "caddy" "docker" ];
+    extraGroups = [ "wheel" "caddy" "docker" "laminar" ];
     isNormalUser = true;
     home = "/home/alan";
     createHome = true;
@@ -284,6 +318,9 @@ in
       "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII8VIII+598QOBxi/52O1Kb19RdUdX0aZmS1/dNoyqc5 alan@hetzner.strongbox"
     ];
   };
+  home-manager = {
+    users.alan = import ../user/server.nix;
+  };
 
   users.users.nixremote = {
     shell = "/bin/sh";
@@ -295,6 +332,7 @@ in
       "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBmDSZnUzIPQowLrKSa24eSb1WFQe7yPjTcDPPe3UY0Q nix@mba"
       "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE9of82WBHK8nr8L9RGeieLMfcAWaFCeCkmvYHM9LCuT nanopi"
       "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIy9jFioBvV0JA0lc+De2N+vDOABGHgCECW6vkD33CE4 sourcehut"
+      "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIII7sWEwsm8JZiJ0LUnjSt0Kg1RXypG6p5AzP/R2n5ca actions@github.com"
     ];
   };
 
@@ -306,40 +344,53 @@ in
   # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
   system.stateVersion = "23.05"; # Did you read the comment?
 
-  services.powerdns = {
-    enable = true;
-    secretFile = config.age.secrets.powerdns.path;
-    extraConfig = ''
-      launch=gsqlite3
-      dnsupdate=yes
-      allow-dnsupdate-from=0.0.0.0/0,::/0
-      only-notify=
-      also-notify=216.218.130.2
-      allow-axfr-ips=216.218.133.2,2001:470:600::2
-      outgoing-axfr-expand-alias=yes
-      expand-alias=yes
-      resolver=1.1.1.1
-      local-address=${net-ip4} ${net-ip6}
-      reuseport=yes
-      log-dns-details=no
-      log-dns-queries=no
-      loglevel=5
-      primary=yes
-      secondary=yes
-      send-signed-notify=no
-      prevent-self-notification=no
-
-      default-soa-edit=inception-increment
-
-      api=yes
-      # replaced by secretFile/envsubst
-      api-key=$API_KEY
-
-      gsqlite3-database=/var/db/pdns/zones.db
-      gsqlite3-pragma-foreign-keys=yes
-      gsqlite3-dnssec=yes
-    '';
-  };
+  services.powerdns =
+    let
+      inherit (lib.lists) flatten;
+      inherit (lib.strings) concatStringsSep;
+      he = {
+        notify = "216.218.130.2";
+        axfr = [
+          "216.218.133.2"
+          "2001:470:600::2"
+        ];
+      };
+      iplist = ips: concatStringsSep "," (flatten ips);
+    in
+    {
+      enable = true;
+      secretFile = config.age.secrets.powerdns.path;
+      extraConfig = ''
+        launch=gsqlite3
+        dnsupdate=yes
+        allow-dnsupdate-from=0.0.0.0/0,::/0
+        only-notify=
+        also-notify=${iplist [ he.notify ]}
+        allow-axfr-ips=${iplist [ he.axfr ]}
+        outgoing-axfr-expand-alias=yes
+        expand-alias=yes
+        resolver=1.1.1.1
+        local-address=${net-ip4} ${net-ip6}
+        reuseport=yes
+        log-dns-details=no
+        log-dns-queries=no
+        loglevel=5
+        primary=yes
+        secondary=yes
+        send-signed-notify=no
+        prevent-self-notification=no
+
+        default-soa-edit=inception-increment
+
+        api=yes
+        # replaced by secretFile/envsubst
+        api-key=$API_KEY
+
+        gsqlite3-database=/var/db/pdns/zones.db
+        gsqlite3-pragma-foreign-keys=yes
+        gsqlite3-dnssec=yes
+      '';
+    };
 
   systemd.services.hagezi-blocklist-update = {
     enable = true;
@@ -498,21 +549,6 @@ in
     };
   };
 
-  systemd.services.backup-etc-nixos = {
-    startAt = "04:30";
-    path = with pkgs; [
-      rdiff-backup
-      openssh
-    ];
-    script = ''
-      rdiff-backup --api-version 201 backup /etc/nixos ${hostname}@home.alanpearce.eu::nixos
-      rdiff-backup --api-version 201 remove increments --older-than 3M ${hostname}@home.alanpearce.eu::nixos
-    '';
-    serviceConfig = {
-      Type = "oneshot";
-    };
-  };
-
   systemd.services.backup-gitolite = {
     startAt = "daily";
     path = with pkgs; [
@@ -555,7 +591,7 @@ in
     };
     acceptTerms = true;
     certs."alanpearce.eu" = {
-      extraDomainNames = [ "*.alanpearce.eu" ];
+      extraDomainNames = [ "*.alanpearce.eu" "*.linde.alanpearce.eu" ];
     };
     certs."dns.alanpearce.eu" = {
       reloadServices = map (x: "kresd@${toString x}") (range 1 config.services.kresd.instances);
@@ -573,259 +609,296 @@ in
       auto_https disable_certs
       default_bind ${net-ip6} ${net-ip4}
     '';
-    virtualHosts = {
-      "http://" = {
-        # Needed for HTTP->HTTPS servers
-      };
-      "${hostname}.alanpearce.eu" = {
-        serverAliases = [ "https://" ];
-        useACMEHost = "alanpearce.eu";
-        extraConfig = ''
-          respond * 204
-        '';
-      };
-      "pdns.alanpearce.eu" = {
-        useACMEHost = "alanpearce.eu";
-        extraConfig = ''
-          log {
-            output discard
-          }
-          reverse_proxy 127.0.0.1:8081
-        '';
-      };
-      "dns.alanpearce.eu" = {
-        useACMEHost = "alanpearce.eu";
-        extraConfig = ''
-          log {
-            output discard
-          }
-          reverse_proxy localhost:443 {
-            transport http {
-              tls_server_name dns.alanpearce.eu
-            }
-          }
-        '';
-      };
-      "files.alanpearce.eu" = {
-        useACMEHost = "alanpearce.eu";
-        extraConfig = ''
-          encode zstd gzip
-          root * /srv/http/files
-          file_server browse
-        '';
-      };
-      "git.alanpearce.eu" =
-        let
-          fcgi = config.services.fcgiwrap;
-          fcgisocket = "${fcgi.socketType}/${fcgi.socketAddress}";
-        in
-        {
+    virtualHosts =
+      let
+        inherit (import ../lib/caddy.nix { inherit lib; }) security-headers;
+      in
+      {
+        "http://" = {
+          # Needed for HTTP->HTTPS servers
+        };
+        "alanpearce.eu" = {
+          serverAliases = [ "www.alanpearce.eu" "test.alanpearce.eu" ];
           useACMEHost = "alanpearce.eu";
           extraConfig = ''
-            root * ${pkgs.cgit-pink}/cgit/
             encode zstd gzip
-            handle_path /custom/* {
-              file_server {
-                root /srv/http/cgit/
-              }
+            root * /srv/http/website/public
+            file_server
+            ${security-headers {}}
+            handle_errors {
+              rewrite * /404.html
+              file_server
             }
-            rewrite /robots.txt /assets/robots.txt
-            handle_path /assets/* {
-              file_server  {
-                hide cgit.cgi
-              }
+          '';
+        };
+        "${hostname}.alanpearce.eu" = {
+          serverAliases = [ "https://" ];
+          useACMEHost = "alanpearce.eu";
+          extraConfig = ''
+            respond * 204
+            ${security-headers {}}
+          '';
+        };
+        "pdns.alanpearce.eu" = {
+          useACMEHost = "alanpearce.eu";
+          extraConfig = ''
+            log {
+              output discard
             }
-            @git_http_backend path_regexp "^/.+/(info/refs|git-upload-pack)$"
-            handle @git_http_backend {
-              reverse_proxy ${fcgisocket} {
-                transport fastcgi {
-                  env SCRIPT_FILENAME ${pkgs.git}/libexec/git-core/git-http-backend
-                  env GIT_PROJECT_ROOT ${config.services.gitolite.dataDir}/repositories
-                }
-              }
+            reverse_proxy 127.0.0.1:8081
+          '';
+        };
+        "id.alanpearce.eu" = {
+          useACMEHost = "alanpearce.eu";
+          extraConfig = ''
+            encode zstd gzip
+            ${security-headers {}}
+            reverse_proxy http://${config.services.dex.settings.web.http}
+          '';
+        };
+        "dns.alanpearce.eu" = {
+          useACMEHost = "alanpearce.eu";
+          extraConfig = ''
+            log {
+              output discard
             }
-            handle {
-              reverse_proxy ${fcgisocket} {
-                transport fastcgi {
-                  env       SCRIPT_FILENAME  {http.vars.root}/cgit.cgi
-                  env       CGIT_CONFIG      ${pkgs.writeText "cgitrc" ''
-                    head-include=/srv/http/cgit/responsive-cgit-css-master/head.html
-                    css=/custom/responsive-cgit-css-master/cgit.css
-                    virtual-root=/
-                    logo=
-                    readme=:README.md
-                    source-filter=${pkgs.cgit-pink}/lib/cgit/filters/syntax-highlighting.py
-                    about-filter=${pkgs.cgit-pink}/lib/cgit/filters/about-formatting.sh
-                    enable-git-config=1
-                    enable-index-owner=0
-                    enable-index-links=1
-                    enable-follow-links=0
-                    enable-log-linecount=1
-                    max-stats=year
-                    snapshots=tar.lz tar.zst zip
-                    cache-size=10240
-                    enable-http-clone=1
-                    enable-commit-graph=1
-                    mimetype-file=${pkgs.nginx}/conf/mime.types
-                    section-from-path=1
-                    noplainemail=1
-                    repository-sort=age
-                    root-title=my personal projects
-                    clone-url=git://git.alanpearce.eu/$CGIT_REPO_URL https://git.alanpearce.eu/$CGIT_REPO_URL
-                    remove-suffix=1
-                    strict-export=git-daemon-export-ok
-                    scan-path=${config.services.gitolite.dataDir}/repositories/
-                  ''}
-                  }
-                }
+            encode zstd gzip
+            reverse_proxy localhost:443 {
+              transport http {
+                tls_server_name dns.alanpearce.eu
+              }
             }
           '';
         };
-      "ntfy.alanpearce.eu" = {
-        useACMEHost = "alanpearce.eu";
-        extraConfig = ''
-          encode zstd gzip
-          reverse_proxy localhost${config.services.ntfy-sh.settings.listen-http}
-        '';
-      };
-      "searchix.alanpearce.eu" = {
-        useACMEHost = "alanpearce.eu";
-        extraConfig = ''
-          reverse_proxy localhost:${toString config.services.searchix.settings.web.port} {
-            health_uri /health
-            health_status 2xx
-          }
-          encode zstd gzip {
-            match {
-              header Content-Type text/*
-              header Content-Type application/json*
-              header Content-Type application/javascript*
-              header Content-Type application/opensearchdescription+xml
-              header Content-Type application/atom+xml*
-              header Content-Type application/rss+xml*
-              header Content-Type image/svg+xml*
-            }
-          }
-        '';
-      };
-      "legit.alanpearce.eu" =
-        let
-          server = config.services.legit.settings.server;
-        in
-        {
+        "files.alanpearce.eu" = {
           useACMEHost = "alanpearce.eu";
           extraConfig = ''
             encode zstd gzip
-            handle_path /static/* {
-              root * /srv/http/legit/src/static
-              file_server
+            ${security-headers {}}
+            root * /srv/http/files
+            file_server browse
+          '';
+        };
+        "ntfy.alanpearce.eu" = {
+          useACMEHost = "alanpearce.eu";
+          extraConfig = ''
+            encode zstd gzip
+            ${security-headers {}}
+            reverse_proxy localhost${config.services.ntfy-sh.settings.listen-http} {
+              health_uri /v1/health
+              health_body `"healthy":true`
             }
-            reverse_proxy ${server.host}:${toString server.port}
           '';
         };
-      "papers.alanpearce.eu" = {
-        extraConfig = ''
-          encode zstd gzip
-          handle_path /static/* {
-            root * ${config.services.paperless.package}/lib/paperless-ngx/static
-            file_server
-          }
-          reverse_proxy localhost:${toString config.services.paperless.port}
-
-        '';
-      };
-      "binarycache.alanpearce.eu" =
-        let
-          ns = config.services.nix-serve;
-        in
-        {
+        "searchix.alanpearce.eu" = {
+          useACMEHost = "alanpearce.eu";
+          serverAliases = [ "searchix.linde.alanpearce.eu" ];
           extraConfig = ''
-            reverse_proxy ${ns.bindAddress}:${toString ns.port}
+            reverse_proxy localhost:${toString config.services.searchix.settings.web.port} {
+              health_uri /health
+              health_status 2xx
+            }
+            encode zstd gzip {
+              match {
+                header Content-Type text/*
+                header Content-Type application/json*
+                header Content-Type application/javascript*
+                header Content-Type application/opensearchdescription+xml
+                header Content-Type application/atom+xml*
+                header Content-Type application/rss+xml*
+                header Content-Type image/svg+xml*
+              }
+            }
           '';
         };
-    };
+        "binarycache.alanpearce.eu" =
+          let
+            ns = config.services.nix-serve;
+          in
+          {
+            extraConfig = ''
+              reverse_proxy ${ns.bindAddress}:${toString ns.port}
+            '';
+          };
+        "ci.alanpearce.eu" =
+          let
+            srv = config.services.laminar;
+          in
+          {
+            extraConfig = ''
+              reverse_proxy ${srv.settings.bindHTTP}
+            '';
+          };
+      };
   };
   systemd.services.caddy.serviceConfig = {
     UMask = "007";
   };
 
-  services.fcgiwrap = {
+  networking.nat = {
     enable = true;
-    group = "gitolite";
-    preforkProcesses = 2;
-    socketType = "tcp6";
-    socketAddress = "[::1]:9000";
+    internalInterfaces = [ "ve-+" ];
+    externalInterface = netif;
+    enableIPv6 = true;
   };
-  services.gitolite = {
-    enable = true;
-    adminPubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII8VIII+598QOBxi/52O1Kb19RdUdX0aZmS1/dNoyqc5 alan@hetzner.strongbox";
-    extraGitoliteRc = ''
-      $RC{UMASK} = 0027;
-      $RC{LOG_EXTRA} = 0;
-      $RC{HOSTNAME} = "${config.networking.hostName}";
-      $RC{LOCAL_CODE} = "$rc{GL_ADMIN_BASE}/local";
-      push( @{$RC{ENABLE}}, 'D' );
-      push( @{$RC{ENABLE}}, 'Shell alan' );
-      push( @{$RC{ENABLE}}, 'cgit' );
-      push( @{$RC{ENABLE}}, 'repo-specific-hooks' );
-    '';
+
+  users.users.paperless = {
+    group = "paperless";
+    uid = config.ids.uids.paperless;
+    home = "/srv/paperless";
   };
-  services.legit = {
-    enable = true;
-    group = "gitolite";
-    settings = {
-      server.name = "legit.alanpearce.eu";
-      dirs = {
-        templates = "/srv/http/legit/src/templates";
+  users.groups.paperless.members = [ "alan" "syncthing" ];
+  containers.papers =
+    let
+      hostDataDir = config.users.users.paperless.home;
+      localAddress6 = "fc00::2";
+      tsHostname = "papers.${ts-domain}";
+      tsPort = 41642;
+    in
+    {
+      # or maybe socket activated?
+      autoStart = true;
+      # does TS need this?
+      enableTun = true;
+      privateNetwork = true;
+      hostAddress6 = "fc00::1";
+      inherit localAddress6;
+      forwardPorts = [{
+        hostPort = tsPort;
+      }];
+      bindMounts = {
+        ${config.services.paperless.dataDir} = {
+          hostPath = hostDataDir;
+          isReadOnly = false;
+        };
       };
-      repo = {
-        scanPath = "/srv/http/legit/repos";
-        readme = [
-          "readme"
-          "readme.md"
-          "README.md"
+      config = {
+        environment.systemPackages = with pkgs; [
+          lsof
         ];
+        networking = {
+          useHostResolvConf = false;
+          resolvconf.enable = false;
+          firewall.trustedInterfaces = [ "tailscale0" ];
+          firewall.rejectPackets = true;
+          nameservers = config.networking.nameservers;
+        };
+        services.resolved = {
+          enable = true;
+          llmnr = "false";
+        };
+        services.tailscale = {
+          enable = true;
+          openFirewall = true;
+          permitCertUid = "caddy";
+          port = tsPort;
+        };
+        services.caddy = {
+          enable = true;
+          email = "caddy@alanpearce.eu";
+          virtualHosts = {
+            "http://" = {
+              # avoid logging to an awkward file name based on the attribute name i.e. http://
+              hostName = "papers";
+              extraConfig = ''
+                redir ${tsHostname}{uri}
+              '';
+            };
+            ${tsHostname} = {
+              extraConfig = ''
+                encode zstd gzip
+                tls {
+                  get_certificate tailscale
+                }
+                handle_path /static/* {
+                  root * ${config.services.paperless.package}/lib/paperless-ngx/static
+                  file_server
+                }
+                reverse_proxy [::1]:${toString config.services.paperless.port}
+              '';
+            };
+          };
+        };
+        services.paperless = {
+          enable = true;
+          address = "[::1]";
+          settings = {
+            PAPERLESS_DBENGINE = "sqlite";
+            PAPERLESS_TIME_ZONE = "Europe/Berlin";
+
+            PAPERLESS_URL = "https://${tsHostname}";
+            PAPERLESS_TRUSTED_PROXIES = "[::1]";
+            PAPERLESS_USE_X_FORWARD_HOST = true;
+            PAPERLESS_USE_X_FORWARD_PORT = true;
+            PAPERLESS_PROXY_SSL_HEADER = [ "HTTP_X_FORWARDED_PROTO" "https" ];
+            PAPERLESS_ENABLE_COMPRESSION = false; # let caddy do it
+
+            PAPERLESS_OCR_SKIP_ARCHIVE_FILE = "with_text";
+            PAPERLESS_OCR_LANGUAGE = "deu+eng";
+            PAPERLESS_IGNORE_DATES = "09.08.90";
+
+            PAPERLESS_TASK_WORKERS = 2;
+            PAPERLESS_THREADS_PER_WORKER = 1;
+            PAPERLESS_NUMBER_OF_SUGGESTED_DATES = 4;
+
+            PAPERLESS_CONSUMER_IGNORE_PATTERN = [ ".DS_STORE/*" "desktop.ini" ".stfolder/*" ".stversions/*" ];
+
+            PAPERLESS_FILENAME_FORMAT = "{correspondent}/{created} {title} {asn}";
+            PAPERLESS_FILENAME_FORMAT_REMOVE_NONE = true;
+          };
+        };
+        system.stateVersion = "24.11";
       };
     };
-  };
-  users.groups.git.gid = config.ids.gids.git;
-  services.gitDaemon = {
-    enable = true;
-    user = "git";
-    group = "gitolite";
-    basePath = "${config.services.gitolite.dataDir}/repositories/";
-  };
 
-  users.groups.paperless.members = [ "alan" "syncthing" ];
-  services.paperless = {
+  services.etcd = {
     enable = true;
-    package = pkgs.paperless-ngx;
-    dataDir = "/srv/paperless";
-    settings = {
-      PAPERLESS_DBENGINE = "sqlite";
-      PAPERLESS_TIME_ZONE = "Europe/Berlin";
-
-      PAPERLESS_URL = "https://papers.alanpearce.eu";
-      PAPERLESS_TRUSTED_PROXIES = "127.0.0.1";
-      PAPERLESS_USE_X_FORWARD_HOST = true;
-      PAPERLESS_USE_X_FORWARD_PORT = true;
-      PAPERLESS_PROXY_SSL_HEADER = [ "HTTP_X_FORWARDED_PROTO" "https" ];
-      PAPERLESS_ENABLE_COMPRESSION = false; # let caddy do it
-
-      PAPERLESS_OCR_SKIP_ARCHIVE_FILE = "with_text";
-      PAPERLESS_OCR_LANGUAGE = "deu+eng";
-      PAPERLESS_IGNORE_DATES = "09.08.90";
-
-      PAPERLESS_TASK_WORKERS = 2;
-      PAPERLESS_THREADS_PER_WORKER = 1;
-      PAPERLESS_NUMBER_OF_SUGGESTED_DATES = 4;
-
-      PAPERLESS_CONSUMER_IGNORE_PATTERN = [ ".DS_STORE/*" "desktop.ini" ".stfolder/*" ".stversions/*" ];
+    initialClusterState = "existing";
+    dataDir = "/var/lib/etcd"; # TODO backup
+  };
 
-      PAPERLESS_FILENAME_FORMAT = "{correspondent}/{created} {title} {asn}";
-      PAPERLESS_FILENAME_FORMAT_REMOVE_NONE = true;
+  services.dex =
+    let
+      issuer = "https://id.alanpearce.eu/";
+    in
+    {
+      enable = true;
+      environmentFile = config.age.secrets.dex.path;
+      settings = {
+        inherit issuer;
+        storage = {
+          type = "etcd";
+          config = {
+            endpoints = config.services.etcd.listenClientUrls;
+            namespace = "dex/";
+          };
+        };
+        web.http = "127.0.0.1:5556";
+        connectors = [{
+          type = "github";
+          id = "github";
+          name = "GitHub";
+          config = {
+            clientID = "$GITHUB_CLIENT_ID";
+            clientSecret = "$GITHUB_CLIENT_SECRET";
+            redirectURI = "${issuer}callback";
+            orgs = [{
+              name = "alan-pearce";
+            }];
+            teamNameField = "slug";
+            useLoginAsID = true;
+          };
+        }];
+        staticClients = [
+          {
+            name = "Tailscale";
+            id = "oCaiv7aije1thaep0eib";
+            secretEnv = "TAILSCALE_CLIENT_SECRET";
+            redirectURIs = [ "https://login.tailscale.com/a/oauth_response" ];
+          }
+        ];
+      };
     };
-  };
 
   services.syncthing = {
     enable = true;
@@ -839,46 +912,112 @@ in
   services.searchix = {
     enable = true;
     settings = {
-      web = {
-        baseURL = "https://searchix.alanpearce.eu";
-        sentryDSN = "https://26d4cd8d20157ae2f6b4726ceae1a563@o4507187730120704.ingest.de.sentry.io/4507187734970448";
-        contentSecurityPolicy = {
-          script-src = [
-            "'self'"
-            "https://gc.zgo.at"
-            "https://js-de.sentry-cdn.com"
-            "https://browser.sentry-cdn.com"
-          ];
-          img-src = [
-            "'self'"
-            "https://gc.zgo.at"
-          ];
-          connect-src = [
-            "'self'"
-            "https://searchix.goatcounter.com/count"
-            "*.sentry.io"
-          ];
-          worker-src = [
-            "blob:"
-          ];
+      web =
+        let
+          baseURL = "https://searchix.alanpearce.eu";
+        in
+        {
+          inherit baseURL;
+          sentryDSN = "https://26d4cd8d20157ae2f6b4726ceae1a563@o4507187730120704.ingest.de.sentry.io/4507187734970448";
+          contentSecurityPolicy =
+            let
+              self = "'self'";
+            in
+            {
+              script-src = [
+                (baseURL + "/static/")
+                "https://gc.zgo.at"
+                "https://js-de.sentry-cdn.com"
+                "https://browser.sentry-cdn.com"
+              ];
+              img-src = [
+                self
+                "https://gc.zgo.at"
+              ];
+              connect-src = [
+                self
+                "https://searchix.goatcounter.com/count"
+                "*.sentry.io"
+              ];
+              worker-src = [
+                "blob:"
+              ];
+            };
+          extraHeadHTML = ''
+            <script async
+              src="https://js-de.sentry-cdn.com/d735e99613a86e1625fb85d0e8e762de.min.js"
+              crossorigin="anonymous"></script>
+            <script data-goatcounter="https://searchix.goatcounter.com/count"
+                    async src="//gc.zgo.at/count.v4.js"
+                    crossorigin="anonymous"
+                    integrity="sha384-nRw6qfbWyJha9LhsOtSb2YJDyZdKvvCFh0fJYlkquSFjUxp9FVNugbfy8q1jdxI+"></script>
+          '';
         };
-        extraHeadHTML = ''
-          <script async
-            src="https://js-de.sentry-cdn.com/d735e99613a86e1625fb85d0e8e762de.min.js"
-            crossorigin="anonymous"></script>
-          <script data-goatcounter="https://searchix.goatcounter.com/count"
-                async src="//gc.zgo.at/count.js"></script>
-        '';
-      };
 
       importer.sources = {
         darwin = {
           enable = true;
           fetcher = "download";
-          url = "https://alanpearce.github.io/nix-darwin-options";
+          url = "https://alanpearce.github.io/nix-options/darwin";
+        };
+        home-manager = {
+          enable = true;
+          fetcher = "download";
+          url = "https://alanpearce.github.io/nix-options/home-manager";
+        };
+        nixpkgs = {
+          enable = true;
+          fetcher = "channel-nixpkgs";
+          channel = "nixos-unstable";
         };
-        home-manager.enable = true;
+        nixos = {
+          enable = true;
+          fetcher = "channel-nixpkgs";
+          channel = "nixos-unstable";
+        };
+      };
+    };
+  };
+
+  programs.git = {
+    enable = true;
+    package = pkgs.gitMinimal;
+    config = {
+      advice = {
+        detachedHead = false;
+        mergeConflict = false;
       };
     };
   };
+
+  systemd.services.laminar.environment = {
+    NIX_PATH = "nixpkgs=${<nixpkgs>}";
+  };
+  services.laminar = {
+    enable = true;
+    path = with pkgs; [
+      bash
+      stdenv
+      git
+      cached-nix-shell
+      nix
+      config.programs.ssh.package
+      flock
+      just
+    ];
+    settings = {
+      bindHTTP = "[::1]:8002";
+      keepRundirs = 1;
+    };
+  };
+  users.users.laminar = {
+    homeMode = "770";
+  };
+
+  virtualisation.containers = {
+    enable = true;
+    policy = {
+      default = [{ type = "insecureAcceptAnything"; }];
+    };
+  };
 }
diff --git a/system/mba.nix b/system/mba.nix
index c39862cf..abed520b 100644
--- a/system/mba.nix
+++ b/system/mba.nix
@@ -8,6 +8,7 @@
   networking = {
     hostName = "mba";
   };
+  services.tailscale.enable = true;
 
   services.activate-system.enable = true;
 
diff --git a/system/nanopi.nix b/system/nanopi.nix
index 45eae872..5083f9e7 100755
--- a/system/nanopi.nix
+++ b/system/nanopi.nix
@@ -1,16 +1,19 @@
 { config
 , pkgs
 , lib
-, inputs
 , ...
 }:
 let
   fsTypes = [ "f2fs" "ext" "exfat" "vfat" ];
+  domain = "home.arpa";
+  ts_domain = "hydra-pinecone.ts.net";
 in
 {
   imports = [
     ./nanopi-hardware.nix
-    (inputs.nixos-hardware + "/friendlyarm/nanopi-r5s")
+    <agenix/modules/age.nix>
+    <nixos-hardware/friendlyarm/nanopi-r5s>
+    <home-manager/nixos>
   ];
 
   age.secrets = {
@@ -68,6 +71,26 @@ in
     };
   };
 
+  systemd.services.backup-golink = {
+    enable = true;
+    startAt = "daily";
+    description = "Export short links from golink";
+    path = with pkgs; [ curl gitMinimal ];
+    script = ''
+      [ -d golink ] || git init --quiet golink --initial-branch=main --shared=world
+      git config --global user.email linde@alanpearce.eu
+      cd golink
+      curl https://go.${ts_domain}/.export > links.json
+      git add links.json
+      git commit -m $(date +%F)
+    '';
+    serviceConfig = {
+      Type = "oneshot";
+      User = "linde";
+      WorkingDirectory = config.users.users.linde.home;
+    };
+  };
+
   services.journald.extraConfig = ''
     MaxRetentionSec=1 month
   '';
@@ -85,61 +108,33 @@ in
   systemd.network.config.networkConfig = {
     SpeedMeter = true;
   };
+
   networking = {
     hostName = "nanopi";
-    domain = "lan";
+    domain = domain;
+    search = [ domain ];
+    hosts = {
+      "fd7a:115c:a1e0::53" = [ "tailscale" "ts" ];
+      "192.168.100.1" = [ "modem" "pyur" ];
+      "192.168.4.1" = [ "lte" ];
+    };
     useDHCP = false;
     useNetworkd = true;
-    nameservers = [
-      "176.9.93.198"
-      "176.9.1.117"
-      "2a01:4f8:151:34aa::198"
-      "2a01:4f8:141:316d::117"
-    ];
+    nat = {
+      enable = true;
+      internalInterfaces = [ "bridge0" "lan1" "lan2" ];
+      externalInterface = "wan0";
+    };
     firewall = {
       enable = true;
       rejectPackets = true;
       logRefusedConnections = false;
       pingLimit = "5/second";
       filterForward = true; # we are a router
-      allowedUDPPorts = [
-        53
-        123
-      ];
-      allowedTCPPorts = [
-        53
-        123
-        80
-        443
+      trustedInterfaces = [
+        "bridge0"
+        "tailscale0"
       ];
-      interfaces.bridge0 = {
-        allowedTCPPorts = [
-          53
-          67
-          139
-          445
-          1883
-          3000
-          3689
-          5357
-          5533 # SmartDNS
-          8096
-          9091 # Transmission
-        ];
-        allowedUDPPorts = [
-          53
-          67
-          69
-          137
-          4011 # PXE
-          5533 # SmartDNS
-          5353
-          5355 # LLMNR
-          3702 # Samba WSDD
-          41641
-          51827
-        ];
-      };
       interfaces.wan0 = {
         allowedTCPPorts = [
           6980 # aria2c
@@ -151,8 +146,10 @@ in
         ];
       };
       extraForwardRules = ''
-        iifname { "wan0", "wlan0", "wwan0" } oifname { "lan1", "lan2", "bridge0" } icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-request, mld-listener-query, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept
-        iifname { "lan1", "lan2", "bridge0" } oifname { "wan0", "wlan0", "wwan0" } accept
+        iifname { "wlan0", "lte0" } oifname { "lan1", "lan2", "bridge0" } icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-request, mld-listener-query, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept
+        iifname { "lan1", "lan2", "bridge0" } oifname { "wlan0", "lte0" } accept
+        iifname "tailscale0" oifname "bridge0" accept
+        iifname "bridge0" oifname "tailscale0" accept
       '';
     };
     nftables = {
@@ -163,7 +160,7 @@ in
           content = ''
             chain postrouting {
               type nat hook postrouting priority srcnat; policy accept;
-              oifname { "wan0", "wlan0", "wwan0" } masquerade
+              oifname { "wlan0", "lte0" } masquerade
             }
             chain prerouting {
               type nat hook prerouting priority dstnat;
@@ -185,7 +182,57 @@ in
       # };
     };
   };
-  services.resolved.enable = false;
+
+  networking = {
+    resolvconf = {
+      # having this enabled (the default) is pointless
+      # a) this device has fixed upstream nameservers
+      enable = false;
+      # b) it makes tailscale think it should change the search domains for MagicDNS
+      # ... due to this:
+      # useLocalResolver = false;
+      # which is set by kresd?!
+      # https://github.com/NixOS/nixpkgs/blob/7780e5160e011b39019797a4c4b1a4babc80d1bf/nixos/modules/services/networking/kresd.nix#L113
+    };
+    nameservers = lib.optionals config.services.dnsmasq.enable [
+      "::1"
+      "127.0.0.1"
+    ];
+  };
+  services.resolved = {
+    # this allows link-specific DNS configuration, which is useful.
+    enable = true;
+    # why use simple boolean when string do trick?
+    llmnr = "false";
+    dnssec = "true";
+    fallbackDns = [
+      "9.9.9.9"
+      "149.112.112.112"
+      "2620::fe:fe"
+      "2620::fe:9"
+      "116.203.248.56"
+      "2a01:4f8:c012:23a4::1"
+    ];
+  };
+
+  # leaving this here just in case I ever think about disabling both `resolvconf` and `resolved`
+  # I thought that there would have been a fallback that does this anyway, but apparently not.
+  environment.etc."resolv.conf".text = lib.mkDefault (lib.optionalString
+    (
+      !config.networking.resolvconf.enable
+      &&
+      !config.services.resolved.enable
+    ) ''
+    search ${domain} ${ts_domain}
+    nameserver ::1
+    nameserver 127.0.0.1
+    options edns0
+  '');
+
+  services.tailscale = {
+    enable = true;
+    extraUpFlags = [ "--accept-dns=false" "--advertise-routes=10.0.0.0/20,fd12:d04f:65d:42::/56" ];
+  };
 
   programs.command-not-found.enable = false;
 
@@ -193,6 +240,10 @@ in
     enable = true;
     openFirewall = true;
     startWhenNeeded = false;
+    settings = {
+      PasswordAuthentication = false;
+      KbdInteractiveAuthentication = false;
+    };
   };
   programs.mosh.enable = true;
   services.sshguard = {
@@ -203,7 +254,7 @@ in
   systemd.network = {
     enable = true;
     wait-online = {
-      ignoredInterfaces = [ "wan0" "wlan0" "wwan0" ];
+      extraArgs = [ "--interface" "bridge0" ];
     };
     links = {
       "10-name-lan1" = {
@@ -233,10 +284,10 @@ in
           Name = "wlan0";
         };
       };
-      "10-name-wwan0" = {
+      "10-name-lte0" = {
         matchConfig.MACAddress = "34:4b:50:00:00:00";
         linkConfig = {
-          Name = "wwan0";
+          Name = "lte0";
         };
       };
     };
@@ -254,7 +305,6 @@ in
         bridge = [ "bridge0" ];
         linkConfig = {
           MACAddress = "82:E0:06:9C:8E:7C";
-          RequiredForOnline = "no";
         };
         networkConfig.LinkLocalAddressing = "no";
       };
@@ -265,11 +315,20 @@ in
           "10.0.0.1/20"
           "fd12:d04f:65d:42::1/56"
         ];
+        addresses = [
+          {
+            Address = "fe80::1/64";
+            Scope = "link";
+          }
+        ];
         networkConfig = {
           IPv6AcceptRA = false;
-          IPv6SendRA = true;
+          IPv6SendRA = false;
           DHCPPrefixDelegation = true;
           ConfigureWithoutCarrier = true;
+          MulticastDNS = true;
+          BindCarrier = [ "lan0" "lan1" ];
+          Domains = [ domain ];
         };
         dhcpPrefixDelegationConfig = {
           UplinkInterface = "wan0";
@@ -277,17 +336,9 @@ in
           Assign = true;
           Token = "::1";
         };
-        ipv6SendRAConfig = {
-          RouterLifetimeSec = 1800;
-          EmitDNS = true;
-          DNS = "fd12:d04f:65d:42::1";
-          EmitDomains = true;
-          Domains = [ config.networking.domain ];
-        };
       };
-      "50-wwan0" = {
-        matchConfig.Name = "wwan0";
-        linkConfig.RequiredForOnline = false;
+      "50-lte0" = {
+        matchConfig.Name = "lte0";
         networkConfig = {
           DHCP = "yes";
           IPv6AcceptRA = true;
@@ -296,17 +347,16 @@ in
         dhcpV4Config = {
           UseDNS = false;
           SendHostname = false;
-          RouteMetric = 2048;
+          UseRoutes = false;
         };
         ipv6AcceptRAConfig.UseDNS = false;
         routes = [
           {
-            routeConfig = {
-              Gateway = "_dhcp4";
-              QuickAck = true;
-              InitialCongestionWindow = 30;
-              InitialAdvertisedReceiveWindow = 30;
-            };
+            Gateway = "_dhcp4";
+            Metric = 2048;
+            QuickAck = true;
+            InitialCongestionWindow = 30;
+            InitialAdvertisedReceiveWindow = 30;
           }
         ];
         cakeConfig = {
@@ -320,7 +370,6 @@ in
       };
       "50-wan" = {
         matchConfig.Name = "wan0";
-        linkConfig.RequiredForOnline = "no";
         networkConfig = {
           DHCP = "yes";
           IPv6AcceptRA = true;
@@ -328,6 +377,7 @@ in
         };
         dhcpV4Config = {
           UseDNS = false;
+          UseRoutes = false;
           SendHostname = false;
           SendRelease = false;
           UseHostname = false;
@@ -343,15 +393,28 @@ in
         };
         ipv6AcceptRAConfig = {
           UseDNS = false;
+          UseGateway = false;
         };
         addresses = [
           {
-            addressConfig = {
-              Address = "192.168.100.10/24";
-              # Peer = "192.168.100.1/32";
-              Label = "wan0:0";
-              # Scope = "link";
-            };
+            Address = "192.168.100.10/24";
+            # Peer = "192.168.100.1/32";
+            Label = "wan0:0";
+            # Scope = "link";
+          }
+        ];
+        routes = [
+          {
+            Gateway = "_dhcp4";
+            QuickAck = true;
+            InitialCongestionWindow = 30;
+            InitialAdvertisedReceiveWindow = 30;
+          }
+          {
+            Gateway = "_ipv6ra";
+            QuickAck = true;
+            InitialCongestionWindow = 30;
+            InitialAdvertisedReceiveWindow = 30;
           }
         ];
         cakeConfig = {
@@ -365,7 +428,6 @@ in
       };
       "60-wlan" = {
         matchConfig.MACAddress = "9c:53:22:33:bf:e9";
-        linkConfig.RequiredForOnline = "no";
         networkConfig = {
           DHCP = "yes";
           IPForward = "yes";
@@ -380,13 +442,11 @@ in
         };
         routes = [
           {
-            routeConfig = {
-              Metric = 2048;
-              Gateway = "_dhcp4";
-              QuickAck = true;
-              InitialCongestionWindow = 30;
-              InitialAdvertisedReceiveWindow = 30;
-            };
+            Metric = 2048;
+            Gateway = "_dhcp4";
+            QuickAck = true;
+            InitialCongestionWindow = 30;
+            InitialAdvertisedReceiveWindow = 30;
           }
         ];
         cakeConfig = {
@@ -415,12 +475,13 @@ in
 
   services.dnsmasq = {
     enable = true;
-    resolveLocalQueries = true;
+    # let systemd-resolved.do this
+    resolveLocalQueries = false;
     alwaysKeepRunning = true;
     settings = {
       local-ttl = 60;
-      domain = "lan";
-      dhcp-fqdn = false;
+      domain = domain;
+      dhcp-fqdn = true;
       domain-needed = true;
       bogus-priv = true;
       no-resolv = true;
@@ -434,34 +495,42 @@ in
         "2620::fe:9"
         "116.203.248.56"
         "2a01:4f8:c012:23a4::1"
-        # "127.0.0.1#5553"
-        # "::1#5553"
-        "127.0.0.1#5533"
-        "::1#5533"
+        # kresd
+        "127.0.0.1#5553"
+        "::1#5553"
+        # smartdns
+        # "127.0.0.1#5533"
+        # "::1#5533"
+        "/ts.net/tailscale"
       ];
       localise-queries = true;
       cname = [
-        "homeassistant,ha"
+        "ha,home-assistant"
       ];
       interface-name = [
-        "home.alanpearce.eu,wan0"
-        "nanopi.alanpearce.eu,wan0"
-        "nanopi.lan.alanpearce.eu,bridge0"
-        "syncthing.lan.alanpearce.eu,bridge0"
-        "wan,wan0"
-        "wlan,wlan0"
-        "wwan,wwan0"
+        "nanopi.${domain},bridge0"
+        "wan.${domain},wan0"
+        "wlan.${domain},wlan0"
       ];
       interface = [
+        "lo"
         "bridge0"
       ];
+      no-dhcp-interface = [
+        "tailscale0"
+      ];
       # auth-zone = "lan,wan0";
       # auth-server = [
       #   "nanopi.alanpearce.eu,wan0"
       # ];
-      bind-interfaces = false;
+      bind-interfaces = true;
 
-      no-hosts = true;
+      # if this is false, a remote query for nanopi returns 127.0.0.2, because that's in /etc/hosts
+      no-hosts = false;
+      expand-hosts = true;
+
+      dnssec = true;
+      trust-anchor = ".,20326,8,2,E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D";
 
       enable-ra = true;
       dhcp-lease-max = 240;
@@ -469,19 +538,18 @@ in
       dhcp-rapid-commit = true;
       dhcp-range = [
         "10.0.1.0,10.0.1.250,12h"
-        "::, constructor:bridge0, ra-stateless, 48h"
-        "fd12:d04f:65d::, ra-stateless, ra-names, 48h"
+        "fd12:d04f:65d:42::,slaac,ra-names,48h"
+        "::,constructor:bridge0,ra-stateless,48h"
       ];
       dhcp-host = [
         "00:a0:de:b3:0c:01,10.0.0.50,wxa-50"
         "10:f0:68:12:b1:e0,10.0.0.11,Ruckus"
         "9c:93:4e:ad:05:c8,10.0.0.210,xerox-b210"
         "00:08:9b:f5:b8:25,10.0.0.42,dontpanic"
-        "d8:3a:dd:34:85:cc,d8:3a:dd:34:85:cd,10.0.0.81,ha"
+        "d8:3a:dd:34:85:cc,d8:3a:dd:34:85:cd,10.0.0.81,home-assistant"
       ];
       dhcp-option = [
         "option:ntp-server,0.0.0.0"
-        "option:dns-server,0.0.0.0,10.0.0.81"
         "option:tftp-server,0.0.0.0"
         "option:ip-forward-enable,0" # ip-forwarding
         "252,\"\\n\""
@@ -512,7 +580,7 @@ in
 
   services.networkd-dispatcher = {
     # broken?
-    enable = false;
+    enable = true;
     rules = {
       update-home-address = {
         onState = [ "configured" "configuring" ];
@@ -527,6 +595,18 @@ in
           exit 0
         '';
       };
+      tailscale-subnet-router-optimisation = {
+        onState = [ "routable" ];
+        script = ''
+          #!${pkgs.runtimeShell}
+          set -eu
+
+          if [[ $IFACE == "wan0" && $OperationalState == "routable" ]]
+          then
+            ${pkgs.ethtool}/bin/ethtool -K $IFACE rx-udp-gro-forwarding on rx-gro-list off
+          fi
+        '';
+      };
     };
   };
 
@@ -555,6 +635,7 @@ in
       "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMvcW4Z9VxOQgEJjsRC1uSMwEJ4vru9BwjT+Z50nawp4 lan"
     ];
   };
+  home-manager.users.alan = import ../user/nanopi.nix;
 
   users.groups = {
     linde.members = [ ];
@@ -566,6 +647,7 @@ in
       isSystemUser = true;
       shell = "/bin/sh";
       home = "/srv/backup/linde";
+      homeMode = "755";
       createHome = true;
       packages = with pkgs; [ rdiff-backup ];
       openssh.authorizedKeys.keys = [
@@ -614,7 +696,7 @@ in
   };
   nixpkgs.config.allowUnfree = true;
   system.autoUpgrade = {
-    enable = false;
+    enable = true;
     dates = "04:15";
     randomizedDelaySec = "59 min";
     flake = "git+https://git.alanpearce.eu/nixfiles";
@@ -700,22 +782,6 @@ in
     '';
   };
 
-  services.avahi = {
-    enable = true;
-    nssmdns4 = true;
-    denyInterfaces = [ "wan0" "wwan0" "wlan0" ];
-    browseDomains = [
-      "alanpearce.eu"
-    ];
-    publish = {
-      enable = true;
-      hinfo = true;
-      addresses = true;
-      userServices = true;
-      workstation = true;
-    };
-  };
-
   services.samba = {
     enable = true;
     enableNmbd = false;
@@ -786,7 +852,7 @@ in
         "10.0.0.1:53 -group lan -exclude-default-group"
       ];
       nameserver = [
-        "/lan/lan"
+        "/${domain}/${domain}"
       ];
       dualstack-ip-selection = true;
       dualstack-ip-selection-threshold = 10;
diff --git a/system/prefect.nix b/system/prefect.nix
index 12f0dd56..801692e3 100644
--- a/system/prefect.nix
+++ b/system/prefect.nix
@@ -10,23 +10,47 @@
     ./settings/configuration/user.nix
     ./settings/hardware/audio.nix
     ./settings/hardware/bare-metal.nix
-    ./settings/hardware/personal-computer.nix
     ./settings/hardware/mouse.nix
     ./settings/hardware/systemd-boot.nix
     ./settings/hardware/nvidia-gpu.nix
     ./settings/hardware/keyboard.nix
     ./settings/hardware/keyboard-lofree.nix
+    ./settings/hardware/trezor.nix
     ./settings/services/syncthing.nix
-    ./settings/services/zeroconf.nix
+    ./settings/services/virtualisation.nix
     ./settings/user-interface.nix
     ./settings/programs/base.nix
-    ./settings/programs/gnupg.nix
     ./settings/programs/kde.nix
     ./settings/programs/shell.nix
     ./settings/programs/docker.nix
     ./settings/gaming.nix
+    <nixos-hardware/common/cpu/amd>
+    <nixos-hardware/common/cpu/amd/pstate.nix>
+    <nixos-hardware/common/pc/ssd>
+    <nixos-hardware/common/pc>
+    <nixos-hardware/common/gpu/nvidia>
   ];
 
+  virtualisation.vmVariant = {
+    disabledModules = [
+      ./settings/hardware/nvidia-gpu.nix
+      ./settings/hardware/bare-metal.nix
+      ./settings/gaming.nix
+      ./settings/user-interface.nix
+      ./settings/programs/kde.nix
+      <nixos-hardware/common/cpu/amd>
+      <nixos-hardware/common/cpu/amd/pstate.nix>
+      <nixos-hardware/common/pc/ssd>
+      <nixos-hardware/common/pc>
+      <nixos-hardware/common/gpu/nvidia>
+    ];
+    services.qemuGuest.enable = true;
+    virtualisation = {
+      memorySize = 4096;
+      cores = 4;
+    };
+  };
+
   nixpkgs.hostPlatform = "x86_64-linux";
 
   services.xserver.screenSection = ''
@@ -40,9 +64,6 @@
     user = "alan";
     enable = true;
   };
-  services.displayManager.sddm = {
-    enableHidpi = false;
-  };
 
   boot.kernelPackages = pkgs.linuxPackages_xanmod;
   boot.extraModulePackages = with config.boot.kernelPackages; [
@@ -102,36 +123,57 @@
   };
 
   systemd.network = {
-    enable = true;
     networks."40-enp7s0" = {
+      matchConfig = {
+        Name = "enp7s0";
+      };
       dhcpV4Config = {
-        UseDNS = true;
+        UseDomains = true;
+      };
+      dhcpV6Config = {
+        UseDomains = true;
       };
       ipv6AcceptRAConfig = {
-        UseDNS = true;
+        UseDomains = true;
+      };
+      networkConfig = {
+        MulticastDNS = true;
       };
     };
   };
   networking = {
+    hostName = "prefect";
     useDHCP = false;
     useNetworkd = true;
     interfaces.enp7s0 = {
       useDHCP = true;
     };
+    hosts = {
+      "fd7a:115c:a1e0::53" = [ "tailscale" "ts" ];
+    };
+
+    nftables = {
+      enable = true;
+    };
+    firewall = {
+      extraInputRules = ''
+        ip saddr 10.0.0.0/8 accept
+        ip6 saddr { fd00::/8, fe80::/10 } accept
+      '';
+    };
   };
-  networking.nftables = {
-    enable = true;
-  };
-  networking.firewall = {
-    allowedTCPPorts = [ 80 443 139 445 1024 ];
-    extraInputRules = ''
-      ip saddr 10.0.0.0/8 accept
-      ip6 saddr { fd00::/8, fe80::/10 } accept
-    '';
+
+  services.resolved = {
+    llmnr = "false";
+    dnssec = "true";
   };
 
-  networking = {
-    hostName = "prefect";
+  services.tailscale = {
+    enable = true;
+    extraUpFlags = [
+      "--accept-dns=true"
+      "--accept-routes=false"
+    ];
   };
 
   system.stateVersion = "23.05";
diff --git a/system/settings/base.nix b/system/settings/base.nix
index 5eee9088..81dab9a1 100644
--- a/system/settings/base.nix
+++ b/system/settings/base.nix
@@ -1,14 +1,9 @@
 { config
 , pkgs
 , lib
-, inputs
 , ...
 }:
-let
-  inherit (inputs) self;
-in
 {
   boot.loader.timeout = lib.mkDefault 1;
   services.irqbalance.enable = true;
-  system.configurationRevision = toString (self.rev or self.dirtyRev or self.lastModified or "unknown");
 }
diff --git a/system/settings/configuration/networking.nix b/system/settings/configuration/networking.nix
deleted file mode 100644
index ad4200b1..00000000
--- a/system/settings/configuration/networking.nix
+++ /dev/null
@@ -1,11 +0,0 @@
-{ config
-, pkgs
-, ...
-}: {
-  environment.systemPackages = with pkgs; [ lxqt.lxqt-policykit ]; # provides a default authentification client for policykit
-  services.gvfs.enable = true; # enables gvfs
-
-  imports = [
-    ../services/zeroconf.nix
-  ];
-}
diff --git a/system/settings/configuration/nix-linux.nix b/system/settings/configuration/nix-linux.nix
index 3ee9f420..e11b0389 100644
--- a/system/settings/configuration/nix-linux.nix
+++ b/system/settings/configuration/nix-linux.nix
@@ -20,7 +20,6 @@
   system.autoUpgrade = {
     enable = true;
     flags = [ "--max-jobs" "2" ];
-    flake = "/home/alan/projects/alanpearce/nixfiles";
   };
   systemd.services.nixos-upgrade = {
     script = pkgs.lib.mkForce ''
diff --git a/system/settings/configuration/nix.nix b/system/settings/configuration/nix.nix
index c8db7836..b28fde18 100644
--- a/system/settings/configuration/nix.nix
+++ b/system/settings/configuration/nix.nix
@@ -3,9 +3,7 @@
 , pkgs
 , ...
 }: {
-  imports = [
-    ../../../pin.nix
-  ];
+  imports = [ ../pin.nix ];
   nix = {
     settings = {
       cores = lib.mkDefault 0;
@@ -14,6 +12,15 @@
       keep-derivations = true;
       experimental-features = "nix-command flakes";
       warn-dirty = false;
+      substituters = [
+        "https://nix-community.cachix.org"
+        "https://binarycache.alanpearce.eu"
+      ];
+
+      trusted-public-keys = [
+        "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
+        "binarycache.alanpearce.eu:ZwqO3XMuajPictjwih8OY2+RXnOKpjZEZFHJjGSxAI4="
+      ];
     };
 
     gc = {
diff --git a/system/settings/configuration/user.nix b/system/settings/configuration/user.nix
index 3a7f9620..9d6fed69 100644
--- a/system/settings/configuration/user.nix
+++ b/system/settings/configuration/user.nix
@@ -15,6 +15,7 @@
       "dialout"
       "pipewire"
       "networkmanager"
+      "libvirtd"
       "video"
     ];
     initialPassword = "password";
diff --git a/system/settings/darwin.nix b/system/settings/darwin.nix
index aa55cce5..6d5b357d 100644
--- a/system/settings/darwin.nix
+++ b/system/settings/darwin.nix
@@ -16,6 +16,7 @@
     [ "/run/current-system/sw" "/nix/var/nix/profiles/default" ]
   ];
 
+  environment.darwinConfig = "$HOME/.config/nixpkgs/darwin-configuration.nix";
   nix = {
     daemonIOLowPriority = true;
     gc = {
@@ -24,7 +25,6 @@
     };
     settings.extra-platforms = "aarch64-darwin x86_64-darwin";
 
-    linux-builder.enable = true;
     settings.trusted-users = [ "@admin" ];
   };
 
@@ -32,7 +32,13 @@
     allowUnfree = true;
   };
 
-  launchd.user.agents.lorri = lib.mkIf config.services.lorri.enable {
+  # needed so that nix-darwin can activate the system as root
+  security.sudo.extraConfig = ''
+    Defaults	env_keep += "NIX_PATH"
+  '';
+
+  services.lorri.enable = true;
+  launchd.user.agents.lorri = {
     serviceConfig = {
       RunAtLoad = lib.mkForce false;
       Sockets = {
diff --git a/system/settings/gaming.nix b/system/settings/gaming.nix
index 17f25065..d11d5a3c 100644
--- a/system/settings/gaming.nix
+++ b/system/settings/gaming.nix
@@ -19,9 +19,9 @@
   };
   fonts.fontconfig.cache32Bit = true;
   hardware.steam-hardware.enable = true;
-  hardware.opengl = {
+  hardware.graphics = {
     enable = true;
-    driSupport32Bit = true;
+    enable32Bit = true;
   };
   hardware.pulseaudio.support32Bit = true;
   services.pipewire.alsa.support32Bit = true;
diff --git a/system/settings/hardware/intel-gpu.nix b/system/settings/hardware/intel-gpu.nix
deleted file mode 100644
index 494cb86e..00000000
--- a/system/settings/hardware/intel-gpu.nix
+++ /dev/null
@@ -1,18 +0,0 @@
-{ config
-, pkgs
-, ...
-}: {
-  # https://wiki.gentoo.org/wiki/Intel#Feature_support
-  services.xserver = {
-    useGlamor = true;
-    deviceSection = ''
-      Option      "DRI"            "3"
-    '';
-    videoDrivers = [ "intel" ];
-  };
-
-  boot.kernelParams = [
-    "i915.enable_guc=2"
-    "i915.fastboot=1"
-  ];
-}
diff --git a/system/settings/hardware/laptop.nix b/system/settings/hardware/laptop.nix
deleted file mode 100644
index bd66fb8f..00000000
--- a/system/settings/hardware/laptop.nix
+++ /dev/null
@@ -1,79 +0,0 @@
-{ config
-, pkgs
-, lib
-, ...
-}: {
-  imports = [
-    ./bluetooth.nix
-    ./bluetooth-audio.nix
-    ./connman.nix
-    ./iwd.nix
-    ./personal-computer.nix
-    ../user-interface.nix
-  ];
-
-  boot.kernelModules = [ "coretemp" ];
-
-  environment.systemPackages = with pkgs; [
-    powerstat
-    powertop
-
-    arandr
-    autorandr
-  ];
-
-  programs.light.enable = true;
-
-  services.autorandr = {
-    enable = true;
-    defaultTarget = "common";
-  };
-  systemd.services.autorandr.wantedBy = [ "graphical.target" ];
-
-  environment.etc.autorandr = {
-    enable = true;
-    source = ../../autorandr;
-    target = "xdg/autorandr";
-  };
-
-  services.logind = {
-    lidSwitch = "suspend";
-    lidSwitchExternalPower = "ignore";
-    extraConfig = ''
-      IdleAction=suspend
-      IdleActionSec=600
-    '';
-  };
-
-  services.acpid = {
-    enable = true;
-    lidEventCommands = ''
-      ${pkgs.autorandr}/bin/autorandr --batch --change
-    '';
-  };
-
-  services.tlp = {
-    extraConfig = ''
-      CPU_SCALING_GOVERNOR_ON_BAT=powersave
-      ENERGY_PERF_POLICY_ON_BAT="balance_power"
-
-      SOUND_POWER_SAVE_ON_AC=60
-      DEVICES_TO_DISABLE_ON_BAT_NOT_IN_USE="bluetooth wwan"
-    '';
-  };
-
-  services.xserver = {
-    libinput = {
-      enable = lib.mkDefault true;
-      naturalScrolling = true;
-      disableWhileTyping = true;
-    };
-    displayManager.sessionCommands = ''
-      ${pkgs.autorandr}/bin/autorandr --change --force
-    '';
-  };
-
-  systemd.services.nixos-upgrade.unitConfig.ConditionACPower = true;
-  systemd.services.nix-gc.unitConfig.ConditionACPower = true;
-  systemd.services.docker-prune.unitConfig.ConditionACPower = true;
-}
diff --git a/system/settings/hardware/mouse.nix b/system/settings/hardware/mouse.nix
index b74d17aa..d4a232af 100644
--- a/system/settings/hardware/mouse.nix
+++ b/system/settings/hardware/mouse.nix
@@ -2,7 +2,7 @@
 , pkgs
 , ...
 }: {
-  services.xserver.libinput = {
+  services.libinput = {
     enable = true;
     mouse = {
       accelProfile = "flat";
diff --git a/system/settings/hardware/network-manager.nix b/system/settings/hardware/network-manager.nix
deleted file mode 100644
index a27ca892..00000000
--- a/system/settings/hardware/network-manager.nix
+++ /dev/null
@@ -1,16 +0,0 @@
-{ config
-, lib
-, pkgs
-, ...
-}: {
-  networking = {
-    networkmanager = {
-      enable = true;
-    };
-  };
-
-  environment.systemPackages = with pkgs; [
-    networkmanagerapplet
-    networkmanager_dmenu
-  ];
-}
diff --git a/system/settings/hardware/personal-computer.nix b/system/settings/hardware/personal-computer.nix
deleted file mode 100644
index 35824136..00000000
--- a/system/settings/hardware/personal-computer.nix
+++ /dev/null
@@ -1,8 +0,0 @@
-{ config
-, pkgs
-, lib
-, ...
-}: {
-  boot.kernelPackages = lib.mkDefault pkgs.linuxPackages_zen;
-  powerManagement.cpuFreqGovernor = "schedutil";
-}
diff --git a/system/settings/hardware/thinkpad.nix b/system/settings/hardware/thinkpad.nix
deleted file mode 100644
index 649f626a..00000000
--- a/system/settings/hardware/thinkpad.nix
+++ /dev/null
@@ -1,23 +0,0 @@
-{ config
-, pkgs
-, ...
-}: {
-  boot.kernelModules = [ ];
-  boot.blacklistedKernelModules = [ "thinkpad_ec" ];
-  boot.extraModulePackages = with config.boot.kernelPackages; [
-    acpi_call
-  ];
-
-  services.fwupd = {
-    enable = true;
-  };
-
-  services.thinkfan = {
-    enable = true;
-  };
-
-  imports = [
-    ./bare-metal.nix
-    ./laptop.nix
-  ];
-}
diff --git a/system/settings/hardware/trackball.nix b/system/settings/hardware/trackball.nix
deleted file mode 100644
index c2f7e68c..00000000
--- a/system/settings/hardware/trackball.nix
+++ /dev/null
@@ -1,15 +0,0 @@
-{ config
-, pkgs
-, ...
-}: {
-  services.xserver.config = ''
-    Section "InputClass"
-        Identifier "Trackball (No Acceleration)"
-        MatchIsPointer "yes"
-        MatchIsTouchpad "no"
-        MatchProduct "Trackball"
-        Option "AccelerationProfile" "-1"
-        Option "AccelerationScheme" "none"
-    EndSection
-  '';
-}
diff --git a/system/settings/hardware/trezor.nix b/system/settings/hardware/trezor.nix
index 1004833a..3883d76f 100644
--- a/system/settings/hardware/trezor.nix
+++ b/system/settings/hardware/trezor.nix
@@ -5,13 +5,7 @@
 }: {
   services.trezord.enable = true;
   environment.systemPackages = with pkgs; [
-    gnupg
-    pinentry
     (python3.withPackages (ps: with ps; [ trezor_agent wheel ]))
     trezor-suite
   ];
-  programs.gnupg.agent = {
-    enable = lib.mkForce false;
-    enableSSHSupport = lib.mkForce false;
-  };
 }
diff --git a/system/settings/machines/t470s.nix b/system/settings/machines/t470s.nix
deleted file mode 100644
index 5f1f4a1c..00000000
--- a/system/settings/machines/t470s.nix
+++ /dev/null
@@ -1,57 +0,0 @@
-{ config
-, pkgs
-, ...
-}: {
-  hardware.usbWwan.enable = false; # unused
-  systemd.services.ModemManager.enable = false;
-
-  hardware.enableRedistributableFirmware = true;
-
-  boot.extraModprobeConfig = ''
-    options thinkpad_acpi fan_control=1
-  '';
-
-  services.thinkfan.sensors = ''
-    hwmon /sys/devices/platform/coretemp.0/hwmon/hwmon0/temp3_input
-    hwmon /sys/devices/platform/coretemp.0/hwmon/hwmon0/temp1_input
-    hwmon /sys/devices/platform/coretemp.0/hwmon/hwmon0/temp2_input
-  '';
-  services.thinkfan.levels = ''
-    (0,     0,      48)
-    (1,     45,     52)
-    (2,     50,     57)
-    (3,     55,     63)
-    (6,     60,     65)
-    (7,     60,     85)
-    (127,   80,     32767)
-  '';
-
-  boot.postBootCommands = ''
-    echo bfq > /sys/block/nvme0n1/queue/scheduler
-  '';
-
-  hardware.pulseaudio.extraConfig = ''
-    load-module module-alsa-sink device=hw:0,7
-  '';
-
-  services.tlp.extraConfig = ''
-    DISK_DEVICES="nvme0n1"
-    DISK_IOSCHED="keep"
-  '';
-
-  services.xserver = {
-    dpi = 109;
-    monitorSection = ''
-      DisplaySize 310 176
-    '';
-  };
-
-  environment.systemPackages = with pkgs; [
-    nvme-cli
-  ];
-
-  imports = [
-    ../hardware/intel-gpu.nix
-    ../hardware/thinkpad.nix
-  ];
-}
diff --git a/system/settings/pin.nix b/system/settings/pin.nix
new file mode 100644
index 00000000..533149fe
--- /dev/null
+++ b/system/settings/pin.nix
@@ -0,0 +1,12 @@
+let
+  inherit (import ../../sources.nix) nixPath sources;
+in
+{
+  nix = {
+    inherit nixPath;
+    registry.nixpkgs.to = {
+      type = "path";
+      path = sources.nixpkgs;
+    };
+  };
+}
diff --git a/system/settings/programs/barrier.nix b/system/settings/programs/barrier.nix
deleted file mode 100644
index 76e1b06b..00000000
--- a/system/settings/programs/barrier.nix
+++ /dev/null
@@ -1,10 +0,0 @@
-{ config
-, pkgs
-, ...
-}: {
-  environment.systemPackages = with pkgs; [
-    barrier
-  ];
-
-  networking.firewall.allowedTCPPorts = [ 24800 ];
-}
diff --git a/system/settings/programs/base.nix b/system/settings/programs/base.nix
index bfc81312..47ed4c07 100644
--- a/system/settings/programs/base.nix
+++ b/system/settings/programs/base.nix
@@ -1,26 +1,11 @@
 { pkgs, ... }: {
-  services.lorri.enable = true;
   environment.systemPackages = with pkgs; [
     home-manager
+    brotli
     lzma
     lzop
     zstd
   ] ++ (lib.optionals (stdenv.isLinux) [
     psmisc
   ]);
-  nix.settings = {
-    substituters = [
-      "https://nix-community.cachix.org"
-      "https://deploy-rs.cachix.org"
-      "https://binarycache.alanpearce.eu"
-      "https://deploy-rs.cachix.org"
-    ];
-
-    trusted-public-keys = [
-      "deploy-rs.cachix.org-1:xfNobmiwF/vzvK1gpfediPwpdIP0rpDV2rYqx40zdSI="
-      "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
-      "deploy-rs.cachix.org-1:xfNobmiwF/vzvK1gpfediPwpdIP0rpDV2rYqx40zdSI="
-      "binarycache.alanpearce.eu:ZwqO3XMuajPictjwih8OY2+RXnOKpjZEZFHJjGSxAI4="
-    ];
-  };
 }
diff --git a/system/settings/programs/gnome.nix b/system/settings/programs/gnome.nix
deleted file mode 100644
index f9618009..00000000
--- a/system/settings/programs/gnome.nix
+++ /dev/null
@@ -1,26 +0,0 @@
-{ config
-, lib
-, pkgs
-, ...
-}:
-with lib; {
-  services = {
-    gnome3 = {
-      gnome-documents.enable = false;
-      gnome-user-share.enable = false;
-      gnome-online-accounts.enable = false;
-      tracker.enable = false;
-    };
-    telepathy.enable = false;
-
-    xserver = {
-      desktopManager.gnome3 = {
-        enable = true;
-        extraGSettingsOverrides = ''
-          [org.gnome.desktop.input-sources]
-          sources=[('xkb','${config.services.xserver.layout + (optionalString (config.services.xserver.xkbVariant != "") ("+" + config.services.xserver.xkbVariant))}')]
-        '';
-      };
-    };
-  };
-}
diff --git a/system/settings/programs/gnupg.nix b/system/settings/programs/gnupg.nix
deleted file mode 100644
index f17263c9..00000000
--- a/system/settings/programs/gnupg.nix
+++ /dev/null
@@ -1,12 +0,0 @@
-{ config
-, pkgs
-, lib
-, ...
-}: {
-  environment.systemPackages = with pkgs; [
-    gnupg
-    pinentry
-    (python3.withPackages (ps: with ps; [ trezor_agent wheel ]))
-  ];
-  environment.variables.GNUPGHOME = "$HOME/.gnupg/trezor/";
-}
diff --git a/system/settings/programs/kde.nix b/system/settings/programs/kde.nix
index 1a753cf2..1cf3c917 100644
--- a/system/settings/programs/kde.nix
+++ b/system/settings/programs/kde.nix
@@ -3,13 +3,16 @@
 , pkgs
 , ...
 }:
-with lib; {
+{
   services = {
     desktopManager = {
       plasma6.enable = true;
     };
     displayManager = {
-      sddm.enable = true;
+      sddm = {
+        enable = true;
+        enableHidpi = lib.mkDefault false;
+      };
     };
 
     physlock.enable = lib.mkForce false;
diff --git a/system/settings/programs/shell.nix b/system/settings/programs/shell.nix
index 87372033..680985cd 100644
--- a/system/settings/programs/shell.nix
+++ b/system/settings/programs/shell.nix
@@ -4,6 +4,7 @@
 }: {
   programs.fish = {
     enable = true;
+    useBabelfish = true;
   };
   users.users.alan.shell = pkgs.fish;
 }
diff --git a/system/settings/programs/tor.nix b/system/settings/programs/tor.nix
deleted file mode 100644
index 31521857..00000000
--- a/system/settings/programs/tor.nix
+++ /dev/null
@@ -1,27 +0,0 @@
-{ config
-, pkgs
-, lib
-, ...
-}: {
-  services.tor = {
-    enable = true;
-    client = {
-      enable = true;
-      socksListenAddress = {
-        IPv6Traffic = true;
-        port = 9050;
-      };
-    };
-    torsocks = {
-      enable = true;
-    };
-  };
-  systemd.services.tor.wantedBy = lib.mkForce [ ];
-  systemd.timers.tor = {
-    description = "Delayed startup of Tor";
-    wantedBy = [ "timers.target" ];
-    timerConfig = {
-      OnActiveSec = "1 min";
-    };
-  };
-}
diff --git a/system/settings/programs/window-manager.nix b/system/settings/programs/window-manager.nix
deleted file mode 100644
index bbe4c638..00000000
--- a/system/settings/programs/window-manager.nix
+++ /dev/null
@@ -1,57 +0,0 @@
-{ config
-, pkgs
-, lib
-, ...
-}: {
-  services.xserver = {
-    desktopManager.xterm.enable = false;
-
-    displayManager = {
-      autoLogin = {
-        user = "alan";
-        enable = false;
-      };
-      lightdm = {
-        enable = true;
-        greeter.enable = true;
-        greeters.mini = {
-          enable = false;
-          user = "alan";
-        };
-      };
-      sessionCommands = ''
-        ${pkgs.xorg.xrdb}/bin/xrdb -merge $HOME/.xresources/main
-        ${pkgs.xorg.xsetroot}/bin/xsetroot -cursor_name left_ptr -solid '#4d4d4c'
-      '' ++ (lib.optionalString config.networking.networkmanager.enable ''
-        ${pkgs.networkmanagerapplet}/bin/nm-applet &
-      '');
-    };
-    xautolock = {
-      enable = true;
-      locker = "${pkgs.i3lock}/bin/i3lock -n";
-      enableNotifier = true;
-      notifier = "${pkgs.libnotify}/bin/notify-send \"Locking in 10 seconds\"";
-      time = 5;
-    };
-  };
-
-  services.xserver.displayManager.setupCommands = ''
-    ${pkgs.redshift}/bin/redshift \
-    -l ${toString config.location.latitude}:${toString config.location.longitude} \
-    -t ${toString config.services.redshift.temperature.day}:${toString config.services.redshift.temperature.night} \
-    -b 1:1 \
-    -o \
-    -r
-  '';
-
-  environment.systemPackages = with pkgs; [
-    dmenu
-    libnotify # for notify-send
-    xterm
-    rofi
-    sxhkd
-    maim
-
-    perlPackages.FileMimeInfo # xdg-utils uses this when no DE
-  ];
-}
diff --git a/system/settings/programs/xfce.nix b/system/settings/programs/xfce.nix
deleted file mode 100644
index a896810a..00000000
--- a/system/settings/programs/xfce.nix
+++ /dev/null
@@ -1,8 +0,0 @@
-{ pkgs, ... }: {
-  services.xserver.desktopManager.xfce = {
-    enable = true;
-  };
-  environment.systemPackages = with pkgs; [
-    xfce.xfce4-panel-profiles
-  ];
-}
diff --git a/system/settings/services/git-server.nix b/system/settings/services/git-server.nix
new file mode 100644
index 00000000..54e8560c
--- /dev/null
+++ b/system/settings/services/git-server.nix
@@ -0,0 +1,279 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+let
+  inherit (lib) pipe flatten concatMapAttrs mergeAttrsList mapAttrsToList;
+  inherit (import ../../../lib/caddy.nix { inherit lib; }) security-headers;
+  repos = "${config.services.gitolite.dataDir}/repositories";
+
+  mirrors = {
+    sourcehut = {
+      hostname = "git.sr.ht";
+      username = "~alanpearce";
+    };
+    codeberg = {
+      hostname = "codeberg.org";
+      username = "alanpearce";
+    };
+    github = {
+      hostname = "github.com";
+      username = "alanpearce";
+    };
+  };
+
+  repoMirrors = {
+    nixfiles = [ "sourcehut" ];
+    searchix = [ "sourcehut" ];
+    website = [ "sourcehut" ];
+    nix-packages = [ "sourcehut" "github" ];
+    zola-bearblog = [ "sourcehut" "codeberg" ];
+  };
+
+  createMirrorService =
+    name: { hostname, username }:
+    {
+      "mirror-to-${name}@" = {
+        path = with pkgs; [ gitMinimal openssh ];
+        serviceConfig = {
+          Type = "oneshot";
+          User = "gitolite";
+          WorkingDirectory = "${repos}/%i.git";
+          ExecStart = "${pkgs.gitMinimal}/bin/git push --mirror git@${hostname}:${username}/%i";
+        };
+        unitConfig = {
+          # only mirror public repositories
+          ConditionPathExists = "${repos}/%i.git/git-daemon-export-ok";
+        };
+      };
+    };
+
+  createMirrorPath = name: { hostname, username }:
+    {
+      "mirror-to-${name}@" = {
+        pathConfig = {
+          PathChanged = "${repos}/%i.git/refs/heads";
+          StartLimitIntervalSec = "1h";
+          StartLimitBurst = 5;
+        };
+      };
+    };
+
+
+  mkMirrorWants = repo: map (target: "mirror-to-${target}@${repo}.path");
+in
+{
+  services.fcgiwrap = {
+    enable = true;
+    user = "gitolite";
+    group = "gitolite";
+    preforkProcesses = 2;
+    socketType = "tcp6";
+    socketAddress = "[::1]:9000";
+  };
+  services.gitolite = {
+    enable = true;
+    adminPubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII8VIII+598QOBxi/52O1Kb19RdUdX0aZmS1/dNoyqc5 alan@hetzner.strongbox";
+    extraGitoliteRc = ''
+      $RC{UMASK} = 0027;
+      $RC{LOG_EXTRA} = 0;
+      $RC{HOSTNAME} = "${config.networking.hostName}";
+      $RC{LOCAL_CODE} = "$rc{GL_ADMIN_BASE}/local";
+      push( @{$RC{ENABLE}}, 'D' );
+      push( @{$RC{ENABLE}}, 'Shell alan' );
+      push( @{$RC{ENABLE}}, 'cgit' );
+      push( @{$RC{ENABLE}}, 'repo-specific-hooks' );
+    '';
+  };
+  services.legit = {
+    enable = true;
+    group = "gitolite";
+    settings = {
+      server.name = "legit.alanpearce.eu";
+      dirs = {
+        templates = "/srv/http/legit/src/templates";
+      };
+      repo = {
+        scanPath = "/srv/http/legit/repos";
+        readme = [
+          "readme"
+          "readme.md"
+          "README.md"
+        ];
+      };
+    };
+  };
+  services.gitDaemon = {
+    enable = true;
+    user = "gitolite";
+    group = "gitolite";
+    basePath = repos;
+  };
+
+  services.caddy.virtualHosts = {
+    "git.alanpearce.eu" =
+      let
+        fcgi = config.services.fcgiwrap;
+        fcgisocket = "${fcgi.socketType}/${fcgi.socketAddress}";
+      in
+      {
+        useACMEHost = "alanpearce.eu";
+        extraConfig = ''
+          root * ${pkgs.cgit-pink}/cgit/
+          encode zstd gzip
+          ${security-headers {
+            overrides.content-security-policy = {
+              default-src = [ "none" ];
+              base-uri = [ "none" ];
+              style-src = [ "self" "unsafe-inline" ];
+              script-src = [ "self" "unsafe-inline" ];
+              form-action = [ "self" ];
+              connect-src = [ "self" ];
+              img-src = [ "https" ];
+              object-src = [ "none" ];
+            };
+          }}
+          handle_path /custom/* {
+            file_server {
+              root /srv/http/cgit/
+            }
+          }
+          rewrite /robots.txt /assets/robots.txt
+          handle_path /assets/* {
+            file_server  {
+              hide cgit.cgi
+            }
+          }
+          @git_http_backend path_regexp "^.*/(HEAD|info/refs|objects/info/[^/]+|git-upload-pack)$"
+          handle @git_http_backend {
+            reverse_proxy ${fcgisocket} {
+              transport fastcgi {
+                env SCRIPT_FILENAME ${pkgs.git}/libexec/git-core/git-http-backend
+                env GIT_PROJECT_ROOT ${repos}
+              }
+            }
+          }
+          handle {
+            reverse_proxy ${fcgisocket} {
+              transport fastcgi {
+                env       SCRIPT_FILENAME  {http.vars.root}/cgit.cgi
+                env       CGIT_CONFIG      ${pkgs.writeText "cgitrc" ''
+                  head-include=/srv/http/cgit/responsive-cgit-css-master/head.html
+                  css=/custom/responsive-cgit-css-master/cgit.css
+                  virtual-root=/
+                  logo=
+                  readme=:README.md
+                  source-filter=${pkgs.cgit-pink}/lib/cgit/filters/syntax-highlighting.py
+                  about-filter=${pkgs.cgit-pink}/lib/cgit/filters/about-formatting.sh
+                  enable-git-config=1
+                  enable-index-owner=0
+                  enable-index-links=1
+                  enable-follow-links=0
+                  enable-log-linecount=1
+                  max-stats=year
+                  snapshots=tar.lz tar.zst zip
+                  cache-size=10240
+                  enable-http-clone=1
+                  enable-commit-graph=1
+                  mimetype-file=${pkgs.nginx}/conf/mime.types
+                  section-from-path=1
+                  noplainemail=1
+                  repository-sort=age
+                  root-title=my personal projects
+                  clone-url=git://git.alanpearce.eu/$CGIT_REPO_URL https://git.alanpearce.eu/$CGIT_REPO_URL
+                  remove-suffix=1
+                  strict-export=git-daemon-export-ok
+                  scan-path=${repos}
+                ''}
+                }
+              }
+          }
+        '';
+      };
+
+    "legit.alanpearce.eu" =
+      let
+        server = config.services.legit.settings.server;
+      in
+      {
+        useACMEHost = "alanpearce.eu";
+        extraConfig = ''
+          encode zstd gzip
+          handle_path /static/* {
+            root * /srv/http/legit/src/static
+            file_server
+          }
+          ${security-headers {
+            overrides.content-security-policy = {
+              default-src = [ "none" ];
+              base-uri = [ "none" ];
+              style-src = [ "self" ];
+              script-src = [ "none" ];
+              form-action = [ "self" ];
+              connect-src = [ "self" ];
+              img-src = [ "https" ];
+              object-src = [ "none" ];
+            };
+          }}
+          reverse_proxy ${server.host}:${toString server.port}
+        '';
+      };
+  };
+
+  programs.ssh = with pkgs; {
+    knownHostsFiles = [
+      (writeText "github.keys" ''
+        # github.com:22 SSH-2.0-babeld-05989c77
+        # github.com:22 SSH-2.0-babeld-05989c77
+        # github.com:22 SSH-2.0-babeld-05989c77
+        # github.com:22 SSH-2.0-babeld-05989c77
+        # github.com:22 SSH-2.0-babeld-05989c77
+        github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=
+        github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=
+        github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl
+      '')
+      (writeText "gitlab.keys" ''
+        # gitlab.com:22 SSH-2.0-GitLab-SSHD
+        # gitlab.com:22 SSH-2.0-GitLab-SSHD
+        # gitlab.com:22 SSH-2.0-GitLab-SSHD
+        # gitlab.com:22 SSH-2.0-GitLab-SSHD
+        # gitlab.com:22 SSH-2.0-GitLab-SSHD
+        gitlab.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9
+        gitlab.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY=
+        gitlab.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf
+      '')
+      (writeText "codeberg.keys" ''
+        # codeberg.org:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2
+        # codeberg.org:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2
+        # codeberg.org:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2
+        # codeberg.org:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2
+        # codeberg.org:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2
+        codeberg.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8hZi7K1/2E2uBX8gwPRJAHvRAob+3Sn+y2hxiEhN0buv1igjYFTgFO2qQD8vLfU/HT/P/rqvEeTvaDfY1y/vcvQ8+YuUYyTwE2UaVU5aJv89y6PEZBYycaJCPdGIfZlLMmjilh/Sk8IWSEK6dQr+g686lu5cSWrFW60ixWpHpEVB26eRWin3lKYWSQGMwwKv4LwmW3ouqqs4Z4vsqRFqXJ/eCi3yhpT+nOjljXvZKiYTpYajqUC48IHAxTWugrKe1vXWOPxVXXMQEPsaIRc2hpK+v1LmfB7GnEGvF1UAKnEZbUuiD9PBEeD5a1MZQIzcoPWCrTxipEpuXQ5Tni4mN
+        codeberg.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL2pDxWr18SoiDJCGZ5LmxPygTlPu+cCKSkpqkvCyQzl5xmIMeKNdfdBpfbCGDPoZQghePzFZkKJNR/v9Win3Sc=
+        codeberg.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIVIC02vnjFyL+I4RHfvIGNtOgJMe769VTF1VR4EB3ZB
+      '')
+      (writeText "sr.ht.keys" ''
+        # git.sr.ht:22 SSH-2.0-OpenSSH_9.6
+        # git.sr.ht:22 SSH-2.0-OpenSSH_9.6
+        # git.sr.ht:22 SSH-2.0-OpenSSH_9.6
+        # git.sr.ht:22 SSH-2.0-OpenSSH_9.6
+        # git.sr.ht:22 SSH-2.0-OpenSSH_9.6
+        git.sr.ht ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDZ+l/lvYmaeOAPeijHL8d4794Am0MOvmXPyvHTtrqvgmvCJB8pen/qkQX2S1fgl9VkMGSNxbp7NF7HmKgs5ajTGV9mB5A5zq+161lcp5+f1qmn3Dp1MWKp/AzejWXKW+dwPBd3kkudDBA1fa3uK6g1gK5nLw3qcuv/V4emX9zv3P2ZNlq9XRvBxGY2KzaCyCXVkL48RVTTJJnYbVdRuq8/jQkDRA8lHvGvKI+jqnljmZi2aIrK9OGT2gkCtfyTw2GvNDV6aZ0bEza7nDLU/I+xmByAOO79R1Uk4EYCvSc1WXDZqhiuO2sZRmVxa0pQSBDn1DB3rpvqPYW+UvKB3SOz
+        git.sr.ht ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCj6y+cJlqK3BHZRLZuM+KP2zGPrh4H66DacfliU1E2DHAd1GGwF4g1jwu3L8gOZUTIvUptqWTkmglpYhFp4Iy4=
+        git.sr.ht ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMZvRd4EtM7R+IHVMWmDkVU3VLQTSwQDSAvW0t2Tkj60
+      '')
+    ];
+  };
+
+  systemd.services = concatMapAttrs createMirrorService mirrors;
+  systemd.paths = concatMapAttrs createMirrorPath mirrors;
+  systemd.targets.git-mirroring = {
+    wantedBy = [ "multi-user.target" ];
+    wants = pipe
+      repoMirrors [
+      (mapAttrsToList mkMirrorWants)
+      flatten
+    ];
+  };
+}
diff --git a/system/settings/services/virtualisation.nix b/system/settings/services/virtualisation.nix
index dbe041c7..172dfcec 100644
--- a/system/settings/services/virtualisation.nix
+++ b/system/settings/services/virtualisation.nix
@@ -12,9 +12,11 @@
       runAsRoot = false;
     };
   };
+  programs.virt-manager = {
+    enable = true;
+  };
 
   environment.systemPackages = with pkgs; [
-    virt-manager
     OVMF
   ];
 }
diff --git a/system/settings/services/xserver.nix b/system/settings/services/xserver.nix
index c5a82d48..29f181ee 100644
--- a/system/settings/services/xserver.nix
+++ b/system/settings/services/xserver.nix
@@ -20,12 +20,6 @@ with lib; {
     xorg.xdpyinfo
     xclip
     xfontsel
-
-    arc-theme
-    arc-icon-theme
-
-    gtk-engine-murrine
-    gtk_engines
   ];
 
   fonts = {
@@ -57,24 +51,15 @@ with lib; {
     };
     packages = with pkgs;
       [
-        gohufont
-        dina-font
-        terminus_font
-
         corefonts
-
         xorg.fontmiscmisc
         xorg.fontcursormisc
       ]
       ++ lib.optionals config.fonts.fontconfig.antialias [
         cantarell-fonts
 
-        fira
-        fira-code
-        fira-mono
         ibm-plex
 
-        oxygenfonts
         noto-fonts-color-emoji
 
         office-code-pro
diff --git a/system/settings/user-interface.nix b/system/settings/user-interface.nix
index 20cac135..d9d3297f 100644
--- a/system/settings/user-interface.nix
+++ b/system/settings/user-interface.nix
@@ -4,23 +4,14 @@
 , ...
 }: {
   documentation.info.enable = true;
-  nixpkgs.config.firefox.enableOfficialBranding = true;
 
   environment.systemPackages = with pkgs; [
-    aria2
-    pcmanfm
-
     epdfview
-    geeqie
 
     lxappearance
     lxrandr
     lxtask
 
-    mpv
-
-    cifs-utils
-
     trash-cli
   ];
 
@@ -59,11 +50,9 @@
     };
   };
 
-  programs.dconf.enable = true;
-
   programs.nh = {
     enable = true;
-    flake = "/home/alan/projects/alanpearce.eu/nixfiles";
+    flake = builtins.toString ../..;
     clean = {
       enable = true;
       extraArgs = "--keep-since 14d";
diff --git a/user/.gitignore b/user/.gitignore
deleted file mode 100644
index d7bc17e5..00000000
--- a/user/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-home.nix
-result
\ No newline at end of file
diff --git a/user/config.nix b/user/config.nix
index c12a0bb1..5a420d7b 100644
--- a/user/config.nix
+++ b/user/config.nix
@@ -1,5 +1,16 @@
-{ pkgs }: {
-  allowUnfree = true;
-  allowUnfreePredicate = pkg: true;
+{ pkgs }:
+let
+  inherit (pkgs) lib;
+in
+{
+  allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
+    "beeper"
+    "discord"
+    "input-fonts"
+    "tabnine"
+  ];
   input-fonts.acceptLicense = true;
+  permittedInsecurePackages = [
+    "electron-28.3.3" # for logseq 0.10.9
+  ];
 }
diff --git a/user/emacs/early-init.el b/user/emacs/early-init.el
index 29dadb92..d24c661d 100644
--- a/user/emacs/early-init.el
+++ b/user/emacs/early-init.el
@@ -4,9 +4,16 @@
       frame-inhibit-implied-resize t
       byte-compile-warnings '(not free-vars unresolved noruntime lexical make-local cl-functions))
 
+(require 'xdg)
+(startup-redirect-eln-cache
+ (expand-file-name "emacs/native-compile"
+                   (xdg-cache-home)))
+
 ;; Disable all the bars, unless on macOS, in which case, keep the menu bar.
 (unless (eq window-system 'ns)
   (menu-bar-mode -1))
 (scroll-bar-mode -1)
 (tool-bar-mode -1)
 (set-fringe-mode '(4 . 4))
+
+(setenv "LSP_USE_PLISTS" "true") ; must match with lsp-mode override
diff --git a/user/emacs/init.el b/user/emacs/init.el
index dddd0ec0..b666e033 100644
--- a/user/emacs/init.el
+++ b/user/emacs/init.el
@@ -90,6 +90,10 @@
                                                    original-stimmung-themes-string))
                     (load-theme current-theme :noconfirm)))))))
 
+(global-set-key (kbd "<pinch>") 'ignore)
+(global-set-key (kbd "<C-wheel-up>") 'ignore)
+(global-set-key (kbd "<C-wheel-down>") 'ignore)
+
 (setq font-lock-maximum-decoration '((t . 1))
       jit-lock-stealth-time 1.25
       jit-lock-stealth-nice 0.5
@@ -133,7 +137,11 @@
 (setq-default display-line-numbers 'relative
               display-line-numbers-widen t
               display-line-numbers-width 4)
-(setq frame-resize-pixelwise t)
+(setq frame-resize-pixelwise t
+      window-resize-pixelwise t
+      display-buffer-alist `(("\\*\\(?:shell\\|compilation\\)\\*" display-buffer-in-side-window
+                              (side . bottom) (slot . 0) (preserve-size . (nil . t))
+                              (no-other-window . t) (no-delete-other-windows . t))))
 
 (defun noct-relative ()
   "Show relative line numbers."
@@ -478,6 +486,12 @@ _C-k_: prev  _u_pper              _=_: upper/lower       _s_mart resolve
   :defer 2
   :config (evil-commentary-mode +1))
 
+(use-package evil-lion
+  :after evil
+  :defer 10
+  :config (progn
+            (evil-lion-mode +1)))
+
 (use-package evil-matchit
   :after evil
   :defer 2
@@ -568,7 +582,7 @@ _C-k_: prev  _u_pper              _=_: upper/lower       _s_mart resolve
             (setq kind-icon-default-face 'corfu-default)
             (add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter)))
 
-(use-package tabnine
+(use-package tabnine-core
   :config (progn
             (setq tabnine-binaries-folder "~/.local/tabnine")
             (with-demoted-errors "TabNine error: %s"
@@ -635,11 +649,10 @@ _C-k_: prev  _u_pper              _=_: upper/lower       _s_mart resolve
 ;;;; Auto-reloading
 
 (use-package autorevert
-  :defer 1
   :config (progn
             (setq auto-revert-verbose nil
                   auto-revert-use-notify t)
-            (global-auto-revert-mode t)))
+            (global-auto-revert-mode 1)))
 
 (setq delete-by-moving-to-trash t)
 
@@ -666,11 +679,18 @@ _C-k_: prev  _u_pper              _=_: upper/lower       _s_mart resolve
             (add-to-list 'tramp-default-proxies-alist
                          `(,(regexp-quote (system-name)) nil nil))))
 
+(use-package ssh-deploy
+  :config (progn
+            (ssh-deploy-line-mode +1)
+            (ssh-deploy-add-find-file-hook)
+            (ssh-deploy-add-after-save-hook)))
+
 ;;; Directories
 
 (setq dired-dwim-target t
       dired-recursive-copies 'top
       dired-listing-switches "-alh --group-directories-first"
+      dired-kill-when-opening-new-dired-buffer t
       dired-recursive-deletes (if delete-by-moving-to-trash
                                   'always
                                 'top))
@@ -707,7 +727,9 @@ _C-k_: prev  _u_pper              _=_: upper/lower       _s_mart resolve
 
 (use-package eshell-toggle
   :commands (eshell-toggle)
-  :general ("C-`" #'eshell-toggle))
+  :general ("C-`" #'eshell-toggle)
+  :config (progn
+            (setq eshell-toggle-find-project-root-package 'project)))
 
 (declare-function eshell-push-command "esh-buf-stack" (CMD))
 (defun my-bind-esh-push ()
@@ -777,6 +799,13 @@ _C-k_: prev  _u_pper              _=_: upper/lower       _s_mart resolve
 (with-eval-after-load 'project
   (add-to-list 'project-vc-extra-root-markers "go.mod"))
 
+(setq-default go-ts-mode-indent-offset 2)
+(use-package templ-ts-mode
+  :gfhook #'eglot-format-before-save-mode
+  :defer t
+  :config (progn
+            (setq-default go-ts-mode-indent-offset 2)))
+
 ;;;; nim
 (use-package nim-mode
   :defer t
@@ -786,6 +815,7 @@ _C-k_: prev  _u_pper              _=_: upper/lower       _s_mart resolve
 
 ;;;; js
 (setq js-enabled-frameworks '(javascript))
+(add-to-list 'auto-mode-alist '("\\.[cm]js\\'" . js-ts-mode))
 
 ;;;; typescript
 
@@ -798,7 +828,7 @@ _C-k_: prev  _u_pper              _=_: upper/lower       _s_mart resolve
 (add-hook 'compilation-filter-hook #'colourise-compilation-buffer)
 
 ;;;; shell
-(general-add-hook 'sh-mode-hook
+(general-add-hook '(sh-mode-hook bash-ts-mode-hook fish-mode-hook)
                   (lambda ()
                     (general-add-hook 'after-save-hook
                                       #'executable-make-buffer-file-executable-if-script-p :append :local)))
@@ -806,8 +836,6 @@ _C-k_: prev  _u_pper              _=_: upper/lower       _s_mart resolve
 (add-to-list 'auto-mode-alist '("\\.env\\'" . conf-unix-mode))
 (add-to-list 'auto-mode-alist '("\\.zsh\\'" . shell-script-mode))
 (add-to-list 'auto-mode-alist '("zshenv\\'" . shell-script-mode))
-(add-to-list 'auto-mode-alist '("zshrc\\'"  . shell-script-mode))
-(setq sh-shell-file "/usr/bin/env zsh")
 
 (add-hook 'shell-mode-hook 'ansi-color-for-comint-mode-on)
 
@@ -882,14 +910,16 @@ _C-k_: prev  _u_pper              _=_: upper/lower       _s_mart resolve
 
 (use-package web-mode
   :mode (("\\.html?.erb\\'" . web-mode)
+         ("\\.gotmpl\\'" . web-mode)
          ("\\.tmpl\\'" . web-mode))
   :config (setq web-mode-enable-auto-pairing nil
                 web-mode-style-padding 2
                 web-mode-script-padding 2
-                web-mode-engines-alist '(("go" . "\\.tmpl\\'"))))
+                web-mode-engines-alist '(("go" . "\\.tmpl\\'")
+                                         ("go" . "\\.gotmpl\\'"))))
 
 (use-package emmet-mode
-  :ghook '(web-mode-hook sgml-mode-hook))
+  :ghook '(web-mode-hook sgml-mode-hook templ-ts-mode-hook))
 
 ;;; IDE features
 
@@ -941,6 +971,32 @@ _C-k_: prev  _u_pper              _=_: upper/lower       _s_mart resolve
               (evil-ex-define-cmd "pb" #'project-switch-to-buffer)
               (evil-ex-define-cmd "psw[itch]" #'project-switch-project))))
 
+(use-package treemacs
+  :general (:keymaps 'treemacs-mode-map
+                     [mouse-1] #'treemacs-single-click-expand-action)
+  :config (progn
+            (treemacs-project-follow-mode t)
+            (setq treemacs-is-never-other-window t
+                  treemacs-select-when-already-in-treemacs 'move-back
+                  treemacs-eldoc-display nil
+                  treemacs-indentation '(8 px)
+                  treemacs-show-hidden-files nil
+                  treemacs-recenter-after-project-jump 'on-distance
+                  treemacs-missing-project-action 'remove)))
+
+(use-package treemacs-evil
+  :after treemacs)
+
+(use-package treemacs-magit
+  :after treemacs)
+
+(use-package treemacs-nerd-icons
+  :after treemacs
+  :init (progn
+          (setq treemacs-nerd-icons-tab " "))
+  :config (progn
+            (treemacs-load-theme "simple")))
+
 (use-package consult-ghq
   :defer 5
   :general (:keymaps 'project-prefix-map
@@ -961,12 +1017,15 @@ _C-k_: prev  _u_pper              _=_: upper/lower       _s_mart resolve
   :general ([remap project-vc-dir] #'magit-project-status)
   (:keymaps 'project-prefix-map "m" #'magit-project-status)
   :init (progn
-          (with-eval-after-load 'project
-            (add-to-list 'project-switch-commands '(magit-project-status "Magit") t)))
+          (defvar magit-auto-revert-mode nil)
+          (setq magit-auto-revert-mode nil)
+          (require 'project)
+          (add-to-list 'project-switch-commands '(magit-project-status "Magit") t))
   :config (progn
             (setq magit-section-visibility-indicator nil
                   magit-diff-refine-hunk t
-                  magit-auto-revert-immediately t
+                  magit-auto-revert-mode nil
+                  magit-auto-revert-immediately nil ; unnecessary when global-auto-revert-mode is enabled
                   magit-display-buffer-function #'magit-display-buffer-fullcolumn-most-v1)
             (remove-hook 'magit-status-sections-hook 'magit-insert-tags-header)
             (remove-hook 'magit-section-highlight-hook 'magit-section-highlight)
@@ -974,6 +1033,11 @@ _C-k_: prev  _u_pper              _=_: upper/lower       _s_mart resolve
             (remove-hook 'magit-section-highlight-hook 'magit-diff-highlight)
             (require 'magit-extras)))
 
+(use-package magit-todos
+  :after magit
+  :config (progn
+            (magit-todos-mode +1)))
+
 (use-package git-gutter-fringe
   :defer 5
   :config (progn
@@ -1001,7 +1065,9 @@ _C-k_: prev  _u_pper              _=_: upper/lower       _s_mart resolve
   :defer 2
   :config (progn
             (editorconfig-mode +1)
-            (setq editorconfig-lisp-use-default-indent t)))
+            (setq editorconfig-lisp-use-default-indent t)
+            (setf (alist-get 'templ-ts-mode editorconfig-indentation-alist)
+                  'go-ts-mode-indent-offset)))
 
 (setq-default ispell-dictionary "en_GB-ise-w_accents")
 (setq ispell-extra-args '("--sug-mode=ultra" "--camel-case"))
@@ -1034,11 +1100,22 @@ _C-k_: prev  _u_pper              _=_: upper/lower       _s_mart resolve
 
 (defvaralias 'nix-ts-mode-hook 'nix-mode-hook)
 
+(use-package consult-lsp
+  :commands (consult-lsp-symbols
+             consult-lsp-diagnostics))
+
+(define-minor-mode eglot-format-before-save-mode
+  "Whether to ask the LSP to format the buffer before saving"
+  :init-val nil
+  (if eglot-format-before-save-mode
+      (add-hook 'before-save-hook #'eglot-format-buffer nil 'local)
+    (remove-hook 'before-save-hook #'eglot-format-buffer 'local)))
+
 (use-package eglot
   :defer 3
   :general (:states 'normal :keymaps 'eglot-mode-map
                     "gr" #'xref-find-references
-                    "C-t" #'xref-pop-marker-stack)
+                    "C-t" #'xref-go-back)
   :ghook ('(typescript-mode-hook
             dockerfile-mode-hook
             yaml-mode-hook
@@ -1048,6 +1125,7 @@ _C-k_: prev  _u_pper              _=_: upper/lower       _s_mart resolve
             nim-mode-hook
             html-mode-hook
             nix-mode-hook
+            templ-ts-mode-hook
             toml-ts-mode-hook
             haskell-mode-hook)
           #'eglot-ensure)
@@ -1055,14 +1133,14 @@ _C-k_: prev  _u_pper              _=_: upper/lower       _s_mart resolve
             (when (assoc 'nix-mode eglot-server-programs)
               (setf (car (assoc 'nix-mode eglot-server-programs)) '(nix-mode nix-ts-mode)))
             (nconc eglot-server-programs '((toml-ts-mode "taplo" "lsp" "stdio")))
-            (advice-add 'eglot--message :before-while (lambda (formatstring &rest rest)
+            (advice-add 'eglot--message :before-until (lambda (formatstring &rest rest)
                                                         (s-starts-with-p "Connected!" formatstring)))
             (defun my/setup-eglot-eldoc ()
               (push 'flymake-eldoc-function eldoc-documentation-functions))
             (add-hook 'eglot-managed-mode-hook 'my/setup-eglot-eldoc)
             (setq-default eglot-workspace-configuration
                           '( :yaml (:keyOrdering nil)
-                             :nix (:autoArchive t)
+                             :nil (:nix (:flake (:autoArchive t)))
                              :gopls ( :staticcheck t
                                       :usePlaceholders t)))
             (defun my/eglot-capf ()
@@ -1090,12 +1168,13 @@ _C-k_: prev  _u_pper              _=_: upper/lower       _s_mart resolve
   ('lsp-completion-mode-hook #'my/lsp-mode-setup-completion)
   :general (:states 'normal :keymaps 'lsp-mode-map
                     "gr" #'xref-find-references
-                    "C-t" #'xref-pop-marker-stack)
+                    "C-t" #'xref-go-back)
   :config (progn
             (setq lsp-auto-guess-root t
                   lsp-auto-execute-action nil
                   lsp-headerline-breadcrumb-enable nil
                   lsp-enable-suggest-server-download nil
+                  lsp-modeline-diagnostics-enable nil
                   lsp-completion-provider :none ; value `:capf' is actually for company :(
                   lsp-diagnostics-provider t     ; this means prefer flymake over flycheck, why‽
                   )
@@ -1131,16 +1210,18 @@ _C-k_: prev  _u_pper              _=_: upper/lower       _s_mart resolve
       (call-interactively #'lsp-rename)
     (call-interactively #'eglot-rename)))
 
-;; Inside a javascript project, it's common to install tools locally to
-;; the project.  This will allows emacs to find their executables.
+(defun my/ls-consult-symbol ()
+  (interactive)
+  (if lsp-mode
+      (call-interactively #'consult-lsp-symbols)
+    (call-interactively #'consult-eglot-symbols)))
 
-(use-package add-node-modules-path
-  :config (setq add-node-modules-max-depth 6)
-  :ghook ('(feature-mode-hook
-            js-base-mode-hook
-            json-ts-mode-hook
-            typescript-ts-mode-hook)
-          #'add-node-modules-path))
+(defun my/ls-code-actions ()
+  (interactive)
+  (call-interactively
+   (if lsp-mode
+       #'lsp-execute-code-action
+     #'eglot-code-actions)))
 
 ;;;; Reformat on save
 
@@ -1166,15 +1247,13 @@ _C-k_: prev  _u_pper              _=_: upper/lower       _s_mart resolve
             (setq apheleia-formatters
                   (append apheleia-formatters '((nixpkgs-fmt "nixpkgs-fmt")
                                                 (golines "golines")
+                                                (taplo "taplo" "format" "-")
                                                 (prettier-gotmpl
                                                  "prettier" "--stdin-filepath" filepath
                                                  "--parser=go-template" (apheleia-formatters-indent "--use-tabs" "--tab-width")))))
-            (setf (alist-get 'go-ts-mode apheleia-mode-alist)
-                  '(golines)
-                  (alist-get 'web-mode apheleia-mode-alist)
-                  '(prettier-gotmpl))
             (setq apheleia-mode-alist (append apheleia-mode-alist '((nix-ts-mode . nixpkgs-fmt)
-                                                                    (nix-mode . nixpkgs-fmt))))
+                                                                    (nix-mode . nixpkgs-fmt)
+                                                                    (toml-ts-mode . taplo))))
             (add-hook 'apheleia-mode-hook #'turn-off-format-all-mode))
   :init (progn
           (apheleia-global-mode +1)))
@@ -1188,14 +1267,16 @@ _C-k_: prev  _u_pper              _=_: upper/lower       _s_mart resolve
   "w" '(:keymap evil-window-map :package evil)
   "x" '(:keymap ctl-x-map)
   "c" (general-simulate-key "C-c")
-  "j" #'consult-eglot-symbols
+  "j" #'my/ls-consult-symbol
   "r" #'my/ls-rename
   "q" #'evil-delete-buffer
   "p" '(:keymap project-prefix-map :package project)
   "v" #'split-window-right
   "o" #'other-window
+  "s" #'treemacs-select-window
   "u" #'universal-argument
   ";" #'execute-extended-command
+  "a" #'my/ls-code-actions
   "bb" #'consult-buffer
   "bx" #'kill-this-buffer
   "br" #'revert-buffer
@@ -1222,7 +1303,8 @@ _C-k_: prev  _u_pper              _=_: upper/lower       _s_mart resolve
   "iu" #'insert-char
   "xe" #'eval-last-sexp
   "xx" #'eval-defun
-  "xi" #'consult-imenu)
+  "xi" #'consult-imenu
+  "z" '(:keymap ssh-deploy-prefix-map :package ssh-deploy))
 
 (let ((mail-config (expand-file-name "mail.el" user-emacs-directory)))
   (if (file-readable-p mail-config)
diff --git a/user/gnupg/dirmngr.conf b/user/gnupg/dirmngr.conf
deleted file mode 100644
index f69421d7..00000000
--- a/user/gnupg/dirmngr.conf
+++ /dev/null
@@ -1,3 +0,0 @@
-keyserver hkps://keys.openpgp.org
-# keyserver hkp://zkaan2xfbuxia2wpf7ofnkbz6r5zdbbvxbunvp5g2iebopbfc4iqmbad.onion
-# use-tor
diff --git a/user/gnupg/gpa.conf b/user/gnupg/gpa.conf
deleted file mode 100644
index 2e33e80b..00000000
--- a/user/gnupg/gpa.conf
+++ /dev/null
@@ -1,2 +0,0 @@
-default-key 5FA779613E2AB0EEFC6DD3056A56F2A314E23293
-detailed-view
diff --git a/user/gnupg/gpg-agent.conf b/user/gnupg/gpg-agent.conf
deleted file mode 100644
index 52eb1dca..00000000
--- a/user/gnupg/gpg-agent.conf
+++ /dev/null
@@ -1,3 +0,0 @@
-enable-ssh-support
-default-cache-ttl 600
-max-cache-ttl 7200
diff --git a/user/gnupg/gpg.conf b/user/gnupg/gpg.conf
deleted file mode 100644
index 61df93cf..00000000
--- a/user/gnupg/gpg.conf
+++ /dev/null
@@ -1,83 +0,0 @@
-#
-# This is an implementation of the Riseup OpenPGP Best Practices
-# https://help.riseup.net/en/security/message-security/openpgp/best-practices
-#
-
-#-----------------------------
-# default key
-#-----------------------------
-
-# The default key to sign with. If this option is not used, the default key is
-# the first key found in the secret keyring
-
-# default-key 
-
-#-----------------------------
-# behavior
-#-----------------------------
-
-# Disable inclusion of the version string in ASCII armored output
-no-emit-version
-
-# Disable comment string in clear text signatures and ASCII armored messages
-no-comments
-
-# Display long key IDs
-keyid-format 0xlong
-
-# List all keys (or the specified ones) along with their fingerprints
-with-fingerprint
-
-# Display the calculated validity of user IDs during key listings
-list-options show-uid-validity
-verify-options show-uid-validity
-
-# Try to use the GnuPG-Agent. With this option, GnuPG first tries to connect to
-# the agent before it asks for a passphrase.
-use-agent
-
-#-----------------------------
-# keyserver
-#-----------------------------
-
-# This is the server that --recv-keys, --send-keys, and --search-keys will
-# communicate with to receive keys from, send keys to, and search for keys on
-keyserver hkps://keys.openpgp.org
-
-# Provide a certificate store to override the system default
-# Get this from https://sks-keyservers.net/sks-keyservers.netCA.pem
-# keyserver-options ca-cert-file=.gnupg/sks-keyservers.netCA.pem
-
-# Set the proxy to use for HTTP and HKP keyservers - default to the standard
-# local Tor socks proxy
-# It is encouraged to use Tor for improved anonymity. Preferrably use either a
-# dedicated SOCKSPort for GnuPG and/or enable IsolateDestPort and
-# IsolateDestAddr
-# keyserver-options http-proxy=socks5-hostname://127.0.0.1:9050
-
-# When using --refresh-keys, if the key in question has a preferred keyserver
-# URL, then disable use of that preferred keyserver to refresh the key from
-keyserver-options no-honor-keyserver-url
-# When searching for a key with --search-keys, include keys that are marked on
-# the keyserver as revoked
-keyserver-options include-revoked
-
-
-#-----------------------------
-# algorithm and ciphers
-#-----------------------------
-
-# list of personal digest preferences. When multiple digests are supported by
-# all recipients, choose the strongest one
-personal-cipher-preferences AES256 AES192 AES CAST5
-
-# list of personal digest preferences. When multiple ciphers are supported by
-# all recipients, choose the strongest one
-personal-digest-preferences SHA512 SHA384 SHA256 SHA224
-
-# message digest algorithm used when signing a key
-cert-digest-algo SHA512
-
-# This preference list is used for new keys and becomes the default for
-# "setpref" in the edit menu
-default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed
\ No newline at end of file
diff --git a/user/gnupg/trezor/dirmngr.conf b/user/gnupg/trezor/dirmngr.conf
deleted file mode 100644
index 17612d46..00000000
--- a/user/gnupg/trezor/dirmngr.conf
+++ /dev/null
@@ -1 +0,0 @@
-keyserver hkps://keys.openpgp.org
\ No newline at end of file
diff --git a/user/mba.nix b/user/mba.nix
index a205fea4..ca7b0706 100644
--- a/user/mba.nix
+++ b/user/mba.nix
@@ -4,7 +4,6 @@
     ./settings/development/base.nix
     ./settings/development/javascript.nix
     ./settings/development/golang.nix
-    ./settings/development/lisp.nix
     ./settings/development/web.nix
     ./settings/darwin.nix
     ./settings/emacs.nix
@@ -15,10 +14,12 @@
     ./settings/ssh.nix
     ./settings/tabnine.nix
     ./settings/user-interface.nix
+    <private>
+    <private/ssh.nix>
   ];
 
   home.username = "alan";
-  home.homeDirectory = "/home/alan";
+  home.homeDirectory = "/Users/alan";
   home.stateVersion = "22.11";
 
   launchd.agents.colima = {
diff --git a/user/nanopi.nix b/user/nanopi.nix
index 44561eda..f2dfceac 100644
--- a/user/nanopi.nix
+++ b/user/nanopi.nix
@@ -7,7 +7,6 @@
     ./settings/git.nix
     ./settings/nix.nix
     ./settings/nixos.nix
-    ./settings/music-management.nix
     ./settings/ssh.nix
   ];
   home = {
diff --git a/user/nvim/init.lua b/user/nvim/init.lua
index f7bb020a..f9162167 100644
--- a/user/nvim/init.lua
+++ b/user/nvim/init.lua
@@ -2,6 +2,7 @@ vim.opt.autoindent = true
 
 vim.opt.background = "light"
 vim.opt.number = true
+vim.opt.clipboard = "unnamed"
 
 vim.g.mapleader = ","
 
diff --git a/user/prefect.nix b/user/prefect.nix
index b5c4789e..6cf31cbc 100644
--- a/user/prefect.nix
+++ b/user/prefect.nix
@@ -7,24 +7,21 @@
     ./settings/dunst.nix
     ./settings/emacs.nix
     ./settings/development/base.nix
-    ./settings/development/lisp.nix
     ./settings/development/golang.nix
     ./settings/development/javascript.nix
     ./settings/development/web.nix
     ./settings/fish.nix
     ./settings/git.nix
     ./settings/gnupg.nix
-    ./settings/i3.nix
     ./settings/music.nix
     ./settings/nixos.nix
-    ./settings/rofi.nix
     ./settings/passwords.nix
     ./settings/ssh.nix
-    ./settings/sxhkd.nix
     ./settings/tabnine.nix
-    ./settings/trezor.nix
     ./settings/user-interface.nix
     ./settings/xresources.nix
+    <private>
+    <private/ssh.nix>
   ];
 
   home.username = "alan";
@@ -33,8 +30,7 @@
     transgui
   ];
 
-  xsession.windowManager.i3.config.startup = [
-  ];
+  xsession.enable = true;
 
   home.stateVersion = "22.11";
 }
diff --git a/user/server.nix b/user/server.nix
index 235e6908..9b3db3fc 100644
--- a/user/server.nix
+++ b/user/server.nix
@@ -9,6 +9,7 @@
     ./settings/nix.nix
     ./settings/nixos.nix
     ./settings/development/base.nix
+    <private>
   ];
   home = {
     username = "alan";
diff --git a/user/settings/base.nix b/user/settings/base.nix
index 6725b6db..52cb14ac 100644
--- a/user/settings/base.nix
+++ b/user/settings/base.nix
@@ -1,4 +1,4 @@
-{ config
+args@{ config
 , lib
 , pkgs
 , ...
@@ -6,7 +6,10 @@
   imports = [
     ./neovim.nix
     ./shell.nix
+    (import <nix-index-database/home-manager-module.nix>
+      (args // { databases = import <nix-index-database/packages.nix>; }))
   ];
+
   # Let Home Manager install and manage itself.
   programs.home-manager.enable = true;
   manual = {
@@ -22,11 +25,8 @@
     in
     {
       EMAIL = "alan@alanpearce.eu";
-      MANPAGER = "bat -l man -p";
       ABDUCO_SOCKET_DIR = "${state}/abduco";
-      GNUPGHOME = "${data}/gnupg";
       SOLARGRAPH_CACHE = "${cache}/solargraph";
-      ASPELL_CONF = "per-conf ${conf}/aspell/aspell.conf; personal ${conf}/aspell/en.pws; repl ${conf}/aspell/en.prepl; dict-dir ${config.home.profileDirectory}/lib/aspell";
       ELECTRUMDIR = "${data}/electrum";
       DOCKER_CONFIG = "${conf}/docker";
       npm_config_userconfig = "${conf}/npm/config";
@@ -72,7 +72,7 @@
     ];
     config = {
       theme = "ansi";
-      style = "header-filename,header-filesize,grid,rule";
+      style = "header-filename,header-filesize,rule";
     };
   };
   programs.nix-index-database.comma.enable = true;
@@ -99,9 +99,11 @@
       abduco
       dvtm
       walk
-      (aspellWithDicts (d: [ d.en d.en-computers d.en-science d.de ]))
-    ]
-    ++ (
+      nuspell
+    ] ++ (with pkgs.hunspellDicts; [
+      en-gb-large
+      de-de
+    ]) ++ (
       if !stdenv.isDarwin
       then [
         file
diff --git a/user/settings/development/base.nix b/user/settings/development/base.nix
index b1c636ad..5bf54172 100644
--- a/user/settings/development/base.nix
+++ b/user/settings/development/base.nix
@@ -1,5 +1,4 @@
 { config
-, lib
 , pkgs
 , ...
 }:
@@ -20,6 +19,7 @@
 
       mosh
 
+      curlHTTP3
       xh
       htmlq
       jq
@@ -27,7 +27,6 @@
       miller
       watchexec
       entr
-      httping
 
       diffoscopeMinimal
 
@@ -39,11 +38,12 @@
       colima
       docker-client
     ] else [
-      zeal
+      httping
     ]);
 
   home.sessionVariables = {
     FLY_NO_UPDATE_CHECK = "1";
+    MOSH_TITLE_NOPREFIX = "1";
     LIMA_INSTANCE = "nixos";
   };
 
@@ -57,7 +57,10 @@
         insert_final_newline = true;
         indent_style = "space";
         indent_size = 2;
-        tab_width = 4;
+        tab_width = 2;
+      };
+      "*.fish" = {
+        indent_size = 4;
       };
       justfile = {
         indent_style = "tab";
@@ -75,6 +78,7 @@
     ea = "direnv allow";
     ex = "direnv exec";
     es = "direnv status";
+    curl3 = "${pkgs.curlHTTP3}/bin/curl --http3";
   };
   programs.direnv = {
     enable = true;
@@ -84,6 +88,7 @@
     config = {
       global = {
         disable_stdin = true;
+        load_dotenv = true;
         strict_env = true;
         hide_env_diff = true;
       };
@@ -104,4 +109,7 @@
       }
     '';
   };
+  services.lorri = {
+    enable = pkgs.stdenv.isLinux;
+  };
 }
diff --git a/user/settings/development/golang.nix b/user/settings/development/golang.nix
index 6373a1c4..a9293a31 100644
--- a/user/settings/development/golang.nix
+++ b/user/settings/development/golang.nix
@@ -6,12 +6,16 @@
     gogetdoc
     gotools
     golines
+    impl
+    gomodifytags
     golangci-lint
     golangci-lint-langserver
-    nodePackages.prettier-plugin-go-template
+    personal.prettier-plugin-go-template
   ];
   programs.emacs.extraPackages = epkgs: (with epkgs; [
     go-eldoc
+    go-tag
+    templ-ts-mode
   ]);
   programs.neovim.plugins = with pkgs.vimPlugins; [
     coc-go
diff --git a/user/settings/development/javascript.nix b/user/settings/development/javascript.nix
index 4d196cb4..fa418cc1 100644
--- a/user/settings/development/javascript.nix
+++ b/user/settings/development/javascript.nix
@@ -23,10 +23,6 @@
     };
   };
 
-  programs.emacs.extraPackages = epkgs: (with epkgs; [
-    add-node-modules-path
-  ]);
-
   home.shellAliases = {
     bn = "bun";
     bni = "bun install";
diff --git a/user/settings/development/web.nix b/user/settings/development/web.nix
index 4d26416c..77b3e01d 100644
--- a/user/settings/development/web.nix
+++ b/user/settings/development/web.nix
@@ -1,5 +1,6 @@
 { config
 , pkgs
+, lib
 , ...
 }: {
   home.packages = with pkgs.nodePackages; [
@@ -10,12 +11,90 @@
   ] ++ (with pkgs; [
     flyctl
     prettierd
-    htmlformat
+    personal.htmlformat
+    nodePackages.vercel
   ]);
   home.shellAliases = {
     # 0.2.25 current completion command only affects `flyctl`, although `fly` is a link to `flyctl`
     fly = "flyctl";
   };
+  programs.chromium = lib.mkIf pkgs.stdenv.isLinux {
+    enable = true;
+    package = pkgs.ungoogled-chromium;
+    extensions = [
+      # # uBlock origin
+      { id = "cjpalhdlnbpafiamejdnhcphjbkeiagm"; }
+      {
+        id = "ocaahdebbfolfmndjeplogmgcagdmblk";
+        updateUrl = "https://raw.githubusercontent.com/NeverDecaf/chromium-web-store/master/updates.xml";
+      }
+    ];
+    dictionaries = with pkgs.hunspellDictsChromium; [
+      en-gb
+      de-de
+    ];
+  };
+  programs.firefox = {
+    enable = pkgs.stdenv.isLinux;
+    package = pkgs.firefox-devedition;
+    profiles.dev-edition-default = {
+      search.default = "DuckDuckGo";
+      extensions = with pkgs.nur.repos.rycee.firefox-addons; [
+        a11ycss
+        disable-javascript
+        laboratory-by-mozilla
+        side-view
+        ublock-origin
+      ];
+      settings = {
+        "browser.aboutConfig.showWarning" = false;
+        "browser.theme.content-theme" = 1;
+        "browser.theme.toolbar-theme" = 1;
+        "browser.tabs.firefox-view" = false;
+        "extensions.activeThemeID" = "firefox-compact-light@mozilla.org";
+      };
+    };
+    policies = {
+      AutoFillCreditCardEnabled = false;
+      CaptivePortal = false;
+      Cookies = {
+        Behavior = "reject-foreign";
+      };
+      SanitizeOnShutdown = {
+        Cache = true;
+        Cookies = true;
+        FormData = true;
+      };
+      DisableFirefoxAccounts = true;
+      DisableFirefoxScreenShots = true;
+      DisableFirefoxStudies = true;
+      DisableMasterPasswordCreation = true;
+      DisablePasswordReveal = true;
+      DisablePocket = true;
+      DisableTelemetry = true;
+      DNSOverHTTPS.Enabled = false;
+      DontCheckDefaultBrowser = true;
+      EnableTrackingProtection = true;
+      GoToIntranetSiteForSingleWordEntryInAddressBar = true;
+      Homepage.URL = "http://localhost:7331";
+      NewTabPage = false;
+      NoDefaultBookmarks = true;
+      OfferToSaveLogins = false;
+      OverrideFirstRunPage = "";
+      OverridePostUpdatePage = "";
+      PasswordManagerEnabled = false;
+      PrintingEnabled = false;
+      SearchBar = "separate";
+      ShowHomeButton = true;
+      UserMessaging = {
+        ExtensionRecommendations = false;
+        FeatureRecommendations = false;
+        UrlbarInterventions = false;
+        SkipOnboarding = true;
+        MoreFromMozilla = false;
+      };
+    };
+  };
   programs.emacs.extraPackages = epkgs: (with epkgs; [
     caddyfile-mode
     emmet-mode
diff --git a/user/settings/emacs.nix b/user/settings/emacs.nix
index a65f8d8c..cc9deab6 100644
--- a/user/settings/emacs.nix
+++ b/user/settings/emacs.nix
@@ -6,13 +6,6 @@
 let
   inherit (pkgs) stdenv;
 
-  nativeCompileDirectory = "${config.xdg.cacheHome}/emacs/native-compile/";
-
-  darwinPath = pkgs.runCommandLocal "path_helper " { } ''
-    eval $(/usr/libexec/path_helper)
-    echo -n $PATH > $out
-  '';
-
   editorScript = pkgs.writeScriptBin "edit" ''
     #!${pkgs.runtimeShell}
     if [ -z "$1" ]; then
@@ -34,6 +27,11 @@ in
     xfuncname = "^(((;;;+ )|\\(|([ \t]+\\(((cl-|el-patch-)?def(un|var|macro|method|custom)|gb/))).*)$";
   };
 
+  services.emacs = lib.mkIf stdenv.isLinux {
+    enable = true;
+    package = config.programs.emacs.finalPackage;
+    client.enable = true;
+  };
   programs.emacs = {
     enable = true;
     package = lib.mkDefault (pkgs.emacs29.override { withGTK3 = true; });
@@ -65,6 +63,7 @@ in
         cdg = "cd (project-root)";
       };
     };
+
     extraPackages = epkgs: (with epkgs;
       [
         ace-link
@@ -80,6 +79,7 @@ in
         consult-dir
         consult-ghq
         consult-eglot
+        consult-lsp
         crux
         dired-git-info
         docker-compose-mode
@@ -100,6 +100,7 @@ in
         evil-commentary
         evil-embrace
         evil-exchange
+        evil-lion
         evil-matchit
         evil-mu4e
         evil-numbers
@@ -130,6 +131,7 @@ in
         lsp-mode
         lispyville
         magit
+        magit-todos
         markdown-mode
         marginalia
         nerd-icons
@@ -142,11 +144,16 @@ in
         quickrun
         rainbow-mode
         rainbow-delimiters
+        ssh-deploy
         stimmung-themes
         systemd
         tempel
         tempel-collection
         eglot-tempel
+        treemacs
+        treemacs-evil
+        treemacs-magit
+        treemacs-nerd-icons
         treesit-grammars.with-all-grammars
         treesit-auto
         vc-msg
@@ -166,23 +173,36 @@ in
               --replace-fail '"prettier"' '"prettierd"'
           '';
         });
+      just-mode = self.melpaPackages.just-mode.overrideAttrs (old: {
+        src = pkgs.fetchFromGitHub {
+          owner = "alanpearce";
+          repo = "just-mode.el";
+          rev = "08eb25e0641b4b6d79aa39182c70b9d40c56fc02";
+          sha256 = "13ccphbd95bn79pqbw6ycnfy1z8yd32swrhd1ljl7gwbhi7q6s0p";
+          # date = "2024-05-01T22:22:02+02:00";
+        };
+      });
+      treemacs-nerd-icons = self.melpaPackages.treemacs-nerd-icons.overrideAttrs (old: {
+        src = pkgs.fetchFromGitHub {
+          owner = "aaronmiller";
+          repo = "treemacs-nerd-icons";
+          sha256 = "171pdi5y9zym26iqi02c5p7zw9i7xxhv4csnjb7qlkkczha17jgp";
+          rev = "90b4f0868eea1ea923dee97d2c5457c21a61f37a";
+          # date = "2023-11-02T13:42:55-04:00";
+        };
+      });
       lsp-mode = self.melpaPackages.lsp-mode.overrideAttrs {
-        LSP_USE_PLISTS = "true"; # sync with below
+        LSP_USE_PLISTS = "true"; # must be set in early-init
       };
     };
     extraConfig = ''
-      (setenv "LSP_USE_PLISTS" "true") ; sync with above
       (with-eval-after-load 'editorconfig
-        (setq editorconfig-exec-path "${pkgs.editorconfig-core-c}/bin/editorconfig"))
-      (when (featurep 'native-compile)
-        (setq native-compile-target-directory "${nativeCompileDirectory}")
-        (add-to-list 'native-comp-eln-load-path "${nativeCompileDirectory}" :append))
+        (defvar editorconfig-exec-path "${pkgs.editorconfig-core-c}/bin/editorconfig"))
     '' + lib.optionalString stdenv.isDarwin ''
       (with-eval-after-load 'files
-        (setq insert-directory-program "${pkgs.coreutils-prefixed}/bin/gls"))
+        (defvar insert-directory-program "${pkgs.coreutils-prefixed}/bin/gls"))
       (with-eval-after-load 'dired
-        (setq dired-use-ls-dired t))
-      (setq exec-path (parse-colon-path (setenv "PATH" "${pkgs.lib.readFile darwinPath}")))
+        (defvar dired-use-ls-dired t))
     '';
   };
   home.packages = with pkgs; [
diff --git a/user/settings/fish/functions/dired.fish b/user/settings/fish/functions/dired.fish
index 97174b35..a59ffac2 100644
--- a/user/settings/fish/functions/dired.fish
+++ b/user/settings/fish/functions/dired.fish
@@ -4,5 +4,5 @@ function dired
     else
         set --function repo $pwd
     end
-    emacsclient -e "(magit-status \"$argv[1]\")"
+    emacsclient --suppress-output --eval "(dired \"$argv[1]\")"
 end
diff --git a/user/settings/fish/functions/magit.fish b/user/settings/fish/functions/magit.fish
index 048fd42f..96c90096 100644
--- a/user/settings/fish/functions/magit.fish
+++ b/user/settings/fish/functions/magit.fish
@@ -4,5 +4,5 @@ function magit
     else
         set --function repo $pwd
     end
-    emacsclient -e "(magit-status \"$argv[1]\")"
+    emacsclient --suppress-output --eval "(magit-status \"$argv[1]\")"
 end
diff --git a/user/settings/fish/functions/newest.fish b/user/settings/fish/functions/newest.fish
index 73e26dcc..3f2fa66f 100644
--- a/user/settings/fish/functions/newest.fish
+++ b/user/settings/fish/functions/newest.fish
@@ -4,9 +4,11 @@ function newest
         return 1
     end
     for arg in $argv
-        fd --hidden --print0 --max-depth 1 . $arg |
-            bfs -files0-from - \( -name .git -prune \) -o \( -printf '%TY%Tm%Td%TR %TF %h/%f\n' \) |
-            sort --reverse --key 1n,1 | head --lines 1 |
-            cut -d ' ' -f 2,3
+        if test -d $arg
+            fd --hidden --print0 --max-depth 1 . $arg |
+                bfs -files0-from - \( -name .git -prune \) -o \( -printf '%TY%Tm%Td%TH%TM %TF %h/%f\n' \) |
+                sort --key 1nr,1 | head --lines 1 |
+                cut -d ' ' -f 2,3
+        end
     end
 end
diff --git a/user/settings/fish/functions/oldest.fish b/user/settings/fish/functions/oldest.fish
index 66aad720..140d74cc 100644
--- a/user/settings/fish/functions/oldest.fish
+++ b/user/settings/fish/functions/oldest.fish
@@ -4,9 +4,11 @@ function oldest
         return 1
     end
     for arg in $argv
-        fd --hidden --print0 --max-depth 1 . $arg |
-            bfs -files0-from - \( -name .git -prune \) -o \( -printf '%TY%Tm%Td%TR %TF %h/%f\n' \) |
-            sort --key 1n,1 | head --lines 1 |
-            cut -d ' ' -f 2,3
+        if test -d $arg
+            fd --hidden --print0 --max-depth 1 . $arg |
+                bfs -files0-from - \( -name .git -prune \) -o \( -printf '%TY%Tm%Td%TH%TM %TF %h/%f\n' \) |
+                sort --key 1n,1 | head --lines 1 |
+                cut -d ' ' -f 2,3
+        end
     end
 end
diff --git a/user/settings/git.nix b/user/settings/git.nix
index 18368864..769b3728 100644
--- a/user/settings/git.nix
+++ b/user/settings/git.nix
@@ -17,6 +17,11 @@
       init = {
         defaultBranch = "main";
       };
+      advice = {
+        addEmptyPathspec = false;
+        detachedHead = false;
+        mergeConflict = false;
+      };
       ghq = {
         root = "${config.home.homeDirectory}/projects";
         user = "alanpearce";
diff --git a/user/settings/gnupg.nix b/user/settings/gnupg.nix
index 913b5d27..d719b618 100644
--- a/user/settings/gnupg.nix
+++ b/user/settings/gnupg.nix
@@ -2,8 +2,18 @@
 , pkgs
 , ...
 }: {
-  home.file.".gnupg" = {
-    recursive = true;
-    source = ../gnupg;
+  programs.gpg = {
+    enable = true;
+    homedir = "${config.xdg.dataHome}/gnupg";
+    settings = {
+      keyserver = "hkps://keys.openpgp.org";
+    };
+  };
+  services.gpg-agent = {
+    enable = true;
+    pinentryPackage = with pkgs;
+      if stdenv.isDarwin
+      then pinentry_mac
+      else pinentry-qt;
   };
 }
diff --git a/user/settings/i3.nix b/user/settings/i3.nix
index 79a7811d..e8951062 100644
--- a/user/settings/i3.nix
+++ b/user/settings/i3.nix
@@ -7,6 +7,9 @@
     recursive = true;
     source = ../i3/i3status;
   };
+  home.sessionVariables = {
+    TERMINAL = "xterm";
+  };
   xsession.windowManager.i3 =
     let
       mod = "Mod4";
diff --git a/user/settings/neovim.nix b/user/settings/neovim.nix
index 315a309b..dcee5fde 100644
--- a/user/settings/neovim.nix
+++ b/user/settings/neovim.nix
@@ -31,16 +31,22 @@
       enable = true;
       settings = {
         "json.enable" = true;
-        "suggest.enablePreview" = true;
-        "coc.preferences.formatOnSaveFiletypes" = [ "nix" ];
-        "nil.formatting.command" = "nixpkgs-fmt";
-        semantictokens = {
-          filetypes = [ "nix" ];
-        };
         languageserver = {
           nix = {
             command = "${pkgs.nil}/bin/nil";
             filetypes = [ "nix" ];
+            rootPatterns = [ "flake.nix" ];
+            settings = {
+              coc.preferences.formatOnSaveFiletypes = [ "nix" ];
+              links.tooltip = true;
+              semanticTokens = {
+                filetypes = [ "nix" ];
+              };
+              nil = {
+                formatting.command = [ "nixpkgs-fmt" ];
+                nix.flake.autoArchive = true;
+              };
+            };
           };
           lua = {
             command = "${pkgs.lua-language-server}/bin/lua-language-server";
diff --git a/user/settings/nix.nix b/user/settings/nix.nix
index e0fb3d3e..66c00daf 100644
--- a/user/settings/nix.nix
+++ b/user/settings/nix.nix
@@ -1,25 +1,26 @@
 { config
 , pkgs
+, lib
 , ...
 }:
 let
   toml = pkgs.formats.toml { };
 in
 {
-  imports = [
-    ../../pin.nix
-  ];
   nixpkgs.config = import ../config.nix;
   nix = {
     enable = true;
-    package = pkgs.nix;
+    # needed for "standalone" home-manager, conflicts with module
+    package = lib.mkDefault pkgs.nix;
     settings = {
       use-xdg-base-directories = true;
     };
   };
 
   home.packages = with pkgs; [
+    cached-nix-shell
     nil
+    npins
     nix-prefetch-scripts
     nix-init
     nix-update
@@ -29,11 +30,10 @@ in
     nixpkgs-lint
     nixpkgs-review
     nix-output-monitor
-    cachix
   ];
   xdg.configFile."nix-init/config.toml".source = toml.generate "config.toml" {
     maintainers = [ "alanpearce" ];
-    nixpkgs = "builtins.getFlake \"nixpkgs\"";
+    nixpkgs = "<nixpkgs>";
   };
   programs.emacs.extraPackages = epkgs: (with epkgs; [
     nix-mode
diff --git a/user/settings/nixos.nix b/user/settings/nixos.nix
index 05b87333..85ea7f72 100644
--- a/user/settings/nixos.nix
+++ b/user/settings/nixos.nix
@@ -6,8 +6,8 @@
 
   home.shellAliases = {
     srb = "nixos-rebuild";
-    rbs = "nixos-rebuild switch";
-    rbb = "nixos-rebuild boot";
+    rbs = "nixos-rebuild switch --fast";
+    rbb = "nixos-rebuild boot --fast";
     rbr = "nixos-rebuild switch --rollback";
   };
 }
diff --git a/user/settings/shell.nix b/user/settings/shell.nix
index 5358f618..fe2b4690 100644
--- a/user/settings/shell.nix
+++ b/user/settings/shell.nix
@@ -30,7 +30,7 @@ in
       llr = "ll -t";
 
       c = "lk";
-      "c," = "cd $(ghq list -p nixfiles)";
+      "c," = "cd ${config.home.homeDirectory + "/projects/alanpearce.eu/nixfiles"}";
       cg = "cd $(git root)";
       cdg = "cd $(git root)";
 
@@ -125,7 +125,11 @@ in
       hol = "home-manager generations";
       hox = "home-manager expire-generations '-30 days'";
 
-      nsh = "nix shell";
+      lw = "lorri watch";
+      lw1 = "lorri watch --once";
+      lwo = "lorri watch --once";
+
+      nsh = "nix-shell";
       nb = "nix build";
       nl = "nix log"; # shadows `coreutils.nl`, but I've never used that yet
       nr = "nix run";
@@ -135,6 +139,8 @@ in
       nfp = "nix flake prefetch";
       nfu = "nix flake update";
       nfl = "nix flake lock";
+      nfsh = "nix shell";
+      ndev = "nix develop";
       nlg = "nix-env --list-generations";
       snlg = "sudo nix-env --list-generations --profile /nix/var/nix/profiles/system";
       ngc = "nix-collect-garbage --delete-older-than 30d";
@@ -142,6 +148,7 @@ in
     };
   };
   home.packages = with pkgs; [
+    babashka
     fzf
     up
   ];
diff --git a/user/settings/ssh.nix b/user/settings/ssh.nix
index f5073c8d..b7c46bba 100644
--- a/user/settings/ssh.nix
+++ b/user/settings/ssh.nix
@@ -12,6 +12,10 @@
     serverAliveInterval = 15;
     extraConfig = ''
       VerifyHostKeyDNS ask
+      CanonicalizeHostname yes
+      CanonicalizeFallbackLocal no
+      CanonicalizeMaxDots 0
+      CanonicalDomains home.arpa hydra-pinecone.ts.net
     '';
     includes = [
       "local.ssh_config"
diff --git a/user/settings/tabnine.nix b/user/settings/tabnine.nix
index 377b8217..9502c68d 100644
--- a/user/settings/tabnine.nix
+++ b/user/settings/tabnine.nix
@@ -21,18 +21,28 @@
       disable_local_when_using_battery = false;
       enable_power_saving_mode = false;
       enable_telemetry = false;
+      exclude_file_masks = true;
       force_local_hub = true;
       generation = 0;
+      guuid = null;
+      gusr.gcgdc = "0000000000000000000000000000000000000000000000000000000000000000";
+      has_git_repos = null;
+      heartbeat_interval_seconds = null;
       hide_deep_information_message = false;
       hide_promotional_message = true;
       hosted_deep_completions_enabled = "Disabled";
       ignore_all_lsp = false;
       inline_suggestions_mode = true;
+      inline_suggestions_mode_clients = null;
+      last_service_level = null;
       line_suggestions = true;
       local_enabled = "Yes";
       local_indexing = null;
       local_model_size = null;
+      manually_selected_model = null;
+      model_hash_override = null;
       num_of_suggestions = 5;
+      omit_prefix_suggestions = null;
       onboarding = {
         model_type = null;
         skipped_login = true;
@@ -40,6 +50,7 @@
       };
       rate_limit_amount = null;
       rate_limit_interval_seconds = null;
+      rlhf = null;
       semantic_status = {
         css = "Enabled";
         dockerfile = "Enabled";
@@ -57,7 +68,9 @@
       tabnine_cloud_certificate_domain = null;
       tabnine_cloud_host = null;
       tabnine_cloud_port = null;
+      tabnine_hub_port = null;
       use_specialized_model_if_available = true;
+      user_understands_that_enabling_tabnine_cloud_sends_code_to_tabnine_servers = false;
     };
     lspConfig.language = {
       typescript = {
diff --git a/user/settings/trezor.nix b/user/settings/trezor.nix
deleted file mode 100644
index 6996d9b0..00000000
--- a/user/settings/trezor.nix
+++ /dev/null
@@ -1,10 +0,0 @@
-{ config
-, pkgs
-, ...
-}: {
-  home.file.".ssh/agent.config" = {
-    text = ''
-      ecdsa-curve-name = ed25519
-    '';
-  };
-}
diff --git a/user/settings/user-interface.nix b/user/settings/user-interface.nix
index 6adf57ff..29de4439 100644
--- a/user/settings/user-interface.nix
+++ b/user/settings/user-interface.nix
@@ -9,9 +9,6 @@ in
   imports = [
     ./kitty.nix
   ];
-  home.sessionVariables = {
-    TERMINAL = "xterm";
-  };
 
   services.ssh-agent = {
     enable = stdenv.hostPlatform.isLinux;
@@ -47,11 +44,15 @@ in
       })
     ]
     ++ lib.optionals (!stdenv.isDarwin) (with pkgs; [
-      logseq
+      logseq # 0.10.9 is insecure, see ../config.nix
       (discord.override { withOpenASAR = true; })
 
+      zeal
       falkon
-      mu
       beeper
+      kdePackages.neochat
+      kdePackages.kleopatra
     ]);
+  services.lorri.enableNotifications = true;
+  services.emacs.startWithUserSession = "graphical";
 }