Render each machine's SSH client config from its own identity record #81
No reviewers
Labels
No labels
blocked
bug
decision
duplicate
enhancement
help wanted
invalid
landed?
question
ready-to-merge
stale
wontfix
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
allod/archetypes!81
Loading…
Reference in a new issue
No description provided.
Delete branch "agent/ssh-hosts-by-identity"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Every dev VM's SSH client config becomes one host block, the forge, keyed to that VM's own key; the hypervisor's SSH address book (
identity.sshHosts) no longer reaches dev VMs at all. The hypervisor's own config is unchanged: byte-identical on the public template, and identical in effect for an entry that carriesextraOptions(only the line order inside that block moves). The Home Manager 26.05matchBlocksdeprecation warnings this framework emitted stop. NosshHostsdata changes and the nexus rotation scripts read the same fields. A new flake check resolves fixture configs with a realssh -G, and against the deploy template the hypervisor renders identically whileallod-devkeeps its forge block byte-for-byte and loses the five entries keyed to a key it does not hold. If it is wrong, revert this PR; nothing else depends on the two new modules. Landing order for a deploy lock: take this before or together with the profile deletions (allod/profiles#10), never after them, because a dev VM whose profile copy is already gone while this change is absent gets the address book's forge block with the hypervisor's key and cannot reach the forge.What changes
modules/home-shared.nixkeeps username, home directory, git andprograms.ssh.enable; it no longer mapsidentity.sshHostsintoprograms.ssh.matchBlocksand no longer special-cases the forge host.modules/host-ssh-hosts.nix(new, imported bymkHypervisoronly) translates eachsshHostsentry toprograms.ssh.settings:hostname,user,port,identityFile,identitiesOnlyto their directive names;localForwardstoLocalForward, restating the defaults Home Manager's legacy submodule applied (bind addresslocalhost, host address unset) so the rendered line is the same;extraOptionsmerged flat. An unknown key, an unknown forward sub-key, anextraOptionskey colliding with a translated directive (compared case-insensitively, as OpenSSH does), or a string-valuedextraOptions.SetEnv(the one directive Home Manager's settings renderer takes only as an attribute set) is an evaluation error naming the host and the key. Nolib.mkDefaulton this path.modules/dev-ssh-forge.nix(new, imported bymkDevVmonly) renders the forge block fromruntimeIdentity.forgeHost,runtimeIdentity.forgePortandidentity.sshKeyName. ItsIdentityFilekeepslib.mkDefaultfor the transition: a profile that still carries its copy of the block wins without a clash. The follow-up that drops it is triggered by a deploy lock in which no profile input defines the block; after that a stray copy is a type clash at evaluation, not a silent winner.checks/ssh-settings.nix(new, registered asssh-settings): a hypervisor fixture with a plain host, aport+identitiesOnlyhost, anextraOptionshost and alocalForwardshost, each resolved withssh -G -F <generated config>and asserted by exact line; a dev fixture asserting exactly oneHostblock, the forge, resolving to~/.ssh/<sshKeyName>; a dev fixture beside an unmigrated legacymatchBlocksprofile copy asserting the copy's key wins and evaluation is clean; a fixture rendering the samelocalForwardsdata through the new module and through the legacy option and asserting equal text; assertions that neither production module definesmatchBlocksand that the hypervisor and dev fixtures carry no warnings; one sabotage fixture (an entry withproxyJump) proving the translation fails loud. Four further assertions read the framework's own machines through the production builders, with names taken from inventory: every dev machine's generated config holds exactly oneHostline naming its forge, and the hypervisor's holds one persshHostsentry, so a builder that stops importing one of the modules fails the check. The dev half of that is masked while the pinned profiles input still defines an identical forge block (the two authors render the same bytes, which is the defect this arc removes); it becomes load-bearing when allod/profiles#10 lands, and a wrongforgeHostfrom the builder already fails it today.The translation function and the
ssh -Gcheck shape are harvested from the closed allod/archetypes#80, extended withlocalForwards, which that branch rejected and which deployment address books carry. The companion template change is allod/secrets#33, addinglocalForwardsandextraOptionsentries so the composed hypervisor render exercises every shape the translation claims.One correction to the issue text: the hypervisor's rendered
~/.ssh/configis byte-identical only for entries withoutextraOptions. The legacy option appended those after the typed directives; the flat merge sorts them in with the rest. Same directives, samessh -Gresolution, different line order inside that block. Measured below.Risk
R3: a shared interface consumed by every dev VM and by the hypervisor's user profile, with cross-repo sequencing against the profile deletions. The worst credible failure after validation is a deployment address book carrying a field shape the translation does not know, which is a loud evaluation error at the next lock bump, not a silent config change. The authoritative data is untouched. Rollback is a revert.
Validation
Run by the integrator at
c213a34:nix build .#checks.x86_64-linux.ssh-settings --rebuildexits 0. The worker showed the check failing on a sabotaged expected key name and on a drifted forward default, then reverted.nix flake check --no-buildexits 0 (worker run, alone); the onlymatchBlockstraces are the two deliberate legacy fixtures.allod/deploymaster) with--override-input archetypes <this branch>:nexus's rendered~/.ssh/configis byte-identical to master;allod-dev's keeps the forge block byte-for-byte and loses the five other blocks. ThematchBlockswarnings are gone from both evaluations.nexusunder master archetypes (Home Manager's legacy conversion) versus under this branch:ssh -Goutput is identical for all six hosts, includinglocalforward [127.0.0.1]:8443 [192.0.2.31]:443,localforward [localhost]:5900 [192.0.2.31]:5900andserveraliveinterval 30; the text differs only in the position ofServerAliveIntervalinsideexample-provider-support.nexus,allod-devandprivacy-1evaluate to their toplevel derivations with no evaluation warnings;allod-dev's config is the single forge block, identical with and without the profile copy present.ssh -Gterms and each dev VM reduced to its forge block.Cold cross-family review:
gpt-5.6-solviacodex exec, reasoning effortxhigh, read the diff at2a6d3f7together with allod/secrets#33 and allod/profiles#10 (Claude-family workers authored all three). Four findings, all confirmed by tracing and fixed inc213a34. Blocker: the module comment claimed the framework change and the profile deletions may enter a deploy lock in either order; true of evaluation only, and the profiles-first order leaves a dev VM with the hypervisor's forge key. Comment corrected; the constraint is stated at the top of this body and of allod/profiles#10. Should-fix, three: anextraOptions.SetEnvstring that the legacy option rendered verbatim fails inside Home Manager with an error naming no host, now a named evaluation error; the collision guard compared directive names case-sensitively while OpenSSH does not, soextraOptions.HOSTNAMEbesidehostnamerendered both with the wrong one winning, now compared lower-cased; and the check pinned the two modules in isolation but not the builders' imports, now pinned through the framework's own machines as described above. The reviewer traced and cleared: string, list and nullidentityFilevalues and the forward endpoint defaults against the pinned Home Manager rendering; legacy and migrated profile copies both overriding the transitionalmkDefault; the sabotagetryEvalbeing forced; and the fixtures carrying only RFC 5737 addresses and no key material. No second reading follows the fix round: the fixes are settled by the rebuilt check, the worker's captured error texts for the two new throws, the hypervisor wiring assertion shown failing with its import removed, and the renders above re-run atc213a34(unchanged).Closes allod/archetypes#54
Refs allod/strategy#56