mirror of
https://github.com/Such-Software/adaptive-multi-protocol-browser.git
synced 2026-10-19 23:38:32 +02:00
Add local Arti provider bootstrap
This commit is contained in:
+14
-2
@@ -1,6 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import socket
|
||||
|
||||
@@ -34,7 +36,7 @@ class TransportAdapter:
|
||||
manage_supported: bool = True
|
||||
|
||||
def inspect(self) -> TransportStatus:
|
||||
installed = self.builtin_running or any(shutil.which(command) for command in self.commands)
|
||||
installed = self.builtin_running or any(_command_available(command) for command in self.commands)
|
||||
running = self.builtin_running or (
|
||||
bool(self.connect_host) and bool(self.connect_port) and _can_connect(self.connect_host, self.connect_port)
|
||||
)
|
||||
@@ -84,7 +86,7 @@ ADAPTERS: dict[str, TransportAdapter] = {
|
||||
start_strategy="start managed Arti SOCKS proxy",
|
||||
stop_policy="stop only AMPB-owned Tor daemon",
|
||||
note="Tor via existing SOCKS proxy or managed Arti/Tor runtime",
|
||||
commands=("arti", "tor"),
|
||||
commands=("ampb-bundled-arti", "arti", "tor"),
|
||||
connect_host="127.0.0.1",
|
||||
connect_port=9050,
|
||||
),
|
||||
@@ -148,6 +150,16 @@ def adapter_for(name: str) -> TransportAdapter | None:
|
||||
return ADAPTERS.get(name)
|
||||
|
||||
|
||||
def _command_available(command: str) -> bool:
|
||||
if command == "ampb-bundled-arti":
|
||||
path = Path(os.environ.get("AMPB_BROWSER_BUILD_ROOT", "/tmp/ampb-browser-build")) / "providers/arti/bin/arti"
|
||||
return path.exists() and path.is_file()
|
||||
if "/" in command:
|
||||
path = Path(command)
|
||||
return path.exists() and path.is_file()
|
||||
return bool(shutil.which(command))
|
||||
|
||||
|
||||
def _can_connect(host: str, port: int) -> bool:
|
||||
try:
|
||||
with socket.create_connection((host, port), timeout=0.2):
|
||||
|
||||
@@ -153,6 +153,7 @@ def _cmd_open(
|
||||
status="ready" if transport_result.ready else transport_result.status,
|
||||
message=transport_result.message,
|
||||
transport_setup_status=transport_result.status,
|
||||
transport_setup_provider=transport_result.provider,
|
||||
transport_setup_message=transport_result.message,
|
||||
)
|
||||
open_plan = execute_open(open_plan, root=Path.cwd())
|
||||
@@ -181,6 +182,7 @@ def _cmd_open(
|
||||
f"runtime_path={runtime_path} "
|
||||
f"user_js_path={user_js_path} "
|
||||
f"transport_setup_status={open_plan.transport_setup_status} "
|
||||
f"transport_setup_provider={open_plan.transport_setup_provider} "
|
||||
f"transport_setup_message=\"{_safe(open_plan.transport_setup_message)}\" "
|
||||
f"launch_command=\"{_safe(launch_command)}\" "
|
||||
f"setup_steps=\"{_safe(setup_steps)}\" "
|
||||
|
||||
@@ -34,6 +34,7 @@ class OpenPlan:
|
||||
message: str
|
||||
launch_spec: BrowserLaunchSpec | None = None
|
||||
transport_setup_status: str = "-"
|
||||
transport_setup_provider: str = "-"
|
||||
transport_setup_message: str = "-"
|
||||
|
||||
|
||||
|
||||
+132
-60
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import json
|
||||
from pathlib import Path
|
||||
import os
|
||||
import shutil
|
||||
@@ -13,9 +14,19 @@ from .adapters import TransportStatus, adapter_for
|
||||
from .config import AppConfig
|
||||
|
||||
|
||||
ARTI_RELATIVE_PATH = Path("providers/arti/bin/arti")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TorProvider:
|
||||
kind: str
|
||||
binary: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ManagedTransportResult:
|
||||
transport: str
|
||||
provider: str
|
||||
status: str
|
||||
endpoint: str
|
||||
owned: bool
|
||||
@@ -39,12 +50,13 @@ def ensure_transport_ready(
|
||||
) -> ManagedTransportResult:
|
||||
adapter = adapter_for(transport)
|
||||
if adapter is None:
|
||||
return ManagedTransportResult(transport, "unsupported", "-", False, 0, "-", (), "transport adapter not found")
|
||||
return ManagedTransportResult(transport, "-", "unsupported", "-", False, 0, "-", (), "transport adapter not found")
|
||||
|
||||
status = status or adapter.inspect()
|
||||
if status.adoptable:
|
||||
return ManagedTransportResult(
|
||||
transport,
|
||||
"-",
|
||||
"ready",
|
||||
status.endpoint,
|
||||
False,
|
||||
@@ -57,6 +69,7 @@ def ensure_transport_ready(
|
||||
if transport != "tor":
|
||||
return ManagedTransportResult(
|
||||
transport,
|
||||
"-",
|
||||
"unsupported",
|
||||
status.endpoint,
|
||||
False,
|
||||
@@ -76,28 +89,130 @@ def _start_tor(
|
||||
endpoint: str,
|
||||
wait_seconds: float,
|
||||
) -> ManagedTransportResult:
|
||||
binary = _transport_binary(config, "tor")
|
||||
provider = _tor_provider(config)
|
||||
state_dir = _managed_state_dir(root, config, "tor")
|
||||
if not binary:
|
||||
if not provider:
|
||||
return ManagedTransportResult(
|
||||
"tor",
|
||||
"-",
|
||||
"missing-provider",
|
||||
endpoint,
|
||||
False,
|
||||
0,
|
||||
state_dir,
|
||||
(),
|
||||
"Tor provider not found; set AMPB_TOR_BIN or transports.tor.binary_path",
|
||||
"Tor provider not found; run tools/browser-tor-provider-build.sh or set AMPB_ARTI_BIN, AMPB_TOR_BIN, or transports.tor.binary_path",
|
||||
)
|
||||
|
||||
state_path = Path(state_dir)
|
||||
data_dir = state_path / "data"
|
||||
log_path = state_path / "tor.log"
|
||||
data_dir.mkdir(parents=True, exist_ok=True)
|
||||
state_path.mkdir(parents=True, exist_ok=True)
|
||||
_chmod_private(state_path)
|
||||
|
||||
command = _tor_command(provider, endpoint=endpoint, state_path=state_path)
|
||||
try:
|
||||
with (state_path / f"{provider.kind}.log").open("ab") as log_file:
|
||||
process = subprocess.Popen(command, stdout=log_file, stderr=log_file, start_new_session=True) # noqa: S603
|
||||
except OSError as exc:
|
||||
return ManagedTransportResult(
|
||||
"tor",
|
||||
provider.kind,
|
||||
"start-failed",
|
||||
endpoint,
|
||||
False,
|
||||
0,
|
||||
state_dir,
|
||||
command,
|
||||
f"managed {provider.kind} could not start: {exc}",
|
||||
)
|
||||
|
||||
if _wait_for_endpoint(endpoint, timeout_seconds=wait_seconds):
|
||||
return ManagedTransportResult(
|
||||
"tor",
|
||||
provider.kind,
|
||||
"started",
|
||||
endpoint,
|
||||
True,
|
||||
process.pid,
|
||||
state_dir,
|
||||
command,
|
||||
f"started managed {provider.kind} transport",
|
||||
)
|
||||
|
||||
process.terminate()
|
||||
return ManagedTransportResult(
|
||||
"tor",
|
||||
provider.kind,
|
||||
"start-timeout",
|
||||
endpoint,
|
||||
True,
|
||||
process.pid,
|
||||
state_dir,
|
||||
command,
|
||||
f"managed {provider.kind} did not become ready at {endpoint}",
|
||||
)
|
||||
|
||||
|
||||
def _tor_provider(config: AppConfig) -> TorProvider | None:
|
||||
arti_path = os.environ.get("AMPB_ARTI_BIN") or _bundled_arti_path()
|
||||
if arti_path:
|
||||
return TorProvider("arti", arti_path)
|
||||
|
||||
tor_path = os.environ.get("AMPB_TOR_BIN")
|
||||
if tor_path:
|
||||
return TorProvider("tor", tor_path)
|
||||
|
||||
config_path = config.transport_binary("tor")
|
||||
if config_path:
|
||||
return TorProvider(_infer_provider_kind(config_path), config_path)
|
||||
|
||||
system_tor = shutil.which("tor")
|
||||
if system_tor:
|
||||
return TorProvider("tor", system_tor)
|
||||
return None
|
||||
|
||||
|
||||
def _bundled_arti_path() -> str:
|
||||
build_root = Path(os.environ.get("AMPB_BROWSER_BUILD_ROOT", "/tmp/ampb-browser-build"))
|
||||
candidate = build_root / ARTI_RELATIVE_PATH
|
||||
if candidate.exists() and candidate.is_file():
|
||||
return str(candidate)
|
||||
return ""
|
||||
|
||||
|
||||
def _infer_provider_kind(binary_path: str) -> str:
|
||||
name = Path(binary_path).name.lower()
|
||||
if name.startswith("arti"):
|
||||
return "arti"
|
||||
return "tor"
|
||||
|
||||
|
||||
def _tor_command(provider: TorProvider, *, endpoint: str, state_path: Path) -> tuple[str, ...]:
|
||||
host, port = _endpoint_host_port(endpoint)
|
||||
command = (
|
||||
binary,
|
||||
if provider.kind == "arti":
|
||||
state_dir = state_path / "arti-state"
|
||||
cache_dir = state_path / "arti-cache"
|
||||
state_dir.mkdir(parents=True, exist_ok=True)
|
||||
cache_dir.mkdir(parents=True, exist_ok=True)
|
||||
_chmod_private(state_dir)
|
||||
_chmod_private(cache_dir)
|
||||
return (
|
||||
provider.binary,
|
||||
"proxy",
|
||||
"-p",
|
||||
str(port),
|
||||
"-o",
|
||||
f"storage.state_dir={json.dumps(str(state_dir))}",
|
||||
"-o",
|
||||
f"storage.cache_dir={json.dumps(str(cache_dir))}",
|
||||
"-l",
|
||||
"warn",
|
||||
)
|
||||
|
||||
data_dir = state_path / "tor-data"
|
||||
data_dir.mkdir(parents=True, exist_ok=True)
|
||||
_chmod_private(data_dir)
|
||||
return (
|
||||
provider.binary,
|
||||
"--SocksPort",
|
||||
f"{host}:{port}",
|
||||
"--DataDirectory",
|
||||
@@ -105,64 +220,21 @@ def _start_tor(
|
||||
"--ClientOnly",
|
||||
"1",
|
||||
"--Log",
|
||||
f"notice file {log_path}",
|
||||
f"notice file {state_path / 'tor.log'}",
|
||||
)
|
||||
try:
|
||||
process = subprocess.Popen(command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) # noqa: S603
|
||||
except OSError as exc:
|
||||
return ManagedTransportResult(
|
||||
"tor",
|
||||
"start-failed",
|
||||
endpoint,
|
||||
False,
|
||||
0,
|
||||
state_dir,
|
||||
command,
|
||||
f"managed Tor could not start: {exc}",
|
||||
)
|
||||
|
||||
if _wait_for_endpoint(endpoint, timeout_seconds=wait_seconds):
|
||||
return ManagedTransportResult(
|
||||
"tor",
|
||||
"started",
|
||||
endpoint,
|
||||
True,
|
||||
process.pid,
|
||||
state_dir,
|
||||
command,
|
||||
"started managed Tor transport",
|
||||
)
|
||||
|
||||
process.terminate()
|
||||
return ManagedTransportResult(
|
||||
"tor",
|
||||
"start-timeout",
|
||||
endpoint,
|
||||
True,
|
||||
process.pid,
|
||||
state_dir,
|
||||
command,
|
||||
f"managed Tor did not become ready at {endpoint}",
|
||||
)
|
||||
|
||||
|
||||
def _transport_binary(config: AppConfig, transport: str) -> str:
|
||||
env_name = f"AMPB_{transport.upper()}_BIN"
|
||||
env_path = os.environ.get(env_name)
|
||||
if env_path:
|
||||
return env_path
|
||||
config_path = config.transport_binary(transport)
|
||||
if config_path:
|
||||
return config_path
|
||||
if transport == "tor":
|
||||
return shutil.which("tor") or ""
|
||||
return ""
|
||||
|
||||
|
||||
def _managed_state_dir(root: Path, config: AppConfig, transport: str) -> str:
|
||||
return str(root / config.state_dir / "transports" / transport)
|
||||
|
||||
|
||||
def _chmod_private(path: Path) -> None:
|
||||
try:
|
||||
path.chmod(0o700)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def _wait_for_endpoint(endpoint: str, *, timeout_seconds: float) -> bool:
|
||||
deadline = time.monotonic() + timeout_seconds
|
||||
while time.monotonic() <= deadline:
|
||||
|
||||
Reference in New Issue
Block a user