-
-
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 5d7ce95
Showing
9 changed files
with
558 additions
and
0 deletions.
There are no files selected for viewing
175 changes: 175 additions & 0 deletions
175
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,175 @@ | ||
import argparse | ||
import os | ||
import sys | ||
from pathlib import Path | ||
from textwrap import dedent | ||
from typing import Final | ||
|
||
from .log import info | ||
from .models import Action, Flake | ||
from .process import run_capture, run_cmd, run_exec | ||
|
||
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, | ||
no_out_link: bool = True, | ||
keep_going: bool = False, | ||
) -> 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] | ||
# TODO: kwargs magic? | ||
if no_out_link: | ||
run_args.append("--no-out-link") | ||
if keep_going: | ||
run_args.append("--keep-going") | ||
return Path(run_capture(run_args).strip()) | ||
|
||
|
||
def nix_flake_build(attr: str, flake: Flake, no_link: bool = True) -> Path: | ||
run_args = [ | ||
"nix", | ||
*FLAKE_FLAGS, | ||
"build", | ||
"--print-out-paths", | ||
f"{flake}.{attr}", | ||
] | ||
# TODO: kwargs magic? | ||
if no_link: | ||
run_args.append("--no-link") | ||
|
||
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", ""), | ||
}, | ||
) | ||
|
||
|
||
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:]) | ||
|
||
if r.install_grub: | ||
info(f"{argv[0]}: --install-grub deprecated, use --install-bootloader instead") | ||
r.install_bootloader = True | ||
|
||
return r | ||
|
||
|
||
def main() -> None: | ||
args = parse_args(sys.argv) | ||
if args.help or args.action is None: | ||
run_exec(["man", "8", "nixos-rebuild"]) | ||
|
||
profile = Path("/nix/var/nix/profiles/system") | ||
if args.profile_name != "system": | ||
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) | ||
if flake and (args.file or args.attr): | ||
sys.exit("error: '--flake' cannot be used with '--file' or '--attr'") | ||
|
||
match action := Action(args.action): | ||
case Action.SWITCH | Action.BOOT: | ||
info("building the system configuration...") | ||
if flake: | ||
path_to_config = nix_flake_build("config.system.build.toplevel", flake) | ||
else: | ||
path_to_config = nix_build("system", args.attr, args.file) | ||
nix_set_profile(profile, path_to_config) | ||
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, | ||
no_link=False, | ||
) | ||
else: | ||
path_to_config = nix_build( | ||
attr, | ||
args.attr, | ||
args.file, | ||
no_out_link=False, | ||
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 _: | ||
# TODO: replace with Typing.assert_never once all Actions are | ||
# implemented | ||
raise NotImplementedError(str(action)) | ||
|
||
|
||
if __name__ == "__main__": | ||
try: | ||
main() | ||
except KeyboardInterrupt: | ||
sys.exit(130) |
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,4 @@ | ||
import sys | ||
from functools import partial | ||
|
||
info = partial(print, file=sys.stderr) |
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,38 @@ | ||
import subprocess | ||
import sys | ||
from typing import Any | ||
|
||
from .log import info | ||
|
||
|
||
# Similar to `subprocess.run`, but handles Ctrl+C gracefully | ||
def run_cmd( | ||
args: list[str], | ||
check: bool = True, | ||
**kwargs: Any, | ||
) -> subprocess.Popen[Any]: | ||
proc = subprocess.Popen(args, **kwargs) | ||
try: | ||
proc.wait() | ||
except KeyboardInterrupt: | ||
proc.terminate() | ||
proc.wait() | ||
sys.exit(proc.returncode or 130) | ||
|
||
if check and proc.returncode: | ||
info(f"warning: error(s) occured while running command:\n$ {' '.join(args)}") | ||
sys.exit(proc.returncode) | ||
|
||
return proc | ||
|
||
|
||
def run_capture(args: list[str]) -> str: | ||
r = run_cmd(args, text=True, stdout=subprocess.PIPE) | ||
assert r.stdout is not None, "stdout is None" | ||
return str(r.stdout.read()) | ||
|
||
|
||
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,60 @@ | ||
{ | ||
lib, | ||
installShellFiles, | ||
nix, | ||
nixos-rebuild, | ||
python3Packages, | ||
}: | ||
let | ||
fallback = import ./../../../../nixos/modules/installer/tools/nix-fallback-paths.nix; | ||
fs = lib.fileset; | ||
in | ||
python3Packages.buildPythonApplication { | ||
pname = "nixos-rebuild-ng"; | ||
version = "0.1"; | ||
src = fs.toSource { | ||
root = ./.; | ||
fileset = fs.unions [ | ||
./nixos_rebuild | ||
./pyproject.toml | ||
./tests | ||
]; | ||
}; | ||
pyproject = true; | ||
|
||
nativeBuildInputs = [ | ||
installShellFiles | ||
python3Packages.setuptools | ||
]; | ||
|
||
postInstall = '' | ||
installManPage ${nixos-rebuild}/share/man/man8/nixos-rebuild.8 | ||
installShellCompletion \ | ||
--bash ${nixos-rebuild}/share/bash-completion/completions/_nixos-rebuild | ||
''; | ||
|
||
doCheck = true; | ||
nativeCheckInputs = with python3Packages; [ | ||
pytestCheckHook | ||
mypy | ||
ruff | ||
black | ||
]; | ||
postCheck = '' | ||
echo -e "\x1b[32m## run mypy\x1b[0m" | ||
mypy --strict nixos_rebuild | ||
echo -e "\x1b[32m## run ruff\x1b[0m" | ||
ruff check . | ||
echo -e "\x1b[32m## run ruff format\x1b[0m" | ||
ruff format --check . | ||
''; | ||
|
||
meta = { | ||
description = "Rebuild your NixOS configuration and switch to it, on local hosts and remote"; | ||
homepage = "https://github.com/NixOS/nixpkgs/tree/master/pkgs/os-specific/linux/nixos-rebuild"; | ||
license = lib.licenses.mit; | ||
maintainers = [ lib.maintainers.thiagokokada ]; | ||
mainProgram = "nixos-rebuild"; | ||
}; | ||
} |
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,10 @@ | ||
[build-system] | ||
requires = ["setuptools"] | ||
build-backend = "setuptools.build_meta" | ||
|
||
[project] | ||
name = "nixos-rebuild-ng" | ||
version = "0.0.0" | ||
|
||
[project.scripts] | ||
nixos-rebuild = "nixos_rebuild:main" |
Oops, something went wrong.