···5import sys
6from pathlib import Path
7from subprocess import CalledProcessError, run
08from typing import Final, assert_never
910from . import nix, tmpdir
11from .constants import EXECUTABLE, WITH_NIX_2_18, WITH_REEXEC, WITH_SHELL_FILES
12-from .models import Action, BuildAttr, Flake, ImageVariants, NRError, Profile
13from .process import Remote, cleanup_ssh
14from .utils import Args, LogFormatter, tabulate
15···99 "--attr",
100 "-A",
101 help="Enable and build the NixOS system from nix file and use the "
102- + "specified attribute path from file specified by the --file option",
103 )
104 main_parser.add_argument(
105 "--flake",
···117 "--install-bootloader",
118 action="store_true",
119 help="Causes the boot loader to be (re)installed on the device specified "
120- + "by the relevant configuration options",
121 )
122 main_parser.add_argument(
123 "--install-grub",
···142 "--upgrade",
143 action="store_true",
144 help="Update the root user's channel named 'nixos' before rebuilding "
145- + "the system and channels which have a file named '.update-on-nixos-rebuild'",
146 )
147 main_parser.add_argument(
148 "--upgrade-all",
···186 main_parser.add_argument(
187 "--image-variant",
188 help="Selects an image variant to build from the "
189- + "config.system.build.images attribute of the given configuration",
190 )
191 main_parser.add_argument("action", choices=Action.values(), nargs="?")
192···321 # - Exec format error (e.g.: another OS/CPU arch)
322 logger.warning(
323 "could not re-exec in a newer version of nixos-rebuild, "
324- + "using current version",
325 exc_info=logger.isEnabledFor(logging.DEBUG),
326 )
327 # We already run clean-up, let's re-exec in the current version
···329 os.execve(current, argv, os.environ | {"_NIXOS_REBUILD_REEXEC": "1"})
3303310000000000000000000000000000000332def execute(argv: list[str]) -> None:
333 args, args_groups = parse_args(argv)
334···393 no_link = action in (Action.SWITCH, Action.BOOT)
394 rollback = bool(args.rollback)
395396- def validate_image_variant(variants: ImageVariants) -> None:
397- if args.image_variant not in variants:
398- raise NRError(
399- "please specify one of the following "
400- + "supported image variants via --image-variant:\n"
401- + "\n".join(f"- {v}" for v in variants)
402- )
403-404 match action:
405 case Action.BUILD_IMAGE if flake:
406 variants = nix.get_build_image_variants_flake(
407 flake,
408 eval_flags=flake_common_flags,
409 )
410- validate_image_variant(variants)
411 attr = f"config.system.build.images.{args.image_variant}"
412 case Action.BUILD_IMAGE:
413 variants = nix.get_build_image_variants(
414 build_attr,
415 instantiate_flags=common_flags,
416 )
417- validate_image_variant(variants)
418 attr = f"config.system.build.images.{args.image_variant}"
419 case Action.BUILD_VM:
420 attr = "config.system.build.vm"
···435 if maybe_path_to_config: # kinda silly but this makes mypy happy
436 path_to_config = maybe_path_to_config
437 else:
438- raise NRError("could not find previous generation")
439 case (_, True, _, _):
440- raise NRError(f"--rollback is incompatible with '{action}'")
00441 case (_, False, Remote(_), Flake(_)):
442 path_to_config = nix.build_remote_flake(
443 attr,
···488 copy_flags=copy_flags,
489 )
490 if action in (Action.SWITCH, Action.BOOT):
0491 nix.set_profile(
492 profile,
493 path_to_config,
···5import sys
6from pathlib import Path
7from subprocess import CalledProcessError, run
8+from textwrap import dedent
9from typing import Final, assert_never
1011from . import nix, tmpdir
12from .constants import EXECUTABLE, WITH_NIX_2_18, WITH_REEXEC, WITH_SHELL_FILES
13+from .models import Action, BuildAttr, Flake, ImageVariants, NixOSRebuildError, Profile
14from .process import Remote, cleanup_ssh
15from .utils import Args, LogFormatter, tabulate
16···100 "--attr",
101 "-A",
102 help="Enable and build the NixOS system from nix file and use the "
103+ "specified attribute path from file specified by the --file option",
104 )
105 main_parser.add_argument(
106 "--flake",
···118 "--install-bootloader",
119 action="store_true",
120 help="Causes the boot loader to be (re)installed on the device specified "
121+ "by the relevant configuration options",
122 )
123 main_parser.add_argument(
124 "--install-grub",
···143 "--upgrade",
144 action="store_true",
145 help="Update the root user's channel named 'nixos' before rebuilding "
146+ "the system and channels which have a file named '.update-on-nixos-rebuild'",
147 )
148 main_parser.add_argument(
149 "--upgrade-all",
···187 main_parser.add_argument(
188 "--image-variant",
189 help="Selects an image variant to build from the "
190+ "config.system.build.images attribute of the given configuration",
191 )
192 main_parser.add_argument("action", choices=Action.values(), nargs="?")
193···322 # - Exec format error (e.g.: another OS/CPU arch)
323 logger.warning(
324 "could not re-exec in a newer version of nixos-rebuild, "
325+ "using current version",
326 exc_info=logger.isEnabledFor(logging.DEBUG),
327 )
328 # We already run clean-up, let's re-exec in the current version
···330 os.execve(current, argv, os.environ | {"_NIXOS_REBUILD_REEXEC": "1"})
331332333+def validate_image_variant(image_variant: str, variants: ImageVariants) -> None:
334+ if image_variant not in variants:
335+ raise NixOSRebuildError(
336+ "please specify one of the following supported image variants via "
337+ "--image-variant:\n" + "\n".join(f"- {v}" for v in variants)
338+ )
339+340+341+def validate_nixos_config(path_to_config: Path) -> None:
342+ if not (path_to_config / "nixos-version").exists() and not os.environ.get(
343+ "NIXOS_REBUILD_I_UNDERSTAND_THE_CONSEQUENCES_PLEASE_BREAK_MY_SYSTEM"
344+ ):
345+ msg = dedent(
346+ # the lowercase for the first letter below is proposital
347+ f"""
348+ your NixOS configuration path seems to be missing essential files.
349+ To avoid corrupting your current NixOS installation, the activation will abort.
350+351+ This could be caused by Nix bug: https://github.com/NixOS/nix/issues/13367.
352+ This is the evaluated NixOS configuration path: {path_to_config}.
353+ Change the directory to somewhere else (e.g., `cd $HOME`) before trying again.
354+355+ If you think this is a mistake, you can set the environment variable
356+ NIXOS_REBUILD_I_UNDERSTAND_THE_CONSEQUENCES_PLEASE_BREAK_MY_SYSTEM to 1
357+ and re-run the command to continue.
358+ Please open an issue if this is the case.
359+ """
360+ ).strip()
361+ raise NixOSRebuildError(msg)
362+363+364def execute(argv: list[str]) -> None:
365 args, args_groups = parse_args(argv)
366···425 no_link = action in (Action.SWITCH, Action.BOOT)
426 rollback = bool(args.rollback)
42700000000428 match action:
429 case Action.BUILD_IMAGE if flake:
430 variants = nix.get_build_image_variants_flake(
431 flake,
432 eval_flags=flake_common_flags,
433 )
434+ validate_image_variant(args.image_variant, variants)
435 attr = f"config.system.build.images.{args.image_variant}"
436 case Action.BUILD_IMAGE:
437 variants = nix.get_build_image_variants(
438 build_attr,
439 instantiate_flags=common_flags,
440 )
441+ validate_image_variant(args.image_variant, variants)
442 attr = f"config.system.build.images.{args.image_variant}"
443 case Action.BUILD_VM:
444 attr = "config.system.build.vm"
···459 if maybe_path_to_config: # kinda silly but this makes mypy happy
460 path_to_config = maybe_path_to_config
461 else:
462+ raise NixOSRebuildError("could not find previous generation")
463 case (_, True, _, _):
464+ raise NixOSRebuildError(
465+ f"--rollback is incompatible with '{action}'"
466+ )
467 case (_, False, Remote(_), Flake(_)):
468 path_to_config = nix.build_remote_flake(
469 attr,
···514 copy_flags=copy_flags,
515 )
516 if action in (Action.SWITCH, Action.BOOT):
517+ validate_nixos_config(path_to_config)
518 nix.set_profile(
519 profile,
520 path_to_config,
···20 Generation,
21 GenerationJson,
22 ImageVariants,
23- NRError,
24 Profile,
25 Remote,
26)
···256 )
257 else:
258 if flake_flags:
259- raise NRError("'edit' does not support extra Nix flags")
260 nixos_config = Path(
261 os.getenv("NIXOS_CONFIG") or find_file("nixos-config") or "/etc/nixos"
262 )
···266 if nixos_config.exists():
267 run_wrapper([os.getenv("EDITOR", "nano"), nixos_config], check=False)
268 else:
269- raise NRError("cannot find NixOS config file")
270271272def find_file(file: str, nix_flags: Args | None = None) -> Path | None:
···424 and if this is the current active profile or not.
425 """
426 if not profile.path.exists():
427- raise NRError(f"no profile '{profile.name}' found")
428429 def parse_path(path: Path, profile: Profile) -> Generation:
430 entry_id = path.name.split("-")[1]
···456 and if this is the current active profile or not.
457 """
458 if not profile.path.exists():
459- raise NRError(f"no profile '{profile.name}' found")
460461 # Using `nix-env --list-generations` needs root to lock the profile
462 r = run_wrapper(
···635 """
636 if specialisation:
637 if action not in (Action.SWITCH, Action.TEST):
638- raise NRError(
639 "'--specialisation' can only be used with 'switch' and 'test'"
640 )
641 path_to_config = path_to_config / f"specialisation/{specialisation}"
642643 if not path_to_config.exists():
644- raise NRError(f"specialisation not found: {specialisation}")
645646 r = run_wrapper(
647 ["test", "-d", "/run/systemd/system"],
···652 if r.returncode:
653 logger.debug(
654 "skipping systemd-run to switch configuration since systemd is "
655- + "not working in target host"
656 )
657 cmd = []
658
···20 Generation,
21 GenerationJson,
22 ImageVariants,
23+ NixOSRebuildError,
24 Profile,
25 Remote,
26)
···256 )
257 else:
258 if flake_flags:
259+ raise NixOSRebuildError("'edit' does not support extra Nix flags")
260 nixos_config = Path(
261 os.getenv("NIXOS_CONFIG") or find_file("nixos-config") or "/etc/nixos"
262 )
···266 if nixos_config.exists():
267 run_wrapper([os.getenv("EDITOR", "nano"), nixos_config], check=False)
268 else:
269+ raise NixOSRebuildError("cannot find NixOS config file")
270271272def find_file(file: str, nix_flags: Args | None = None) -> Path | None:
···424 and if this is the current active profile or not.
425 """
426 if not profile.path.exists():
427+ raise NixOSRebuildError(f"no profile '{profile.name}' found")
428429 def parse_path(path: Path, profile: Profile) -> Generation:
430 entry_id = path.name.split("-")[1]
···456 and if this is the current active profile or not.
457 """
458 if not profile.path.exists():
459+ raise NixOSRebuildError(f"no profile '{profile.name}' found")
460461 # Using `nix-env --list-generations` needs root to lock the profile
462 r = run_wrapper(
···635 """
636 if specialisation:
637 if action not in (Action.SWITCH, Action.TEST):
638+ raise NixOSRebuildError(
639 "'--specialisation' can only be used with 'switch' and 'test'"
640 )
641 path_to_config = path_to_config / f"specialisation/{specialisation}"
642643 if not path_to_config.exists():
644+ raise NixOSRebuildError(f"specialisation not found: {specialisation}")
645646 r = run_wrapper(
647 ["test", "-d", "/run/systemd/system"],
···652 if r.returncode:
653 logger.debug(
654 "skipping systemd-run to switch configuration since systemd is "
655+ "not working in target host"
656 )
657 cmd = []
658
···55 if o in ["-t", "-tt", "RequestTTY=yes", "RequestTTY=force"]:
56 logger.warning(
57 f"detected option '{o}' in NIX_SSHOPTS. SSH's TTY may "
58- + "cause issues, it is recommended to remove this option"
59 )
60 if not ask_sudo_password:
61 logger.warning(
62 "if you want to prompt for sudo password use "
63- + "'--ask-sudo-password' option instead"
64 )
6566···161 if sudo and remote and remote.sudo_password is None:
162 logger.error(
163 "while running command with remote sudo, did you forget to use "
164- + "--ask-sudo-password?"
165 )
166 raise
167
···55 if o in ["-t", "-tt", "RequestTTY=yes", "RequestTTY=force"]:
56 logger.warning(
57 f"detected option '{o}' in NIX_SSHOPTS. SSH's TTY may "
58+ "cause issues, it is recommended to remove this option"
59 )
60 if not ask_sudo_password:
61 logger.warning(
62 "if you want to prompt for sudo password use "
63+ "'--ask-sudo-password' option instead"
64 )
6566···161 if sudo and remote and remote.sudo_password is None:
162 logger.error(
163 "while running command with remote sudo, did you forget to use "
164+ "--ask-sudo-password?"
165 )
166 raise
167