diff --git a/.claude/tasks/0005-fish-shell-module.md b/.claude/tasks/0005-fish-shell-module.md new file mode 100644 index 0000000..7c0ce56 --- /dev/null +++ b/.claude/tasks/0005-fish-shell-module.md @@ -0,0 +1,34 @@ +--- +spec: laptop-mvi +blocked-by: 0001-skeleton-and-building-host +--- + +## What to build + +A fish `Module`, configured natively via home-manager, that makes the shell feel like the current CachyOS setup but is correct for NixOS. Translate the existing `cachyos-config.fish` and the rest of the fish snapshot under `reference/home/.config/fish/`: the greeting, the bat-manpager, the `done` and bang-bang plugins, the helper functions, and the eza/nav aliases. Drop or replace every Arch/pacman-specific part with its NixOS equivalent. Set fish as the default login shell. + +Configure the plugins natively through home-manager rather than a fish plugin manager. + +## Acceptance criteria + +- [x] A fish `Module` (following the `Enable convention`) is enabled on `neogaia` and configured natively via home-manager. +- [x] The greeting, bat-manpager, `done` and bang-bang plugins, helper functions, and eza/nav aliases from the reference config are reproduced. +- [x] All Arch/pacman-specific parts are dropped or replaced with NixOS equivalents. +- [x] fish is the user's default login shell. +- [x] The `neogaia` toplevel still builds with the fish `Module` enabled. + +## Implementation Notes + +- **Plugins are native, not inlined.** `done` and `bang-bang` come from `pkgs.fishPlugins.*` via `programs.fish.plugins`; home-manager drops them into `~/.config/fish/conf.d/` where fish auto-sources them. + The reference config inlined the bang-bang `__history_previous_command` functions and binds by hand; the plugin supplies those, so they are not re-inlined. +- **`done` tuning uses `set -g`, not `set -U`.** The reference set `__done_min_cmd_duration`/`__done_notification_urgency_level` as universal variables, which persist to the universal-variable file and then ignore config changes. + A declarative config must own these each session, so they are set global (`set -g`) in `interactiveShellInit`. +- **Arch/pacman aliases dropped:** `grubup`, `fixpacman`, `mirror` (`cachyos-rate-mirrors`), `apt`/`apt-get` (`man pacman`), `big` (`expac`), `gitpkg`, `rip` (`expac`). + **Replaced with NixOS equivalents:** `update` → `sudo nixos-rebuild switch` (was `pacman -Syu`), `cleanup` → `sudo nix-collect-garbage -d` (was `pacman -Rns`). +- **Machine-specific PATH hacks dropped, per spec story 6 ("no impure environment lookup").** The hardcoded `BUN_INSTALL`, the `node-v24...` tarball path, and `~/Applications/depot_tools` from `config.fish` are absolute/impure and are not reproduced; such tooling should be Nix-provided when its own Module arrives. + The portable bits are kept: `~/.local/bin` on PATH (guarded) and sourcing `~/.fish_profile`. +- **`env.fish` and `rustup.fish` deliberately not carried over.** `ANDROID_HOME`/platform-tools (Android SDK) and `source ~/.cargo/env.fish` (rust) are dev-toolchain integrations outside the MVI's core tooling (fish/tmux/nvim/Claude Code); they belong to future per-toolchain Modules that provide those tools through Nix rather than sourcing an impure env file. +- **`hw` (`hwinfo --short`) and `tb` (`nc termbin.com 9999`) dropped.** These are generic rather than pacman-specific, but each needs an extra package (`hwinfo`, a `netcat`) that the minimal install does not otherwise pull in; left out of the MVI and easy to add later. +- **`copy` kept verbatim** (including the upstream `trim-right` call) to preserve exact parity with the current shell. +- **Verification.** The `neogaia` system toplevel builds (the spec's primary seam). + The rendered `~/.config/fish/` was inspected in the build output: aliases, the three helper functions, the `fastfetch` greeting, the bat manpager, the `done` tuning vars, and the `conf.d/plugin-done.fish` + `conf.d/plugin-bang-bang.fish` plugin files are all present; `users.users.alexion.shell` resolves to `pkgs.fish`. diff --git a/hosts/neogaia/default.nix b/hosts/neogaia/default.nix index 1170b15..49f8925 100644 --- a/hosts/neogaia/default.nix +++ b/hosts/neogaia/default.nix @@ -35,6 +35,9 @@ # An SSH daemon so the rest of the setup can be driven over the network. services.openssh.enable = true; + # fish as the login shell. + modules.fish.enable = true; + # Locale preferences for the base system. time.timeZone = "America/New_York"; i18n.defaultLocale = "en_GB.UTF-8"; diff --git a/modules/fish.nix b/modules/fish.nix new file mode 100644 index 0000000..a1ac01d --- /dev/null +++ b/modules/fish.nix @@ -0,0 +1,141 @@ +{ + config, + lib, + pkgs, + ... +}: +# fish for the primary user, configured natively through home-manager and set +# as their default login shell. Wires the done and bang-bang plugins, a +# fastfetch greeting, a bat-backed manpager, helper functions, and the eza and +# navigation aliases. +let + cfg = config.modules.fish; + user = config.user.name; +in +{ + options.modules.fish.enable = lib.mkEnableOption "fish as the user's shell, configured via home-manager"; + + config = lib.mkIf cfg.enable { + # System-level fish: registers it in /etc/shells and installs vendor + # completions. + programs.fish.enable = true; + users.users.${user}.shell = pkgs.fish; + + home-manager.users.${user} = { + home.packages = with pkgs; [ + eza + bat + fastfetch + wget + ]; + + programs.fish = { + enable = true; + + # done notifies when a long command finishes; bang-bang restores the !! + # and !$ history bindings. + plugins = [ + { + name = "done"; + src = pkgs.fishPlugins.done.src; + } + { + name = "bang-bang"; + src = pkgs.fishPlugins.bang-bang.src; + } + ]; + + shellAliases = { + # Replace ls with eza. + ls = "eza -al --color=always --group-directories-first --icons"; + la = "eza -a --color=always --group-directories-first --icons"; + ll = "eza -l --color=always --group-directories-first --icons"; + lt = "eza -aT --color=always --group-directories-first --icons"; + "l." = "eza -a | grep -e '^\\.'"; + + # Walk up the tree. + ".." = "cd .."; + "..." = "cd ../.."; + "...." = "cd ../../.."; + "....." = "cd ../../../.."; + "......" = "cd ../../../../.."; + + vi = "nvim"; + vim = "nvim"; + cp = "cp -v"; + tmx = "tmux new-session -A -s"; + + tarnow = "tar -acf "; + untar = "tar -zxvf "; + wget = "wget -c "; + psmem = "ps auxf | sort -nr -k 4"; + psmem10 = "ps auxf | sort -nr -k 4 | head -10"; + dir = "dir --color=auto"; + vdir = "vdir --color=auto"; + grep = "grep --color=auto"; + fgrep = "fgrep --color=auto"; + egrep = "egrep --color=auto"; + jctl = "journalctl -p 3 -xb"; + please = "sudo"; + + # Rebuild the system, and reclaim disk from old generations. + update = "sudo nixos-rebuild switch"; + cleanup = "sudo nix-collect-garbage -d"; + }; + + functions = { + # Run fastfetch as the welcome message. + fish_greeting = "fastfetch"; + + history = { + description = "Show command history with timestamps"; + body = "builtin history --show-time='%F %T '"; + }; + + backup = { + description = "Copy to .bak"; + argumentNames = "filename"; + body = "cp $filename $filename.bak"; + }; + + copy = { + description = "Copy a file, or recursively copy a source directory into a destination"; + body = '' + set count (count $argv | tr -d \n) + if test "$count" = 2; and test -d "$argv[1]" + set from (echo $argv[1] | trim-right /) + set to (echo $argv[2]) + command cp -r $from $to + else + command cp $argv + end + ''; + }; + }; + + interactiveShellInit = '' + set -gx EDITOR nvim + set -gx VISUAL nvim + + # Render man pages through bat. + set -x MANROFFOPT "-c" + set -x MANPAGER "sh -c 'col -bx | bat -l man -p'" + + # Tune the done plugin: only notify for commands past 10s, at low urgency. + set -g __done_min_cmd_duration 10000 + set -g __done_notification_urgency_level low + + # Prepend ~/.local/bin to PATH when it exists. + if test -d ~/.local/bin + fish_add_path ~/.local/bin + end + + # Apply fish-compatible profile overrides if present. + if test -f ~/.fish_profile + source ~/.fish_profile + end + ''; + }; + }; + }; +}