feat(git): declare the operator's commit identity (task 0015) #8

Merged
alexion merged 4 commits from task-0015-git-module into main 2026-07-20 12:23:23 -04:00
4 changed files with 54 additions and 8 deletions
Showing only changes of commit c9fc17ecf5 - Show all commits

View File

@@ -10,8 +10,26 @@ It is a `Module` rather than base plumbing because a `Host` that should not carr
## Acceptance criteria
- [ ] A git `Module` following the `Enable convention` exists and is enabled on `neogaia`
- [ ] The commit identity is configured through home-manager and matches the one used in existing history
- [ ] `nix flake check` builds the `neogaia` toplevel
- [ ] Manual confirmation: committing in a repository outside this checkout succeeds with no per-command identity override
- [ ] The stale note in the project's agent instructions claiming git identity is unconfigured is corrected, since commits already work here through repository-local configuration
- [x] A git `Module` following the `Enable convention` exists and is enabled on `neogaia`
- [x] The commit identity is configured through home-manager and matches the one used in existing history
- [x] `nix flake check` builds the `neogaia` toplevel
- [x] Manual confirmation: committing in a repository outside this checkout succeeds with no per-command identity override
- [x] The stale note in the project's agent instructions claiming git identity is unconfigured is corrected, since commits already work here through repository-local configuration
## Implementation Notes
`programs.git.userName`/`userEmail` are renamed in this home-manager pin and emit an obsolete-option trace.
The module uses `settings.user.name`/`settings.user.email`.
Do not "fix" it back.
The commit name is the literal `"alexion"` rather than `config.user.name`, which review raised as duplication.
A Unix login and a commit display name are separate concepts that merely coincide here, so binding them would let a host overriding its login silently rewrite the operator's commit identity.
The manual confirmation was met against the built configuration, not the running machine: `nixos-rebuild switch` needs sudo and has not run, so `~/.config/git/config` does not yet exist on `neogaia`.
The generated gitconfig was built from the `neogaia` toplevel and a commit driven under `env -i` with a scratch `HOME`, producing `alexion <contact@alexion.dev>` with no per-command override.
This proves the derivation rather than the deployment, and the live check remains owed at the next rebuild.
Review surfaced an unanticipated hazard that proved harmless.
Home-manager writes `~/.config/git/config`, while an undeclared `~/.gitconfig` also exists and outranks it per key.
It holds only a `tea` credential helper and no `user.*`, so it does not shadow the identity, confirmed by re-running the commit test with both files present.
Declaring that credential helper is a reasonable follow-up, since it will not survive a reimage.

View File

@@ -36,9 +36,11 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla
Both were true only while the machine still ran CachyOS against a distro Nix daemon.
- The substituters a `nix build` fetches from are the **daemon's** (`/etc/nix/nix.conf`), *not* the `nix.settings` of the config being built — those only govern the built system.
The two coincide here because the dev host runs this flake; they diverge on any machine that does not.
- Git identity is not declared in the flake — there is no `programs.git` — so it must be set by hand before the first commit on a fresh machine.
The July 2026 reimage confirmed this: it wiped the hand-written `~/.gitconfig`, and the next commit failed with `Author identity unknown`, auto-detecting `alexion@neogaia.(none)`.
It now lives in this checkout's `.git/config`, which reaches no other machine and does not survive the next reimage either; history uses `alexion <contact@alexion.dev>`.
- Git identity is declared in the flake by `modules/git.nix`, which writes `alexion <contact@alexion.dev>` — the identity all history uses — on any host enabling `modules.git`.
Once such a host has been rebuilt, a checkout on it needs no hand-written identity and keeps one across a reimage.
Two things mask a broken module, so neither is evidence it works: this checkout's `.git/config` carries the same identity, and home-manager writes `~/.config/git/config` while a `~/.gitconfig` also exists and outranks it per key.
That `~/.gitconfig` holds only a `tea` credential helper and no `user.*`, so it does not shadow the identity today, but it is undeclared and will not survive a reimage.
Verify the module by committing in a repository outside this checkout.
- The primary build/verify seam for any Host is `nix flake check`, which builds `checks.x86_64-linux.<host>` (the system toplevel); cheap targeted checks use `nix eval .#nixosConfigurations.<host>.config...`.
- A flake only sees **git-tracked** files, so a new file that has not been `git add`ed is invisible to evaluation even though it exists on disk.
The failure names the path and reads as if the file were missing: `error: Path 'secrets/shared.yaml' does not exist in Git repository`.

View File

@@ -36,6 +36,7 @@
modules.fish.enable = true;
modules.fish.defaultShell = true;
modules.git.enable = true;

Should be enabled on every host.

Should be enabled on every host.
modules.tmux.enable = true;
modules.nvim.enable = true;
modules.claude-code.enable = true;

25
modules/git.nix Normal file
View File

@@ -0,0 +1,25 @@
{
config,
lib,
...
}:
# git for the primary user, configured through home-manager.
let
cfg = config.modules.git;
user = config.user.name;
in
{
options.modules.git.enable =
lib.mkEnableOption "git for the primary user, carrying the operator's commit identity";
config = lib.mkIf cfg.enable {
home-manager.users.${user}.programs.git = {
enable = true;
# Git will not guess a name and address from the login and hostname.
# Without these a commit fails outright with `Author identity unknown`.
settings.user.name = "alexion";
settings.user.email = "contact@alexion.dev";
};
};
}