From 3d8fab062d89fd1ab3141a77734ce12688a6201f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20B=C3=B6hme?= Date: Mon, 1 Sep 2025 12:43:40 +0200 Subject: [PATCH 1/4] feat: add zerotier to network cli --- .gitignore | 2 + clanServices/zerotier/default.nix | 19 ++++++++- .../clan_lib/network/zerotier/__init__.py | 41 +++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 pkgs/clan-cli/clan_lib/network/zerotier/__init__.py diff --git a/.gitignore b/.gitignore index 75dbd7889..ba5b4a4a2 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,5 @@ pkgs/clan-app/ui/.fonts *.gif *.mp4 *.mkv + +.jj diff --git a/clanServices/zerotier/default.nix b/clanServices/zerotier/default.nix index 04e396b89..b689e0540 100644 --- a/clanServices/zerotier/default.nix +++ b/clanServices/zerotier/default.nix @@ -8,8 +8,25 @@ roles.peer = { perInstance = - { instanceName, roles, ... }: { + instanceName, + roles, + lib, + ... + }: + { + exports.networking = { + priority = lib.mkDefault 20; + # TODO add user space network support to clan-cli + module = "clan_lib.network.zerotier"; + peers = lib.mapAttrs (name: machine: { + host.var = { + machine = name; + generator = "zerotier"; + file = "zerotier-ip"; + }; + }) roles.peer.machines; + }; nixosModule = { config, diff --git a/pkgs/clan-cli/clan_lib/network/zerotier/__init__.py b/pkgs/clan-cli/clan_lib/network/zerotier/__init__.py new file mode 100644 index 000000000..33034e5ec --- /dev/null +++ b/pkgs/clan-cli/clan_lib/network/zerotier/__init__.py @@ -0,0 +1,41 @@ +import logging +import time +from collections.abc import Iterator +from contextlib import contextmanager +from dataclasses import dataclass + +from clan_lib.errors import ClanError +from clan_lib.network import Network, NetworkTechnologyBase, Peer +from clan_lib.ssh.remote import Remote + +log = logging.getLogger(__name__) + + +@dataclass(frozen=True) +class NetworkTechnology(NetworkTechnologyBase): + def is_running(self) -> bool: + return True + + def ping(self, remote: Remote) -> None | float: + if self.is_running(): + try: + # Use the existing SSH reachability check + now = time.time() + remote.check_machine_ssh_reachable() + + return (time.time() - now) * 1000 + + except ClanError as e: + log.debug(f"Error checking peer {remote}: {e}") + return None + return None + + @contextmanager + def connection(self, network: Network) -> Iterator[Network]: + yield network + + def remote(self, peer: Peer) -> "Remote": + return Remote( + address=peer.host, + command_prefix=peer.name, + ) From 104058b79cf896c4823a51b072bdb47b17eb6155 Mon Sep 17 00:00:00 2001 From: Qubasa Date: Tue, 16 Sep 2025 17:29:30 +0200 Subject: [PATCH 2/4] zerotier: increase network prio --- clanServices/zerotier/default.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clanServices/zerotier/default.nix b/clanServices/zerotier/default.nix index b689e0540..7c65f4a76 100644 --- a/clanServices/zerotier/default.nix +++ b/clanServices/zerotier/default.nix @@ -16,10 +16,10 @@ }: { exports.networking = { - priority = lib.mkDefault 20; + priority = lib.mkDefault 900; # TODO add user space network support to clan-cli module = "clan_lib.network.zerotier"; - peers = lib.mapAttrs (name: machine: { + peers = lib.mapAttrs (name: _machine: { host.var = { machine = name; generator = "zerotier"; From 9b39ca42e0938ec56036612033cb6412a6af0b5e Mon Sep 17 00:00:00 2001 From: Qubasa Date: Tue, 16 Sep 2025 21:12:22 +0200 Subject: [PATCH 3/4] clan_lib: implement check_zerotier_running for network overview --- .../clan_lib/network/zerotier/__init__.py | 4 ++- .../clan-cli/clan_lib/network/zerotier/lib.py | 27 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 pkgs/clan-cli/clan_lib/network/zerotier/lib.py diff --git a/pkgs/clan-cli/clan_lib/network/zerotier/__init__.py b/pkgs/clan-cli/clan_lib/network/zerotier/__init__.py index 33034e5ec..2bdf8e352 100644 --- a/pkgs/clan-cli/clan_lib/network/zerotier/__init__.py +++ b/pkgs/clan-cli/clan_lib/network/zerotier/__init__.py @@ -6,6 +6,7 @@ from dataclasses import dataclass from clan_lib.errors import ClanError from clan_lib.network import Network, NetworkTechnologyBase, Peer +from clan_lib.network.zerotier.lib import check_zerotier_running from clan_lib.ssh.remote import Remote log = logging.getLogger(__name__) @@ -14,7 +15,7 @@ log = logging.getLogger(__name__) @dataclass(frozen=True) class NetworkTechnology(NetworkTechnologyBase): def is_running(self) -> bool: - return True + return check_zerotier_running() def ping(self, remote: Remote) -> None | float: if self.is_running(): @@ -32,6 +33,7 @@ class NetworkTechnology(NetworkTechnologyBase): @contextmanager def connection(self, network: Network) -> Iterator[Network]: + # TODO: Implement userspace ZeroTier service start/stop yield network def remote(self, peer: Peer) -> "Remote": diff --git a/pkgs/clan-cli/clan_lib/network/zerotier/lib.py b/pkgs/clan-cli/clan_lib/network/zerotier/lib.py new file mode 100644 index 000000000..c94653e5b --- /dev/null +++ b/pkgs/clan-cli/clan_lib/network/zerotier/lib.py @@ -0,0 +1,27 @@ +import contextlib +import json +import urllib.request +from typing import TypedDict + + +class ZeroTierConfig(TypedDict): + clock: int + online: bool + version: str + versionBuild: int + versionMajor: int + versionMinor: int + versionRev: int + + +def get_zerotier_health() -> ZeroTierConfig: + # Placeholder for actual ZeroTier running check + res = urllib.request.urlopen("http://localhost:9993/health") + return json.load(res) + + +def check_zerotier_running() -> bool: + with contextlib.suppress(urllib.error.URLError): + get_zerotier_health() + return True + return False From 4aff2a9d40e4681051f63dfbd9fbeb77e44b7d8c Mon Sep 17 00:00:00 2001 From: Qubasa Date: Tue, 16 Sep 2025 21:12:52 +0200 Subject: [PATCH 4/4] vars: add machine name to errors --- pkgs/clan-cli/clan_cli/vars/get.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/clan-cli/clan_cli/vars/get.py b/pkgs/clan-cli/clan_cli/vars/get.py index c04e9cbe0..361a7b90e 100644 --- a/pkgs/clan-cli/clan_cli/vars/get.py +++ b/pkgs/clan-cli/clan_cli/vars/get.py @@ -29,10 +29,10 @@ def get_machine_var(machine: Machine, var_id: str) -> Var: if var.id.startswith(var_id): results.append(var) if len(results) == 0: - msg = f"No var found for search string: {var_id}" + msg = f"Couldn't find var: {var_id} for machine: {machine}" raise ClanError(msg) if len(results) > 1: - error = f"Found multiple vars for {var_id}:\n - " + "\n - ".join( + error = f"Found multiple vars in {machine} for {var_id}:\n - " + "\n - ".join( [str(var) for var in results], ) raise ClanError(error)