From 6702b22b9805bc1879715d4111e3764cd4237aed Mon Sep 17 00:00:00 2001
From: George Macon <george.macon@gtri.gatech.edu>
Date: Fri, 10 Feb 2023 15:55:17 -0500
Subject: [PATCH] ssh: install an ssh client

Fixes #3667
---
 modules/misc/news.nix                                | 12 +++++++++++-
 modules/programs/ssh.nix                             |  4 ++++
 tests/modules/programs/ssh/default-config.nix        |  2 ++
 .../forwards-dynamic-bind-path-with-port-asserts.nix |  2 ++
 .../ssh/forwards-dynamic-valid-bind-no-asserts.nix   |  2 ++
 .../forwards-local-bind-path-with-port-asserts.nix   |  2 ++
 .../forwards-local-host-path-with-port-asserts.nix   |  2 ++
 .../forwards-remote-bind-path-with-port-asserts.nix  |  2 ++
 .../forwards-remote-host-path-with-port-asserts.nix  |  2 ++
 tests/modules/programs/ssh/includes.nix              |  2 ++
 tests/modules/programs/ssh/match-blocks-attrs.nix    |  2 ++
 .../programs/ssh/match-blocks-match-and-hosts.nix    |  2 ++
 12 files changed, 35 insertions(+), 1 deletion(-)

diff --git a/modules/misc/news.nix b/modules/misc/news.nix
index fa033a51..3ec2d590 100644
--- a/modules/misc/news.nix
+++ b/modules/misc/news.nix
@@ -103,7 +103,7 @@ in
       entries = mkOption {
         internal = true;
         type = types.listOf entryModule;
-        default = [];
+        default = [ ];
         description = "News entries.";
       };
     };
@@ -1023,6 +1023,16 @@ in
           A new module is available: 'programs.fuzzel'.
         '';
       }
+
+      {
+        time = "2023-05-13T14:34:21+00:00";
+        condition = config.programs.ssh.enable;
+        message = ''
+          The module 'programs.ssh' now installs an SSH client. The installed
+          client is controlled by the 'programs.ssh.package` option, which
+          defaults to 'pkgs.openssh'.
+        '';
+      }
     ];
   };
 }
diff --git a/modules/programs/ssh.nix b/modules/programs/ssh.nix
index ee1f0ef3..1ad627e0 100644
--- a/modules/programs/ssh.nix
+++ b/modules/programs/ssh.nix
@@ -361,6 +361,8 @@ in
   options.programs.ssh = {
     enable = mkEnableOption "SSH client configuration";
 
+    package = mkPackageOption pkgs "openssh" { };
+
     forwardAgent = mkOption {
       default = false;
       type = types.bool;
@@ -525,6 +527,8 @@ in
       }
     ];
 
+    home.packages = [ cfg.package ];
+
     home.file.".ssh/config".text =
       let
         sortedMatchBlocks = hm.dag.topoSort cfg.matchBlocks;
diff --git a/tests/modules/programs/ssh/default-config.nix b/tests/modules/programs/ssh/default-config.nix
index 6d7e5508..c059d021 100644
--- a/tests/modules/programs/ssh/default-config.nix
+++ b/tests/modules/programs/ssh/default-config.nix
@@ -6,6 +6,8 @@ with lib;
   config = {
     programs.ssh = { enable = true; };
 
+    test.stubs.openssh = { };
+
     home.file.assertions.text = builtins.toJSON
       (map (a: a.message) (filter (a: !a.assertion) config.assertions));
 
diff --git a/tests/modules/programs/ssh/forwards-dynamic-bind-path-with-port-asserts.nix b/tests/modules/programs/ssh/forwards-dynamic-bind-path-with-port-asserts.nix
index e841b5bc..1be55aef 100644
--- a/tests/modules/programs/ssh/forwards-dynamic-bind-path-with-port-asserts.nix
+++ b/tests/modules/programs/ssh/forwards-dynamic-bind-path-with-port-asserts.nix
@@ -17,6 +17,8 @@ with lib;
       };
     };
 
+    test.stubs.openssh = { };
+
     test.asserts.assertions.expected = [ "Forwarded paths cannot have ports." ];
   };
 }
diff --git a/tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts.nix b/tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts.nix
index d0c3a732..8a371402 100644
--- a/tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts.nix
+++ b/tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts.nix
@@ -27,6 +27,8 @@ with lib;
     home.file.result.text = builtins.toJSON
       (map (a: a.message) (filter (a: !a.assertion) config.assertions));
 
+    test.stubs.openssh = { };
+
     nmt.script = ''
       assertFileExists home-files/.ssh/config
       assertFileContent \
