From 648d95b248a4689693418da549840940ad1f1362 Mon Sep 17 00:00:00 2001 From: Qubasa Date: Tue, 17 Dec 2024 12:13:07 +0100 Subject: [PATCH] clan-cli: Make clan ssh automatically start tor --- pkgs/clan-cli/clan_cli/ssh/deploy_info.py | 21 +++++------ pkgs/clan-cli/clan_cli/ssh/host.py | 1 + pkgs/clan-cli/clan_cli/ssh/tor.py | 44 +++++++++++++++++++---- 3 files changed, 46 insertions(+), 20 deletions(-) diff --git a/pkgs/clan-cli/clan_cli/ssh/deploy_info.py b/pkgs/clan-cli/clan_cli/ssh/deploy_info.py index 17ecd60f2..84ea5f834 100644 --- a/pkgs/clan-cli/clan_cli/ssh/deploy_info.py +++ b/pkgs/clan-cli/clan_cli/ssh/deploy_info.py @@ -2,16 +2,16 @@ import argparse import ipaddress import json import logging -import shlex from dataclasses import dataclass from pathlib import Path from typing import Any +from clan_cli.async_run import AsyncRuntime from clan_cli.cmd import run -from clan_cli.errors import ClanError, TorConnectionError, TorSocksError +from clan_cli.errors import ClanError from clan_cli.nix import nix_shell from clan_cli.ssh.host import Host, is_ssh_reachable -from clan_cli.ssh.tor import TorTarget, ssh_tor_reachable +from clan_cli.ssh.tor import TorTarget, spawn_tor, ssh_tor_reachable log = logging.getLogger(__name__) @@ -63,12 +63,13 @@ def parse_qr_code(picture_file: Path) -> DeployInfo: return DeployInfo.from_json(json.loads(data)) -def ssh_shell_from_deploy(deploy_info: DeployInfo) -> None: +def ssh_shell_from_deploy(deploy_info: DeployInfo, runtime: AsyncRuntime) -> None: if host := find_reachable_host(deploy_info): host.connect_ssh_shell(password=deploy_info.pwd) else: log.info("Could not reach host via clearnet 'addrs'") log.info(f"Trying to reach host via tor '{deploy_info.tor}'") + spawn_tor(runtime) if not deploy_info.tor: msg = "No tor address provided, please provide a tor address." raise ClanError(msg) @@ -98,15 +99,9 @@ def ssh_command(args: argparse.Namespace) -> None: if not deploy_info: msg = "No --json or --png data provided" raise ClanError(msg) - try: - ssh_shell_from_deploy(deploy_info) - except TorSocksError as ex: - log.error(ex) - tor_cmd = nix_shell(["nixpkgs#tor"], ["tor"]) - log.error("Is Tor running? If not, you can start it by running:") - log.error(f"{' '.join(shlex.quote(arg) for arg in tor_cmd)}") - except TorConnectionError: - log.error("The onion address is not reachable via Tor.") + + with AsyncRuntime() as runtime: + ssh_shell_from_deploy(deploy_info, runtime) def register_parser(parser: argparse.ArgumentParser) -> None: diff --git a/pkgs/clan-cli/clan_cli/ssh/host.py b/pkgs/clan-cli/clan_cli/ssh/host.py index 688fd92f9..ed7b6f778 100644 --- a/pkgs/clan-cli/clan_cli/ssh/host.py +++ b/pkgs/clan-cli/clan_cli/ssh/host.py @@ -185,6 +185,7 @@ class Host: ssh_opts.extend(["-i", self.key]) if tor_socks: + packages.append("nixpkgs#netcat") ssh_opts.append("-o") ssh_opts.append("ProxyCommand=nc -x 127.0.0.1:9050 -X 5 %h %p") diff --git a/pkgs/clan-cli/clan_cli/ssh/tor.py b/pkgs/clan-cli/clan_cli/ssh/tor.py index f323c81ac..bfe4adbe4 100755 --- a/pkgs/clan-cli/clan_cli/ssh/tor.py +++ b/pkgs/clan-cli/clan_cli/ssh/tor.py @@ -4,9 +4,11 @@ import argparse import logging import socket import struct +import time from dataclasses import dataclass -from clan_cli.cmd import run +from clan_cli.async_run import AsyncRuntime +from clan_cli.cmd import Log, RunOpts, run from clan_cli.errors import TorConnectionError, TorSocksError from clan_cli.nix import nix_shell @@ -96,14 +98,42 @@ def fetch_onion_content(target: TorTarget) -> str: return response.decode("utf-8", errors="replace") -def spawn_tor() -> None: +def is_tor_running() -> bool: + """Checks if Tor is online.""" + try: + tor_online_test() + except TorSocksError: + return False + else: + return True + + +def spawn_tor(runtime: AsyncRuntime) -> None: """ - Spawns a Tor process using `nix-shell`. + Spawns a Tor process using `nix-shell` if Tor is not already running. """ - cmd_args = ["tor"] - packages = ["nixpkgs#tor"] - cmd = nix_shell(packages, cmd_args) - run(cmd) + + def start_tor() -> None: + """Starts Tor process using nix-shell.""" + cmd_args = ["tor", "--HardwareAccel", "1"] + packages = ["nixpkgs#tor"] + cmd = nix_shell(packages, cmd_args) + runtime.async_run(None, run, cmd, RunOpts(log=Log.BOTH)) + log.debug("Attempting to start Tor") + + # Check if Tor is already running + if is_tor_running(): + log.info("Tor is running") + return + + # Attempt to start Tor + start_tor() + + # Continuously check if Tor has started + while not is_tor_running(): + log.debug("Waiting for Tor to start...") + time.sleep(0.2) + log.info("Tor is now running") def tor_online_test() -> bool: