Is it possible to create a copy of a systemd service with minor modifications?

I would like to run a separate instance of the sshd service on my machine. Why I want to do that is at the bottom of the post.

My first attempt was to naively make a copy of the config.systemd.services.sshd and try to combine/union it with the values I wanted overriden. This didn’t work and ended up with errors around infinite recursion during evaluation. I think I tracked that down to the systemd-lib.nix and how it’s evaluating that value, and I think I see how it ends up with an infinite recursion there.

Code I used in this attempt is below.

systemd.services.alt-sshd = let
  oldSSHCOnfig = config.systemd.services.sshd;
in
  oldSSHCOnfig
  // {
    after = ["sshd.service"];
    # after = oldSSHCOnfig ++ ["sshd.service"];
    preStart = "";
    serviceConfig.ExecStart =
      "${pkgs.openssh}/bin/sshd "
      + "-D "
      + # don't detach into a daemon process
      "-f /etc/ssh/sshd_config"
      + "-p 4044";
  };

Is there a way to do this than copying the sshd nix module into my package and disabling the built in one? I found this reddit post that seems to be trying to do the same thing, but that seems to pretty complex. I’m unsure what if any issues are involved with what appears to be a second evaluation of the nixosSystem described in that post.

I need to copy the sshd nix module into my package and disable the built in one?

Why I want to do this

The impetus behind this is to have a the main sshd service bind to port 22, while my alt-sshd service binds to another port.

I’m aware that it’s possible to have sshd listen on multiple ports, but openssh doesn’t log the port a connection came in on, which prevents me from comparing statistics for connections across the different ports, or use different sshguard policies based on the port that’s being connected to.

oldSSHConfig // { /* foo bar */ } probably isn’t the right way to go about it. Probably mkMerge would be better. i.e.

systemd.services.alt-sshd = lib.mkMerge [
  config.systemd.services.sshd

  {
    serviceConfig.ExecStart = lib.mkForce ....;
  }
];

Regardless, I haven’t scoured the documentation myself, but I’d be very surprised if there is in fact no way to tell which port someone logged in through with a single sshd instance using multiple ports.

Using lib.mkMerge doesn’t seem to work, since i end up with running into the infinite recursion problem.

# alt-ssh.nix
{
  config,
  lib,
  pkgs,
  ...
}: let
  inherit (lib) mkOption types;
in {
  options = {
  };

  config = {
    systemd.services.alt-sshd = let
      oldSSHCOnfig = config.systemd.services.sshd;
    in
      lib.mkMerge [
        oldSSHCOnfig
        {
          serviceConfig.ExecStart =
            lib.mkForce "${pkgs.openssh}/bin/sshd "
            + "-D "
            "-f /etc/ssh/sshd_config"
            + "-p 4044";
        }
      ];
  };
}

As far as the SSH service, I looked at the openssh portable source, and the log message only shows the source port, not the destination port. This is the code that convinced me that I couldn’t get the port that was connected to from the logs.

I was also very surprised, but now I’m trying to find way to run a variant of a service to get around this.

I attempted to do this by simply copying the individual attributes, and that mostly worked. However one strange thing happened that I’m not able to understand.

Setting the path of the systemd service attrset to the value in config.systemd.services.sshd.path resulted in a different value being materialized in the final configuration. The nix file below might help explain what confused me…

{
  config,
  lib,
  pkgs,
  ...
}: let
  cfg = config.services.openssh;
  cfgc = config.programs.ssh;
  baseSSHConfig = config.systemd.services.sshd;
in {
  options = {
  };

  config = {
    systemd = let
      service = {
        description = "Additional SSH Daemon";
        after = ["ssh.service"];
        stopIfChanged = false;
        # This works
        path = [cfgc.package pkgs.gawk];
        # This does not
        # path = baseSSHConfig.path;
        environment.LD_LIBRARY_PATH = baseSSHConfig.environment.LD_LIBRARY_PATH;

        restartTriggers = baseSSHConfig.restartTriggers;

        serviceConfig =
          {
            ExecStart = baseSSHConfig.serviceConfig.ExecStart + " -p 4044";
            KillMode = baseSSHConfig.serviceConfig.KillMode;
          }
          // (
            if cfg.startWhenNeeded
            then {
              StandardInput = baseSSHConfig.serviceConfig.StandardInput;
              StandardError = baseSSHConfig.serviceConfig.StandardError;
            }
            else {
              Restart = baseSSHConfig.serviceConfig.Restart;
              Type = baseSSHConfig.serviceConfig.Type;
            }
          );
      };
    in {
      services.alt-sshd = service;
    };
  };
}

When path = baseSSHConfig.path was un-commented instead of the path variable used there, the generated environment file for alt-sshd.service had the path value

Environment="PATH=/nix/store/1s5f2qcr8dp0mxmh8k26cbi1xgcqp8xg-openssh-9.3p1/bin:/nix/store/an56bphjscw6pcj4yyjzi1z0l3lddkfx-gawk-5.2.2/bin:/nix/store/02dr9ymdqpkb75vf0v1z2l91z2q3izy9-coreutils-9.3/bin:/nix/store/92iyjlsh7c66x4mv2bqkj5mwn4059dmz-findutils-4.9.0/bin:/nix/store/7ws5zmh4llmml550x02pqpcyzv5j4m4c-gnugrep-3.11/bin:/nix/store/zdra3vjgn5sbbwhiwp5b2f0c2akkyl6h-gnused-4.9/bin:/nix/store/9adpz6y8s4mgkxfhc9rzy25r7f3wdyll-systemd-253.5/bin:/nix/store/02dr9ymdqpkb75vf0v1z2l91z2q3izy9-coreutils-9.3/bin:/

# Final two elements of PATH are
/nix/store/02dr9ymdqpkb75vf0v1z2l91z2q3izy9-coreutils-9.3/bin:/

Wheras the sshd.service file had the value

Environment="PATH=/nix/store/1s5f2qcr8dp0mxmh8k26cbi1xgcqp8xg-openssh-9.3p1/bin:/nix/store/an56bphjscw6pcj4yyjzi1z0l3lddkfx-gawk-5.2.2/bin:/nix/store/02dr9ymdqpkb75vf0v1z2l91z2q3izy9-coreutils-9.3/bin:/nix/store/92iyjlsh7c66x4mv2bqkj5mwn4059dmz-findutils-4.9.0/bin:/nix/store/7ws5zmh4llmml550x02pqpcyzv5j4m4c-gnugrep-3.11/bin:/nix/store/zdra3vjgn5sbbwhiwp5b2f0c2akkyl6h-gnused-4.9/bin:/nix/store/9adpz6y8s4mgkxfhc9rzy25r7f3wdyll-systemd-253.5/bin:/nix/store/1s5f2qcr8dp0mxmh8k26cbi1xgcqp8xg-openssh-9.3p1/sbin:

# Final element of PATH is
/nix/store/1s5f2qcr8dp0mxmh8k26cbi1xgcqp8xg-openssh-9.3p1/sbin

If anyone knows how that’s possible I’d love to learn why this happens.