fix: persist bridge NIDs across restarts; fix missing Tuple import
bridge.py:
- state_path param: optional JSON file for persisting discovered bridge NIDs
- _load_state: on startup, re-registers each saved NID at its allocated port
so radicle-node reconnects fast without waiting for a re-announce cycle
- _save_state: called whenever a new NID is first discovered
- Added json/Path imports
cli.py:
- cmd_seed: passes state_path={seed_home}/bridge_state.json
- cmd_bridge: passes state_path=~/.radicle-rns/bridge_state.json
- Fixed missing Tuple import (used by _parse_delays return type annotation)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4d3fdcf5f9
commit
3ee1fa8c58
|
|
@ -15,6 +15,7 @@ The bridge:
|
|||
4. Remote bridges forward to their local radicle-node
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
import select
|
||||
|
|
@ -23,6 +24,7 @@ import subprocess
|
|||
import threading
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Callable, Dict, List, Optional, Set, Tuple
|
||||
|
||||
import RNS
|
||||
|
|
@ -92,6 +94,7 @@ class RadicleBridge:
|
|||
auto_seed: bool = True,
|
||||
announce_retry_delays: Tuple[int, ...] = (5, 15, 30),
|
||||
rad_home: Optional[str] = None,
|
||||
state_path: Optional[Path] = None,
|
||||
):
|
||||
"""Initialize the bridge.
|
||||
|
||||
|
|
@ -108,6 +111,8 @@ class RadicleBridge:
|
|||
intervals on LoRa to respect duty cycle limits, e.g. (60, 300, 900).
|
||||
rad_home: RAD_HOME for rad CLI calls. None = use system default.
|
||||
Set to seed home when bridging a dedicated seed node.
|
||||
state_path: JSON file to persist discovered bridge NIDs across restarts.
|
||||
None = no persistence.
|
||||
"""
|
||||
self.listen_port = listen_port
|
||||
self.radicle_host = radicle_host
|
||||
|
|
@ -116,6 +121,7 @@ class RadicleBridge:
|
|||
self.auto_seed = auto_seed
|
||||
self.announce_retry_delays = announce_retry_delays
|
||||
self.rad_home = rad_home
|
||||
self.state_path = state_path
|
||||
|
||||
# Initialize Reticulum
|
||||
self.reticulum = RNS.Reticulum(config_path)
|
||||
|
|
@ -172,6 +178,9 @@ class RadicleBridge:
|
|||
RNS.Transport.register_announce_handler(self._handle_announce)
|
||||
RNS.log("Registered announce handler for bridge discovery", RNS.LOG_INFO)
|
||||
|
||||
# Load persisted NIDs first so radicle-node is ready for reconnects
|
||||
self._load_state()
|
||||
|
||||
# Announce presence; repeat a few times so peers that come up shortly
|
||||
# after us don't miss it due to interface initialisation timing
|
||||
self.announce()
|
||||
|
|
@ -215,6 +224,48 @@ class RadicleBridge:
|
|||
)
|
||||
RNS.Transport.request_path(bridge_hash)
|
||||
|
||||
def _load_state(self):
|
||||
"""Load persisted bridge NIDs and re-register them with radicle-node."""
|
||||
if not self.state_path or not self.state_path.exists():
|
||||
return
|
||||
try:
|
||||
data = json.loads(self.state_path.read_text())
|
||||
nids: Dict[str, str] = data.get("bridge_nids", {})
|
||||
except Exception as e:
|
||||
RNS.log(f"Could not load bridge state: {e}", RNS.LOG_WARNING)
|
||||
return
|
||||
|
||||
for hex_hash, nid in nids.items():
|
||||
try:
|
||||
bridge_hash = bytes.fromhex(hex_hash)
|
||||
except ValueError:
|
||||
continue
|
||||
with self._remote_bridges_lock:
|
||||
self._remote_bridges[bridge_hash] = 0.0 # unknown age; path maintenance will refresh
|
||||
self._bridge_nids[bridge_hash] = nid
|
||||
# Allocate port and re-register with radicle-node so it can connect
|
||||
threading.Thread(
|
||||
target=self._auto_register_seed,
|
||||
args=(nid, bridge_hash),
|
||||
daemon=True,
|
||||
).start()
|
||||
RNS.log(
|
||||
f"Restored bridge state: {hex_hash[:16]} → {nid[:32]}",
|
||||
RNS.LOG_INFO,
|
||||
)
|
||||
|
||||
def _save_state(self):
|
||||
"""Persist current bridge NIDs to disk for next startup."""
|
||||
if not self.state_path:
|
||||
return
|
||||
try:
|
||||
self.state_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with self._remote_bridges_lock:
|
||||
nids = {h.hex(): n for h, n in self._bridge_nids.items()}
|
||||
self.state_path.write_text(json.dumps({"bridge_nids": nids}, indent=2))
|
||||
except Exception as e:
|
||||
RNS.log(f"Could not save bridge state: {e}", RNS.LOG_WARNING)
|
||||
|
||||
def _reconnect_link(
|
||||
self, bridge_hash: bytes, timeout: float = 20.0
|
||||
) -> Optional[RNS.Link]:
|
||||
|
|
@ -660,6 +711,9 @@ class RadicleBridge:
|
|||
daemon=True,
|
||||
).start()
|
||||
|
||||
if nid_is_new:
|
||||
self._save_state()
|
||||
|
||||
if self.auto_seed and nid_is_new:
|
||||
threading.Thread(
|
||||
target=self._auto_register_seed,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import sys
|
|||
import time
|
||||
import signal
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from typing import Optional, Tuple
|
||||
|
||||
DEFAULT_IDENTITY_PATH = Path.home() / ".radicle-rns" / "identity"
|
||||
|
||||
|
|
@ -362,6 +362,7 @@ def cmd_seed(args):
|
|||
auto_seed=True,
|
||||
announce_retry_delays=announce_retry_delays,
|
||||
rad_home=str(seed_home),
|
||||
state_path=seed_home / "bridge_state.json",
|
||||
)
|
||||
bridge.set_local_radicle_nid(nid)
|
||||
|
||||
|
|
@ -457,6 +458,7 @@ def cmd_bridge(args):
|
|||
auto_connect=auto_connect,
|
||||
auto_seed=auto_seed,
|
||||
announce_retry_delays=announce_retry_delays,
|
||||
state_path=Path.home() / ".radicle-rns" / "bridge_state.json",
|
||||
)
|
||||
|
||||
# Resolve local radicle NID: explicit flag > auto-detect from 'rad self'
|
||||
|
|
|
|||
Loading…
Reference in New Issue