Spaces:
Running on Zero
Running on Zero
File size: 4,492 Bytes
8f53c4c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | """HearthNet mesh launcher — start a local node, optionally join the internet mesh.
Local-first by design:
python scripts/start_mesh_node.py
→ starts a pure local node (mDNS/UDP discovery + local HTTP server).
Makes NO outbound internet calls.
python scripts/start_mesh_node.py --connect hf
→ also joins the public HF Space relay hub, so this node meshes
all-to-all with every other connected node over NAT.
python scripts/start_mesh_node.py --connect <invite-or-relay-url>
→ joins the relay hub embedded in an invite (hn1:...) or given directly.
The node stays running (Ctrl-C to stop). While connected, inbound bus calls from
other mesh members are served locally and your calls route to them transparently.
"""
from __future__ import annotations
import argparse
import asyncio
import contextlib
import secrets
HF_SPACE_URL = "https://build-small-hackathon-hearthnet.hf.space"
def _resolve_relay(connect: str) -> str:
"""Map a --connect value to a relay base URL.
Accepts ``hf`` (the public Space), an ``hn1:`` invite (relay extracted), or a
raw http(s) relay URL.
"""
if connect in ("hf", "space"):
return HF_SPACE_URL
if connect.startswith("hn1:"):
from hearthnet.ui.onboarding import decode_invite
blob = decode_invite(connect)
if not blob.relay_url:
raise SystemExit("invite has no relay_url embedded")
return blob.relay_url
if connect.startswith(("http://", "https://")):
return connect.rstrip("/")
raise SystemExit(f"unrecognised --connect value: {connect!r}")
async def _run(args: argparse.Namespace) -> int:
from hearthnet.node import HearthNode
node_id = args.node_id or f"ed25519:mesh-{secrets.token_hex(4)}"
node = HearthNode(node_id, args.name, args.community)
if args.demo_services:
node.install_demo_services(corpus="demo")
else:
with contextlib.suppress(Exception):
node.install_services(corpus="community")
local_caps = sorted({e.descriptor.name for e in node.bus.registry.all_local()})
print(f"[node] {node.display_name} ({node_id})")
print(f"[node] local capabilities: {local_caps}")
await node.start(host=args.host, port=args.port)
print(f"[node] local HTTP server on {args.host}:{args.port} (local-first)")
if args.connect:
relay_url = _resolve_relay(args.connect)
print(f"[mesh] joining relay hub {relay_url} ...")
try:
result = await node.join_relay(relay_url, token=args.token or None)
except Exception as exc: # surface a clear startup error, keep node local
print(f"[mesh] x relay join failed: {exc}")
print(" The Space may be asleep/building. Open its UI once and retry.")
else:
members = [m.get("node_id", "")[:24] for m in result.get("roster", [])]
print(f"[mesh] + joined. {len(members)} other member(s): {members}")
print("[mesh] all-to-all: your bus calls now route to mesh peers over NAT.")
else:
print("[mesh] running local-only (no relay). Use --connect hf to mesh.")
print("[node] up. Press Ctrl-C to stop.")
stop = asyncio.Event()
try:
await stop.wait()
except (KeyboardInterrupt, asyncio.CancelledError):
pass
finally:
print("\n[node] shutting down ...")
await node.stop()
return 0
def main() -> None:
parser = argparse.ArgumentParser(description="Start a HearthNet mesh node")
parser.add_argument("--name", default="Mesh Node", help="display name")
parser.add_argument("--node-id", default="", help="full node id (default: random)")
parser.add_argument("--community", default="ed25519:community", help="community id")
parser.add_argument("--host", default="127.0.0.1", help="local HTTP bind host")
parser.add_argument("--port", type=int, default=7080, help="local HTTP port")
parser.add_argument(
"--connect",
default="",
help="'hf', an hn1: invite, or a relay URL to join the internet mesh",
)
parser.add_argument("--token", default="", help="optional relay join token")
parser.add_argument(
"--demo-services",
action="store_true",
help="install fast echo/demo services instead of real local backends",
)
args = parser.parse_args()
raise SystemExit(asyncio.run(_run(args)))
if __name__ == "__main__":
main()
|