mirror of
https://github.com/Such-Software/adaptive-multi-protocol-browser.git
synced 2026-10-17 14:31:46 +02:00
Harden browser docs and transport recovery
This commit is contained in:
+9
-1
@@ -15,7 +15,7 @@ from .plan import plan_url
|
||||
from .platforms import PLATFORM_CHOICES
|
||||
from .route_helper import serve_route_helper
|
||||
from .routing import route_url
|
||||
from .transport_manager import ensure_transport_ready, stop_managed_transport, transport_status
|
||||
from .transport_manager import ensure_transport_ready, repair_managed_transport, stop_managed_transport, transport_status
|
||||
from .transports import inspect_transports
|
||||
|
||||
|
||||
@@ -80,6 +80,9 @@ def main(argv: list[str] | None = None) -> int:
|
||||
transport_stop_parser = transport_subcommands.add_parser("stop", help="Stop an AMPB-owned transport.")
|
||||
transport_stop_parser.add_argument("transport")
|
||||
transport_stop_parser.add_argument("--config", type=Path, help="Path to an AMPB config file.")
|
||||
transport_repair_parser = transport_subcommands.add_parser("repair", help="Reset AMPB-owned runtime state for a transport.")
|
||||
transport_repair_parser.add_argument("transport")
|
||||
transport_repair_parser.add_argument("--config", type=Path, help="Path to an AMPB config file.")
|
||||
|
||||
fixture_parser = subcommands.add_parser("fixture", help="Check AMPG fixture manifests.")
|
||||
fixture_subcommands = fixture_parser.add_subparsers(dest="fixture_command", required=True)
|
||||
@@ -286,9 +289,12 @@ def _cmd_transport(args) -> int:
|
||||
result = ensure_transport_ready(args.transport, config=config, root=Path.cwd())
|
||||
elif args.transport_command == "stop":
|
||||
result = stop_managed_transport(args.transport, config=config, root=Path.cwd())
|
||||
elif args.transport_command == "repair":
|
||||
result = repair_managed_transport(args.transport, config=config, root=Path.cwd())
|
||||
else:
|
||||
return 1
|
||||
command = shlex.join(result.command) if result.command else "-"
|
||||
install_command = shlex.join(result.install_command) if result.install_command else "-"
|
||||
print(
|
||||
"AMPBROWSER_TRANSPORT "
|
||||
f"transport={result.transport} "
|
||||
@@ -301,6 +307,8 @@ def _cmd_transport(args) -> int:
|
||||
f"endpoint={result.endpoint} "
|
||||
f"state_dir={result.state_dir} "
|
||||
f"command=\"{_safe(command)}\" "
|
||||
f"install_command=\"{_safe(install_command)}\" "
|
||||
f"setup_hint=\"{_safe(result.setup_hint)}\" "
|
||||
f"message=\"{_safe(result.message)}\""
|
||||
)
|
||||
return 0
|
||||
|
||||
+22
-2
@@ -549,8 +549,9 @@ def _route_helper_background_js() -> str:
|
||||
" }",
|
||||
" if (status.ready) return;",
|
||||
" pendingByTab.set(details.tabId, details.url);",
|
||||
" const installCommand = Array.isArray(status.install_command) ? status.install_command.join(' ') : '';",
|
||||
" const setupUrl = browser.runtime.getURL(",
|
||||
" `setup.html?transport=${encodeURIComponent(transport)}&url=${encodeURIComponent(details.url)}&message=${encodeURIComponent(status.message || '')}`",
|
||||
" `setup.html?transport=${encodeURIComponent(transport)}&url=${encodeURIComponent(details.url)}&message=${encodeURIComponent(status.message || '')}&hint=${encodeURIComponent(status.setup_hint || '')}&install=${encodeURIComponent(installCommand)}`",
|
||||
" );",
|
||||
" await browser.tabs.update(details.tabId, { url: setupUrl });",
|
||||
"}",
|
||||
@@ -584,6 +585,7 @@ def _route_helper_setup_html() -> str:
|
||||
' <p id="body">AMPB needs to start a local transport before opening this route.</p>',
|
||||
' <pre id="url"></pre>',
|
||||
' <p id="message" class="message"></p>',
|
||||
' <pre id="install-command" class="install-command" hidden></pre>',
|
||||
' <button id="approve" type="button">Start and open</button>',
|
||||
' <p id="status" class="status"></p>',
|
||||
" </main>",
|
||||
@@ -604,13 +606,27 @@ def _route_helper_setup_js() -> str:
|
||||
"const transport = params.get('transport') || '';",
|
||||
"const url = params.get('url') || '';",
|
||||
"const message = params.get('message') || '';",
|
||||
"const hint = params.get('hint') || '';",
|
||||
"const install = params.get('install') || '';",
|
||||
"const label = transport === 'tor' ? 'Tor' : transport === 'i2p' ? 'I2P' : transport;",
|
||||
"",
|
||||
"function showInstallCommand(value) {",
|
||||
" const installCommand = document.getElementById('install-command');",
|
||||
" if (!value) {",
|
||||
" installCommand.hidden = true;",
|
||||
" installCommand.textContent = '';",
|
||||
" return;",
|
||||
" }",
|
||||
" installCommand.hidden = false;",
|
||||
" installCommand.textContent = value;",
|
||||
"}",
|
||||
"",
|
||||
"document.getElementById('title').textContent = `Set up ${label}?`;",
|
||||
"document.getElementById('body').textContent = `AMPB can start a managed ${label} session and keep its state isolated.`;",
|
||||
"document.getElementById('url').textContent = url;",
|
||||
"document.getElementById('message').textContent = message;",
|
||||
"document.getElementById('message').textContent = hint || message;",
|
||||
"document.getElementById('approve').textContent = `Start ${label} and open`;",
|
||||
"showInstallCommand(install);",
|
||||
"",
|
||||
"document.getElementById('approve').addEventListener('click', async () => {",
|
||||
" const status = document.getElementById('status');",
|
||||
@@ -621,6 +637,9 @@ def _route_helper_setup_js() -> str:
|
||||
" window.location.href = url;",
|
||||
" return;",
|
||||
" }",
|
||||
" if (result && Array.isArray(result.install_command)) {",
|
||||
" showInstallCommand(result.install_command.join(' '));",
|
||||
" }",
|
||||
" status.textContent = (result && result.message) || `${label} did not become ready.`;",
|
||||
"});",
|
||||
"",
|
||||
@@ -637,6 +656,7 @@ def _route_helper_setup_css() -> str:
|
||||
".eyebrow { color: #36d1c4; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }",
|
||||
"h1 { font-size: 2rem; margin: 0 0 12px; }",
|
||||
"pre { white-space: pre-wrap; overflow-wrap: anywhere; padding: 12px; border: 1px solid #444; }",
|
||||
".install-command { background: #1d1d1d; border-color: #36d1c4; color: #f7f1e8; }",
|
||||
"button { font: inherit; font-weight: 700; padding: 12px 16px; border: 0; background: #36d1c4; color: #111; }",
|
||||
".message, .status { color: #cfc7bb; }",
|
||||
"",
|
||||
|
||||
@@ -126,6 +126,8 @@ def _result_response(result: ManagedTransportResult) -> dict[str, Any]:
|
||||
"state_dir": result.state_dir,
|
||||
"message": result.message,
|
||||
"command": list(result.command),
|
||||
"setup_hint": result.setup_hint,
|
||||
"install_command": list(result.install_command),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import signal
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from urllib.parse import urlparse
|
||||
|
||||
@@ -34,6 +35,12 @@ class I2PProvider:
|
||||
source: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SetupHint:
|
||||
message: str
|
||||
command: tuple[str, ...] = ()
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ManagedTransportResult:
|
||||
transport: str
|
||||
@@ -46,6 +53,8 @@ class ManagedTransportResult:
|
||||
command: tuple[str, ...]
|
||||
message: str
|
||||
provider_source: str = "-"
|
||||
setup_hint: str = "-"
|
||||
install_command: tuple[str, ...] = ()
|
||||
|
||||
@property
|
||||
def ready(self) -> bool:
|
||||
@@ -140,6 +149,8 @@ def transport_status(
|
||||
_managed_state_dir(root, config, transport),
|
||||
(),
|
||||
f"{transport} transport is not running",
|
||||
setup_hint=_stopped_setup_hint(transport, config),
|
||||
install_command=_stopped_install_command(transport, config),
|
||||
)
|
||||
|
||||
|
||||
@@ -244,6 +255,66 @@ def stop_managed_transport(
|
||||
)
|
||||
|
||||
|
||||
def repair_managed_transport(
|
||||
transport: str,
|
||||
*,
|
||||
config: AppConfig,
|
||||
root: Path,
|
||||
wait_seconds: float = 5.0,
|
||||
) -> ManagedTransportResult:
|
||||
adapter = adapter_for(transport)
|
||||
if adapter is None:
|
||||
return ManagedTransportResult(transport, "-", "unsupported", "-", False, 0, "-", (), "transport adapter not found")
|
||||
|
||||
state_dir = _managed_state_dir(root, config, transport)
|
||||
state_path = Path(state_dir)
|
||||
stop_result = stop_managed_transport(transport, config=config, root=root, wait_seconds=wait_seconds)
|
||||
if stop_result.status in {"stop-failed", "stop-timeout"}:
|
||||
return ManagedTransportResult(
|
||||
transport,
|
||||
stop_result.provider,
|
||||
"repair-blocked",
|
||||
stop_result.endpoint,
|
||||
stop_result.owned,
|
||||
stop_result.pid,
|
||||
state_dir,
|
||||
stop_result.command,
|
||||
f"could not repair AMPB-owned {transport}: {stop_result.message}",
|
||||
provider_source=stop_result.provider_source,
|
||||
)
|
||||
|
||||
try:
|
||||
shutil.rmtree(state_path)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except OSError as exc:
|
||||
return ManagedTransportResult(
|
||||
transport,
|
||||
stop_result.provider,
|
||||
"repair-failed",
|
||||
stop_result.endpoint,
|
||||
stop_result.owned,
|
||||
stop_result.pid,
|
||||
state_dir,
|
||||
stop_result.command,
|
||||
f"could not remove AMPB-owned {transport} runtime state: {exc}",
|
||||
provider_source=stop_result.provider_source,
|
||||
)
|
||||
|
||||
return ManagedTransportResult(
|
||||
transport,
|
||||
stop_result.provider,
|
||||
"repaired",
|
||||
stop_result.endpoint,
|
||||
stop_result.owned,
|
||||
stop_result.pid,
|
||||
state_dir,
|
||||
stop_result.command,
|
||||
f"removed AMPB-owned {transport} runtime state; run `ampbrowser transport start {transport}` to rebuild it",
|
||||
provider_source=stop_result.provider_source,
|
||||
)
|
||||
|
||||
|
||||
def _start_tor(
|
||||
*,
|
||||
config: AppConfig,
|
||||
@@ -418,6 +489,7 @@ def _start_i2p(
|
||||
provider = _i2p_provider(config)
|
||||
state_dir = _managed_state_dir(root, config, "i2p")
|
||||
if not provider:
|
||||
setup = _i2p_setup_hint()
|
||||
return ManagedTransportResult(
|
||||
"i2p",
|
||||
"-",
|
||||
@@ -427,8 +499,9 @@ def _start_i2p(
|
||||
0,
|
||||
state_dir,
|
||||
(),
|
||||
"I2P provider not found; on macOS run: brew install i2pd. "
|
||||
"Alternatively set AMPB_I2PD_BIN or transports.i2p.binary_path.",
|
||||
"I2P provider not found. " + setup.message,
|
||||
setup_hint=setup.message,
|
||||
install_command=setup.command,
|
||||
)
|
||||
|
||||
state_path = Path(state_dir)
|
||||
@@ -507,6 +580,33 @@ def _i2p_provider(config: AppConfig) -> I2PProvider | None:
|
||||
return None
|
||||
|
||||
|
||||
def _stopped_setup_hint(transport: str, config: AppConfig) -> str:
|
||||
if transport == "i2p" and _i2p_provider(config) is None:
|
||||
return _i2p_setup_hint().message
|
||||
return "-"
|
||||
|
||||
|
||||
def _stopped_install_command(transport: str, config: AppConfig) -> tuple[str, ...]:
|
||||
if transport == "i2p" and _i2p_provider(config) is None:
|
||||
return _i2p_setup_hint().command
|
||||
return ()
|
||||
|
||||
|
||||
def _i2p_setup_hint() -> SetupHint:
|
||||
override = "Or set AMPB_I2PD_BIN or transports.i2p.binary_path to an i2pd binary."
|
||||
if sys.platform == "darwin":
|
||||
return SetupHint("Install i2pd with Homebrew: brew install i2pd. " + override, ("brew", "install", "i2pd"))
|
||||
if shutil.which("pkg"):
|
||||
return SetupHint("Install i2pd with pkg: pkg install i2pd. " + override, ("pkg", "install", "i2pd"))
|
||||
if shutil.which("apt"):
|
||||
return SetupHint("Install i2pd with apt: sudo apt install i2pd. " + override, ("sudo", "apt", "install", "i2pd"))
|
||||
if shutil.which("dnf"):
|
||||
return SetupHint("Install i2pd with dnf: sudo dnf install i2pd. " + override, ("sudo", "dnf", "install", "i2pd"))
|
||||
if shutil.which("pacman"):
|
||||
return SetupHint("Install i2pd with pacman: sudo pacman -S i2pd. " + override, ("sudo", "pacman", "-S", "i2pd"))
|
||||
return SetupHint("Install i2pd with your package manager. " + override)
|
||||
|
||||
|
||||
def _homebrew_i2pd_path() -> str:
|
||||
brew = shutil.which("brew")
|
||||
if not brew:
|
||||
|
||||
Reference in New Issue
Block a user