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
121
122
123
124
125
126
127
|
"""`meshbay-node status`: what the node is, read even while it is stopped."""
from pathlib import Path
from meshbay_node.config import DEFAULT_CONFIG_PATH, load_config
from meshbay_node.keystore import load_keystore
def status(args) -> None:
import json as _json
import urllib.request
cfg = load_config(args.config or DEFAULT_CONFIG_PATH)
print(f"hub {cfg.hub.url} (user {cfg.hub.username or '—'})")
try:
keys = load_keystore(
path=cfg.keystore.path, unlock_file=cfg.keystore.unlock_file)
print(f"node key {keys.pk_ed25519_b64}")
except FileNotFoundError:
print("node key <no keystore — run: meshbay-node init>")
except Exception as e:
print(f"node key <keystore locked: {e}>")
token_file = cfg.data_dir / "ui-token"
live = None
if token_file.exists():
try:
url = (f"http://127.0.0.1:{cfg.node.ui_port}"
f"/api/status?t={token_file.read_text(encoding="utf-8").strip()}")
with urllib.request.urlopen(url, timeout=3) as r:
live = _json.loads(r.read())
except Exception:
live = None
if live:
print(f"daemon running — {live.get('status')}")
print(f"node_id {live.get('endpoint_hint') or '—'}")
print(f"groups {live.get('group_count', 0)}"
f" files {live.get('total_files', 0)}"
f" peers {live.get('webrtc_peers', 0)}")
needs = live.get("needs", [])
if needs:
_GUIDANCE = {
"node_key_link": (
"Link node key",
f"Copy the node key above and paste it in "
f"Settings → Link Node on {cfg.hub.url}"),
"group_add": (
"Add a group",
"meshbay-node group add <name> --dir /path/to/files"),
"operator_pair": (
"Pair as operator",
"meshbay-node operator pair"),
}
print()
print("action needed:")
for need in needs:
if need.startswith("gek_init:"):
name = need.split(":", 1)[1]
print(f" → Initialize group key for {name}")
print(f" meshbay-node gek init --group \"{name}\"")
elif need in _GUIDANCE:
label, hint = _GUIDANCE[need]
print(f" → {label}")
print(f" {hint}")
else:
print(f" → {need}")
else:
print("daemon not running")
print(f"config {DEFAULT_CONFIG_PATH}")
if not cfg.groups:
print("groups none configured — create a group on the hub, then add")
print(" a [[groups]] entry with its id and a directory")
else:
for g in cfg.groups:
print(f" group {g.name} [{g.visibility}] {g.id or '<no id>'}")
if not g.roots:
print(" <no directory configured>")
for r in g.roots:
label = r.name or Path(r.path).name
flags = []
if getattr(r, 'writable', False) or getattr(r, 'upload', False):
flags.append("rw")
else:
flags.append("ro")
if getattr(r, 'removable', False):
flags.append("removable")
flag_str = f" ({', '.join(flags)})" if flags else ""
live = "" if Path(r.path).expanduser().is_dir() else " [UNAVAILABLE]"
print(f" {label} → {r.path}{flag_str}{live}")
# Node authority: the roster is the source of truth, node.toml the legacy
# form. Read the DB directly so this reports correctly while the daemon is
# stopped — the state an operator is most often in when checking.
import asyncio as _asyncio
from meshbay_node.roster import Roster as _Roster
async def _read_roster() -> tuple[list, int]:
r = _Roster(db_path=cfg.data_dir / "roster.db")
await r.open()
try:
return (await r.list_members()), len(await r.list_invites())
finally:
await r.close()
try:
members, pending = _asyncio.run(_read_roster())
except Exception as e:
members, pending = [], 0
print(f"roster <unreadable: {e}>")
operators = [m for m in members if m["role"] == "operator"
and m["status"] == "active"]
if operators:
for op in operators:
print(f"operator {op.get('username') or op['user_id'][:8]}"
f" key {(op.get('pk_ed25519') or '')[:16]}…"
f" paired {op.get('pinned_at', '?')}")
else:
print("operator NONE PAIRED — file deletion and member invites are")
print(" refused. Run: meshbay-node operator pair")
if pending:
print(f"invites {pending} pending code(s)")
return
|