mirror of
https://github.com/Such-Software/adaptive-multi-protocol-browser.git
synced 2026-10-18 23:08:33 +02:00
Model browser transport provider sources
This commit is contained in:
@@ -218,6 +218,7 @@ def _print_open_plan(prefix: str, open_plan, extra_fields: tuple[str, ...] = ())
|
||||
f"setup_prompt_approval_command=\"{_safe(open_plan.setup_prompt_approval_command)}\" "
|
||||
f"transport_setup_status={open_plan.transport_setup_status} "
|
||||
f"transport_setup_provider={open_plan.transport_setup_provider} "
|
||||
f"transport_setup_provider_source={open_plan.transport_setup_provider_source} "
|
||||
f"transport_setup_owned={str(open_plan.transport_setup_owned).lower()} "
|
||||
f"transport_setup_pid={open_plan.transport_setup_pid} "
|
||||
f"transport_setup_endpoint={open_plan.transport_setup_endpoint} "
|
||||
@@ -259,6 +260,7 @@ def _cmd_transport(args) -> int:
|
||||
"AMPBROWSER_TRANSPORT "
|
||||
f"transport={result.transport} "
|
||||
f"provider={result.provider} "
|
||||
f"provider_source={result.provider_source} "
|
||||
f"status={result.status} "
|
||||
f"ready={str(result.ready).lower()} "
|
||||
f"owned={str(result.owned).lower()} "
|
||||
|
||||
@@ -5,6 +5,7 @@ from pathlib import Path
|
||||
from .adapters import ADAPTERS
|
||||
from .candidates import CANDIDATE_TRANSPORTS
|
||||
from .metadata import BROWSER_BACKENDS, ROUTE_RULES, TRANSPORT_DEFINITIONS
|
||||
from .metadata import PROVIDER_SOURCE_DEFINITIONS, TRANSPORT_PROVIDER_DEFINITIONS
|
||||
from .platforms import PLATFORM_CAPABILITIES
|
||||
|
||||
|
||||
@@ -19,6 +20,7 @@ def generate_docs(root: Path, *, check: bool = False) -> list[Path]:
|
||||
GENERATED_DIR / "candidate-transports.md": _candidate_transports_doc(),
|
||||
GENERATED_DIR / "platform-capabilities.md": _platform_capabilities_doc(),
|
||||
GENERATED_DIR / "browser-strategy.md": _browser_strategy_doc(),
|
||||
GENERATED_DIR / "provider-sources.md": _provider_sources_doc(),
|
||||
}
|
||||
changed: list[Path] = []
|
||||
for rel_path, content in docs.items():
|
||||
@@ -118,3 +120,28 @@ def _browser_strategy_doc() -> str:
|
||||
f"{backend.launch_mode} | {backend.privacy_posture} | {backend.note} |"
|
||||
)
|
||||
return _header("Generated Browser Strategy") + "\n".join(rows) + "\n"
|
||||
|
||||
|
||||
def _provider_sources_doc() -> str:
|
||||
sections = [_header("Generated Provider Sources")]
|
||||
sections.append("## Source Types\n")
|
||||
sections.append("| Source | Discovery | Lifecycle | Platforms | Note |")
|
||||
sections.append("| --- | --- | --- | --- | --- |")
|
||||
for source in PROVIDER_SOURCE_DEFINITIONS:
|
||||
sections.append(
|
||||
f"| `{source.source}` | {source.discovery} | {source.lifecycle} | "
|
||||
f"{source.platforms} | {source.note} |"
|
||||
)
|
||||
sections.append("")
|
||||
|
||||
sections.append("## Transport Providers\n")
|
||||
sections.append("| Transport | Provider | Sources | Endpoint | Status | Note |")
|
||||
sections.append("| --- | --- | --- | --- | --- | --- |")
|
||||
for provider in TRANSPORT_PROVIDER_DEFINITIONS:
|
||||
sources = ", ".join(f"`{source}`" for source in provider.sources)
|
||||
sections.append(
|
||||
f"| `{provider.transport}` | `{provider.provider}` | {sources} | "
|
||||
f"`{provider.endpoint}` | `{provider.status}` | {provider.note} |"
|
||||
)
|
||||
sections.append("")
|
||||
return "\n".join(sections)
|
||||
|
||||
@@ -41,6 +41,7 @@ class OpenPlan:
|
||||
setup_prompt_approval_command: str = "-"
|
||||
transport_setup_status: str = "-"
|
||||
transport_setup_provider: str = "-"
|
||||
transport_setup_provider_source: str = "-"
|
||||
transport_setup_owned: bool = False
|
||||
transport_setup_pid: int = 0
|
||||
transport_setup_endpoint: str = "-"
|
||||
|
||||
@@ -31,6 +31,25 @@ class BrowserBackend:
|
||||
note: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ProviderSourceDefinition:
|
||||
source: str
|
||||
discovery: str
|
||||
lifecycle: str
|
||||
platforms: str
|
||||
note: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TransportProviderDefinition:
|
||||
transport: str
|
||||
provider: str
|
||||
sources: tuple[str, ...]
|
||||
endpoint: str
|
||||
status: str
|
||||
note: str
|
||||
|
||||
|
||||
ROUTE_RULES = (
|
||||
RouteRule("*.onion", "tor", "tor", "route through Tor SOCKS"),
|
||||
RouteRule("*.i2p", "i2p", "i2p", "route through I2P HTTP proxy"),
|
||||
@@ -125,3 +144,91 @@ BROWSER_BACKENDS = (
|
||||
"Used for Gemini and similar lightweight document transports.",
|
||||
),
|
||||
)
|
||||
|
||||
PROVIDER_SOURCE_DEFINITIONS = (
|
||||
ProviderSourceDefinition(
|
||||
"configured",
|
||||
"explicit config or environment variable",
|
||||
"operator override; AMPB owns profile policy but not the binary provenance",
|
||||
"desktop, android, ios",
|
||||
"Used for development, custom bundles, and reviewed advanced installs.",
|
||||
),
|
||||
ProviderSourceDefinition(
|
||||
"bundled-sidecar",
|
||||
"provider binary shipped with AMPB or the build workspace",
|
||||
"AMPB-owned process and isolated state",
|
||||
"desktop, android",
|
||||
"Default desktop target for Tor, I2P, IPFS, Reticulum, and similar daemon-style transports.",
|
||||
),
|
||||
ProviderSourceDefinition(
|
||||
"embedded-library",
|
||||
"transport library linked into the app process",
|
||||
"AMPB-owned foreground session and app-private state",
|
||||
"android, ios",
|
||||
"Preferred mobile shape when platform policy makes sidecar daemons awkward.",
|
||||
),
|
||||
ProviderSourceDefinition(
|
||||
"system-adopted",
|
||||
"healthy local proxy or service already running",
|
||||
"operator-owned service; AMPB only routes browser traffic to it",
|
||||
"desktop, android",
|
||||
"Keeps power users and existing deployments working without duplicate daemons.",
|
||||
),
|
||||
ProviderSourceDefinition(
|
||||
"system-package",
|
||||
"known package manager install such as brew, apt, pkg, or platform provider",
|
||||
"installed only after user consent, then run with AMPB-owned state",
|
||||
"desktop, android",
|
||||
"Fallback when the app did not ship that provider yet.",
|
||||
),
|
||||
ProviderSourceDefinition(
|
||||
"builtin-renderer",
|
||||
"browser-native renderer or fetch path",
|
||||
"AMPB-owned code path with no external daemon",
|
||||
"desktop, android, ios",
|
||||
"Used for Gemini and other lightweight document transports.",
|
||||
),
|
||||
)
|
||||
|
||||
TRANSPORT_PROVIDER_DEFINITIONS = (
|
||||
TransportProviderDefinition(
|
||||
"tor",
|
||||
"arti/tor",
|
||||
("configured", "bundled-sidecar", "embedded-library", "system-adopted", "system-package"),
|
||||
"socks5://127.0.0.1:9050",
|
||||
"active",
|
||||
"Desktop uses managed Arti/Tor sidecars today; mobile should prefer embedded or app-owned foreground providers.",
|
||||
),
|
||||
TransportProviderDefinition(
|
||||
"i2p",
|
||||
"i2pd/i2p-router",
|
||||
("configured", "bundled-sidecar", "embedded-library", "system-adopted", "system-package"),
|
||||
"http://127.0.0.1:4444",
|
||||
"active",
|
||||
"Desktop can adopt or manage i2pd; mobile provider packaging is planned behind the same contract.",
|
||||
),
|
||||
TransportProviderDefinition(
|
||||
"gemini",
|
||||
"gemini-native-viewer",
|
||||
("builtin-renderer",),
|
||||
"builtin://gemtext-renderer",
|
||||
"planned",
|
||||
"No daemon is required once the built-in fetch/render path exists.",
|
||||
),
|
||||
TransportProviderDefinition(
|
||||
"ipfs",
|
||||
"kubo/ipfs",
|
||||
("configured", "bundled-sidecar", "system-adopted", "system-package"),
|
||||
"http://127.0.0.1:8080",
|
||||
"planned",
|
||||
"Content-addressed browsing uses the same provider lifecycle without treating IPFS as anonymity.",
|
||||
),
|
||||
TransportProviderDefinition(
|
||||
"reticulum",
|
||||
"rnsd/rnstatus",
|
||||
("configured", "bundled-sidecar", "system-adopted", "system-package"),
|
||||
"rns://local",
|
||||
"planned",
|
||||
"Reticulum browsing may still need operator-owned physical or link-layer interface setup.",
|
||||
),
|
||||
)
|
||||
|
||||
@@ -23,6 +23,7 @@ def launch_open_plan(open_plan: OpenPlan, *, config: AppConfig, root: Path) -> O
|
||||
message=transport_result.message,
|
||||
transport_setup_status=transport_result.status,
|
||||
transport_setup_provider=transport_result.provider,
|
||||
transport_setup_provider_source=transport_result.provider_source,
|
||||
transport_setup_owned=transport_result.owned,
|
||||
transport_setup_pid=transport_result.pid,
|
||||
transport_setup_endpoint=transport_result.endpoint,
|
||||
|
||||
@@ -16,6 +16,7 @@ from .config import AppConfig
|
||||
|
||||
|
||||
ARTI_RELATIVE_PATH = Path("providers/arti/bin/arti")
|
||||
I2PD_RELATIVE_PATH = Path("providers/i2pd/bin/i2pd")
|
||||
OWNED_STATE_FILE = "ampb-owned.json"
|
||||
|
||||
|
||||
@@ -23,12 +24,14 @@ OWNED_STATE_FILE = "ampb-owned.json"
|
||||
class TorProvider:
|
||||
kind: str
|
||||
binary: str
|
||||
source: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class I2PProvider:
|
||||
kind: str
|
||||
binary: str
|
||||
source: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -42,6 +45,7 @@ class ManagedTransportResult:
|
||||
state_dir: str
|
||||
command: tuple[str, ...]
|
||||
message: str
|
||||
provider_source: str = "-"
|
||||
|
||||
@property
|
||||
def ready(self) -> bool:
|
||||
@@ -76,6 +80,7 @@ def ensure_transport_ready(
|
||||
_managed_state_dir(root, config, transport),
|
||||
(),
|
||||
f"adopted existing {transport} transport",
|
||||
provider_source="system-adopted",
|
||||
)
|
||||
|
||||
if transport == "tor":
|
||||
@@ -122,6 +127,7 @@ def transport_status(
|
||||
_managed_state_dir(root, config, transport),
|
||||
(),
|
||||
f"{transport} transport is running but not AMPB-owned",
|
||||
provider_source="system-adopted",
|
||||
)
|
||||
|
||||
return ManagedTransportResult(
|
||||
@@ -149,6 +155,7 @@ def stop_managed_transport(
|
||||
endpoint = str(state.get("endpoint") or _endpoint_for(transport))
|
||||
state_dir = _managed_state_dir(root, config, transport)
|
||||
provider = str(state.get("provider") or "-")
|
||||
provider_source = str(state.get("provider_source") or "-")
|
||||
command = tuple(str(part) for part in state.get("command", ()))
|
||||
pid = _state_pid(state)
|
||||
|
||||
@@ -163,6 +170,7 @@ def stop_managed_transport(
|
||||
state_dir,
|
||||
(),
|
||||
f"no AMPB-owned {transport} process is recorded",
|
||||
provider_source=provider_source,
|
||||
)
|
||||
|
||||
if not _pid_alive(pid):
|
||||
@@ -181,6 +189,7 @@ def stop_managed_transport(
|
||||
if cleaned
|
||||
else f"could not remove stale AMPB-owned {transport} state"
|
||||
),
|
||||
provider_source=provider_source,
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -196,6 +205,7 @@ def stop_managed_transport(
|
||||
state_dir,
|
||||
command,
|
||||
f"could not stop AMPB-owned {transport}: {exc}",
|
||||
provider_source=provider_source,
|
||||
)
|
||||
|
||||
deadline = time.monotonic() + wait_seconds
|
||||
@@ -216,6 +226,7 @@ def stop_managed_transport(
|
||||
if cleaned
|
||||
else f"stopped AMPB-owned {transport} but could not remove state"
|
||||
),
|
||||
provider_source=provider_source,
|
||||
)
|
||||
time.sleep(0.1)
|
||||
|
||||
@@ -229,6 +240,7 @@ def stop_managed_transport(
|
||||
state_dir,
|
||||
command,
|
||||
f"AMPB-owned {transport} did not stop before timeout",
|
||||
provider_source=provider_source,
|
||||
)
|
||||
|
||||
|
||||
@@ -273,6 +285,7 @@ def _start_tor(
|
||||
state_dir,
|
||||
command,
|
||||
f"managed {provider.kind} could not start: {exc}",
|
||||
provider_source=provider.source,
|
||||
)
|
||||
|
||||
if _wait_for_endpoint(endpoint, timeout_seconds=wait_seconds):
|
||||
@@ -286,6 +299,7 @@ def _start_tor(
|
||||
state_dir,
|
||||
command,
|
||||
f"started managed {provider.kind} transport",
|
||||
provider_source=provider.source,
|
||||
)
|
||||
_write_owned_state(root, config, result)
|
||||
return result
|
||||
@@ -301,31 +315,49 @@ def _start_tor(
|
||||
state_dir,
|
||||
command,
|
||||
f"managed {provider.kind} did not become ready at {endpoint}",
|
||||
provider_source=provider.source,
|
||||
)
|
||||
|
||||
|
||||
def _tor_provider(config: AppConfig) -> TorProvider | None:
|
||||
arti_path = os.environ.get("AMPB_ARTI_BIN") or _bundled_arti_path()
|
||||
arti_path = os.environ.get("AMPB_ARTI_BIN")
|
||||
if arti_path:
|
||||
return TorProvider("arti", arti_path)
|
||||
return TorProvider("arti", arti_path, "configured")
|
||||
|
||||
tor_path = os.environ.get("AMPB_TOR_BIN")
|
||||
if tor_path:
|
||||
return TorProvider("tor", tor_path)
|
||||
return TorProvider("tor", tor_path, "configured")
|
||||
|
||||
config_path = config.transport_binary("tor")
|
||||
if config_path:
|
||||
return TorProvider(_infer_provider_kind(config_path), config_path)
|
||||
return TorProvider(_infer_provider_kind(config_path), config_path, "configured")
|
||||
|
||||
bundled_arti = _bundled_arti_path()
|
||||
if bundled_arti:
|
||||
return TorProvider("arti", bundled_arti, "bundled-sidecar")
|
||||
|
||||
system_tor = shutil.which("tor")
|
||||
if system_tor:
|
||||
return TorProvider("tor", system_tor)
|
||||
return TorProvider("tor", system_tor, "system-package")
|
||||
return None
|
||||
|
||||
|
||||
def _bundled_arti_path() -> str:
|
||||
return _bundled_provider_path(ARTI_RELATIVE_PATH)
|
||||
|
||||
|
||||
def _bundled_i2pd_path() -> str:
|
||||
return _bundled_provider_path(I2PD_RELATIVE_PATH)
|
||||
|
||||
|
||||
def _bundled_provider_path(relative_path: Path) -> str:
|
||||
provider_root = os.environ.get("AMPB_PROVIDER_ROOT")
|
||||
if provider_root:
|
||||
candidate = Path(provider_root) / relative_path
|
||||
if candidate.exists() and candidate.is_file():
|
||||
return str(candidate)
|
||||
build_root = Path(os.environ.get("AMPB_BROWSER_BUILD_ROOT", "/tmp/ampb-browser-build"))
|
||||
candidate = build_root / ARTI_RELATIVE_PATH
|
||||
candidate = build_root / relative_path
|
||||
if candidate.exists() and candidate.is_file():
|
||||
return str(candidate)
|
||||
return ""
|
||||
@@ -418,6 +450,7 @@ def _start_i2p(
|
||||
state_dir,
|
||||
command,
|
||||
f"managed {provider.kind} could not start: {exc}",
|
||||
provider_source=provider.source,
|
||||
)
|
||||
|
||||
if _wait_for_endpoint(endpoint, timeout_seconds=wait_seconds):
|
||||
@@ -431,6 +464,7 @@ def _start_i2p(
|
||||
state_dir,
|
||||
command,
|
||||
f"started managed {provider.kind} transport",
|
||||
provider_source=provider.source,
|
||||
)
|
||||
_write_owned_state(root, config, result)
|
||||
return result
|
||||
@@ -446,25 +480,30 @@ def _start_i2p(
|
||||
state_dir,
|
||||
command,
|
||||
f"managed {provider.kind} did not become ready at {endpoint}",
|
||||
provider_source=provider.source,
|
||||
)
|
||||
|
||||
|
||||
def _i2p_provider(config: AppConfig) -> I2PProvider | None:
|
||||
env_path = os.environ.get("AMPB_I2PD_BIN")
|
||||
if env_path:
|
||||
return I2PProvider("i2pd", env_path)
|
||||
return I2PProvider("i2pd", env_path, "configured")
|
||||
|
||||
config_path = config.transport_binary("i2p")
|
||||
if config_path:
|
||||
return I2PProvider("i2pd", config_path)
|
||||
return I2PProvider("i2pd", config_path, "configured")
|
||||
|
||||
bundled_i2pd = _bundled_i2pd_path()
|
||||
if bundled_i2pd:
|
||||
return I2PProvider("i2pd", bundled_i2pd, "bundled-sidecar")
|
||||
|
||||
system_i2pd = shutil.which("i2pd")
|
||||
if system_i2pd:
|
||||
return I2PProvider("i2pd", system_i2pd)
|
||||
return I2PProvider("i2pd", system_i2pd, "system-package")
|
||||
|
||||
homebrew_i2pd = _homebrew_i2pd_path()
|
||||
if homebrew_i2pd:
|
||||
return I2PProvider("i2pd", homebrew_i2pd)
|
||||
return I2PProvider("i2pd", homebrew_i2pd, "system-package")
|
||||
return None
|
||||
|
||||
|
||||
@@ -575,6 +614,7 @@ def _write_owned_state(root: Path, config: AppConfig, result: ManagedTransportRe
|
||||
"pid": result.pid,
|
||||
"state_dir": result.state_dir,
|
||||
"command": list(result.command),
|
||||
"provider_source": result.provider_source,
|
||||
"started_at": int(time.time()),
|
||||
}
|
||||
state_path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||
@@ -595,6 +635,7 @@ def _owned_transport_result(
|
||||
|
||||
pid = _state_pid(state)
|
||||
provider = str(state.get("provider") or "-")
|
||||
provider_source = str(state.get("provider_source") or "-")
|
||||
endpoint = str(state.get("endpoint") or _endpoint_for(transport))
|
||||
state_dir = str(state.get("state_dir") or _managed_state_dir(root, config, transport))
|
||||
command = tuple(str(part) for part in state.get("command", ()))
|
||||
@@ -615,6 +656,7 @@ def _owned_transport_result(
|
||||
if cleaned
|
||||
else f"could not remove stale AMPB-owned {transport} state"
|
||||
),
|
||||
provider_source=provider_source,
|
||||
)
|
||||
|
||||
if _wait_for_endpoint(endpoint, timeout_seconds=wait_seconds):
|
||||
@@ -628,6 +670,7 @@ def _owned_transport_result(
|
||||
state_dir,
|
||||
command,
|
||||
f"using running AMPB-owned {transport}",
|
||||
provider_source=provider_source,
|
||||
)
|
||||
|
||||
return ManagedTransportResult(
|
||||
@@ -640,6 +683,7 @@ def _owned_transport_result(
|
||||
state_dir,
|
||||
command,
|
||||
f"AMPB-owned {transport} process is running but endpoint is not ready",
|
||||
provider_source=provider_source,
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user