mirror of
https://github.com/NixOS/nixpkgs.git
synced 2026-10-02 21:10:08 +00:00
nixos-rebuild-ng: detect systemd-run version
`systemd-run --output=cat` is only supported from version 261 and onwards. NixOS 26.05 (current stable) ships with systemd 260, so it means using `systemd-run --output=cat` for anyone upgrading from the current stable for the next stable will result in switch failures. For now let's add a version check for `systemd-run` and only use `--output=cat` if the version is newer than 261. The code can be removed after release-27.05.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import textwrap
|
||||
import uuid
|
||||
@@ -47,9 +48,6 @@ SWITCH_TO_CONFIGURATION_CMD_PREFIX: Final = [
|
||||
"NIXOS_NO_CHECK",
|
||||
"--collect",
|
||||
"--no-ask-password",
|
||||
"--wait",
|
||||
"--verbose",
|
||||
"--output=cat",
|
||||
"--quiet",
|
||||
"--service-type=exec",
|
||||
"--unit=nixos-rebuild-switch-to-configuration",
|
||||
@@ -734,6 +732,10 @@ def switch_to_configuration(
|
||||
cmd = []
|
||||
elif os.environ.get("NIXOS_REBUILD_NO_SYSTEMD_RUN"):
|
||||
cmd = []
|
||||
elif _systemd_run_supports_output_cat(target_host):
|
||||
cmd = [*cmd, "--wait", "--verbose", "--output=cat"]
|
||||
else:
|
||||
cmd = [*cmd, "--pipe"]
|
||||
|
||||
run_wrapper(
|
||||
[*cmd, path_to_config / "bin/switch-to-configuration", str(action)],
|
||||
@@ -753,6 +755,23 @@ def switch_to_configuration(
|
||||
)
|
||||
|
||||
|
||||
# TODO: remove this after release-27.05 and assume systemd 261+
|
||||
def _systemd_run_supports_output_cat(target_host: Remote | None) -> bool:
|
||||
"""Check whether the target's systemd-run supports --output=cat (systemd 261+)."""
|
||||
try:
|
||||
result = run_wrapper(
|
||||
["systemd-run", "--version"],
|
||||
remote=target_host,
|
||||
capture_output=True,
|
||||
)
|
||||
except CalledProcessError:
|
||||
logger.debug("systemd-run version detection failed, assuming <261")
|
||||
return False
|
||||
|
||||
match = re.search(r"^systemd (\d+)(?:\D|$)", result.stdout, re.MULTILINE)
|
||||
return match is not None and int(match.group(1)) >= 261
|
||||
|
||||
|
||||
def upgrade_channels(
|
||||
all_channels: bool = False,
|
||||
elevate: Elevator = NO_ELEVATOR,
|
||||
|
||||
@@ -227,6 +227,8 @@ def test_execute_nix_boot(mock_run: Mock, tmp_path: Path) -> None:
|
||||
return CompletedProcess([], 0, "nixpkgs-rev")
|
||||
elif args[0] == "nix-build":
|
||||
return CompletedProcess([], 0, str(config_path))
|
||||
elif "systemd-run" in args and "--version" in args:
|
||||
return CompletedProcess([], 0, "systemd 261 (261.2)")
|
||||
else:
|
||||
return CompletedProcess([], 0)
|
||||
|
||||
@@ -234,7 +236,7 @@ def test_execute_nix_boot(mock_run: Mock, tmp_path: Path) -> None:
|
||||
|
||||
nr.execute(["nixos-rebuild", "boot", "--no-flake", "-vvv", "--no-reexec"])
|
||||
|
||||
assert mock_run.call_count == 8
|
||||
assert mock_run.call_count == 9
|
||||
mock_run.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
@@ -289,9 +291,18 @@ def test_execute_nix_boot(mock_run: Mock, tmp_path: Path) -> None:
|
||||
check=False,
|
||||
**DEFAULT_RUN_KWARGS,
|
||||
),
|
||||
call(
|
||||
["systemd-run", "--version"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
**DEFAULT_RUN_KWARGS,
|
||||
),
|
||||
call(
|
||||
[
|
||||
*nr.nix.SWITCH_TO_CONFIGURATION_CMD_PREFIX,
|
||||
"--wait",
|
||||
"--verbose",
|
||||
"--output=cat",
|
||||
config_path / "bin/switch-to-configuration",
|
||||
"boot",
|
||||
],
|
||||
@@ -571,6 +582,8 @@ def test_execute_nix_switch_flake(mock_run: Mock, tmp_path: Path) -> None:
|
||||
return CompletedProcess([], 0, str(config_path))
|
||||
elif args[0] == "nix-instantiate":
|
||||
return CompletedProcess([], 1)
|
||||
elif "systemd-run" in args and "--version" in args:
|
||||
return CompletedProcess([], 0, "systemd 258 (258.2)")
|
||||
else:
|
||||
return CompletedProcess([], 0)
|
||||
|
||||
@@ -593,7 +606,7 @@ def test_execute_nix_switch_flake(mock_run: Mock, tmp_path: Path) -> None:
|
||||
]
|
||||
)
|
||||
|
||||
assert mock_run.call_count == 5
|
||||
assert mock_run.call_count == 6
|
||||
mock_run.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
@@ -637,6 +650,12 @@ def test_execute_nix_switch_flake(mock_run: Mock, tmp_path: Path) -> None:
|
||||
check=False,
|
||||
**DEFAULT_RUN_KWARGS,
|
||||
),
|
||||
call(
|
||||
["systemd-run", "--version"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
**DEFAULT_RUN_KWARGS,
|
||||
),
|
||||
call(
|
||||
[
|
||||
"sudo",
|
||||
@@ -644,6 +663,7 @@ def test_execute_nix_switch_flake(mock_run: Mock, tmp_path: Path) -> None:
|
||||
"-i",
|
||||
"NIXOS_INSTALL_BOOTLOADER=1",
|
||||
*nr.nix.SWITCH_TO_CONFIGURATION_CMD_PREFIX,
|
||||
"--pipe",
|
||||
config_path / "bin/switch-to-configuration",
|
||||
"switch",
|
||||
],
|
||||
@@ -685,6 +705,8 @@ def test_execute_nix_switch_build_target_host_custom_profile(
|
||||
return CompletedProcess([], 0, "/tmp/tmpdir")
|
||||
elif args[0] == "ssh" and "readlink" in args:
|
||||
return CompletedProcess([], 0, str(config_path))
|
||||
elif "systemd-run" in args and "--version" in args:
|
||||
return CompletedProcess([], 0, "systemd 258 (258.2)")
|
||||
else:
|
||||
return CompletedProcess([], 0)
|
||||
|
||||
@@ -712,7 +734,7 @@ def test_execute_nix_switch_build_target_host_custom_profile(
|
||||
]
|
||||
)
|
||||
|
||||
assert mock_run.call_count == 13
|
||||
assert mock_run.call_count == 14
|
||||
mock_run.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
@@ -901,6 +923,23 @@ def test_execute_nix_switch_build_target_host_custom_profile(
|
||||
check=False,
|
||||
**DEFAULT_RUN_KWARGS,
|
||||
),
|
||||
call(
|
||||
[
|
||||
"ssh",
|
||||
*nr.process.SSH_DEFAULT_OPTS,
|
||||
"user@target-host",
|
||||
"--",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"""'exec /usr/bin/env -i PATH="${PATH-}" "$@"'""",
|
||||
"sh",
|
||||
"systemd-run",
|
||||
"--version",
|
||||
],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
**DEFAULT_RUN_KWARGS,
|
||||
),
|
||||
call(
|
||||
[
|
||||
"ssh",
|
||||
@@ -913,6 +952,7 @@ def test_execute_nix_switch_build_target_host_custom_profile(
|
||||
"""'exec /usr/bin/env -i PATH="${PATH-}" LOCALE_ARCHIVE="${LOCALE_ARCHIVE-}" NIXOS_NO_CHECK="${NIXOS_NO_CHECK-}" NIXOS_INSTALL_BOOTLOADER=0 "$@"'""",
|
||||
"sh",
|
||||
*nr.nix.SWITCH_TO_CONFIGURATION_CMD_PREFIX,
|
||||
"--pipe",
|
||||
str(config_path / "bin/switch-to-configuration"),
|
||||
"switch",
|
||||
],
|
||||
@@ -944,6 +984,8 @@ def test_execute_nix_switch_flake_target_host(
|
||||
return CompletedProcess([], 0, str(config_path))
|
||||
elif args[0] == "nix-instantiate":
|
||||
return CompletedProcess([], 1)
|
||||
elif "systemd-run" in args and "--version" in args:
|
||||
return CompletedProcess([], 0, "systemd 258 (258.2)")
|
||||
else:
|
||||
return CompletedProcess([], 0)
|
||||
|
||||
@@ -962,7 +1004,7 @@ def test_execute_nix_switch_flake_target_host(
|
||||
]
|
||||
)
|
||||
|
||||
assert mock_run.call_count == 6
|
||||
assert mock_run.call_count == 7
|
||||
mock_run.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
@@ -1027,6 +1069,23 @@ def test_execute_nix_switch_flake_target_host(
|
||||
check=False,
|
||||
**DEFAULT_RUN_KWARGS,
|
||||
),
|
||||
call(
|
||||
[
|
||||
"ssh",
|
||||
*nr.process.SSH_DEFAULT_OPTS,
|
||||
"user@localhost",
|
||||
"--",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"""'exec /usr/bin/env -i PATH="${PATH-}" "$@"'""",
|
||||
"sh",
|
||||
"systemd-run",
|
||||
"--version",
|
||||
],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
**DEFAULT_RUN_KWARGS,
|
||||
),
|
||||
call(
|
||||
[
|
||||
"ssh",
|
||||
@@ -1039,6 +1098,7 @@ def test_execute_nix_switch_flake_target_host(
|
||||
"""'exec /usr/bin/env -i PATH="${PATH-}" LOCALE_ARCHIVE="${LOCALE_ARCHIVE-}" NIXOS_NO_CHECK="${NIXOS_NO_CHECK-}" NIXOS_INSTALL_BOOTLOADER=0 "$@"'""",
|
||||
"sh",
|
||||
*nr.nix.SWITCH_TO_CONFIGURATION_CMD_PREFIX,
|
||||
"--pipe",
|
||||
str(config_path / "bin/switch-to-configuration"),
|
||||
"switch",
|
||||
],
|
||||
@@ -1072,6 +1132,8 @@ def test_execute_nix_switch_flake_build_host(
|
||||
return CompletedProcess([], 0, str(config_path))
|
||||
elif args[0] == "nix-instantiate":
|
||||
return CompletedProcess([], 1)
|
||||
elif "systemd-run" in args and "--version" in args:
|
||||
return CompletedProcess([], 0, "systemd 258 (258.2)")
|
||||
else:
|
||||
return CompletedProcess([], 0)
|
||||
|
||||
@@ -1089,7 +1151,7 @@ def test_execute_nix_switch_flake_build_host(
|
||||
]
|
||||
)
|
||||
|
||||
assert mock_run.call_count == 8
|
||||
assert mock_run.call_count == 9
|
||||
mock_run.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
@@ -1164,9 +1226,16 @@ def test_execute_nix_switch_flake_build_host(
|
||||
check=False,
|
||||
**DEFAULT_RUN_KWARGS,
|
||||
),
|
||||
call(
|
||||
["systemd-run", "--version"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
**DEFAULT_RUN_KWARGS,
|
||||
),
|
||||
call(
|
||||
[
|
||||
*nr.nix.SWITCH_TO_CONFIGURATION_CMD_PREFIX,
|
||||
"--pipe",
|
||||
config_path / "bin/switch-to-configuration",
|
||||
"switch",
|
||||
],
|
||||
@@ -1527,7 +1596,7 @@ def test_execute_switch_store_path(mock_run: Mock, tmp_path: Path) -> None:
|
||||
)
|
||||
|
||||
# --store-path skips build and write_version_suffix, so only activation calls
|
||||
assert mock_run.call_count == 4
|
||||
assert mock_run.call_count == 5
|
||||
mock_run.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
@@ -1552,9 +1621,16 @@ def test_execute_switch_store_path(mock_run: Mock, tmp_path: Path) -> None:
|
||||
check=False,
|
||||
**DEFAULT_RUN_KWARGS,
|
||||
),
|
||||
call(
|
||||
["systemd-run", "--version"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
**DEFAULT_RUN_KWARGS,
|
||||
),
|
||||
call(
|
||||
[
|
||||
*nr.nix.SWITCH_TO_CONFIGURATION_CMD_PREFIX,
|
||||
"--pipe",
|
||||
config_path / "bin/switch-to-configuration",
|
||||
"switch",
|
||||
],
|
||||
@@ -1600,9 +1676,15 @@ def test_execute_switch_store_path_target_host(
|
||||
)
|
||||
|
||||
# --store-path skips build and write_version_suffix, so only copy/activation calls
|
||||
assert mock_run.call_count == 6
|
||||
assert mock_run.call_count == 7
|
||||
mock_run.assert_has_calls(
|
||||
[
|
||||
call(
|
||||
["nix-instantiate", "--find-file", "nixos-system"],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
**DEFAULT_RUN_KWARGS,
|
||||
),
|
||||
call(
|
||||
["nix-copy-closure", "--to", "user@remote-host", config_path],
|
||||
check=True,
|
||||
@@ -1662,6 +1744,23 @@ def test_execute_switch_store_path_target_host(
|
||||
check=False,
|
||||
**DEFAULT_RUN_KWARGS,
|
||||
),
|
||||
call(
|
||||
[
|
||||
"ssh",
|
||||
*nr.process.SSH_DEFAULT_OPTS,
|
||||
"user@remote-host",
|
||||
"--",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"""'exec /usr/bin/env -i PATH="${PATH-}" "$@"'""",
|
||||
"sh",
|
||||
"systemd-run",
|
||||
"--version",
|
||||
],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
**DEFAULT_RUN_KWARGS,
|
||||
),
|
||||
call(
|
||||
[
|
||||
"ssh",
|
||||
@@ -1674,6 +1773,7 @@ def test_execute_switch_store_path_target_host(
|
||||
"""'exec /usr/bin/env -i PATH="${PATH-}" LOCALE_ARCHIVE="${LOCALE_ARCHIVE-}" NIXOS_NO_CHECK="${NIXOS_NO_CHECK-}" NIXOS_INSTALL_BOOTLOADER=0 "$@"'""",
|
||||
"sh",
|
||||
*nr.nix.SWITCH_TO_CONFIGURATION_CMD_PREFIX,
|
||||
"--pipe",
|
||||
str(config_path / "bin/switch-to-configuration"),
|
||||
"switch",
|
||||
],
|
||||
|
||||
@@ -957,9 +957,16 @@ def test_switch_to_configuration_without_systemd_run_env_var(
|
||||
)
|
||||
|
||||
|
||||
@patch(
|
||||
get_qualified_name(n._systemd_run_supports_output_cat, n),
|
||||
autospec=True,
|
||||
return_value=True,
|
||||
)
|
||||
@patch(get_qualified_name(n.run_wrapper, n), autospec=True)
|
||||
def test_switch_to_configuration_with_systemd_run(
|
||||
mock_run: Mock, monkeypatch: MonkeyPatch
|
||||
mock_run: Mock,
|
||||
mock_supports_output_cat: Mock,
|
||||
monkeypatch: MonkeyPatch,
|
||||
) -> None:
|
||||
profile_path = Path("/path/to/profile")
|
||||
config_path = Path("/path/to/config")
|
||||
@@ -979,6 +986,9 @@ def test_switch_to_configuration_with_systemd_run(
|
||||
mock_run.assert_called_with(
|
||||
[
|
||||
*n.SWITCH_TO_CONFIGURATION_CMD_PREFIX,
|
||||
"--wait",
|
||||
"--verbose",
|
||||
"--output=cat",
|
||||
profile_path / "bin/switch-to-configuration",
|
||||
"switch",
|
||||
],
|
||||
@@ -992,6 +1002,7 @@ def test_switch_to_configuration_with_systemd_run(
|
||||
stdout=sys.stderr,
|
||||
)
|
||||
|
||||
mock_supports_output_cat.return_value = False
|
||||
target_host = m.Remote("user@localhost", [], "ssh")
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setenv("LOCALE_ARCHIVE", "/path/to/locale")
|
||||
@@ -1009,6 +1020,7 @@ def test_switch_to_configuration_with_systemd_run(
|
||||
mock_run.assert_called_with(
|
||||
[
|
||||
*n.SWITCH_TO_CONFIGURATION_CMD_PREFIX,
|
||||
"--pipe",
|
||||
config_path / "specialisation/special/bin/switch-to-configuration",
|
||||
"test",
|
||||
],
|
||||
@@ -1023,6 +1035,33 @@ def test_switch_to_configuration_with_systemd_run(
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("version_output", "expected"),
|
||||
[
|
||||
("systemd 260 (260.1)\n", False),
|
||||
(
|
||||
textwrap.dedent("""\
|
||||
systemd 261 (261.2)
|
||||
+PAM +AUDIT -SELINUX +APPARMOR +IMA +IPE +SMACK +SECCOMP +GCRYPT -GNUTLS +OPENSSL +ACL +BLKID +CURL +ELFUTILS +FIDO2 +IDN2 +KMOD +LIBCRYPTSETUP +LIBCRYPTSETUP_PLUGINS +LIBFDISK +PCRE2 +PWQUALITY +P11KIT +QRENCODE +TPM2 +BZIP2 +LZ4 +XZ +ZLIB +ZSTD +BPF_FRAMEWORK -BTF -XKBCOMMON +UTMP +LIBARCHIVE
|
||||
"""),
|
||||
True,
|
||||
),
|
||||
("systemd 270 (270.2)\n", True),
|
||||
("unexpected output\n", False),
|
||||
],
|
||||
)
|
||||
@patch(get_qualified_name(n.run_wrapper, n), autospec=True)
|
||||
def test_systemd_run_supports_output_cat(
|
||||
mock_run: Mock, version_output: str, expected: bool
|
||||
) -> None:
|
||||
mock_run.return_value = CompletedProcess([], 0, stdout=version_output)
|
||||
|
||||
assert n._systemd_run_supports_output_cat(None) is expected
|
||||
mock_run.assert_called_once_with(
|
||||
["systemd-run", "--version"], remote=None, capture_output=True
|
||||
)
|
||||
|
||||
|
||||
@patch("os.geteuid", autospec=True, return_value=1000)
|
||||
@patch(get_qualified_name(n.run_wrapper, n), autospec=True)
|
||||
def test_upgrade_channels(mock_run: Mock, mock_geteuid: Mock, tmpdir: Path) -> None:
|
||||
|
||||
Reference in New Issue
Block a user