diff --git a/flake.nix b/flake.nix index fc1b2a8..457279b 100644 --- a/flake.nix +++ b/flake.nix @@ -552,5 +552,113 @@ machine.crash() ''; }; + + # `nix develop` — hot-reload loop for dotfiles/quickshell. + # + # hosts/terra/home.nix ships the shell via `xdg.configFile."quickshell"`, + # which COPIES the tree into the store, so ~/.config/quickshell is a + # read-only symlink into /nix/store and every QML tweak costs a + # nixos-rebuild. quickshell DOES hot-reload on file save — but only for + # the files it is watching, which are those frozen store copies. Pointing + # it at the working tree with `qs -p` restores edit-save-see, no rebuild. + # + # quickshell keys instance identity on the CONFIG PATH, so a working-tree + # instance and the store-backed one are two different instances that would + # both map layer-shell bars onto every output. Hence a swap, not a second + # instance — and the swap starts dev FIRST, killing the packaged shell + # only once dev is confirmed up, so a QML error in the working tree leaves + # you on your normal bar instead of no bar at all. + # + # Every kill is scoped to one config (`qs kill` = default only, `qs kill + # -p` = that path only). A blanket kill would also take out unrelated + # quickshell instances — pkgs/rishot.nix is one. + # + # Deliberately NOT wired to direnv (no .envrc in this repo): programs.direnv + # is enabled for this user, so a `use flake` would swap the running desktop + # shell on every `cd` into the checkout, including over ssh. + devShells.${system}.default = + let + pkgs = nixpkgs.legacyPackages.${system}; + # Same nixpkgs terra's home.nix takes pkgs.quickshell from, so the dev + # instance is the identical build to the packaged one. + qs = "${nixpkgs.legacyPackages.${system}.quickshell}/bin/qs"; + git = "${nixpkgs.legacyPackages.${system}.git}/bin/git"; + + # Resolved at RUN time, not build time: the entire point is to run the + # working tree, and `self` here is only a store snapshot of it. + preamble = '' + root="$(${git} rev-parse --show-toplevel 2>/dev/null || pwd)" + cfg="$root/dotfiles/quickshell" + if [ ! -f "$cfg/shell.qml" ]; then + echo "no shell.qml under $cfg — run this from the homelab checkout" >&2 + exit 1 + fi + # `qs list` exits 0 whether or not it found anything, and only emits + # json when it did — so "json came back" is the liveness test. + running() { ${qs} list -p "$1" -j 2>/dev/null | grep -q '"id"'; } + prod_running() { ${qs} list -j 2>/dev/null | grep -q '"id"'; } + ''; + + qs-dev = pkgs.writeShellScriptBin "qs-dev" '' + set -uo pipefail + ${preamble} + + if [ -z "''${WAYLAND_DISPLAY:-}" ]; then + echo "qs-dev: no WAYLAND_DISPLAY — refusing to swap the desktop shell" >&2 + exit 1 + fi + + if running "$cfg"; then + echo "qs-dev: already running from $cfg" + exit 0 + fi + + ${qs} -d -p "$cfg" + + # Confirm it came up before touching the packaged shell. + for _ in $(seq 1 50); do + running "$cfg" && break + sleep 0.1 + done + + if ! running "$cfg"; then + echo "qs-dev: dev shell failed to start — packaged shell left alone" >&2 + echo "qs-dev: run 'qs -p $cfg' in the foreground to see the QML error" >&2 + exit 1 + fi + + ${qs} kill || true + echo "qs-dev: live on $cfg — edits there now hot-reload" + ''; + + qs-prod = pkgs.writeShellScriptBin "qs-prod" '' + set -uo pipefail + ${preamble} + + running "$cfg" && ${qs} kill -p "$cfg" || true + prod_running || ${qs} -d + echo "qs-prod: back on ~/.config/quickshell" + ''; + in + pkgs.mkShell { + packages = [ pkgs.quickshell qs-dev qs-prod ]; + + # Swap on entry, swap back on exit. Three guards: + # - interactive only ($- has i). `nix develop --command X` EXECs X, + # replacing the shell that set the trap, so the restore would + # never run and you'd be left on the dev instance. Non-interactive + # use gets the explicit `nix develop -c qs-dev` instead. + # - WAYLAND_DISPLAY, so entering the shell over ssh cannot kill the + # desktop's bar and leave nothing in its place. + # - a sentinel, so a nested `nix develop` does not swap (and then + # restore) a second time. + shellHook = '' + if [[ $- == *i* ]] && [ -n "''${WAYLAND_DISPLAY:-}" ] && [ -z "''${HOMELAB_QS_DEV:-}" ]; then + export HOMELAB_QS_DEV=1 + qs-dev && trap qs-prod EXIT + fi + echo "homelab devshell — qs-dev (working tree) / qs-prod (packaged); exit restores" + ''; + }; }; }