refactor(flake): add user registry and multi-user host support
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.
This commit is contained in:
+213
@@ -0,0 +1,213 @@
|
||||
# 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 |
|
||||
Reference in New Issue
Block a user