diff --git a/.gitignore b/.gitignore index ac891aee..91893285 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .direnv result* +pkgs/clan-cli/clan_cli/nixpkgs # python __pycache__ diff --git a/pkgs/clan-cli/clan_cli/dirs.py b/pkgs/clan-cli/clan_cli/dirs.py index 6b499929..316448a2 100644 --- a/pkgs/clan-cli/clan_cli/dirs.py +++ b/pkgs/clan-cli/clan_cli/dirs.py @@ -24,3 +24,19 @@ def user_config_dir() -> Path: return Path(os.path.expanduser("~/Library/Application Support/")) else: return Path(os.getenv("XDG_CONFIG_HOME", os.path.expanduser("~/.config"))) + + +def module_root() -> Path: + return Path(__file__).parent + + +def flake_registry() -> Path: + return module_root() / "nixpkgs" / "flake-registry.json" + + +def nixpkgs() -> Path: + return (module_root() / "nixpkgs" / "path").resolve() + + +def unfree_nixpkgs() -> Path: + return module_root() / "nixpkgs" / "unfree" diff --git a/pkgs/clan-cli/clan_cli/nix.py b/pkgs/clan-cli/clan_cli/nix.py index 4173ba93..b34db8d1 100644 --- a/pkgs/clan-cli/clan_cli/nix.py +++ b/pkgs/clan-cli/clan_cli/nix.py @@ -1,10 +1,42 @@ import os +from .dirs import flake_registry, unfree_nixpkgs + def nix_shell(packages: list[str], cmd: list[str]) -> list[str]: - flake = os.environ.get("CLAN_FLAKE") - # in unittest we will have all binaries provided - if flake is None: + # we cannot use nix-shell inside the nix sandbox + # in our tests we just make sure we have all the packages + if os.environ.get("IN_NIX_SANDBOX"): return cmd - wrapped_packages = [f"path:{flake}#{p}" for p in packages] - return ["nix", "shell"] + wrapped_packages + ["-c"] + cmd + wrapped_packages = [f"nixpkgs#{p}" for p in packages] + return ( + [ + "nix", + "shell", + "--extra-experimental-features", + "nix-command flakes", + "--flake-registry", + str(flake_registry()), + ] + + wrapped_packages + + ["-c"] + + cmd + ) + + +def unfree_nix_shell(packages: list[str], cmd: list[str]) -> list[str]: + if os.environ.get("IN_NIX_SANDBOX"): + return cmd + return ( + [ + "nix", + "shell", + "--extra-experimental-features", + "nix-command flakes", + "-f", + str(unfree_nixpkgs()), + ] + + packages + + ["-c"] + + cmd + ) diff --git a/pkgs/clan-cli/clan_cli/zerotier/__init__.py b/pkgs/clan-cli/clan_cli/zerotier/__init__.py index f52fd8e6..597f061f 100644 --- a/pkgs/clan-cli/clan_cli/zerotier/__init__.py +++ b/pkgs/clan-cli/clan_cli/zerotier/__init__.py @@ -9,7 +9,7 @@ from tempfile import TemporaryDirectory from typing import Any, Iterator, Optional from ..errors import ClanError -from ..nix import nix_shell +from ..nix import nix_shell, unfree_nix_shell def try_bind_port(port: int) -> bool: @@ -87,7 +87,10 @@ def zerotier_controller() -> Iterator[ZerotierController]: controller_port = find_free_port(range(10000, 65535)) if controller_port is None: raise ClanError("cannot find a free port for zerotier controller") - cmd = nix_shell(["bash", "zerotierone"], ["bash", "-c", "command -v zerotier-one"]) + + cmd = unfree_nix_shell( + ["bash", "zerotierone"], ["bash", "-c", "command -v zerotier-one"] + ) res = subprocess.run( cmd, check=True, @@ -102,7 +105,6 @@ def zerotier_controller() -> Iterator[ZerotierController]: raise ClanError( f"zerotier-one executable needs to come from /nix/store: {zerotier_exe}" ) - with TemporaryDirectory() as d: tempdir = Path(d) home = tempdir / "zerotier-one" diff --git a/pkgs/clan-cli/default.nix b/pkgs/clan-cli/default.nix index c57ed1ce..7636b15c 100644 --- a/pkgs/clan-cli/default.nix +++ b/pkgs/clan-cli/default.nix @@ -48,6 +48,17 @@ let chmod -R +w $out rm $out/clan_cli/config/jsonschema cp -r ${self + /lib/jsonschema} $out/clan_cli/config/jsonschema + ln -s ${nixpkgs} $out/clan_cli/nixpkgs + ''; + nixpkgs = runCommand "nixpkgs" { } '' + mkdir -p $out/unfree + cat > $out/unfree/default.nix < $out/flake-registry.json < Generator[pytest.MonkeyPatch, None, None]: yield mp -# fixture for the example flake located under ./example_flake -# The flake is a template that is copied to a temporary location. -# Variables like __CLAN_NIXPKGS__ are replaced with the value of the -# CLAN_NIXPKGS environment variable. @pytest.fixture(scope="module") def machine_flake(monkeymodule: pytest.MonkeyPatch) -> Generator[Path, None, None]: - CLAN_NIXPKGS = os.environ.get("CLAN_NIXPKGS", "") - if CLAN_NIXPKGS == "": - raise Exception("CLAN_NIXPKGS not set") template = Path(__file__).parent / "machine_flake" # copy the template to a new temporary location with tempfile.TemporaryDirectory() as tmpdir_: @@ -49,7 +44,7 @@ def machine_flake(monkeymodule: pytest.MonkeyPatch) -> Generator[Path, None, Non # provided by get_clan_flake_toplevel flake_nix = flake / "flake.nix" flake_nix.write_text( - flake_nix.read_text().replace("__CLAN_NIXPKGS__", CLAN_NIXPKGS) + flake_nix.read_text().replace("__NIXPKGS__", str(nixpkgs())) ) # check that an empty config is returned if no json file exists monkeymodule.chdir(flake) diff --git a/pkgs/clan-cli/tests/machine_flake/flake.nix b/pkgs/clan-cli/tests/machine_flake/flake.nix index f245d04d..8eefc68a 100644 --- a/pkgs/clan-cli/tests/machine_flake/flake.nix +++ b/pkgs/clan-cli/tests/machine_flake/flake.nix @@ -1,7 +1,7 @@ { inputs = { # this placeholder is replaced by the path to nixpkgs - nixpkgs.url = "__CLAN_NIXPKGS__"; + nixpkgs.url = "__NIXPKGS__"; }; outputs = _inputs: { diff --git a/pkgs/clan-cli/tests/test_ssh_cli.py b/pkgs/clan-cli/tests/test_ssh_cli.py index 88415711..f89b783f 100644 --- a/pkgs/clan-cli/tests/test_ssh_cli.py +++ b/pkgs/clan-cli/tests/test_ssh_cli.py @@ -1,12 +1,13 @@ +import os import sys from typing import Union import pytest import pytest_subprocess.fake_process -from environment import mock_env from pytest_subprocess import utils import clan_cli +from clan_cli.dirs import flake_registry from clan_cli.ssh import cli @@ -21,56 +22,70 @@ def test_no_args( # using fp fixture from pytest-subprocess -def test_ssh_no_pass(fp: pytest_subprocess.fake_process.FakeProcess) -> None: - with mock_env(CLAN_FLAKE="/mocked-flake"): - host = "somehost" - user = "user" - cmd: list[Union[str, utils.Any]] = [ - "nix", - "shell", - "path:/mocked-flake#tor", - "path:/mocked-flake#openssh", - "-c", - "torify", - "ssh", - "-o", - "UserKnownHostsFile=/dev/null", - "-o", - "StrictHostKeyChecking=no", - f"{user}@{host}", - fp.any(), - ] - fp.register(cmd) - cli.ssh( - host=host, - user=user, - ) - assert fp.call_count(cmd) == 1 +def test_ssh_no_pass( + fp: pytest_subprocess.fake_process.FakeProcess, monkeypatch: pytest.MonkeyPatch +) -> None: + host = "somehost" + user = "user" + if os.environ.get("IN_NIX_SANDBOX"): + monkeypatch.delenv("IN_NIX_SANDBOX") + cmd: list[Union[str, utils.Any]] = [ + "nix", + "shell", + "--extra-experimental-features", + "nix-command flakes", + "--flake-registry", + str(flake_registry()), + "nixpkgs#tor", + "nixpkgs#openssh", + "-c", + "torify", + "ssh", + "-o", + "UserKnownHostsFile=/dev/null", + "-o", + "StrictHostKeyChecking=no", + f"{user}@{host}", + fp.any(), + ] + fp.register(cmd) + cli.ssh( + host=host, + user=user, + ) + assert fp.call_count(cmd) == 1 -def test_ssh_with_pass(fp: pytest_subprocess.fake_process.FakeProcess) -> None: - with mock_env(CLAN_FLAKE="/mocked-flake"): - host = "somehost" - user = "user" - cmd: list[Union[str, utils.Any]] = [ - "nix", - "shell", - "path:/mocked-flake#tor", - "path:/mocked-flake#openssh", - "path:/mocked-flake#sshpass", - "-c", - "torify", - "sshpass", - "-p", - fp.any(), - ] - fp.register(cmd) - cli.ssh( - host=host, - user=user, - password="XXX", - ) - assert fp.call_count(cmd) == 1 +def test_ssh_with_pass( + fp: pytest_subprocess.fake_process.FakeProcess, monkeypatch: pytest.MonkeyPatch +) -> None: + host = "somehost" + user = "user" + if os.environ.get("IN_NIX_SANDBOX"): + monkeypatch.delenv("IN_NIX_SANDBOX") + cmd: list[Union[str, utils.Any]] = [ + "nix", + "shell", + "--extra-experimental-features", + "nix-command flakes", + "--flake-registry", + str(flake_registry()), + "nixpkgs#tor", + "nixpkgs#openssh", + "nixpkgs#sshpass", + "-c", + "torify", + "sshpass", + "-p", + fp.any(), + ] + fp.register(cmd) + cli.ssh( + host=host, + user=user, + password="XXX", + ) + assert fp.call_count(cmd) == 1 def test_qrcode_scan(fp: pytest_subprocess.fake_process.FakeProcess) -> None: