-
-
Notifications
You must be signed in to change notification settings - Fork 14.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d25ccf9
commit 5ff4130
Showing
12 changed files
with
637 additions
and
0 deletions.
There are no files selected for viewing
152 changes: 152 additions & 0 deletions
152
pkgs/by-name/ni/nixos-rebuild-ng/nixos_rebuild/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
import argparse | ||
import sys | ||
from pathlib import Path | ||
from textwrap import dedent | ||
from typing import assert_never | ||
|
||
from .models import Action, Flake | ||
from .nix import ( | ||
edit, | ||
nix_build, | ||
nix_flake_build, | ||
nix_set_profile, | ||
nix_switch_to_configuration, | ||
) | ||
from .process import run_exec | ||
from .utils import info | ||
|
||
|
||
def parse_args(argv: list[str]) -> argparse.Namespace: | ||
parser = argparse.ArgumentParser( | ||
prog="nixos-rebuild", | ||
description="Reconfigure a NixOS machine", | ||
add_help=False, | ||
) | ||
parser.add_argument("--help", action="store_true") | ||
parser.add_argument("--file", "-f") | ||
parser.add_argument("--attr", "-A") | ||
parser.add_argument("--flake", nargs="?", const=True) | ||
parser.add_argument("--no-flake", dest="flake", action="store_false") | ||
parser.add_argument("--install-bootloader", action="store_true") | ||
# TODO: add deprecated=True in Python >=3.13 | ||
parser.add_argument("--install-grub", action="store_true") | ||
parser.add_argument("--profile-name", default="system") | ||
parser.add_argument("action", choices=Action.values(), nargs="?") | ||
r = parser.parse_args(argv[1:]) | ||
|
||
# https://github.com/NixOS/nixpkgs/blob/master/pkgs/os-specific/linux/nixos-rebuild/nixos-rebuild.sh#L56 | ||
if r.action == Action.DRY_RUN.value: | ||
r.action = Action.DRY_BUILD.value | ||
|
||
if r.install_grub: | ||
info(f"{argv[0]}: --install-grub deprecated, use --install-bootloader instead") | ||
r.install_bootloader = True | ||
|
||
if r.flake and (r.file or r.attr): | ||
sys.exit("error: '--flake' cannot be used with '--file' or '--attr'") | ||
|
||
return r | ||
|
||
|
||
def run(argv: list[str]) -> None: | ||
args = parse_args(argv) | ||
if args.help or args.action is None: | ||
run_exec(["man", "8", "nixos-rebuild"]) | ||
|
||
if args.profile_name == "system": | ||
profile = Path("/nix/var/nix/profiles/system") | ||
else: | ||
profile = Path("/nix/var/nix/profiles/system-profiles") / args.profile_name | ||
profile.parent.mkdir(mode=0o755, parents=True, exist_ok=True) | ||
|
||
flake = Flake.from_arg(args.flake) | ||
|
||
match action := Action(args.action): | ||
case ( | ||
Action.SWITCH | ||
| Action.BOOT | ||
| Action.TEST | ||
| Action.BUILD | ||
| Action.DRY_BUILD | ||
| Action.DRY_ACTIVATE | ||
): | ||
set_profile = action in (Action.SWITCH, Action.BOOT) | ||
switch_to_configuration = action in ( | ||
Action.SWITCH, | ||
Action.BOOT, | ||
Action.TEST, | ||
Action.DRY_ACTIVATE, | ||
) | ||
no_link = action in (Action.SWITCH, Action.BOOT) | ||
keep_going = action in ( | ||
Action.TEST, | ||
Action.BUILD, | ||
Action.DRY_BUILD, | ||
Action.DRY_ACTIVATE, | ||
) | ||
dry_run = action == Action.DRY_BUILD | ||
info("building the system configuration...") | ||
if flake: | ||
path_to_config = nix_flake_build( | ||
"config.system.build.toplevel", | ||
flake, | ||
no_link=no_link, | ||
keep_going=keep_going, | ||
dry_run=dry_run, | ||
) | ||
else: | ||
path_to_config = nix_build( | ||
"system", | ||
args.attr, | ||
args.file, | ||
no_out_link=no_link, | ||
keep_going=keep_going, | ||
dry_run=dry_run, | ||
) | ||
if set_profile: | ||
nix_set_profile(profile, path_to_config) | ||
if switch_to_configuration: | ||
nix_switch_to_configuration( | ||
path_to_config, action, args.install_bootloader | ||
) | ||
case Action.BUILD_VM | Action.BUILD_VM_WITH_BOOTLOADER: | ||
info("building the system configuration...") | ||
attr = "vm" if action == Action.BUILD_VM else "vmWithBootLoader" | ||
if flake: | ||
path_to_config = nix_flake_build( | ||
f"config.system.build.{attr}", | ||
flake, | ||
) | ||
else: | ||
path_to_config = nix_build( | ||
attr, | ||
args.attr, | ||
args.file, | ||
keep_going=True, | ||
) | ||
print( | ||
dedent(f""" | ||
Done. The virtual machine can be started by running {next(path_to_config.glob("bin/run-*-vm"))} | ||
""") | ||
) | ||
case Action.EDIT: | ||
if args.file or args.attr: | ||
sys.exit("error: '--file' and '--attr' are not supported with 'edit'") | ||
edit(flake) | ||
case Action.DRY_RUN: | ||
assert False, "DRY_RUN should be a DRY_BUILD alias" | ||
case Action.REPL | Action.LIST_GENERATIONS: | ||
raise NotImplementedError(action) | ||
case _: | ||
assert_never(action) | ||
|
||
|
||
def main() -> None: | ||
try: | ||
run(sys.argv) | ||
except KeyboardInterrupt: | ||
sys.exit(130) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
from __future__ import annotations | ||
|
||
import platform | ||
import re | ||
from dataclasses import dataclass | ||
from enum import Enum | ||
from pathlib import Path | ||
from typing import Any | ||
|
||
|
||
class Action(Enum): | ||
SWITCH = "switch" | ||
BOOT = "boot" | ||
TEST = "test" | ||
BUILD = "build" | ||
EDIT = "edit" | ||
REPL = "repl" | ||
DRY_BUILD = "dry-build" | ||
DRY_RUN = "dry-run" | ||
DRY_ACTIVATE = "dry-activate" | ||
BUILD_VM = "build-vm" | ||
BUILD_VM_WITH_BOOTLOADER = "build-vm-with-bootloader" | ||
LIST_GENERATIONS = "list-generations" | ||
|
||
def __str__(self) -> str: | ||
return self.value | ||
|
||
@staticmethod | ||
def values() -> list[str]: | ||
return [a.value for a in Action] | ||
|
||
|
||
@dataclass | ||
class Flake: | ||
path: Path | ||
attr: str | ||
|
||
def __str__(self) -> str: | ||
return f"{self.path}#{self.attr}" | ||
|
||
@staticmethod | ||
def parse(flake_str: str) -> Flake: | ||
m = re.match(r"^(?P<path>[^\#]*)\#?(?P<attr>[^\#\"]*)$", flake_str) | ||
assert m is not None, "match is None" | ||
attr = m.group("attr") | ||
if not attr: | ||
hostname = platform.node() or "default" | ||
attr = f"nixosConfigurations.{hostname}" | ||
else: | ||
attr = f"nixosConfigurations.{attr}" | ||
return Flake(Path(m.group("path")), attr) | ||
|
||
@staticmethod | ||
def from_arg(flake_arg: Any) -> Flake | None: | ||
match flake_arg: | ||
case str(s): | ||
return Flake.parse(s) | ||
case True: | ||
return Flake.parse(".") | ||
case False: | ||
return None | ||
case _: | ||
# Use /etc/nixos/flake.nix if it exists. | ||
default_path = Path("/etc/nixos/flake.nix") | ||
if default_path.exists(): | ||
# It can be a symlink to the actual flake. | ||
if default_path.is_symlink(): | ||
default_path = default_path.readlink() | ||
return Flake.parse(str(default_path.parent)) | ||
else: | ||
return None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import os | ||
import sys | ||
from pathlib import Path | ||
from typing import Final | ||
|
||
from .models import Action, Flake | ||
from .process import run_capture, run_cmd, run_exec | ||
from .utils import kwargs_to_flags | ||
|
||
FLAKE_FLAGS: Final = ["--extra-experimental-features", "nix-command flakes"] | ||
|
||
|
||
def edit(flake: Flake | None) -> None: | ||
if flake: | ||
# TODO: lockFlags | ||
run_exec(["nix", *FLAKE_FLAGS, "edit", "--", str(flake)]) | ||
else: | ||
nixos_config = Path( | ||
os.environ.get("NIXOS_CONFIG") | ||
or run_capture(["nix-instantiate", "--find-file", "nixos-config"]) | ||
or "/etc/nixos/default.nix" | ||
) | ||
if nixos_config.is_dir(): | ||
nixos_config /= "default.nix" | ||
|
||
if nixos_config.exists(): | ||
run_exec([os.environ.get("EDITOR", "nano"), str(nixos_config)]) | ||
else: | ||
sys.exit("warning: cannot find NixOS config file") | ||
|
||
|
||
def nix_build( | ||
attr: str, | ||
pre_attr: str | None, | ||
file: str | None, | ||
**kwargs: bool | str, | ||
) -> Path: | ||
if pre_attr or file: | ||
run_args = [ | ||
"nix-build", | ||
file or "default.nix", | ||
"--attr", | ||
f"{'.'.join([x for x in [pre_attr, attr] if x])}", | ||
] | ||
else: | ||
run_args = ["nix-build", "<nixpkgs/nixos>", "--attr", attr] | ||
run_args = kwargs_to_flags(run_args, **kwargs) | ||
return Path(run_capture(run_args).strip()) | ||
|
||
|
||
def nix_flake_build(attr: str, flake: Flake, **kwargs: bool | str) -> Path: | ||
run_args = [ | ||
"nix", | ||
*FLAKE_FLAGS, | ||
"build", | ||
"--print-out-paths", | ||
f"{flake}.{attr}", | ||
] | ||
run_args = kwargs_to_flags(run_args, **kwargs) | ||
return Path(run_capture(run_args).strip()) | ||
|
||
|
||
def nix_set_profile(profile: Path, path_to_config: Path) -> None: | ||
run_cmd(["nix-env", "-p", str(profile), "--set", str(path_to_config)]) | ||
|
||
|
||
def nix_switch_to_configuration( | ||
path_to_config: Path, | ||
action: Action, | ||
install_bootloader: bool = False, | ||
) -> None: | ||
run_cmd( | ||
[str(path_to_config / "bin/switch-to-configuration"), str(action)], | ||
env={ | ||
"NIXOS_INSTALL_BOOTLOADER": "1" if install_bootloader else "0", | ||
"LOCALE_ARCHIVE": os.environ.get("LOCALE_ARCHIVE", ""), | ||
}, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import subprocess | ||
import sys | ||
from typing import Any | ||
|
||
|
||
def run_cmd( | ||
args: list[str], | ||
check: bool = True, | ||
**kwargs: Any, | ||
) -> subprocess.CompletedProcess[str]: | ||
r = subprocess.run(args, text=True, **kwargs) | ||
|
||
if check: | ||
try: | ||
r.check_returncode() | ||
except subprocess.CalledProcessError as ex: | ||
sys.exit(str(ex)) | ||
|
||
return r | ||
|
||
|
||
def run_capture(args: list[str]) -> str: | ||
r = run_cmd(args, stdout=subprocess.PIPE) | ||
return r.stdout | ||
|
||
|
||
def run_exec(args: list[str]) -> None: | ||
# We will exit anyway, so ignore the check here | ||
r = run_cmd(args, check=False) | ||
sys.exit(r.returncode) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import sys | ||
from functools import partial | ||
|
||
info = partial(print, file=sys.stderr) | ||
|
||
|
||
def kwargs_to_flags(flags: list[str], **kwargs: bool | str) -> list[str]: | ||
for k, v in kwargs.items(): | ||
f = f"--{'-'.join(k.split('_'))}" | ||
match v: | ||
case True: | ||
flags.append(f) | ||
case str(): | ||
flags.append(f) | ||
flags.append(v) | ||
return flags |
Oops, something went wrong.