feat: expose browser control via ServiceLink RPC
Testing / test (push) Successful in 1m30s
Testing / remote-protocol-compat (0.9.3) (push) Successful in 1m23s
Testing / remote-protocol-compat (0.9.5) (push) Successful in 1m12s
Build & Publish Package / publish (push) Successful in 1m2s
Package Extension / package-extension (push) Successful in 1m12s
Testing / test (push) Successful in 1m30s
Testing / remote-protocol-compat (0.9.3) (push) Successful in 1m23s
Testing / remote-protocol-compat (0.9.5) (push) Successful in 1m12s
Build & Publish Package / publish (push) Successful in 1m2s
Package Extension / package-extension (push) Successful in 1m12s
- Add the ServiceLink submodule and register it in .gitmodules. - Add a link-serve command that exposes selected browser-cli commands over an HTTP /rpc endpoint. - Require bearer-token authentication by default, with explicit insecure opt-in for trusted loopback/local deployments. - Allow the existing serve daemon to run the ServiceLink RPC endpoint alongside the native TCP remote server.
This commit is contained in:
@@ -45,9 +45,32 @@ __all__ = [
|
||||
default=False,
|
||||
help="Disable response compression / msgpack even for clients that support it.",
|
||||
)
|
||||
@click.option(
|
||||
"--rpc",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Also expose a ServiceLink HTTP /rpc endpoint (for mesh nodes) in the same process.",
|
||||
)
|
||||
@click.option("--rpc-port", default=8770, show_default=True, type=int, help="Port for the /rpc endpoint (with --rpc).")
|
||||
@click.option(
|
||||
"--rpc-token",
|
||||
default=None,
|
||||
metavar="SECRET",
|
||||
help="Bearer token required on /rpc (with --rpc).",
|
||||
)
|
||||
@click.option(
|
||||
"--rpc-insecure",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Allow --rpc with no token (DANGEROUS: full browser control to anyone who can reach the port).",
|
||||
)
|
||||
@click.pass_context
|
||||
def cmd_serve(ctx, host, port, no_auth, auth_keys_file, no_compress):
|
||||
"""Expose this browser over TCP so remote hosts can control it."""
|
||||
def cmd_serve(ctx, host, port, no_auth, auth_keys_file, no_compress, rpc, rpc_port, rpc_token, rpc_insecure):
|
||||
"""Expose this browser over TCP so remote hosts can control it.
|
||||
|
||||
With --rpc, additionally serve the ServiceLink mesh over HTTP /rpc on
|
||||
--rpc-port, so the native TCP protocol and the node mesh share one daemon.
|
||||
"""
|
||||
profile = ctx.obj.get("browser") if ctx.obj else None
|
||||
compress = not no_compress
|
||||
|
||||
@@ -61,16 +84,40 @@ def cmd_serve(ctx, host, port, no_auth, auth_keys_file, no_compress):
|
||||
if auth_keys_path is False:
|
||||
sys.exit(1)
|
||||
|
||||
if rpc and not rpc_token and not rpc_insecure:
|
||||
console.print(
|
||||
"[red]Error:[/red] --rpc requires --rpc-token (this endpoint can control your "
|
||||
"browser and read its cookies). Use --rpc-insecure to override on a trusted host."
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
_print_startup(host, port, profile, auth_keys_path, compress)
|
||||
if rpc:
|
||||
console.print(f" Mesh: [green]ServiceLink HTTP[/green] [cyan]{host}:{rpc_port}/rpc[/cyan]")
|
||||
if not rpc_token:
|
||||
console.print("[yellow] /rpc auth disabled (--rpc-insecure)[/yellow]")
|
||||
|
||||
try:
|
||||
asyncio.run(_serve_async(host, port, profile, auth_keys_path, compress))
|
||||
if rpc:
|
||||
asyncio.run(_serve_with_rpc(host, port, profile, auth_keys_path, compress, rpc_port, rpc_token))
|
||||
else:
|
||||
asyncio.run(_serve_async(host, port, profile, auth_keys_path, compress))
|
||||
except OSError as e:
|
||||
console.print(f"[red]Cannot bind to {host}:{port}:[/red] {e}")
|
||||
sys.exit(1)
|
||||
except KeyboardInterrupt:
|
||||
console.print("[yellow]Stopped.[/yellow]")
|
||||
|
||||
async def _serve_with_rpc(host, port, profile, auth_keys_path, compress, rpc_port, rpc_token):
|
||||
"""Run the native TCP server and the ServiceLink HTTP /rpc server together."""
|
||||
from browser_cli.commands import link_serve
|
||||
|
||||
sl = link_serve._import_servicelink()
|
||||
await asyncio.gather(
|
||||
_serve_async(host, port, profile, auth_keys_path, compress),
|
||||
link_serve._serve(sl, host, rpc_port, profile, rpc_token),
|
||||
)
|
||||
|
||||
def _resolve_auth_keys_path(auth_keys_file: str | None, no_auth: bool) -> Path | None | bool:
|
||||
if auth_keys_file:
|
||||
from browser_cli.auth import load_authorized_keys
|
||||
|
||||
Reference in New Issue
Block a user