From 55730a2e34e71fafaf9b95eab98fb7b062b5f194 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Fri, 19 Jun 2026 10:04:07 -0500 Subject: [PATCH] ssh: document DAG ordering patterns Expand the settings example so users can discover stable headers and list-shaped DAG helpers for ordered Host and Match blocks. --- modules/programs/ssh.nix | 51 +++++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/modules/programs/ssh.nix b/modules/programs/ssh.nix index ca904b5a..17038dab 100644 --- a/modules/programs/ssh.nix +++ b/modules/programs/ssh.nix @@ -678,13 +678,29 @@ in ); default = { }; example = literalExpression '' - { - "github.com" = { - HostName = "github.com"; + # Tagged hosts must appear before Match blocks that select those tags. + lib.hm.dag.entriesBefore "corp-host" [ "corp-match" ] [ + { + header = "Host build.corp"; + HostName = "build.corp.example.org"; + User = "builder"; + Tag = "corp"; + } + + { + header = "Host git.corp"; + HostName = "git.corp.example.org"; User = "git"; - IdentityFile = "~/.ssh/github"; + Tag = "corp"; + } + ] + // { + # Bare attribute names become Host patterns. + "*.internal.example.org" = { + User = "internal"; }; + # Literal Host headers can use the full OpenSSH syntax. "Host *.example.org" = lib.hm.dag.entryBefore [ "github.com" ] { IdentityFile = "~/.ssh/example"; LocalForward = [ @@ -698,7 +714,20 @@ in DynamicForward = "127.0.0.1:1080"; }; - "Match host *.corp exec \"test -f ~/.corp\"" = { + # Literal Match headers can be ordered by their attribute name. + "Match host *.vpn.example.org" = lib.hm.dag.entryAfter [ "github.com" ] { + ProxyJump = "vpn"; + }; + + "github.com" = { + HostName = "github.com"; + User = "git"; + IdentityFile = "~/.ssh/github"; + }; + + # Keep long or dynamic SSH headers out of DAG references. + corp-match = lib.hm.dag.entryAfter [ "github.com" ] { + header = "Match tagged corp exec \"test -f ~/.corp\""; ProxyJump = "bastion"; RemoteForward = { bind.port = 8081; @@ -707,6 +736,13 @@ in }; }; } + # Generated groups can also be placed after named blocks. + // lib.hm.dag.entriesAfter "corp-fallback" [ "corp-match" ] [ + { + header = "Host *.corp"; + User = "corp"; + } + ] ''; description = '' OpenSSH client configuration blocks written to @@ -716,7 +752,10 @@ in start with `Host ` or `Match `, in which case they are written literally as block headers. If the order of rules matter then use the DAG functions to express the dependencies as shown in - the example. + the example. Use {option}`programs.ssh.settings..header` + when the block should have a short stable name for DAG ordering, + and use `lib.hm.dag.entriesBefore` or + `lib.hm.dag.entriesAfter` to order generated groups of blocks. See {manpage}`ssh_config(5)`