Render a dev VM's own external SSH aliases beside its forge entry #83

Closed
opened 2026-09-20 04:56:39 +01:00 by vnprc-agent · 2 comments

Render, beside a dev VM's forge entry, the SSH aliases its own identity record declares for external hosts it reaches with its own key, so a VM can hold an alias without receiving the hypervisor's address book. Part of allod/strategy#57.

Primary goals:

  • Consume the per-VM list — mkDevVm imports the existing translation module with sshHosts = identity.sshHosts or { }, where identity is the machine's devIdentities record; a secrets fork that predates the field composes unchanged.
  • Refuse a forge-named alias in the framework too — an entry named runtimeIdentity.forgeHost is an evaluation error naming the machine, because a duplicate at normal priority would silently replace the block dev-ssh-forge.nix renders (or, after allod/archetypes#82, clash on type with a message that names no cause). The secrets projection refuses it first; this is the layer beneath.
  • Template carries the path — allod/secrets: devVMs.<vm>.sshHosts on the template's dev machine with one synthetic external host (address in 192.0.2.0/24, an extraOptions.HostKeyAlias), and the projection in devIdentities with identityFile defaulting to ~/.ssh/${sshKeyName}, identitiesOnly to true, refusing the forge host and any machine of the deployment (compared case-insensitively, as OpenSSH matches Host patterns), an alias that is not one literal token matching [A-Za-z0-9][A-Za-z0-9._-]* (a space or glob character renders a pattern that can capture the forge host), and an entry without a string hostname.
  • Checked end to end — checks/ssh-settings.nix gains a dev fixture with a per-VM host: ssh -G resolves it to the VM's key and the extra directive, the forge entry is untouched beside it, and a forge-named alias throws. The composed check that pins the builders' wiring covers the new import the same way it covers dev-ssh-forge.nix.

Current state and specifics

flake.nix (mkDevVm) imports modules/home-shared.nix, modules/dev-ssh-forge.nix and the rest; modules/host-ssh-hosts.nix is the translation from the address book's field names (hostname, user, port, identityFile, identitiesOnly, localForwards, extraOptions) to programs.ssh.settings, imported by mkHypervisor alone. Its name says hypervisor; once both builders import it, a neutral name such as ssh-hosts.nix reads better, and the header comment's "imported by the hypervisor builder alone" goes. The per-VM entries arrive already defaulted from the secrets projection, so the module needs no change beyond being imported twice with different data.

The secrets template's devIdentities (flake.nix, the builtins.mapAttrs (name: vm: ...) identity.devVMs block) gains sshHosts = <projection> name vm;. The projection is a dozen lines: reserved names are builtins.attrNames identity.devVMs ++ builtins.attrNames identity.privacyVMs ++ [ identity.hostname identity.forgeHost ]; each entry is { identityFile = "~/.ssh/${vm.sshKeyName}"; identitiesOnly = true; } // entry. A check in that repo proves each refusal fires on sabotaged input (an upper-cased machine name, *, a two-pattern name, a literal Host header, a missing hostname, a non-attrset) and that a machine without the field projects to { }.

Scope

In: flake.nix builder wiring, the translation module's name and header, checks/ssh-settings.nix; in allod/secrets, identity.nix, flake.nix and a check. Out: identity.sshHosts and the hypervisor; the transitional mkDefault, allod/archetypes#82; any deployment's data.

Render, beside a dev VM's forge entry, the SSH aliases its own identity record declares for external hosts it reaches with its own key, so a VM can hold an alias without receiving the hypervisor's address book. Part of allod/strategy#57. Primary goals: - **Consume the per-VM list** — `mkDevVm` imports the existing translation module with `sshHosts = identity.sshHosts or { }`, where `identity` is the machine's `devIdentities` record; a secrets fork that predates the field composes unchanged. - **Refuse a forge-named alias in the framework too** — an entry named `runtimeIdentity.forgeHost` is an evaluation error naming the machine, because a duplicate at normal priority would silently replace the block `dev-ssh-forge.nix` renders (or, after allod/archetypes#82, clash on type with a message that names no cause). The secrets projection refuses it first; this is the layer beneath. - **Template carries the path** — allod/secrets: `devVMs.<vm>.sshHosts` on the template's dev machine with one synthetic external host (address in `192.0.2.0/24`, an `extraOptions.HostKeyAlias`), and the projection in `devIdentities` with `identityFile` defaulting to `~/.ssh/${sshKeyName}`, `identitiesOnly` to `true`, refusing the forge host and any machine of the deployment (compared case-insensitively, as OpenSSH matches `Host` patterns), an alias that is not one literal token matching `[A-Za-z0-9][A-Za-z0-9._-]*` (a space or glob character renders a pattern that can capture the forge host), and an entry without a string hostname. - **Checked end to end** — `checks/ssh-settings.nix` gains a dev fixture with a per-VM host: `ssh -G` resolves it to the VM's key and the extra directive, the forge entry is untouched beside it, and a forge-named alias throws. The composed check that pins the builders' wiring covers the new import the same way it covers `dev-ssh-forge.nix`. ### Current state and specifics `flake.nix` (`mkDevVm`) imports `modules/home-shared.nix`, `modules/dev-ssh-forge.nix` and the rest; `modules/host-ssh-hosts.nix` is the translation from the address book's field names (`hostname`, `user`, `port`, `identityFile`, `identitiesOnly`, `localForwards`, `extraOptions`) to `programs.ssh.settings`, imported by `mkHypervisor` alone. Its name says hypervisor; once both builders import it, a neutral name such as `ssh-hosts.nix` reads better, and the header comment's "imported by the hypervisor builder alone" goes. The per-VM entries arrive already defaulted from the secrets projection, so the module needs no change beyond being imported twice with different data. The secrets template's `devIdentities` (`flake.nix`, the `builtins.mapAttrs (name: vm: ...) identity.devVMs` block) gains `sshHosts = <projection> name vm;`. The projection is a dozen lines: reserved names are `builtins.attrNames identity.devVMs ++ builtins.attrNames identity.privacyVMs ++ [ identity.hostname identity.forgeHost ]`; each entry is `{ identityFile = "~/.ssh/${vm.sshKeyName}"; identitiesOnly = true; } // entry`. A check in that repo proves each refusal fires on sabotaged input (an upper-cased machine name, `*`, a two-pattern name, a literal `Host ` header, a missing hostname, a non-attrset) and that a machine without the field projects to `{ }`. ### Scope In: `flake.nix` builder wiring, the translation module's name and header, `checks/ssh-settings.nix`; in allod/secrets, `identity.nix`, `flake.nix` and a check. Out: `identity.sshHosts` and the hypervisor; the transitional `mkDefault`, allod/archetypes#82; any deployment's data.
Author

Landed: the dev builder consumes identity.sshHosts through the shared translation and the template carries the projection; allod/archetypes#82 dropped the transitional mkDefault alongside. Part of allod/strategy#57.

Landed: the dev builder consumes identity.sshHosts through the shared translation and the template carries the projection; allod/archetypes#82 dropped the transitional mkDefault alongside. Part of allod/strategy#57.
Author

Reopened: the closing comment above was wrong. What landed after the first refactor is allod/archetypes#82 only (447213e, 680442d); nothing yet consumes identity.sshHosts in the dev VM builder, and a dev machine composed from a secrets export that carries the field still renders only the forge block, measured against a deployment's lock today. This issue is the open implementation task as written.

Reopened: the closing comment above was wrong. What landed after the first refactor is allod/archetypes#82 only (447213e, 680442d); nothing yet consumes identity.sshHosts in the dev VM builder, and a dev machine composed from a secrets export that carries the field still renders only the forge block, measured against a deployment's lock today. This issue is the open implementation task as written.
vnprc closed this issue 2026-09-20 14:47:00 +01:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
allod/archetypes#83
No description provided.