Compare commits

...

19 Commits

Author SHA1 Message Date
Emma Thorpe d38e3ed616 chore(flake): treefmt + deadnix/statix + pre-commit; relocate work module
CI / flake (pull_request) Failing after 1m22s
- treefmt-nix drives `nix fmt` and the formatting check (nixfmt/shfmt/
  prettier; generated files and flake.lock excluded). Replaces the
  bespoke find-based check.
- deadnix and statix as flake checks and pre-commit hooks; deadnix
  ignores module-arg patterns, statix.toml disables the two house-style
  lints (repeated_keys, empty_pattern). Fixed the one real deadnix hit
  (unused overlay arg) and statix hit (use inherit for claude-code).
- git-hooks.nix installs the pre-commit gate via the devShell.
- .editorconfig for the base style.
- Move system/modules/work/default.nix -> lyrathorpe/home/work.nix (it is
  a home-manager module). README gains a Development section; docs
  reformatted by the new formatter.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:57:21 +01:00
Emma Thorpe 6356e07364 feat(nixos): disk hygiene, dedupe shared options, fix MacPro docs
- common-nixos: nix.settings.auto-optimise-store + larger download buffer.
- workstation: fstrim, boot.tmp.cleanOnBoot, and the shared graphical
  options moved here from the per-host configs (pipewire, swaylock PAM
  stub, redistributable firmware) -- MBP-Asahi gains audio it lacked.
- T400: zramSwap for the low-RAM host.
- MBP-Asahi: nixos-apple-silicon binary cache substituter.
- MacPro31 README: describe the real (LVM/UUID) hardware config; it is no
  longer a placeholder.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:56:58 +01:00
Emma Thorpe 4dcf0e8cdd feat(home): theme CLI tools, add staples, env defaults and mime apps
- Catppuccin Mocha for fzf (colors), bat (catppuccin/bat tmTheme) and
  git delta (syntax-theme + navigate/line-numbers/side-by-side).
- CLI staples on every host: ripgrep, fd, jq, btop, plus gh (SSH) and
  tea (Gitea CLI).
- home.sessionVariables: PAGER, MANPAGER (bat), VISUAL; xdg.enable.
- xdg.mimeApps defaults (web->Firefox, directories->nemo).
- Document the stateVersion pin. README updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:56:58 +01:00
Emma Thorpe 0616e3db30 docs: sync shell/keybinding docs with the rest of the branch
CI / flake (pull_request) Failing after 1m15s
Update the interactive-shell README and keybindings reference for changes
made after the initial docs commit: no scheduled GC (manual only),
NO_TMUX escape hatch, default-terminal tmux-256color + truecolor, the
JetBrainsMono Nerd Font (new Fonts section + iTerm2 caveat), the
UseKeychain IgnoreUnknown guard, and the vim-tmux-navigator (Ctrl-hjkl) +
resurrect save/restore tmux bindings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:25:18 +01:00
Emma Thorpe 761d02ddda fix(ssh): guard macOS UseKeychain with IgnoreUnknown
CI / flake (pull_request) Failing after 1m10s
nixpkgs' openssh lacks Apple's keychain patch, so `UseKeychain yes` is
rejected as "Bad configuration option" when that ssh is on PATH. Prefix
it with `IgnoreUnknown UseKeychain` (the module emits IgnoreUnknown first)
so a non-Apple ssh skips it while Apple's ssh still honours it. Still
Darwin-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:20:58 +01:00
Emma Thorpe 1c15c55605 feat(fonts): JetBrains Mono Nerd Font on every host
The tmux statusline draws powerline/Nerd glyphs that default fonts lack,
so they render as blank/"?". tmux runs on every host (not just the Sway
ones), so install the font in the shared common-nixos module rather than
swaywm -- a future console-only or non-Sway host gets it too. The Mac
installs it via the Darwin config (/Library/Fonts). foot names it as its
main font (home/sway.nix).

On macOS, iTerm2's font is still a GUI setting: Settings -> Profiles ->
Text -> Font -> "JetBrainsMono Nerd Font".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:10:41 +01:00
Emma Thorpe a0dcb258c9 fix(tmux): use tmux-256color (not tmux-direct); add NO_TMUX hatch
tmux-direct as default-terminal desyncs zsh's line redraw on some
terminals (iTerm2: duplicated characters on Tab, stray newlines). Switch
to the standard tmux-256color and advertise truecolor per outer terminal
via terminal-features (add xterm-256color:RGB alongside the foot ones).

Also add a NO_TMUX escape hatch to the auto-start guard, so
`NO_TMUX=1 <terminal>` opens a bare shell.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:04:45 +01:00
Emma Thorpe 19792c9390 fix(nh): drop the automatic GC timer; keep nh for rebuilds
The scheduled `nh clean` only reclaimed disk and risked reaping store
paths the current generation still references (notably on nix-darwin).
Keep `programs.nh` (nicer rebuilds + $NH_FLAKE) but remove clean.enable;
GC manually (`nh clean all` / `nix-collect-garbage -d`) when nothing
important is running. The resetZcompdump activation stays as a safety net
for stale completion dumps across rebuilds/manual GC.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 13:51:05 +01:00
Emma Thorpe ac1c04d157 docs: document the interactive shell environment
Add lyrathorpe/home/README.md covering the zsh / CLI tools / tmux / git /
ssh features and nice-to-haves configured across shell.nix and git.nix
(history, fzf/zoxide/direnv/eza/bat, nix-index, nh, tmux plugins +
auto-start, git aliases/settings/signing, ssh agent + Gitea host, the
zcompdump/GC maintenance behaviours, and per-host differences). Link it
from the top-level README alongside the keybindings reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 13:25:25 +01:00
Emma Thorpe d1548644f5 feat(ssh): pin the Gitea host to its IP, overriding DNS
Set HostName 10.187.1.76 on the code.emmathe.dev block so the Gitea
remote resolves to the fixed IP without relying on DNS (same user, port
30009 and key).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 13:23:04 +01:00
Emma Thorpe 7b41584d5c fix(shell): migrate ssh to the settings API; reset stale zcompdump
The home-manager bump deprecated programs.ssh.addKeysToAgent /
matchBlocks / the implicit default block. Move to programs.ssh.settings
with enableDefaultConfig = false, carrying the old defaults under
settings."*" plus AddKeysToAgent, the Darwin UseKeychain, and the
code.emmathe.dev (Port 30009) host. Silences all three ssh warnings.

