nixos-rebuild-ng: allow overriding default ssh opts via environment (#538973)

This commit is contained in:
Thiago Kenji Okada
2026-07-08 15:32:16 +00:00
committed by GitHub
5 changed files with 74 additions and 4 deletions

View File

@@ -379,6 +379,11 @@ NIX_PATH
NIX_SSHOPTS
Additional options to be passed to ssh on the command line.
NIXOS_REBUILD_SSH_DEFAULT_OPTS
Replaces the built-in default ssh options (connection sharing via a
private _ControlMaster_ that is closed on exit). If empty, no
default options are added.
NIX_SUDOOPTS
Additional options to be passed to sudo on the command line.

View File

@@ -26,7 +26,7 @@ from .models import (
Profile,
Remote,
)
from .process import SSH_DEFAULT_OPTS, run_wrapper
from .process import run_wrapper, ssh_default_opts
from .utils import Args, dict_to_flags
FLAKE_FLAGS: Final = ["--extra-experimental-features", "nix-command flakes"]
@@ -193,7 +193,7 @@ def copy_closure(
Also supports copying a closure from a remote to another remote."""
sshopts = os.getenv("NIX_SSHOPTS", "")
env = {"NIX_SSHOPTS": " ".join(filter(lambda x: x, [sshopts, *SSH_DEFAULT_OPTS]))}
env = {"NIX_SSHOPTS": " ".join(filter(lambda x: x, [sshopts, *ssh_default_opts()]))}
def nix_copy_closure(host: Remote, to: bool) -> None:
run_wrapper(

View File

@@ -31,6 +31,14 @@ SSH_DEFAULT_OPTS: Final = [
]
def ssh_default_opts() -> list[str]:
"Default ssh options appended after NIX_SSHOPTS."
env = os.getenv("NIXOS_REBUILD_SSH_DEFAULT_OPTS")
if env is None:
return SSH_DEFAULT_OPTS
return shlex.split(env)
@dataclass(frozen=True)
class Remote:
host: str
@@ -133,7 +141,7 @@ def run_wrapper(
ssh_args: list[Arg] = [
"ssh",
*remote.opts,
*SSH_DEFAULT_OPTS,
*ssh_default_opts(),
remote.ssh_host(),
"--",
*[_quote_remote_arg(a) for a in remote_run_args],
@@ -282,7 +290,7 @@ def _kill_long_running_ssh_process(args: Args, remote: Remote) -> None:
[
"ssh",
*remote.opts,
*SSH_DEFAULT_OPTS,
*ssh_default_opts(),
remote.ssh_host(),
"--",
"pkill",

View File

@@ -275,6 +275,17 @@ def test_copy_closure(monkeypatch: MonkeyPatch) -> None:
},
)
# NIXOS_REBUILD_SSH_DEFAULT_OPTS replaces the ControlMaster defaults
monkeypatch.setenv("NIX_SSHOPTS", "-oControlPath=/run/user/1000/%C")
monkeypatch.setenv("NIXOS_REBUILD_SSH_DEFAULT_OPTS", "")
with patch(get_qualified_name(n.run_wrapper, n), autospec=True) as mock_run:
n.copy_closure(closure, target_host)
mock_run.assert_called_with(
["nix-copy-closure", "--to", "user@target.host", closure],
append_local_env={"NIX_SSHOPTS": "-oControlPath=/run/user/1000/%C"},
)
monkeypatch.delenv("NIXOS_REBUILD_SSH_DEFAULT_OPTS")
monkeypatch.setenv("NIX_SSHOPTS", "--ssh build-target-opt")
env = {"NIX_SSHOPTS": " ".join(["--ssh build-target-opt", *p.SSH_DEFAULT_OPTS])}
with patch(get_qualified_name(n.run_wrapper, n), autospec=True) as mock_run:

View File

@@ -234,6 +234,52 @@ def test_remote_from_name(monkeypatch: MonkeyPatch) -> None:
)
def test_ssh_default_opts(monkeypatch: MonkeyPatch) -> None:
monkeypatch.delenv("NIXOS_REBUILD_SSH_DEFAULT_OPTS", raising=False)
assert p.ssh_default_opts() == p.SSH_DEFAULT_OPTS
monkeypatch.setenv(
"NIXOS_REBUILD_SSH_DEFAULT_OPTS", "-o ControlPath=/run/user/1000/%C"
)
assert p.ssh_default_opts() == ["-o", "ControlPath=/run/user/1000/%C"]
monkeypatch.setenv("NIXOS_REBUILD_SSH_DEFAULT_OPTS", "")
assert p.ssh_default_opts() == []
@patch.dict(
p.os.environ,
{"PATH": "/path/to/bin", "NIXOS_REBUILD_SSH_DEFAULT_OPTS": ""},
clear=True,
)
@patch("subprocess.run", autospec=True)
def test_run_wrapper_ssh_default_opts_override(mock_run: Any) -> None:
p.run_wrapper(
["test"],
check=True,
remote=m.Remote("user@localhost", ["-p", "2222"], "ssh"),
)
mock_run.assert_called_with(
[
"ssh",
"-p",
"2222",
"user@localhost",
"--",
"/bin/sh",
"-c",
"""'exec /usr/bin/env -i PATH="${PATH-}" "$@"'""",
"sh",
"test",
],
check=True,
text=True,
errors="surrogateescape",
env=None,
input=None,
)
def test_ssh_host() -> None:
ssh_remotes = {
"user@[fe80::1%25eth0]": "user@fe80::1%eth0",