Separate user identity (data) from the reusable modules, and let a host declare any number of users instead of exactly one. - users/registry.nix: per-user identity (name, email, groups, authorized and signing keys) as the single source of identity; no user data is hardcoded in the modules. - mkHost takes a `users` set keyed by username; per-user identity is injected into each home config via the `identity` module arg (extraSpecialArgs is per-host, so it cannot carry per-user data). - modules/users.nix builds accounts from the registry; modules/ssh.nix no longer defines authorized keys (the registry owns them); home/git.nix and home/desktop.nix read `identity`; users/emmathorpe/work.nix drops its now-redundant git identity override. - Restructure the tree: users/, home/, modules/, hosts/, lib/ replace the former lyrathorpe/ and system/ layout. - Add standalone homeConfigurations (the portable subset: shell, git, editor, claude) and an exported homeModules output for use on machines not managed by this flake, or as an input to other flakes. Behaviour-preserving for existing hosts: lyrathorpe-mbp and emmathorpe-edaas evaluate to identical derivations; lyrathorpe-t400, lyrathorpe-macpro31 and lyrathorpe-rpi5 differ only by de-duplicating a repeated authorized_keys entry. Fixes the SSH authorized-key leak (one user's key was applied to every account), the hardcoded default git identity, and the hardcoded EDaaS linger setting.
214 lines
20 KiB
Markdown
214 lines
20 KiB
Markdown
# Interactive shell environment
|
|
|
|
Everything the shell, terminal multiplexer, git and ssh do beyond their defaults,
|
|
and where each is defined. All of it is managed declaratively through
|
|
home-manager — edit the listed file and rebuild, never the generated dotfiles.
|
|
|
|
Keyboard shortcuts have their own reference: [`KEYBINDINGS.md`](./KEYBINDINGS.md).
|
|
|
|
| Area | Defined in |
|
|
| -------------------------------------- | ----------------------------------------------------- |
|
|
| zsh, CLI tools, tmux, ssh, auto-tmux | [`shell.nix`](./shell.nix) |
|
|
| git (+ delta, commitizen) | [`git.nix`](./git.nix) |
|
|
| Neovim (nixvim) + LSP | [`editor.nix`](./editor.nix) |
|
|
| Claude Code (CLAUDE.md, style, memory) | [`claude.nix`](./claude.nix) |
|
|
| GUI apps, GTK/Firefox theming, cursor | [`desktop.nix`](./desktop.nix) (graphical hosts only) |
|
|
|
|
Shared by every host via [`default.nix`](./default.nix); the work box also layers
|
|
[`work.nix`](./work.nix) on top (work email, its own ssh config, extra packages,
|
|
and the C#/Helm language servers).
|
|
|
|
---
|
|
|
|
## zsh
|
|
|
|
| Feature | Notes |
|
|
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| oh-my-zsh | plugins `git`, `man`, `sudo` (Esc-Esc to prepend sudo), `colored-man-pages`, `extract`; theme `robbyrussell` |
|
|
| Autosuggestion | fish-style history suggestions as you type (→ to accept) |
|
|
| Syntax highlighting | commands coloured by validity as you type |
|
|
| Completion | menu completion; the dump is rebuilt on every activation (see Maintenance) |
|
|
| History | 100k in-memory/on-disk, deduped, space-prefixed commands ignored, timestamped, **shared live across sessions**; file stays at `~/.zsh_history` |
|
|
| Dotfiles location | `dotDir` is `~/.config/zsh` (XDG) — `.zshrc`/`.zshenv`/`.zcompdump` live there; `~/.zshenv` only bootstraps `$ZDOTDIR` |
|
|
| History substring search | type a fragment, then ↑/↓ cycles matching past commands — works in foot, iTerm2 and the Linux TTY (both CSI and SS3 arrow encodings bound) |
|
|
| Prompt | hostname is prefixed when over SSH |
|
|
|
|
**Aliases:** `ls`/`ll`/`la`/`lt` → `eza` (icons + git), `cls` → `clear`. git aliases live in git.nix (below).
|
|
|
|
## CLI tools
|
|
|
|
| Tool | What it gives you |
|
|
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| `fzf` | `Ctrl-R` fuzzy history, `Ctrl-T` file picker, `Alt-C` fuzzy cd (Catppuccin-themed) |
|
|
| `zoxide` | `z <fragment>` jumps to frecent directories |
|
|
| `direnv` + `nix-direnv` | per-project environments auto-loaded on `cd` (cached Nix dev shells) |
|
|
| `eza` | modern `ls` (drives the ls aliases) |
|
|
| `bat` | syntax-highlighting pager (Catppuccin Mocha theme); behaves like `cat` when piped; also the `MANPAGER` |
|
|
| `ripgrep` / `fd` | fast search (`rg`) and find (`fd`); also back `fzf` |
|
|
| `jq` | JSON processor |
|
|
| `gh` / `tea` | GitHub and Gitea (`code.emmathe.dev`) CLIs; `gh` uses SSH |
|
|
| `nix-index` | `command-not-found`: an unknown command tells you which Nix package provides it (prebuilt DB, no manual indexing) |
|
|
| `comma` (`,`) | run an uninstalled program once: `, cowsay hi` |
|
|
| `nh` | nicer `nixos-rebuild`/`home-manager` with diffs; `$NH_FLAKE` set to the repo. No scheduled GC (it could reap paths a running generation still references) — collect garbage manually with `nh clean all` / `nix-collect-garbage -d` |
|
|
| `btop` | resource monitor, themed Catppuccin Mocha (vendored theme) |
|
|
| `lazygit` | git TUI for staging/rebasing, themed to match (`git.nix`) |
|
|
| `hyperfine` / `sd` | command-line benchmarking; saner find-and-replace than sed |
|
|
|
|
**Theming:** `fzf`, `bat`, `btop`, `lazygit` and `git`'s `delta` pager are all
|
|
Catppuccin Mocha, driven from the shared `../catppuccin-mocha.nix` palette / the
|
|
catppuccin upstream themes.
|
|
|
|
**Env & defaults:** `xdg.enable` on; `PAGER`/`MANPAGER` (bat) set in `default.nix`
|
|
(the editor owns `$EDITOR`/`$VISUAL`); `xdg.mimeApps` maps web→Firefox,
|
|
directories→nemo (`desktop.nix`).
|
|
|
|
## tmux
|
|
|
|
**Auto-start:** opening any interactive terminal — foot, iTerm2, the WSL shell, the
|
|
Linux console — drops you straight into a tmux session named `main` (attach if it
|
|
exists, else create). Panes run a plain non-login zsh. It deliberately does **not**
|
|
fire for SSH sessions, VS Code's integrated terminal, already-inside-tmux, or
|
|
non-interactive shells. Escape hatch: `NO_TMUX=1 <terminal>` opens a bare shell.
|
|
|
|
| Setting | Value |
|
|
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
| Mode keys | vi |
|
|
| Mouse | on |
|
|
| Scrollback | 500000 lines |
|
|
| `escape-time` | 10ms (the 500ms default lagged vim's ESC) |
|
|
| `focus-events` | on (vim autoread) |
|
|
| `base-index` / `pane-base-index` | 1 |
|
|
| Splits | `prefix s` vertical, `prefix v` horizontal (stock `%`/`"` unbound) |
|
|
| Pane nav | `Alt`+arrows (no prefix) |
|
|
| Terminal | `default-terminal tmux-256color`; truecolor advertised per outer terminal (`foot*`, `xterm-256color`/iTerm2) via `terminal-features … RGB` |
|
|
| Clipboard | `set-clipboard on`; foot `terminal-features` advertise truecolor/sync/OSC52/title/cursor |
|
|
|
|
**Plugins:** `sensible`, `vim-tmux-navigator` (Ctrl-h/j/k/l across vim ↔ tmux),
|
|
`yank`, `extrakto` (`prefix`+`Tab`: fzf-grab paths/URLs/text from the pane into
|
|
the prompt), `catppuccin` (Mocha statusline), `resurrect` + `continuum`
|
|
(sessions auto-save and restore across reboots). The statusline draws Nerd-Font
|
|
glyphs — see Fonts.
|
|
|
|
## Fonts
|
|
|
|
**JetBrainsMono Nerd Font**, **Noto Sans** and **Noto Color Emoji** are
|
|
installed on every host (in `common-nixos.nix`, because tmux/terminals run
|
|
everywhere; the Mac installs the Nerd Font to `/Library/Fonts` via the Darwin
|
|
config). `fonts.fontconfig.defaultFonts` maps the generic families so anything
|
|
asking for `monospace` gets the Nerd Font (with emoji fallback) — this also
|
|
gives the WSL box emoji/sans coverage it otherwise lacked. foot uses the Nerd
|
|
Font as its main font automatically. iTerm2's font is a GUI setting — set it to
|
|
_JetBrainsMono Nerd Font_ (Settings → Profiles → Text → Font) so the tmux
|
|
statusline glyphs render instead of `?`.
|
|
|
|
## Editor (Neovim)
|
|
|
|
`nvim` — aliased to `vi`/`vim`, and set as `$EDITOR`/`$VISUAL` — is configured
|
|
declaratively with **nixvim**, so the same plugins and config are baked in on
|
|
every host. Migrated from plain vim; the practical gain is a real LSP stack in
|
|
place of the old (inert) ALE.
|
|
|
|
| Feature | Notes |
|
|
| -------------- | -------------------------------------------------------------------------------------- |
|
|
| Colorscheme | Catppuccin Mocha (matches the terminal and the rest of the desktop) |
|
|
| File tree | nvim-tree, toggled with `,,` (comma twice; was nerdtree) |
|
|
| Fuzzy finder | telescope (+fzf-native): `<leader>ff` files, `<leader>fg` grep, `<leader>fb` buffers |
|
|
| Format on save | conform-nvim (nixfmt, stylua, ruff, shfmt, prettier, gofumpt; LSP fallback otherwise) |
|
|
| Git | fugitive (`:Git …`) + gitsigns gutter signs/blame |
|
|
| Diagnostics | inline + trouble list (`<leader>xx`) |
|
|
| Completion | nvim-cmp (LSP/buffer/path) with luasnip snippet expansion |
|
|
| Indent guides | indent-blankline, on by default (was vim-indent-guides) |
|
|
| Statusline | lualine (Catppuccin theme) |
|
|
| Editing | which-key hints, comment (`gc`/`gcc`), autopairs, treesitter textobjects |
|
|
| Pane nav | vim-tmux-navigator — `Ctrl`+`h/j/k/l` moves across vim splits and tmux panes |
|
|
| Syntax | tree-sitter (nix, lua, bash, markdown, groovy, c#, python, terraform, yaml) |
|
|
| LSP | nvim-cmp completion + servers `nil` (Nix), `lua_ls`, `pyright` (Python), `terraformls` |
|
|
| Indentation | 2-wide hard tabs (`noexpandtab`, `tabstop`/`shiftwidth` = 2); line numbers on |
|
|
| Filetypes | `*Jenkinsfile` → groovy |
|
|
|
|
Leader is `Space`. LSP keymaps (`gd`, `gr`, `K`, `<leader>rn`, `<leader>ca`) and
|
|
the file-tree toggle are listed in
|
|
[`KEYBINDINGS.md`](./KEYBINDINGS.md#neovim). Add a universal language server by
|
|
enabling it under `programs.nixvim.plugins.lsp.servers` in `editor.nix`;
|
|
host-specific ones go in that host's module — the work box (`work.nix`) adds
|
|
`omnisharp` (C#) and `helm_ls` (Helm), kept off the personal machines.
|
|
|
|
## git
|
|
|
|
Pager is **delta**. **commitizen** is installed on every host; `cz` defaults to
|
|
Conventional Commits. **lazygit** (themed) is the TUI. The commit-graph is kept
|
|
current (`gc`/`fetch.writeCommitGraph`) so `lg` stays fast.
|
|
|
|
| Aliases | |
|
|
| ------------------------ | ------------------------------------------------------------------ |
|
|
| `st` `co` `sw` `br` `ci` | status / checkout / switch / branch / commit |
|
|
| `last` `unstage` | last commit / unstage |
|
|
| `amend` `fixup` `undo` | amend-no-edit / `commit --fixup` / soft-reset HEAD~1 (keep staged) |
|
|
| `lg` | graph log, all branches |
|
|
| `cz` `cc` | `git cz <sub>` (e.g. `git cz c`) and `git cc` → commitizen prompt |
|
|
|
|
| Behaviour | |
|
|
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
| Pulls | rebase, with autostash + autosquash |
|
|
| Fetch | prune deleted remote branches |
|
|
| Conflicts | `zdiff3` (shows the common ancestor) |
|
|
| Diffs | histogram algorithm, colour-moved |
|
|
| `rerere` | remembers + replays conflict resolutions |
|
|
| Commit editor | full diff shown (`commit.verbose`) |
|
|
| Misc | branches sorted by date, `column.ui = auto`, `help.autocorrect = prompt`, `push.autoSetupRemote` |
|
|
| Global ignores | `result`, `result-*`, `.direnv`, `*.swp`, `.DS_Store` |
|
|
| Signing | SSH commit + tag signing (`mkDefault`, so a host without the key in its agent can disable it). Personal email `iam@emmathe.dev`; the work box overrides email + signing. |
|
|
|
|
## ssh
|
|
|
|
| Feature | Notes |
|
|
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| ssh-agent | runs on Linux (launchd on macOS); keys added on **first use** so the passphrase is typed once per login session — this also feeds git commit signing |
|
|
| macOS | `UseKeychain` caches the passphrase in the login keychain (guarded by `IgnoreUnknown`, so a non-Apple `ssh` skips it instead of erroring) |
|
|
| Gitea remote | `code.emmathe.dev` → `HostName 10.187.1.76` (DNS-override), `Port 30009`, user `git`, dedicated key, `identitiesOnly` |
|
|
| Defaults | the module's deprecated default block is opted out; equivalents kept under `settings."*"` |
|
|
|
|
The **work box keeps its own `~/.ssh/config`** (home-manager's `programs.ssh` is
|
|
forced off there) but still runs the agent.
|
|
|
|
## Claude Code
|
|
|
|
Managed declaratively by [`claude.nix`](./claude.nix) on every host (the CLI is
|
|
`pkgs.claude-code`, tracked to unstable via the flake overlay).
|
|
|
|
| Managed (static, from Nix) | Left mutable (runtime state) |
|
|
| --------------------------------------------------- | ------------------------------------------------------ |
|
|
| `~/.claude/CLAUDE.md` (persona + memory workflow) | `settings.json` (permissions, model, theme, `/config`) |
|
|
| `~/.claude/output-styles/soviet-engineer.md` | `.credentials.json`, history, caches |
|
|
| `~/.claude/memory/` (read-only symlink to the repo) | |
|
|
|
|
`settings.json` is intentionally **not** managed: Claude rewrites it at runtime
|
|
(interactive permission grants, `/config`), which a read-only store symlink would
|
|
break.
|
|
|
|
**Memory is sourced from this repo.** The files in
|
|
[`claude/memory/`](./claude/memory) are the source of truth; they are symlinked
|
|
read-only into `~/.claude/memory`, so recall works but the runtime "save a
|
|
memory" path does not. To add/change/remove a memory, edit `claude/memory/`
|
|
(one file per memory + the `MEMORY.md` index) and rebuild — `CLAUDE.md` tells
|
|
Claude to route new memories there.
|
|
|
|
## Maintenance behaviours
|
|
|
|
- **zcompdump reset** — `~/.config/zsh/.zcompdump*` (plus legacy `~/.zcompdump*`
|
|
and the cache copy) is removed on every activation, so a stale
|
|
dump (pointing at `/nix/store` paths a rebuild or a manual GC removed) can't
|
|
break completion with `_git: function definition file not found`.
|
|
- **GC** — no scheduled timer; collect garbage deliberately (`nh clean all` /
|
|
`nix-collect-garbage -d`) when no important session is running.
|
|
|
|
## Per-host differences
|
|
|
|
| | Personal Linux (sway) | macOS | Work WSL (EDaaS) |
|
|
| --------------------------- | --------------------- | ----------------- | --------------------------- |
|
|
| Auto-tmux | yes (foot/TTY) | yes (iTerm2) | yes (WSL shell) |
|
|
| git email | `iam@emmathe.dev` | `iam@emmathe.dev` | `…@citrix.com` (work) |
|
|
| ssh config managed | yes | yes | no (keeps corporate config) |
|
|
| ssh-agent | yes | launchd | yes (work module) |
|
|
| GUI / theming (desktop.nix) | yes | no | no |
|