diff --git a/tests/modules/programs/ssh/forwards-local-bind-path-with-port-asserts.nix b/tests/modules/programs/ssh/forwards-local-bind-path-with-port-asserts.nix
index e7ac454e..2b03b56e 100644
--- a/tests/modules/programs/ssh/forwards-local-bind-path-with-port-asserts.nix
+++ b/tests/modules/programs/ssh/forwards-local-bind-path-with-port-asserts.nix
@@ -21,6 +21,8 @@ with lib;
       };
     };
 
+    test.stubs.openssh = { };
+
     test.asserts.assertions.expected = [ "Forwarded paths cannot have ports." ];
   };
 }
diff --git a/tests/modules/programs/ssh/forwards-local-host-path-with-port-asserts.nix b/tests/modules/programs/ssh/forwards-local-host-path-with-port-asserts.nix
index 890459c8..aa72e352 100644
--- a/tests/modules/programs/ssh/forwards-local-host-path-with-port-asserts.nix
+++ b/tests/modules/programs/ssh/forwards-local-host-path-with-port-asserts.nix
@@ -21,6 +21,8 @@ with lib;
       };
     };
 
+    test.stubs.openssh = { };
+
     test.asserts.assertions.expected = [ "Forwarded paths cannot have ports." ];
   };
 }
diff --git a/tests/modules/programs/ssh/forwards-remote-bind-path-with-port-asserts.nix b/tests/modules/programs/ssh/forwards-remote-bind-path-with-port-asserts.nix
index ece7d795..e4e83390 100644
--- a/tests/modules/programs/ssh/forwards-remote-bind-path-with-port-asserts.nix
+++ b/tests/modules/programs/ssh/forwards-remote-bind-path-with-port-asserts.nix
@@ -21,6 +21,8 @@ with lib;
       };
     };
 
+    test.stubs.openssh = { };
+
     test.asserts.assertions.expected = [ "Forwarded paths cannot have ports." ];
   };
 }
diff --git a/tests/modules/programs/ssh/forwards-remote-host-path-with-port-asserts.nix b/tests/modules/programs/ssh/forwards-remote-host-path-with-port-asserts.nix
index b1228f4e..e4332346 100644
--- a/tests/modules/programs/ssh/forwards-remote-host-path-with-port-asserts.nix
+++ b/tests/modules/programs/ssh/forwards-remote-host-path-with-port-asserts.nix
@@ -21,6 +21,8 @@ with lib;
       };
     };
 
+    test.stubs.openssh = { };
+
     test.asserts.assertions.expected = [ "Forwarded paths cannot have ports." ];
   };
 }
diff --git a/tests/modules/programs/ssh/includes.nix b/tests/modules/programs/ssh/includes.nix
index 12e2c6df..def9cf96 100644
--- a/tests/modules/programs/ssh/includes.nix
+++ b/tests/modules/programs/ssh/includes.nix
@@ -7,6 +7,8 @@
       includes = [ "config.d/*" "other/dir" ];
     };
 
+    test.stubs.openssh = { };
+
     nmt.script = ''
       assertFileExists home-files/.ssh/config
       assertFileContains home-files/.ssh/config "Include config.d/* other/dir"
diff --git a/tests/modules/programs/ssh/match-blocks-attrs.nix b/tests/modules/programs/ssh/match-blocks-attrs.nix
index d8584e3a..1b32943a 100644
--- a/tests/modules/programs/ssh/match-blocks-attrs.nix
+++ b/tests/modules/programs/ssh/match-blocks-attrs.nix
@@ -51,6 +51,8 @@ with lib;
     home.file.assertions.text = builtins.toJSON
       (map (a: a.message) (filter (a: !a.assertion) config.assertions));
 
+    test.stubs.openssh = { };
+
     nmt.script = ''
       assertFileExists home-files/.ssh/config
       assertFileContent \
diff --git a/tests/modules/programs/ssh/match-blocks-match-and-hosts.nix b/tests/modules/programs/ssh/match-blocks-match-and-hosts.nix
index aa1e40d0..72ae72ea 100644
--- a/tests/modules/programs/ssh/match-blocks-match-and-hosts.nix
+++ b/tests/modules/programs/ssh/match-blocks-match-and-hosts.nix
@@ -21,6 +21,8 @@ with lib;
     home.file.assertions.text = builtins.toJSON
       (map (a: a.message) (filter (a: !a.assertion) config.assertions));
 
+    test.stubs.openssh = { };
+
     nmt.script = ''
       assertFileExists home-files/.ssh/config
       assertFileContent \