nixos-rebuild-ng: implement _NIXOS_REBUILD_REEXEC

This commit is contained in:
Thiago Kenji Okada
2024-11-30 17:54:31 +00:00
parent 3cadcd1653
commit 3fd384af80
3 changed files with 35 additions and 22 deletions
+1 -9
View File
@@ -91,14 +91,6 @@ ruff format .
conflicting with the current `nixos-rebuild`. This means you can keep both in
your system at the same time, but it also means that a few things like bash
completion are broken right now (since it looks at `nixos-rebuild` binary)
- `_NIXOS_REBUILD_EXEC` is **not** implemented yet, so different from
`nixos-rebuild`, this will use the current version of `nixos-rebuild-ng` in
your `PATH` to build/set profile/switch, while `nixos-rebuild` builds the new
version (the one that will be switched) and re-exec to it instead. This means
that in case of bugs in `nixos-rebuild-ng`, the only way that you will get
them fixed is **after** you switch to a new version
- Ignore any performance advantages of the rewrite right now, because of the 2
caveats above
- Bugs in the profile manipulation can cause corruption of your profile that
may be difficult to fix, so right now I only recommend using
`nixos-rebuild-ng` if you are testing in a VM or in a filesystem with
@@ -111,7 +103,7 @@ ruff format .
- [x] Remote host/builders (via SSH)
- [x] Improve nix arguments handling (e.g.: `nixFlags` vs `copyFlags` in the
old `nixos-rebuild`)
- [ ] `_NIXOS_REBUILD_EXEC`
- [x] `_NIXOS_REBUILD_REEXEC`
- [ ] Port `nixos-rebuild.passthru.tests`
- [ ] Change module system to allow easier opt-in, like
`system.switch.enableNg` for `switch-to-configuration-ng`
@@ -2,6 +2,7 @@ import argparse
import atexit
import json
import logging
import os
import sys
from pathlib import Path
from subprocess import run
@@ -87,6 +88,7 @@ def get_parser() -> tuple[argparse.ArgumentParser, dict[str, argparse.ArgumentPa
main_parser.add_argument("--ask-sudo-password", action="store_true")
main_parser.add_argument("--use-remote-sudo", action="store_true") # deprecated
main_parser.add_argument("--no-ssh-tty", action="store_true") # deprecated
main_parser.add_argument("--fast", action="store_true")
main_parser.add_argument("--build-host")
main_parser.add_argument("--target-host")
main_parser.add_argument("action", choices=Action.values(), nargs="?")
@@ -174,10 +176,6 @@ def execute(argv: list[str]) -> None:
target_host = Remote.from_arg(args.target_host, args.ask_sudo_password, tmpdir_path)
build_attr = BuildAttr.from_arg(args.attr, args.file)
flake = Flake.from_arg(args.flake, target_host)
if args.upgrade or args.upgrade_all:
nix.upgrade_channels(bool(args.upgrade_all))
action = Action(args.action)
# Only run shell scripts from the Nixpkgs tree if the action is
# "switch", "boot", or "test". With other actions (such as "build"),
@@ -185,6 +183,30 @@ def execute(argv: list[str]) -> None:
# executed, so it's safe to run nixos-rebuild against a potentially
# untrusted tree.
can_run = action in (Action.SWITCH, Action.BOOT, Action.TEST)
# Re-exec to a newer version of the script before building to ensure we get
# the latest fixes
if can_run and not args.fast and not os.environ.get("_NIXOS_REBUILD_REEXEC"):
if flake:
drv = nix.build_flake("pkgs.nixos-rebuild-ng", flake, **flake_build_flags)
else:
drv = nix.build("pkgs.nixos-rebuild-ng", build_attr, **build_flags)
new = drv / "bin/nixos-rebuild-ng"
current = Path(argv[0])
# Disable re-exec during development
if current.name != "__main__.py" and new != current:
logging.debug(
"detected newer version of script, re-exec'ing, current=%s, new=%s",
argv[0],
new,
)
cleanup_ssh(tmpdir_path)
tmpdir.cleanup()
os.execve(new, argv, os.environ | {"_NIXOS_REBUILD_REEXEC": "1"})
if args.upgrade or args.upgrade_all:
nix.upgrade_channels(bool(args.upgrade_all))
if can_run and not flake:
nixpkgs_path = nix.find_file("nixpkgs", **build_flags)
rev = nix.get_nixpkgs_rev(nixpkgs_path)
@@ -95,7 +95,7 @@ def test_execute_nix_boot(mock_run: Any, tmp_path: Path) -> None:
CompletedProcess([], 0),
]
nr.execute(["nixos-rebuild", "boot", "--no-flake", "-vvv"])
nr.execute(["nixos-rebuild", "boot", "--no-flake", "-vvv", "--fast"])
assert mock_run.call_count == 6
mock_run.assert_has_calls(
@@ -173,6 +173,7 @@ def test_execute_nix_switch_flake(mock_run: Any, tmp_path: Path) -> None:
"--install-bootloader",
"--sudo",
"--verbose",
"--fast",
]
)
@@ -246,6 +247,7 @@ def test_execute_nix_switch_flake_target_host(
"--use-remote-sudo",
"--target-host",
"user@localhost",
"--fast",
]
)
@@ -339,6 +341,7 @@ def test_execute_nix_switch_flake_build_host(
"/path/to/config#hostname",
"--build-host",
"user@localhost",
"--fast",
]
)
@@ -415,7 +418,9 @@ def test_execute_switch_rollback(mock_run: Any, tmp_path: Path) -> None:
nixpkgs_path = tmp_path / "nixpkgs"
nixpkgs_path.touch()
nr.execute(["nixos-rebuild", "switch", "--rollback", "--install-bootloader"])
nr.execute(
["nixos-rebuild", "switch", "--rollback", "--install-bootloader", "--fast"]
)
assert mock_run.call_count >= 2
# ignoring update_nixpkgs_rev calls
@@ -467,13 +472,7 @@ def test_execute_test_rollback(
]
nr.execute(
[
"nixos-rebuild",
"test",
"--rollback",
"--profile-name",
"foo",
]
["nixos-rebuild", "test", "--rollback", "--profile-name", "foo", "--fast"]
)
assert mock_run.call_count == 2