Compare commits

..

3 Commits

Author SHA1 Message Date
9a860d7715 refactor: flatten the single-file lib and system directories
Each held one default.nix, so the directory added a level to descend without
grouping anything. They are now lib.nix and system.nix beside flake.nix, which
reads as the flake's own scaffolding.

The shared base config's reference to the shared secrets file was relative to
the directory it sat in, so it moves with the file.

Verified to build the same system toplevel as before the move.
2026-07-20 13:02:26 -04:00
58bf41f62d refactor(ssh): state the admitted keys directly instead of deriving them
The access policy was a separate fleet declaration mapping each machine to a
role and a client key, which the module looked up by hostname to derive what to
authorize. Authorizing a key needs the key text and nothing else, so the
per-machine names, the hostname lookup and the role existed only to reconstruct
a grouping that two lists already are.

The module now exposes those two lists and the set a machine admits, and a host
declares what it draws from in its own file. The fleet declaration is gone, and
with it a single-file directory at the repository root.

`authorizedKeys` defaults to the workstation keys. An option of a list type is
not mandatory the way a scalar one is: undeclared, it yields the empty list
rather than failing, and a machine admitting no key is unreachable. The default
makes the safe case the silent one.

Verified to produce the same system as the design it replaces, and against the
running machine in both directions.
2026-07-20 12:57:23 -04:00
e1eb0cb5c9 feat(ssh): adopt the client key as a secret and derive access from roles (task 0019)
The operator's SSH client key existed only as a file created by hand on one
laptop, so a reimage would destroy it and lock the operator out of the remote.
It now lives in neogaia's own secrets file, encrypted to the admin identity and
neogaia alone, and the client is pointed at the decrypted path rather than a
copy in the user's home.

Access becomes a policy over roles instead of a per-host list of keys. A new
fleet declaration names each machine's role and client public key, and every
machine derives what it authorizes from that: a workstation admits workstations
alone, a server admits both, so a compromised server reaches no machine of the
operator's own. Registering a machine is an entry in that one file.

Only neogaia exists, so the server half of the policy is built rather than
exercised. Two assertions reject a machine missing from the fleet and any entry
whose role no policy defines.
2026-07-20 12:41:17 -04:00
4 changed files with 8 additions and 66 deletions

View File

@@ -10,33 +10,8 @@ It is a `Module` rather than base plumbing because a `Host` that should not carr
## Acceptance criteria ## Acceptance criteria
- [x] A git `Module` following the `Enable convention` exists and is enabled on `neogaia` - [ ] 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 - [ ] The commit identity is configured through home-manager and matches the one used in existing history
- [x] `nix flake check` builds the `neogaia` toplevel - [ ] `nix flake check` builds the `neogaia` toplevel
- [x] Manual confirmation: committing in a repository outside this checkout succeeds with no per-command identity override - [ ] 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 - [ ] 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.
Review on the pull request asked for the module on every host, which was first built by defaulting `enable` to true and dropping the per-host line.
The operator then chose the opposite: `enable` defaults to false and each host enables it explicitly, so a host keeps reading as a full checklist of what it carries rather than hiding a default-on module.
Enabling it is therefore a step when adding a host.
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 is met on the running machine.
The operator rebuilt `neogaia`, `~/.config/git/config` is now a home-manager symlink, and a commit in a repository outside this checkout was authored `alexion <contact@alexion.dev>` in the real environment with no per-command override and no identity in the test repository's own config.
Review surfaced an unanticipated hazard that proved harmless.
Home-manager writes `~/.config/git/config`, while an undeclared `~/.gitconfig` also exists and outranks it on any key set in both.
It holds only a `tea` credential helper and no `user.*`, so it does not shadow the identity, confirmed against the deployed configuration.
Declaring that credential helper is a reasonable follow-up, since it will not survive a reimage.
This checkout's `.git/config` still sets the same identity, now redundant.
Removing it would let the module govern here too, so a future breakage surfaces instead of being masked.
It is local, untracked state, so it is left alone rather than changed as part of this task.

View File

@@ -36,16 +36,9 @@ 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. 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 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. The two coincide here because the dev host runs this flake; they diverge on any machine that does not.
- 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`. - 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.
Every new host has to enable it, so that a host reads as a full checklist of what it carries. 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 is deployed on `neogaia` and verified: a commit in a repository outside this checkout is authored `alexion <contact@alexion.dev>` with no override. 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>`.
Verify it that way rather than from this checkout, whose `.git/config` carries the same identity and would mask a broken module.
Home-manager writes `~/.config/git/config`, and `~/.gitconfig` is a second global file that git also reads, outranking it on any key set in both.
`~/.gitconfig` currently holds only a `tea` credential helper and no `user.*`, so it does not shadow the identity, but it is undeclared and will not survive a reimage.
- `git config --global` is a listing and writing filter over `~/.gitconfig` alone, **not** a view of what git resolves.
With both global files present it prints only `~/.gitconfig`, which reads as proof that `~/.config/git/config` is being ignored entirely.
It is not: drop `--global` and both files appear, each key resolving to the last file that sets it.
A `git config --global <key> <value>` write also lands in `~/.gitconfig`, the file that outranks the flake-managed one.
- 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...`. - 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. - 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`. 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

@@ -45,7 +45,6 @@
modules.fish.enable = true; modules.fish.enable = true;
modules.fish.defaultShell = true; modules.fish.defaultShell = true;
modules.git.enable = true;
modules.tmux.enable = true; modules.tmux.enable = true;
modules.nvim.enable = true; modules.nvim.enable = true;
modules.claude-code.enable = true; modules.claude-code.enable = true;

View File

@@ -1,25 +0,0 @@
{
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";
};
};
}