Also drop ~/.zcompdump on each activation: a stale dump caches /nix/store
paths to completion functions, and once a rebuild or the weekly nh GC
removes them compinit fails with "_git: function definition file not
found" for every completion. Deleting it forces a fresh rebuild from the
current fpath.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 11:55:46 +01:00
Emma Thorpe faf2242539 feat(ssh): pin the Gitea remote in the managed ssh config
The flake's origin (ssh://git@code.emmathe.dev) must resolve on every host.
Add a matchBlock for code.emmathe.dev: user git, Port 30009 (Gitea's
non-default SSH port -- the critical bit), the dedicated
~/.ssh/code.emmathe.dev key, and identitiesOnly. The work box keeps its own
ssh config (programs.ssh forced off there) which already has the entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 11:38:26 +01:00
Emma Thorpe 27069e324f feat(git): personal email and commitizen aliases
Set user.email = iam@emmathe.dev on the personal hosts (mkDefault, so the
work module's address still wins on the work box). Add git aliases for
commitizen -- `git cz <sub>` (e.g. `git cz c`) and `git cc` for the commit
prompt; commitizen is already installed on every host (home.packages) and
defaults to the Conventional Commits ruleset.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 11:30:09 +01:00
Emma Thorpe 829f209300 feat(shell): start tmux in every terminal; ssh-agent with auto-add
Move the tmux auto-start out of the graphical-only desktop layer into the
shared shell config so it also covers WSL, iTerm2 and the Linux console
(folded into programs.zsh.initContent via mkMerge alongside the SSH PS1
block). Same guards: interactive, not-already-in-tmux, not-SSH,
not-VS-Code, tmux-present.

ssh: run a user ssh-agent on Linux (macOS uses launchd) and add keys on
first use (addKeysToAgent), so the passphrase is entered once per login
session instead of per commit/push -- which also feeds commit signing.
macOS additionally caches in the login keychain (UseKeychain). The work
box keeps its own ~/.ssh/config (programs.ssh forced off there); its
ssh-agent still runs via the work module.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 11:30:09 +01:00
Emma Thorpe 06bc420948 feat(tmux): auto-start in graphical terminals
Opening a terminal (foot) execs `tmux new-session -A -s main`, so every new
terminal lands in the multiplexer; panes run a plain non-login zsh. Guarded
to interactive, not-already-in-tmux, not-SSH, not-VS-Code, tmux-present --
preventing re-exec loops, hijacked scp/ssh shells, and lockout. Lives in the
graphical desktop layer, so the WSL work box keeps a plain shell.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 11:08:49 +01:00
Emma Thorpe b806359fd6 feat(git): rebase pulls, better diffs/merges, aliases, ignores, signing
settings: pull.rebase + rebase autostash/autosquash, fetch.prune,
merge.conflictStyle=zdiff3, diff histogram + colorMoved, rerere,
commit.verbose, branch.sort, column.ui, help.autocorrect, and a small alias
set (st/co/sw/br/ci/last/unstage/lg). Global ignore file (result, .direnv,
*.swp, .DS_Store).

SSH commit/tag signing on personal hosts too, reusing the existing key
(the work module already signs on the work host). gpgsign is mkDefault so a
host lacking the key in its ssh-agent can disable it -- otherwise commits
there would fail. No personal user.email is set (unknown); signing does not
require one, but author email still falls back to user@host until set.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 11:08:49 +01:00
Emma Thorpe 0c4f555dec feat(vim): add vim-tmux-navigator
Vim half of the tmux plugin so Ctrl-h/j/k/l moves seamlessly between vim
splits and tmux panes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 11:08:49 +01:00
Emma Thorpe 19dfb32cf6 feat(shell): zsh tooling, tmux plugins, nix-index, nh
zsh: history tuning (100k, dedup, share, timestamps); oh-my-zsh sudo /
colored-man-pages / extract; fzf, zoxide, direnv (+nix-direnv), eza, bat;
ls-family aliases. command-not-found via the prebuilt nix-index DB (+comma).
nh with $NH_FLAKE and a weekly user-GC timer.

tmux: escape-time 10 (was the 500ms default -> laggy vim ESC), focus-events,
base-index 1; plugins sensible / vim-tmux-navigator / yank / catppuccin
(mocha statusline) / resurrect / continuum (restore on); renumber-windows
and set-clipboard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 11:08:49 +01:00
Emma Thorpe 79b325676d chore(flake): add nix-index-database input
Prebuilt nix-index database (follows nixpkgs) so command-not-found works
immediately without a manual `nix-index` run. Consumed in shell.nix.
Lock change is purely additive; existing pins are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 11:08:49 +01:00
23 changed files with 942 additions and 172 deletions
+19
View File
@@ -0,0 +1,19 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
[*.{nix,yaml,yml,json,md,sh,toml}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
# Markdown uses trailing whitespace for hard line breaks.
[*.md]
trim_trailing_whitespace = false
+1 -5
View File
@@ -1,10 +1,6 @@
{ {
"$schema": "https://docs.renovatebot.com/renovate-schema.json", "$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [ "extends": ["config:recommended", ":dependencyDashboard", ":semanticCommits"],
"config:recommended",
":dependencyDashboard",
":semanticCommits"
],
"nix": { "nix": {
"enabled": true "enabled": true
}, },
+32 -15
View File
@@ -7,13 +7,13 @@ single flake.
Defined in the host table in [`flake.nix`](./flake.nix): Defined in the host table in [`flake.nix`](./flake.nix):
| Configuration | System | Machine | | Configuration | System | Machine |
| ------------------- | --------------- | ---------------------------------------- | | --------------------- | ---------------- | --------------------------------------------------------------------------- |
| `lyrathorpe-mbp` | `aarch64-linux` | MacBook Pro (Apple Silicon, Asahi) | | `lyrathorpe-mbp` | `aarch64-linux` | MacBook Pro (Apple Silicon, Asahi) |
| `lyrathorpe-t400` | `x86_64-linux` | ThinkPad T400 — [install notes](./system/machine/T400/README.md) | | `lyrathorpe-t400` | `x86_64-linux` | ThinkPad T400 — [install notes](./system/machine/T400/README.md) |
| `lyrathorpe-macpro31` | `x86_64-linux` | Mac Pro 3,1, desktop — [install notes](./system/machine/MacPro31/README.md) | | `lyrathorpe-macpro31` | `x86_64-linux` | Mac Pro 3,1, desktop — [install notes](./system/machine/MacPro31/README.md) |
| `emmathorpe-edaas` | `x86_64-linux` | Work WSL box (NixOS-WSL) | | `emmathorpe-edaas` | `x86_64-linux` | Work WSL box (NixOS-WSL) |
| `lyrathorpe-mac` | `aarch64-darwin` | macOS (nix-darwin) | | `lyrathorpe-mac` | `aarch64-darwin` | macOS (nix-darwin) |
Shared layers: `lyrathorpe/home` (home-manager: shell, git, editor), Shared layers: `lyrathorpe/home` (home-manager: shell, git, editor),
`system/modules/common-nixos.nix` (all NixOS hosts), and `system/modules/common-nixos.nix` (all NixOS hosts), and
@@ -28,10 +28,12 @@ sudo nixos-rebuild switch --flake .#<configuration>
darwin-rebuild switch --flake .#lyrathorpe-mac darwin-rebuild switch --flake .#lyrathorpe-mac
``` ```
## Keybindings ## Shell environment & keybindings
All Sway / tmux / foot / zsh keyboard shortcuts are documented in - Interactive shell features (zsh, tmux, git, ssh, CLI tools, auto-tmux):
[`lyrathorpe/home/KEYBINDINGS.md`](./lyrathorpe/home/KEYBINDINGS.md). [`lyrathorpe/home/README.md`](./lyrathorpe/home/README.md).
- All Sway / tmux / foot / zsh keyboard shortcuts:
[`lyrathorpe/home/KEYBINDINGS.md`](./lyrathorpe/home/KEYBINDINGS.md).
## Login / greeter ## Login / greeter
@@ -46,14 +48,29 @@ WSL work box) keep plain TTY login. The target account needs a password
## MacBook (Asahi) firmware ## MacBook (Asahi) firmware
The MBP host references `system/modules/firmware/` for Apple peripheral The MBP host references `system/modules/firmware/` for Apple peripheral
firmware (Wi-Fi/Bluetooth). Those blobs are **not** redistributable, so the firmware (Wi-Fi/Bluetooth). These blobs are **committed** (tracked) even though
directory is gitignored and a clean checkout will not build `lyrathorpe-mbp` `.gitignore` lists the directory: the flake is `git+file`, so it only sees
until it is populated out-of-band. tracked files — untracking them breaks `lyrathorpe-mbp` evaluation (and the CI
host-eval) because the config can't find the firmware. They are not
redistributable; the repo is private.
Copy the firmware extracted during the Asahi install (from To refresh them, copy the firmware extracted during the Asahi install (from
`/etc/nixos/firmware` on the freshly-installed machine, or re-extract per the `/etc/nixos/firmware`, or re-extract per the
[Asahi NixOS docs](https://github.com/tpwrules/nixos-apple-silicon)) into [Asahi NixOS docs](https://github.com/tpwrules/nixos-apple-silicon)) into
`system/modules/firmware/` before rebuilding that host. `system/modules/firmware/` and commit with `git add -f`.
## Development
A dev shell and a formatting/lint gate are wired through the flake:
- `nix develop` — shell with `deadnix`, `statix`, `treefmt`, and the git
`pre-commit` hooks (installed automatically on first entry).
- `nix fmt` — formats the tree via `treefmt` (nixfmt + shfmt + prettier;
generated files and `flake.lock` are excluded).
- `nix flake check` — runs formatting, `deadnix`, `statix`, the pre-commit
hooks, and evaluates every host. `.editorconfig` carries the base style;
`statix.toml` disables the two house-style lints (`repeated_keys`,
`empty_pattern`).
## CI ## CI
Generated
+106 -4
View File
@@ -40,6 +40,22 @@
} }
}, },
"flake-compat": { "flake-compat": {
"flake": false,
"locked": {
"lastModified": 1767039857,
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
"owner": "NixOS",
"repo": "flake-compat",
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "flake-compat",
"type": "github"
}
},
"flake-compat_2": {
"locked": { "locked": {
"lastModified": 1761640442, "lastModified": 1761640442,
"narHash": "sha256-AtrEP6Jmdvrqiv4x2xa5mrtaIp3OEe8uBYCDZDS+hu8=", "narHash": "sha256-AtrEP6Jmdvrqiv4x2xa5mrtaIp3OEe8uBYCDZDS+hu8=",
@@ -54,7 +70,7 @@
"type": "github" "type": "github"
} }
}, },
"flake-compat_2": { "flake-compat_3": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1767039857, "lastModified": 1767039857,
@@ -90,6 +106,49 @@
"type": "github" "type": "github"
} }
}, },
"git-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1778507602,
"narHash": "sha256-kTwur1wV+01SdqskVMSo6JMEpg71ps3HpbFY2GsflKs=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "61ab0e80d9c7ab14c256b5b453d8b3fb0189ba0a",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"git-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"
}
},
"home-manager": { "home-manager": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
@@ -150,9 +209,29 @@
"type": "github" "type": "github"
} }
}, },
"nix-index-database": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1780816331,
"narHash": "sha256-0BYqs8yKWkOz2Q7+SP18N5E5gmDKSo6LSxIVIa0wWes=",
"owner": "nix-community",
"repo": "nix-index-database",
"rev": "1a2ea89c917781e88508d9fd2b507f2d2a0e173c",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nix-index-database",
"type": "github"
}
},
"nixos-apple-silicon": { "nixos-apple-silicon": {
"inputs": { "inputs": {
"flake-compat": "flake-compat", "flake-compat": "flake-compat_2",
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
] ]
@@ -173,7 +252,7 @@
}, },
"nixos-wsl": { "nixos-wsl": {
"inputs": { "inputs": {
"flake-compat": "flake-compat_2", "flake-compat": "flake-compat_3",
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
] ]
@@ -228,13 +307,36 @@
"inputs": { "inputs": {
"firefox-addons": "firefox-addons", "firefox-addons": "firefox-addons",
"flake-parts": "flake-parts", "flake-parts": "flake-parts",
"git-hooks": "git-hooks",
"home-manager": "home-manager", "home-manager": "home-manager",
"nix-darwin": "nix-darwin", "nix-darwin": "nix-darwin",
"nix-homebrew": "nix-homebrew", "nix-homebrew": "nix-homebrew",
"nix-index-database": "nix-index-database",
"nixos-apple-silicon": "nixos-apple-silicon", "nixos-apple-silicon": "nixos-apple-silicon",
"nixos-wsl": "nixos-wsl", "nixos-wsl": "nixos-wsl",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"nixpkgs-unstable": "nixpkgs-unstable" "nixpkgs-unstable": "nixpkgs-unstable",
"treefmt-nix": "treefmt-nix"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1780220602,
"narHash": "sha256-eynAfOmbmxJnkp7YewvCEbShNnnYJ9gLLqkzsYtBPeM=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "db947814a175b7ca6ded66e21383d938df01c227",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
} }
} }
}, },
+83 -14
View File
@@ -28,6 +28,24 @@
url = "gitlab:rycee/nur-expressions?dir=pkgs/firefox-addons"; url = "gitlab:rycee/nur-expressions?dir=pkgs/firefox-addons";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
# Prebuilt nix-index database so "command not found -> which package
# provides it" works immediately (no manual `nix-index` run). See shell.nix.
nix-index-database = {
url = "github:nix-community/nix-index-database";
inputs.nixpkgs.follows = "nixpkgs";
};
# treefmt-nix: one multi-language formatter driving `nix fmt` and the
# formatting flake check (nixfmt + shfmt + prettier).
treefmt-nix = {
url = "github:numtide/treefmt-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
# git-hooks.nix: declarative pre-commit hooks (nixfmt/deadnix/statix),
# installed into the repo via the devShell.
git-hooks = {
url = "github:cachix/git-hooks.nix";
inputs.nixpkgs.follows = "nixpkgs";
};
}; };
outputs = outputs =
@@ -47,12 +65,14 @@
let let
# claude-code tracks nixpkgs-unstable regardless of the pinned nixpkgs. # claude-code tracks nixpkgs-unstable regardless of the pinned nixpkgs.
overlays = [ overlays = [
(final: prev: { (_final: prev: {
claude-code = inherit
(import nixpkgs-unstable { (import nixpkgs-unstable {
inherit (prev.stdenv.hostPlatform) system; inherit (prev.stdenv.hostPlatform) system;
config.allowUnfree = true; config.allowUnfree = true;
}).claude-code; })
claude-code
;
}) })
]; ];
@@ -239,7 +259,7 @@
]; ];
homeModules = [ homeModules = [
./lyrathorpe/home ./lyrathorpe/home
./system/modules/work/default.nix ./lyrathorpe/home/work.nix
]; ];
}; };
}; };
@@ -262,6 +282,13 @@
}; };
in in
{ {
# flake-parts modules: treefmt-nix wires `nix fmt` + a formatting check;
# git-hooks.nix wires the pre-commit check + devShell installation script.
imports = [
inputs.treefmt-nix.flakeModule
inputs.git-hooks.flakeModule
];
systems = [ systems = [
"x86_64-linux" "x86_64-linux"
"aarch64-linux" "aarch64-linux"
@@ -273,26 +300,68 @@
# nixpkgs instance for that system. Outputs here become per-system # nixpkgs instance for that system. Outputs here become per-system
# attrsets automatically (e.g. devShells.<system>.default). # attrsets automatically (e.g. devShells.<system>.default).
perSystem = perSystem =
{ pkgs, ... }: { config, pkgs, ... }:
{ {
# `nix fmt` formatter for the repo. # treefmt drives `nix fmt` and the formatting check below. nixfmt
formatter = pkgs.nixfmt; # stays the .nix formatter (the tree is already nixfmt-formatted);
# shfmt covers shell and prettier covers markdown/yaml/json.
treefmt = {
projectRootFile = "flake.nix";
programs.nixfmt.enable = true;
programs.shfmt.enable = true;
programs.prettier.enable = true;
# Generated hardware-configuration.nix files are not hand-edited.
settings.global.excludes = [
"*/hardware-configuration.nix" # generated by nixos-generate-config
"flake.lock" # generated by `nix flake lock`
];
};
# Pre-commit hooks: format + lint gate run on commit. The same hooks
# are exposed as a flake check (pre-commit.check.enable defaults true).
pre-commit.settings = {
# Generated by nixos-generate-config; don't lint/reformat (treefmt
# excludes them too).
excludes = [ "hardware-configuration\\.nix$" ];
hooks = {
nixfmt-rfc-style.enable = true;
deadnix = {
enable = true;
# Unused module args ({config,lib,pkgs,...}) are normal; only
# flag genuinely dead bindings.
settings.noLambdaPatternNames = true;
};
statix.enable = true; # reads statix.toml (repeated_keys/empty_pattern disabled)
};
};
# treefmt-nix exposes its own `checks.treefmt`; alias it to
# `formatting` so the existing CI gate (.#checks.*.formatting) keeps
# working without churn.
checks.formatting = config.treefmt.build.check inputs.self;
# deadnix / statix lints as standalone flake checks so `nix flake
# check` flags dead code and antipatterns independently of pre-commit.
checks.deadnix = pkgs.runCommandLocal "check-deadnix" { nativeBuildInputs = [ pkgs.deadnix ]; } ''
deadnix --fail --no-lambda-pattern-names ${./.} && touch $out
'';
checks.statix = pkgs.runCommandLocal "check-statix" { nativeBuildInputs = [ pkgs.statix ]; } ''
statix check -c ${./.} ${./.} && touch $out
'';
# `nix develop` shell with the tooling needed to hack on this flake. # `nix develop` shell with the tooling needed to hack on this flake.
# shellHook installs the git pre-commit hooks into the working tree.
devShells.default = pkgs.mkShellNoCC { devShells.default = pkgs.mkShellNoCC {
packages = with pkgs; [ packages = with pkgs; [
nixfmt nixfmt
nil nil
git git
deadnix
statix
treefmt
]; ];
shellHook = config.pre-commit.installationScript;
}; };
checks.formatting =
pkgs.runCommandLocal "check-formatting" { nativeBuildInputs = [ pkgs.nixfmt ]; }
''
# Generated hardware-configuration.nix files are excluded.
nixfmt --check $(find ${./.} -name '*.nix' -not -name 'hardware-configuration.nix') && touch $out
'';
}; };
# Realise the host tables: each entry becomes a {nixos,darwin}Configuration. # Realise the host tables: each entry becomes a {nixos,darwin}Configuration.
+85 -80
View File
@@ -4,12 +4,12 @@ Every keyboard shortcut configured across this desktop, and where it is defined.
Everything here is managed declaratively through Nix — edit the listed file and Everything here is managed declaratively through Nix — edit the listed file and
rebuild, never the generated dotfiles. rebuild, never the generated dotfiles.
| Area | Defined in | | Area | Defined in |
| --- | --- | | ----------------- | --------------------------------------------------------------------------------------------------------------------- |
| Sway (compositor) | [`sway.nix`](./sway.nix) `config.keybindings` + `config.modes`, plus the home-manager Sway module's built-in defaults | | Sway (compositor) | [`sway.nix`](./sway.nix) `config.keybindings` + `config.modes`, plus the home-manager Sway module's built-in defaults |
| tmux | [`shell.nix`](./shell.nix) `programs.tmux` | | tmux | [`shell.nix`](./shell.nix) `programs.tmux` |
| zsh line editor | [`shell.nix`](./shell.nix) `programs.zsh.historySubstringSearch` | | zsh line editor | [`shell.nix`](./shell.nix) `programs.zsh.historySubstringSearch` |
| foot (terminal) | foot package defaults — only colours are themed (in `sway.nix`) | | foot (terminal) | foot package defaults — only colours are themed (in `sway.nix`) |
**Conventions** **Conventions**
@@ -26,49 +26,49 @@ rebuild, never the generated dotfiles.
### Applications & session ### Applications & session
| Shortcut | Action | | Shortcut | Action |
| --- | --- | | ------------------- | ------------------------------------------------------- |
| `Super`+`Return` | Open a terminal (foot) | | `Super`+`Return` | Open a terminal (foot) |
| `Super`+`Space` | App launcher (sway-launcher-desktop in a floating foot) | | `Super`+`Space` | App launcher (sway-launcher-desktop in a floating foot) |
| `Super`+`d` | App launcher (same as above; module default) | | `Super`+`d` | App launcher (same as above; module default) |
| `Super`+`e` | File manager (nemo) | | `Super`+`e` | File manager (nemo) |
| `Super`+`c` | Clipboard history picker (clipman → fuzzel) | | `Super`+`c` | Clipboard history picker (clipman → fuzzel) |
| `Super`+`l` | Lock screen (swaylock) | | `Super`+`l` | Lock screen (swaylock) |
| `Super`+`Shift`+`q` | Close the focused window | | `Super`+`Shift`+`q` | Close the focused window |
| `Super`+`Shift`+`c` | Reload the Sway config | | `Super`+`Shift`+`c` | Reload the Sway config |
| `Super`+`Shift`+`e` | Exit Sway (asks for confirmation) | | `Super`+`Shift`+`e` | Exit Sway (asks for confirmation) |
### Focus ### Focus
| Shortcut | Action | | Shortcut | Action |
| --- | --- | | ----------------------- | ---------------------------------------- |
| `Super`+`←`/`↓`/`↑`/`→` | Move focus by direction | | `Super`+`←`/`↓`/`↑`/`→` | Move focus by direction |
| `Super`+`h`/`j`/`k` | Move focus left / down / up (vim-style) | | `Super`+`h`/`j`/`k` | Move focus left / down / up (vim-style) |
| `Super`+`a` | Focus the parent container | | `Super`+`a` | Focus the parent container |
| `Super`+`Alt`+`Space` | Toggle focus between tiling and floating | | `Super`+`Alt`+`Space` | Toggle focus between tiling and floating |
> Note: vim focus-right would be `Super`+`l`, but that is bound to **lock** here; > Note: vim focus-right would be `Super`+`l`, but that is bound to **lock** here;
> use `Super`+`→`. > use `Super`+`→`.
### Moving windows ### Moving windows
| Shortcut | Action | | Shortcut | Action |
| --- | --- | | ------------------------------- | ---------------------------------------- |
| `Super`+`Shift`+`←`/`↓`/`↑`/`→` | Move the window by direction | | `Super`+`Shift`+`←`/`↓`/`↑`/`→` | Move the window by direction |
| `Super`+`Shift`+`h`/`j`/`k`/`l` | Move the window left / down / up / right | | `Super`+`Shift`+`h`/`j`/`k`/`l` | Move the window left / down / up / right |
| `Super`+`Shift`+`Space` | Toggle the window floating | | `Super`+`Shift`+`Space` | Toggle the window floating |
Mouse (with `Super` held): left-drag moves a window, right-drag resizes it. Mouse (with `Super` held): left-drag moves a window, right-drag resizes it.
### Layout ### Layout
| Shortcut | Action | | Shortcut | Action |
| --- | --- | | ----------- | -------------------------------------------------------------------------------------- |
| `Super`+`b` | Split horizontally | | `Super`+`b` | Split horizontally |
| `Super`+`v` | Split vertically | | `Super`+`v` | Split vertically |
| `Super`+`s` | Stacking layout | | `Super`+`s` | Stacking layout |
| `Super`+`w` | Tabbed layout | | `Super`+`w` | Tabbed layout |
| `Super`+`f` | Toggle fullscreen | | `Super`+`f` | Toggle fullscreen |
| `Super`+`y` | **Layout submenu**: `s` stacking · `w` tabbed · `e` toggle split · `Return`/`Esc` exit | | `Super`+`y` | **Layout submenu**: `s` stacking · `w` tabbed · `e` toggle split · `Return`/`Esc` exit |
> The layout submenu's `e` (toggle split) is the home for that action since > The layout submenu's `e` (toggle split) is the home for that action since
@@ -76,49 +76,49 @@ Mouse (with `Super` held): left-drag moves a window, right-drag resizes it.
### Workspaces ### Workspaces
| Shortcut | Action | | Shortcut | Action |
| --- | --- | | ----------------------- | --------------------------------- |
| `Super`+`1``0` | Switch to workspace 1…10 | | `Super`+`1``0` | Switch to workspace 1…10 |
| `Super`+`Shift`+`1``0` | Move the window to workspace 1…10 | | `Super`+`Shift`+`1``0` | Move the window to workspace 1…10 |
| `Super`+`z` | Previous workspace | | `Super`+`z` | Previous workspace |
| `Super`+`x` | Next workspace | | `Super`+`x` | Next workspace |
### Scratchpad ### Scratchpad
| Shortcut | Action | | Shortcut | Action |
| --- | --- | | ------------------- | --------------------------------- |
| `Super`+`Shift`+`-` | Move the window to the scratchpad | | `Super`+`Shift`+`-` | Move the window to the scratchpad |
| `Super`+`-` | Show / cycle the scratchpad | | `Super`+`-` | Show / cycle the scratchpad |
### Modes (submenus) ### Modes (submenus)
| Shortcut | Action | | Shortcut | Action |
| --- | --- | | ------------------- | ------------------------------------------------------------------------------------------------------------ |
| `Super`+`r` | **Resize mode**: arrow keys resize; `Return`/`Esc` exit | | `Super`+`r` | **Resize mode**: arrow keys resize; `Return`/`Esc` exit |
| `Super`+`y` | **Layout mode** (see Layout above) | | `Super`+`y` | **Layout mode** (see Layout above) |
| `Super`+`Shift`+`x` | **Power menu**: `l` lock · `e` log out · `s` sleep · `r` reboot · `Shift`+`s` shutdown · `Return`/`Esc` exit | | `Super`+`Shift`+`x` | **Power menu**: `l` lock · `e` log out · `s` sleep · `r` reboot · `Shift`+`s` shutdown · `Return`/`Esc` exit |
### Screenshots ### Screenshots
| Shortcut | Action | | Shortcut | Action |
| --- | --- | | --------------- | ---------------------------------------- |
| `Print` | Select a region → swappy (annotate/save) | | `Print` | Select a region → swappy (annotate/save) |
| `Shift`+`Print` | Focused window → swappy | | `Shift`+`Print` | Focused window → swappy |
### Audio & media ### Audio & media
| Shortcut | Action | | Shortcut | Action |
| --- | --- | | ----------------------------------------------- | ---------------------- |
| `XF86AudioRaiseVolume` / `XF86AudioLowerVolume` | Volume ±5% (wpctl) | | `XF86AudioRaiseVolume` / `XF86AudioLowerVolume` | Volume ±5% (wpctl) |
| `XF86AudioMute` | Toggle output mute | | `XF86AudioMute` | Toggle output mute |
| `XF86AudioMicMute` | Toggle microphone mute | | `XF86AudioMicMute` | Toggle microphone mute |
| `XF86AudioPlay` | Play/pause (playerctl) | | `XF86AudioPlay` | Play/pause (playerctl) |
| `XF86AudioNext` / `XF86AudioPrev` | Next / previous track | | `XF86AudioNext` / `XF86AudioPrev` | Next / previous track |
### Brightness — laptops only ### Brightness — laptops only
| Shortcut | Action | | Shortcut | Action |
| --- | --- | | ----------------------------------------------- | ----------------------------- |
| `XF86MonBrightnessUp` / `XF86MonBrightnessDown` | Backlight ±5% (brightnessctl) | | `XF86MonBrightnessUp` / `XF86MonBrightnessDown` | Backlight ±5% (brightnessctl) |
Present only on portable hosts (T400, MBP); desktops have no internal backlight. Present only on portable hosts (T400, MBP); desktops have no internal backlight.
@@ -129,20 +129,25 @@ Present only on portable hosts (T400, MBP); desktops have no internal backlight.
Prefix is **`Ctrl`+`b`** (default). Copy mode uses **vi** keys. Prefix is **`Ctrl`+`b`** (default). Copy mode uses **vi** keys.
| Shortcut | Action | | Shortcut | Action |
| --- | --- | | --------------------------------------- | -------------------------------------------------------------------------------------------- |
| `Ctrl`+`b` then `v` | Split into left/right panes | | `Ctrl`+`b` then `v` | Split into left/right panes |
| `Ctrl`+`b` then `s` | Split into top/bottom panes | | `Ctrl`+`b` then `s` | Split into top/bottom panes |
| `Alt`+``/``/``/`` | Switch pane by direction (no prefix needed) | | `Ctrl`+`h`/`j`/`k`/`l` | Move between panes — and into/out of vim splits — seamlessly (vim-tmux-navigator, no prefix) |
| `Ctrl`+`b` then `[` | Enter copy mode (then vi motions; `Space`/`Enter` to select/copy) | | `Alt`+``/`→`/`↑`/`` | Switch pane by direction (no prefix needed) |
| `Ctrl`+`b` then `z` | Zoom / unzoom the focused pane | | `Ctrl`+`b` then `[` | Enter copy mode (then vi motions; `Space`/`Enter` to select/copy) |
| `Ctrl`+`b` then `c` | New window | | `Ctrl`+`b` then `z` | Zoom / unzoom the focused pane |
| `Ctrl`+`b` then `n` / `p` | Next / previous window | | `Ctrl`+`b` then `c` | New window |
| `Ctrl`+`b` then `d` | Detach | | `Ctrl`+`b` then `n` / `p` | Next / previous window |
| Mouse | Enabled — click to focus, drag borders, scroll, select | | `Ctrl`+`b` then `d` | Detach |
| `Ctrl`+`b` then `Ctrl`+`s` / `Ctrl`+`r` | Save / restore the session (resurrect; continuum also auto-saves and restores on start) |
| Mouse | Enabled — click to focus, drag borders, scroll, select |
> The stock split keys `%` and `"` are unbound; use `v` / `s` above. `Ctrl`+`b` > The stock split keys `%` and `"` are unbound; use `v` / `s` above. `Ctrl`+`b`
> then `s` is therefore a split, not the session tree. > then `s` is therefore a split, not the session tree.
>
> Sessions persist across reboots (resurrect + continuum). Terminals auto-start
> tmux; `NO_TMUX=1 <terminal>` opens a bare shell instead.
--- ---
@@ -150,22 +155,22 @@ Prefix is **`Ctrl`+`b`** (default). Copy mode uses **vi** keys.
Only colours are themed; these are foot's default key bindings. Only colours are themed; these are foot's default key bindings.
| Shortcut | Action | | Shortcut | Action |
| --- | --- | | --------------------------------------- | ----------------------------- |
| `Ctrl`+`Shift`+`c` / `Ctrl`+`Shift`+`v` | Copy / paste (clipboard) | | `Ctrl`+`Shift`+`c` / `Ctrl`+`Shift`+`v` | Copy / paste (clipboard) |
| `Shift`+`Insert` | Paste primary selection | | `Shift`+`Insert` | Paste primary selection |
| `Ctrl`+`Shift`+`r` | Search scrollback | | `Ctrl`+`Shift`+`r` | Search scrollback |
| `Ctrl`+`+` / `Ctrl`+`-` / `Ctrl`+`0` | Font larger / smaller / reset | | `Ctrl`+`+` / `Ctrl`+`-` / `Ctrl`+`0` | Font larger / smaller / reset |
| `Ctrl`+`Shift`+`u` | URL mode (jump to/open links) | | `Ctrl`+`Shift`+`u` | URL mode (jump to/open links) |
| `Ctrl`+`Shift`+`n` | Spawn a new terminal | | `Ctrl`+`Shift`+`n` | Spawn a new terminal |
| `Shift`+`PageUp` / `Shift`+`PageDown` | Scroll back / forward | | `Shift`+`PageUp` / `Shift`+`PageDown` | Scroll back / forward |
--- ---
## zsh ## zsh
| Shortcut | Action | | Shortcut | Action |
| --- | --- | | --------- | -------------------------------------------------------------------------------------------------- |
| `↑` / `↓` | History **substring** search — type a fragment first, then the arrows cycle matching past commands | | `↑` / `↓` | History **substring** search — type a fragment first, then the arrows cycle matching past commands |
Bound for both CSI and SS3 cursor sequences, so it works in foot, iTerm2 and Bound for both CSI and SS3 cursor sequences, so it works in foot, iTerm2 and
+145
View File
@@ -0,0 +1,145 @@
# 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) |
| vim | [`editor.nix`](./editor.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
[`../../system/modules/work/default.nix`](../../system/modules/work/default.nix)
on top (work email, its own ssh config, extra packages).
---
## 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** |
| 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` / `btop` | JSON processor; resource monitor |
| `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` |
**Theming:** `fzf`, `bat` and `git`'s `delta` pager are all Catppuccin Mocha,
driven from the shared `../catppuccin-mocha.nix` palette / the catppuccin/bat
theme.
**Env & defaults:** `xdg.enable` on; `PAGER`/`MANPAGER` (bat)/`VISUAL` set in
`default.nix`; `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`, `catppuccin` (Mocha statusline), `resurrect` + `continuum`
(sessions auto-save and restore across reboots). The statusline draws Nerd-Font
glyphs — see Fonts.
## Fonts
**JetBrainsMono Nerd Font** is installed on every host (in `common-nixos.nix`,
because tmux runs everywhere; the Mac installs it to `/Library/Fonts` via the
Darwin config). foot uses it 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 `?`.
## git
Pager is **delta**. **commitizen** is installed on every host; `cz` defaults to
Conventional Commits.
| Aliases | |
| ------------------------ | ----------------------------------------------------------------- |
| `st` `co` `sw` `br` `ci` | status / checkout / switch / branch / commit |
| `last` `unstage` | last commit / unstage |
| `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.
## Maintenance behaviours
- **zcompdump reset** — `~/.zcompdump*` 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 |
+21
View File
@@ -9,5 +9,26 @@
./editor.nix ./editor.nix
]; ];
# Manage the XDG base-directory layout and ~/.config files. Tools above
# (bat themes, gh config, ...) write under xdg.configHome; enabling this
# makes the paths explicit and consistent across hosts. No regression: the
# defaults match the conventional ~/.config, ~/.cache, ~/.local/share.
xdg.enable = true;
# Editor itself comes from vim.defaultEditor (sets $EDITOR). Round out the
# rest of the standard env. desktop.nix adds its own Wayland session vars;
# home-manager merges the two attrsets, so these do not clash.
home.sessionVariables = {
VISUAL = "vim";
PAGER = "less -FRX"; # -F quit-if-one-screen, -R raw colour, -X no clear
# Render man pages through bat (themed): col strips backspace overstrike,
# bat -l man -p highlights without its own pager decorations.
MANPAGER = "sh -c 'col -bx | bat -l man -p'";
};
# Pinned to the release first installed on these hosts, NOT the current
# nixpkgs (26.05). stateVersion freezes stateful defaults (file locations,
# service data formats) to that release; bumping it silently migrates that
# state and can break it. Leave it -- it is intentional, not stale.
home.stateVersion = "25.05"; home.stateVersion = "25.05";
} }
+24
View File
@@ -27,6 +27,30 @@
XDG_CURRENT_DESKTOP = "sway"; XDG_CURRENT_DESKTOP = "sway";
}; };
# Default apps for the desktop (writes ~/.config/mimeapps.list). Firefox owns
# the web; nemo owns directories/file URIs; images, PDFs and plain text open
# in Firefox too -- no dedicated GUI viewer/editor is installed and vim is
# terminal-only (no usable GUI .desktop for double-click handoff). Kept
# minimal -- only the handlers actually present on these hosts.
xdg.mimeApps = {
enable = true;
defaultApplications = {
"text/html" = "firefox.desktop";
"x-scheme-handler/http" = "firefox.desktop";
"x-scheme-handler/https" = "firefox.desktop";
"x-scheme-handler/about" = "firefox.desktop";
"x-scheme-handler/unknown" = "firefox.desktop";
"inode/directory" = "nemo.desktop";
"image/png" = "firefox.desktop";
"image/jpeg" = "firefox.desktop";
"image/gif" = "firefox.desktop";
"image/webp" = "firefox.desktop";
"image/svg+xml" = "firefox.desktop";
"application/pdf" = "firefox.desktop";
"text/plain" = "firefox.desktop";
};
};
# Theme GTK apps (nemo, etc.) to match the Catppuccin Mocha desktop. Under # Theme GTK apps (nemo, etc.) to match the Catppuccin Mocha desktop. Under
# Sway there is no XSettings daemon, so GTK reads these from the generated # Sway there is no XSettings daemon, so GTK reads these from the generated
# ~/.config/gtk-{3,4}.0/settings.ini directly. The Mocha theme is dark by # ~/.config/gtk-{3,4}.0/settings.ini directly. The Mocha theme is dark by
+1
View File
@@ -10,6 +10,7 @@
vim-fugitive vim-fugitive
vim-indent-guides vim-indent-guides
catppuccin-vim catppuccin-vim
vim-tmux-navigator # Ctrl-h/j/k/l moves between vim splits and tmux panes
]; ];
settings = { settings = {
expandtab = false; expandtab = false;
+73 -5
View File
@@ -1,6 +1,11 @@
# Version control: git + delta pager + commitizen. The work host layers # Version control: git + delta pager + commitizen. The work host layers
# commit signing and an email override on top (see work/default.nix). # commit signing and an email override on top (see work/default.nix).
{ pkgs, fullName, ... }: {
pkgs,
lib,
fullName,
...
}:
{ {
home.packages = [ home.packages = [
pkgs.commitizen pkgs.commitizen
@@ -11,13 +16,76 @@
package = pkgs.gitFull; package = pkgs.gitFull;
settings = { settings = {
user.name = fullName; user.name = fullName;
push = { # Personal identity. mkDefault so the work module overrides it on the work
autoSetupRemote = true; # host (and to merge cleanly with that plain definition there).
user.email = lib.mkDefault "iam@emmathe.dev";
push.autoSetupRemote = true;
init.defaultBranch = "main";
# Rebase-centric pulls (matches the "always a branch, linear history"
# workflow); stash/restore and reorder fixups automatically.
pull.rebase = true;
rebase = {
autoStash = true;
autoSquash = true;
}; };
init = {
defaultBranch = "main"; fetch.prune = true; # drop deleted remote-tracking branches
merge.conflictStyle = "zdiff3"; # show the common ancestor in conflicts
diff = {
algorithm = "histogram";
colorMoved = "default";
}; };
rerere.enabled = true; # remember + replay conflict resolutions
# delta pager config (programs.delta is enabled below, with git
# integration; these keys land under [delta] in the git config).
# syntax-theme reuses the Catppuccin Mocha tmTheme vendored for bat in
# shell.nix -- delta reads bat's theme directory.
delta = {
syntax-theme = "Catppuccin Mocha";
navigate = true; # n/N to jump between diff hunks
line-numbers = true;
side-by-side = true;
};
commit.verbose = true; # full diff in the commit-message editor
branch.sort = "-committerdate"; # most-recent branches first
column.ui = "auto";
help.autocorrect = "prompt";
alias = {
st = "status";
co = "checkout";
sw = "switch";
br = "branch";
ci = "commit";
last = "log -1 HEAD";
unstage = "reset HEAD --";
lg = "log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(auto)%d%C(reset)' --all";
# commitizen (Conventional Commits, its default ruleset): `git cz c` ->
# `cz commit`, `git cz bump`, etc. `git cc` is a shortcut for the prompt.
cz = "!cz";
cc = "!cz commit";
};
# SSH commit signing on personal hosts too (the work module sets the same
# on the work host). mkDefault so a host without the key in its ssh-agent
# can override to false -- otherwise commits there would fail. Reuses the
# existing ssh key; a dedicated personal key can be swapped in later.
gpg.format = "ssh";
user.signingkey = "key::ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAJMVgeRKnfX1G8coU3nAobI485aeUpGTMqH7+zbKI8o emma.thorpe@cloud.com";
commit.gpgsign = lib.mkDefault true;
tag.gpgsign = lib.mkDefault true;
}; };
# Global ignore file (~/.config/git/ignore).
ignores = [
"result"
"result-*"
".direnv"
"*.swp"
".DS_Store"
];
}; };
programs.delta = { programs.delta = {
+267 -12
View File
@@ -1,6 +1,31 @@
# Interactive shell: zsh + tmux. Wanted on every host. # Interactive shell: zsh + tmux. Wanted on every host.
{ lib, ... }:
{ {
lib,
pkgs,
inputs,
...
}:
let
# Shared Catppuccin Mocha palette: raw 6-hex strings, no leading "#".
ctp = import ../catppuccin-mocha.nix;
in
{
imports = [
# Prebuilt nix-index database -> working command-not-found
# ("cmd not found -> which nix package provides it"), no manual indexing.
inputs.nix-index-database.homeModules.default
];
# CLI staples wanted on every host (search, parse, monitor). ripgrep/fd also
# back fzf and editor integrations; tea is the Gitea CLI for code.emmathe.dev.
home.packages = [
pkgs.ripgrep
pkgs.fd
pkgs.jq
pkgs.btop
pkgs.tea
];
programs.zsh = { programs.zsh = {
enable = true; enable = true;
enableCompletion = true; enableCompletion = true;
@@ -21,35 +46,189 @@
"^[OB" "^[OB"
]; ];
}; };
history.append = true; history = {
append = true; # append, don't overwrite, on shell exit
size = 100000; # in-memory (HISTSIZE)
save = 100000; # on-disk (SAVEHIST)
ignoreDups = true; # drop consecutive duplicates
ignoreSpace = true; # leading-space commands stay out of history
expireDuplicatesFirst = true;
share = true; # live-share history across sessions
extended = true; # record timestamps
};
oh-my-zsh = { oh-my-zsh = {
enable = true; enable = true;
plugins = [ plugins = [
"git" "git"
"man" "man"
"sudo" # double-Esc prefixes the last command with sudo
"colored-man-pages"
"extract" # `extract <archive>` for any format
]; ];
theme = "robbyrussell"; theme = "robbyrussell";
}; };
syntaxHighlighting.enable = true; syntaxHighlighting.enable = true;
# Prefix the prompt with the hostname over SSH. initContent = lib.mkMerge [
initContent = lib.mkOrder 1500 '' # Auto-start tmux in every interactive terminal -- foot, iTerm2, the WSL
if [ "$SSH_CLIENT" ] || [ "$SSH_TTY" ]; then # shell, the Linux console -- so a new terminal lands straight in the
export PS1="%M $PS1" # multiplexer (session "main": attach if present, else create). Panes run
fi # a plain non-login zsh (tmux's default-command "${SHELL}"). Order 200
''; # runs before oh-my-zsh/compinit so the exec replaces the shell before
envExtra = '' # that setup is wasted. Guards, each preventing a real breakage:
alias cls=clear # interactive only -> don't hijack scp / `ssh host cmd` / scripted shells
''; # $TMUX empty -> a pane's zsh won't re-exec tmux (infinite loop)
# not SSH -> don't force inbound SSH logins into a server tmux
# not VS Code -> its integrated terminal manages itself
# tmux on PATH -> a failed exec would otherwise kill the login shell
# $NO_TMUX unset -> escape hatch: `NO_TMUX=1 <term>` opens a bare shell
(lib.mkOrder 200 ''
if [[ $- == *i* ]] \
&& [[ -z "$TMUX" ]] \
&& [[ -z "$NO_TMUX" ]] \
&& [[ -z "$SSH_CONNECTION" && -z "$SSH_TTY" ]] \
&& [[ "$TERM_PROGRAM" != "vscode" ]] \
&& command -v tmux >/dev/null 2>&1; then
exec tmux new-session -A -s main
fi
'')
# Prefix the prompt with the hostname over SSH (mkAfter).
(lib.mkOrder 1500 ''
if [ "$SSH_CLIENT" ] || [ "$SSH_TTY" ]; then
export PS1="%M $PS1"
fi
'')
];
shellAliases = {
# eza's zsh integration also defines these; set explicitly so the
# icons/git intent is obvious.
ls = "eza --icons --git";
ll = "eza --icons --git -l";
la = "eza --icons --git -la";
lt = "eza --icons --git --tree";
cls = "clear";
};
};
# Fuzzy finder: Ctrl-R fuzzy history, Ctrl-T files, Alt-C cd.
programs.fzf = {
enable = true;
enableZshIntegration = true;
# Catppuccin Mocha colours (rendered into FZF_DEFAULT_OPTS --color). Each
# value needs a leading "#"; the palette stores raw hex.
colors = {
"bg" = "#${ctp.base}";
"bg+" = "#${ctp.surface1}"; # current line / selected row
"fg" = "#${ctp.text}";
"fg+" = "#${ctp.text}";
"hl" = "#${ctp.blue}"; # match highlights
"hl+" = "#${ctp.blue}";
"header" = "#${ctp.red}";
"info" = "#${ctp.mauve}";
"marker" = "#${ctp.green}";
"pointer" = "#${ctp.pink}";
"prompt" = "#${ctp.mauve}";
"spinner" = "#${ctp.pink}";
"border" = "#${ctp.surface1}";
};
};
# Frecency directory jumping: `z <fragment>`.
programs.zoxide = {
enable = true;
enableZshIntegration = true;
};
# Per-project environments auto-loaded on cd, with the Nix dev-shell cache.
programs.direnv = {
enable = true;
nix-direnv.enable = true;
};
# Modern ls (drives the ls aliases above).
programs.eza = {
enable = true;
git = true;
icons = "auto"; # boolean form is deprecated
};
# Syntax-highlighting pager, used as `bat` (acts like cat when piped). bat
# ships no Catppuccin theme, so vendor the upstream tmTheme from catppuccin/bat
# (delta in git.nix reuses it as its syntax-theme).
programs.bat = {
enable = true;
config.theme = "Catppuccin Mocha";
themes."Catppuccin Mocha" = {
src = pkgs.fetchFromGitHub {
owner = "catppuccin";
repo = "bat";
rev = "6810349b28055dce54076712fc05fc68da4b8ec0";
sha256 = "1y5sfi7jfr97z1g6vm2mzbsw59j1jizwlmbadvmx842m0i5ak5ll";
};
file = "themes/Catppuccin Mocha.tmTheme";
};
};
# command-not-found backed by the prebuilt nix-index DB (module imported
# above). `comma` runs an uninstalled program once: `, cowsay hi`.
programs.nix-index.enable = true;
programs.nix-index-database.comma.enable = true;
# Nicer nixos-rebuild/home-manager (diffs) + $NH_FLAKE. No automatic clean:
# the scheduled GC's only benefit is reclaiming disk, but it can reap store
# paths the current generation still references (notably on nix-darwin, where
# it broke completion by removing an in-use oh-my-zsh). GC manually instead:
# `nh clean all` / `nix-collect-garbage -d` when nothing important is running.
programs.nh = {
enable = true;
flake = "$HOME/code/nixfiles";
};
# GitHub CLI. Prefer SSH for any git operations it drives, matching the
# ssh-based remotes used elsewhere.
programs.gh = {
enable = true;
settings.git_protocol = "ssh";
}; };
programs.tmux = { programs.tmux = {
enable = true; enable = true;
reverseSplit = true; reverseSplit = true;
terminal = "tmux-direct"; # tmux-256color (not tmux-direct): the standard inside-tmux terminfo.
# tmux-direct's capabilities desync zsh's line redraw on some terminals
# (e.g. iTerm2 -> duplicated chars on Tab, stray newlines). Truecolor is
# advertised per outer terminal via the RGB terminal-features below.
terminal = "tmux-256color";
newSession = true; newSession = true;
keyMode = "vi"; keyMode = "vi";
historyLimit = 500000; historyLimit = 500000;
mouse = true; mouse = true;
escapeTime = 10; # was the 500ms default -> laggy ESC in vim
focusEvents = true; # let vim see focus changes (autoread)
baseIndex = 1; # sets both base-index and pane-base-index
plugins = with pkgs.tmuxPlugins; [
sensible
vim-tmux-navigator # Ctrl-h/j/k/l across vim splits and tmux panes
yank
{
# Catppuccin Mocha statusline (v2 API: flavour + window options must be
# set before the plugin loads, which home-manager does for plugin
# extraConfig; the status modules below go in the main extraConfig,
# which HM appends after all plugins).
plugin = catppuccin;
extraConfig = ''
set -g @catppuccin_flavor 'mocha'
set -g @catppuccin_window_status_style 'rounded'
'';
}
resurrect # save/restore sessions
{
plugin = continuum; # auto-save + restore on tmux start (after resurrect)
extraConfig = ''
set -g @continuum-restore 'on'
'';
}
];
# `reverseSplit = true` already binds s -> vertical and v -> horizontal # `reverseSplit = true` already binds s -> vertical and v -> horizontal
# split (the dotfiles' vim-style splits). # split (the dotfiles' vim-style splits).
extraConfig = '' extraConfig = ''
@@ -66,6 +245,10 @@
bind -n M-Up select-pane -U bind -n M-Up select-pane -U
bind -n M-Down select-pane -D bind -n M-Down select-pane -D
# Truecolor for the outer terminals (foot reports xterm-ish too; iTerm2 is
# xterm-256color). Without this, with tmux-256color as default-terminal,
# 24-bit colour would be quantised to 256.
set -as terminal-features ",xterm-256color:RGB"
# Tell tmux which capabilities the foot terminal supports, so truecolor, # Tell tmux which capabilities the foot terminal supports, so truecolor,
# synchronised output, the system clipboard (OSC 52), window titles and # synchronised output, the system clipboard (OSC 52), window titles and
# cursor styling all pass through. # cursor styling all pass through.
@@ -75,6 +258,78 @@
set -as terminal-features ",foot*:title" set -as terminal-features ",foot*:title"
set -as terminal-features ",foot*:ccolour" set -as terminal-features ",foot*:ccolour"
set -as terminal-features ",foot*:cstyle" set -as terminal-features ",foot*:cstyle"
# No home-manager options for these.
set -g renumber-windows on
set -g set-clipboard on
# Catppuccin v2 statusline. Must run after the plugin has loaded;
# home-manager appends this extraConfig after the whole plugin list.
set -g status-left-length 100
set -g status-right-length 100
set -g status-left ""
set -g status-right "#{E:@catppuccin_status_application}"
set -ag status-right "#{E:@catppuccin_status_session}"
''; '';
}; };
# Add the key to the agent on first use, so the passphrase is typed once per
# login session rather than per commit/push (commit signing uses this agent).
# The work box keeps its own ssh config (see work/default.nix), so this only
# manages ~/.ssh/config on the personal hosts.
programs.ssh = {
enable = true;
# The module's built-in default "*" block is being deprecated; opt out and
# carry the defaults we want ourselves under settings."*".
enableDefaultConfig = false;
settings = {
# Global defaults (rendered last, as ssh_config wants). AddKeysToAgent
# adds the key on first use so the passphrase is typed once per session.
"*" = {
AddKeysToAgent = "yes";
ForwardAgent = false;
Compression = false;
ServerAliveInterval = 0;
ServerAliveCountMax = 3;
HashKnownHosts = false;
UserKnownHostsFile = "~/.ssh/known_hosts";
ControlMaster = "no";
ControlPath = "~/.ssh/master-%r@%n:%p";
ControlPersist = "no";
}
# macOS: also cache the passphrase in the login keychain. UseKeychain
# exists only in Apple's ssh; nixpkgs' openssh (which may be the `ssh` on
# PATH) rejects it as "Bad configuration option". IgnoreUnknown (emitted
# first by the module) makes any non-Apple ssh skip it instead of erroring,
# while Apple's ssh still honours it. Darwin-only.
// lib.optionalAttrs pkgs.stdenv.hostPlatform.isDarwin {
IgnoreUnknown = "UseKeychain";
UseKeychain = "yes";
};
# Gitea remote (the flake's origin) -- required on every host. HostName
# pins the IP so it resolves without DNS. Port 30009 is non-default; pin
# the dedicated key (identitiesOnly avoids "too many authentication
# failures" when the agent holds several keys).
"code.emmathe.dev" = {
HostName = "10.187.1.76";
User = "git";
Port = 30009;
IdentityFile = "~/.ssh/code.emmathe.dev";
IdentitiesOnly = true;
};
};
};
# Run a user ssh-agent on Linux (macOS provides one via launchd). EDaaS also
# enables this in the work module; both being true merges cleanly.
services.ssh-agent.enable = lib.mkIf pkgs.stdenv.hostPlatform.isLinux true;
# Drop the zsh completion dump on every activation. A stale ~/.zcompdump
# caches /nix/store paths to completion functions; once a rebuild or a manual
# GC removes them, compinit fails with "_git: function definition file not
# found" for every completion. Deleting it forces a fresh rebuild from the
# current fpath on the next shell.
home.activation.resetZcompdump = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
$DRY_RUN_CMD rm -f "$HOME"/.zcompdump* "''${XDG_CACHE_HOME:-$HOME/.cache}"/zsh/.zcompdump* 2>/dev/null || true
'';
} }
+3
View File
@@ -237,6 +237,9 @@ in
# text). Only colors-dark is needed; we never set initial-color-theme=light. # text). Only colors-dark is needed; we never set initial-color-theme=light.
settings = { settings = {
main = { main = {
# Nerd Font: monospace plus the powerline/Nerd glyphs the tmux
# statusline uses (otherwise they render as blank/"?").
font = "JetBrainsMono Nerd Font:size=11";
# Advertise as xterm-256color so remote hosts without foot's terminfo # Advertise as xterm-256color so remote hosts without foot's terminfo
# still behave (tmux re-adds foot's RGB/sync/etc. features -- see # still behave (tmux re-adds foot's RGB/sync/etc. features -- see
# shell.nix). The [main] section is the unheadered top of foot.ini. # shell.nix). The [main] section is the unheadered top of foot.ini.
@@ -1,6 +1,12 @@
{ pkgs, ... }: # Home-manager module for the work (EDaaS/WSL) profile: corporate git signing,
# work toolchain packages and tmux tweaks. Imported only by the work host.
{ pkgs, lib, ... }:
{ {
# The work box keeps its own (corporate) ~/.ssh/config; don't let the personal
# programs.ssh (shell.nix) take it over. The ssh-agent below still runs.
programs.ssh.enable = lib.mkForce false;
programs.git = { programs.git = {
settings = { settings = {
commit.gpgsign = true; commit.gpgsign = true;
+8
View File
@@ -0,0 +1,8 @@
# statix lint config. Two default lints are disabled because they flag this
# repo's intentional house style, not bugs:
# repeated_keys - we use `foo.a = ...; foo.b = ...;` (dotted) over nesting.
# empty_pattern - module files use `{ ... }:` / `{ }:` deliberately.
disabled = [
"repeated_keys",
"empty_pattern",
]
+5
View File
@@ -6,6 +6,11 @@
{ {
programs.zsh.enable = true; programs.zsh.enable = true;
# Install the Nerd Font into /Library/Fonts so iTerm2 can use it (set it in
# iTerm2 -> Settings -> Profiles -> Text -> Font: "JetBrainsMono Nerd Font").
# Provides the powerline/Nerd glyphs the tmux statusline draws.
fonts.packages = [ pkgs.nerd-fonts.jetbrains-mono ];
# CLI tooling sourced from nixpkgs instead of Homebrew formulae. Pure library # CLI tooling sourced from nixpkgs instead of Homebrew formulae. Pure library
# dependencies are omitted; nix pulls them into closures automatically. # dependencies are omitted; nix pulls them into closures automatically.
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
+12 -3
View File
@@ -14,9 +14,18 @@
networking.hostName = "Emma-Asahi"; networking.hostName = "Emma-Asahi";
# No fingerprint reader on this machine; empty service still lets swaylock # Audio (PipeWire) and the swaylock PAM stack are inherited from
# authenticate via password. # workstation.nix. hardware.enableRedistributableFirmware is also set there;
security.pam.services.swaylock = { }; # it is harmless here since Asahi supplies its own peripheral firmware below.
# Binary cache for the Asahi kernel/build artifacts, so the MBP pulls prebuilt
# outputs instead of compiling the Asahi kernel locally.
nix.settings = {
substituters = [ "https://nixos-apple-silicon.cachix.org" ];
trusted-public-keys = [
"nixos-apple-silicon.cachix.org-1:8psDu5SA5dAD7qA0zMy5UT292TxeEPzIz8VVEr2Js20="
];
};
# Apple peripheral firmware (Wi-Fi/Bluetooth). The directory is gitignored and # Apple peripheral firmware (Wi-Fi/Bluetooth). The directory is gitignored and
# populated out-of-band -- see README. # populated out-of-band -- see README.
+5 -4
View File
@@ -6,10 +6,11 @@ Flake host: `lyrathorpe-macpro31`. Desktop (`portable = false`, imports
## Hardware configuration ## Hardware configuration
`hardware-configuration.nix` here is a hand-written **placeholder**. On the real `hardware-configuration.nix` here is the real config generated by
machine, run `nixos-generate-config`, replace the file, and commit it. It assumes `nixos-generate-config` on the machine. Root is an **LVM** logical volume
by-label partitions — ESP `ESP` (vfat, mounted at `/boot`), root `nixos` (ext4), (`/dev/mapper/MacPro-Root`, ext4); the ESP (vfat) and swap are referenced by
and `swap` — so either label them at install time or swap in the generated UUIDs. UUID. The initrd carries `dm-snapshot` for the LVM root. Regenerate and commit
if the disk layout changes.
## Bootloader ## Bootloader
+2 -12
View File
@@ -27,19 +27,9 @@
services.openssh.enable = true; services.openssh.enable = true;
networking.firewall.allowedTCPPorts = [ 22 ]; networking.firewall.allowedTCPPorts = [ 22 ];
services.pipewire = { # Dual Harpertown Xeon microcode. Redistributable firmware (GPU/NIC blobs) is
enable = true; # enabled in workstation.nix.
pulse.enable = true;
};
# No fingerprint hardware; empty service still lets swaylock authenticate via
# password.
security.pam.services.swaylock = { };
# Dual Harpertown Xeon microcode + redistributable firmware (e.g. GPU/NIC
# blobs).
hardware.cpu.intel.updateMicrocode = true; hardware.cpu.intel.updateMicrocode = true;
hardware.enableRedistributableFirmware = true;
# GPU note: the stock card varies between units -- ATI Radeon HD 2600 XT or # GPU note: the stock card varies between units -- ATI Radeon HD 2600 XT or
# NVIDIA GeForce 8800 GT. Sway needs a working KMS/modesetting driver; do NOT # NVIDIA GeForce 8800 GT. Sway needs a working KMS/modesetting driver; do NOT
+5 -5
View File
@@ -15,11 +15,11 @@ install time or swap in the generated UUIDs.
`configuration.nix` imports exactly one boot module. Default is `boot-bios.nix`; `configuration.nix` imports exactly one boot module. Default is `boot-bios.nix`;
switch by commenting it out and uncommenting the relevant alternative. switch by commenting it out and uncommenting the relevant alternative.
| Firmware | Module | Notes | | Firmware | Module | Notes |
| --- | --- | --- | | ---------------------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Stock Lenovo BIOS, or coreboot + **SeaBIOS** payload | `boot-bios.nix` | GRUB on the MBR. Set `device` to the real install disk (`/dev/sda` by default). MBR/legacy layout. | | Stock Lenovo BIOS, or coreboot + **SeaBIOS** payload | `boot-bios.nix` | GRUB on the MBR. Set `device` to the real install disk (`/dev/sda` by default). MBR/legacy layout. |
| coreboot + **GRUB** payload | `boot-coreboot-grub.nix` | GRUB is config-only (`device = "nodev"`); NixOS does **not** write to a disk. Your coreboot `grub.cfg` (in the flash chip) must `search` for and `configfile` the on-disk `/boot/grub/grub.cfg`, or chainload the disk's GRUB. | | coreboot + **GRUB** payload | `boot-coreboot-grub.nix` | GRUB is config-only (`device = "nodev"`); NixOS does **not** write to a disk. Your coreboot `grub.cfg` (in the flash chip) must `search` for and `configfile` the on-disk `/boot/grub/grub.cfg`, or chainload the disk's GRUB. |
| coreboot + **Tianocore/edk2 (UEFI)** payload | `boot-coreboot-uefi.nix` | systemd-boot. `canTouchEfiVariables = true` (coreboot honours NVRAM writes). The module **declares its own ESP** (`/boot` vfat, label `ESP`) — when you regenerate `hardware-configuration.nix`, do **not** let it also define `/boot`. Create + label an `ESP` vfat partition (GPT). | | coreboot + **Tianocore/edk2 (UEFI)** payload | `boot-coreboot-uefi.nix` | systemd-boot. `canTouchEfiVariables = true` (coreboot honours NVRAM writes). The module **declares its own ESP** (`/boot` vfat, label `ESP`) — when you regenerate `hardware-configuration.nix`, do **not** let it also define `/boot`. Create + label an `ESP` vfat partition (GPT). |
## Graphics ## Graphics
+5 -12
View File
@@ -18,25 +18,18 @@
console.font = "Lat2-Terminus16"; console.font = "Lat2-Terminus16";
services.pipewire = { # Low-RAM host (4 GiB max): a compressed RAM swap reduces disk paging.
enable = true; zramSwap.enable = true;
pulse.enable = true;
};
# This host accepts SSH, so open 22 (the firewall itself is enabled in # This host accepts SSH, so open 22 (the firewall itself is enabled in
# laptop.nix with a default-deny policy). # laptop.nix with a default-deny policy).
services.openssh.enable = true; services.openssh.enable = true;
networking.firewall.allowedTCPPorts = [ 22 ]; networking.firewall.allowedTCPPorts = [ 22 ];
# The T400's fingerprint reader differs/may be absent; empty service still # Intel Core 2 (Penryn) microcode. Redistributable firmware (enabled in
# lets swaylock authenticate via password. # workstation.nix) supplies the iwlwifi blobs (Intel WiFi Link 5100/5300) and
security.pam.services.swaylock = { }; # the radeon firmware needed by the discrete GPU below.
# Intel Core 2 (Penryn) microcode + redistributable firmware. The latter also
# supplies the iwlwifi blobs (Intel WiFi Link 5100/5300) and the radeon
# firmware needed by the discrete GPU below.
hardware.cpu.intel.updateMicrocode = true; hardware.cpu.intel.updateMicrocode = true;
hardware.enableRedistributableFirmware = true;
# This T400 has the optional discrete GPU fitted: an ATI Mobility Radeon HD # This T400 has the optional discrete GPU fitted: an ATI Mobility Radeon HD
# 3470 (RV620), driven by the open `radeon` KMS driver. Load it in the initrd # 3470 (RV620), driven by the open `radeon` KMS driver. Load it in the initrd
+13
View File
@@ -6,10 +6,23 @@
time.timeZone = "Europe/London"; time.timeZone = "Europe/London";
i18n.defaultLocale = "en_GB.UTF-8"; i18n.defaultLocale = "en_GB.UTF-8";
# Store hygiene. auto-optimise-store hard-links identical files in the store
# after each build (cheap dedupe; NOT a garbage collector -- there is
# deliberately no automatic GC timer). The larger download buffer avoids
# "buffer full" stalls when fetching big NARs over a fast link.
nix.settings.auto-optimise-store = true;
nix.settings.download-buffer-size = 134217728; # 128 MiB
# Minimal system-level CLI available before the home-manager profile loads # Minimal system-level CLI available before the home-manager profile loads
# (e.g. early boot / rescue). User-level tooling lives in home-manager. # (e.g. early boot / rescue). User-level tooling lives in home-manager.
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
git git
fastfetch fastfetch
]; ];
# Terminal font with powerline/Nerd glyphs. Installed on every host because
# the tmux statusline (which uses these glyphs) runs everywhere, not just on
# the Sway/graphical hosts. foot names it explicitly (home/sway.nix); the Mac
# installs it via the Darwin config.
fonts.packages = [ pkgs.nerd-fonts.jetbrains-mono ];
} }
+20
View File
@@ -14,4 +14,24 @@
# Default-deny inbound. Hosts that run a listening service open their own # Default-deny inbound. Hosts that run a listening service open their own
# ports next to where the service is enabled (e.g. sshd -> 22 on X1). # ports next to where the service is enabled (e.g. sshd -> 22 on X1).
networking.firewall.enable = true; networking.firewall.enable = true;
# Disk hygiene for the physical hosts. fstrim reclaims unused SSD blocks on a
# weekly timer; cleanOnBoot wipes /tmp at every boot.
services.fstrim.enable = true;
boot.tmp.cleanOnBoot = true;
# Audio. PipeWire with the PulseAudio shim covers every graphical host; no
# per-machine audio config is needed.
services.pipewire = {
enable = true;
pulse.enable = true;
};
# swaylock PAM stack. None of these machines has working fingerprint auth, so
# an empty service is enough -- swaylock falls back to password.
security.pam.services.swaylock = { };
# Redistributable firmware (GPU/Wi-Fi/NIC blobs) for the x86 hosts. Harmless
# on the Asahi MBP, which supplies its own peripheral firmware out-of-band.
hardware.enableRedistributableFirmware = true;
} }