remoteRF-server 0.1.62__tar.gz → 0.1.64__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/PKG-INFO +25 -12
  2. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/README.md +24 -11
  3. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/pyproject.toml +1 -1
  4. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/host/host_tunnel_server.py +82 -40
  5. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/server/grpc_server.py +2 -2
  6. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server.egg-info/PKG-INFO +25 -12
  7. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/setup.cfg +0 -0
  8. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/__init__.py +0 -0
  9. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/common/__init__.py +0 -0
  10. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/common/grpc/__init__.py +0 -0
  11. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/common/grpc/grpc_host_pb2.py +0 -0
  12. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/common/grpc/grpc_host_pb2_grpc.py +0 -0
  13. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/common/grpc/grpc_pb2.py +0 -0
  14. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/common/grpc/grpc_pb2_grpc.py +0 -0
  15. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/common/utils/__init__.py +0 -0
  16. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/common/utils/ansi_codes.py +0 -0
  17. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/common/utils/api_token.py +0 -0
  18. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/common/utils/db_connection.py +0 -0
  19. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/common/utils/db_location.py +0 -0
  20. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/common/utils/list_string.py +0 -0
  21. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/common/utils/process_arg.py +0 -0
  22. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/drivers/__init__.py +0 -0
  23. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/drivers/adalm_pluto/__init__.py +0 -0
  24. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/drivers/adalm_pluto/pluto_remote_server.py +0 -0
  25. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/host/__init__.py +0 -0
  26. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/host/host_directory_store.py +0 -0
  27. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/server/__init__.py +0 -0
  28. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/server/acc_perms.py +0 -0
  29. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/server/cert_provider.py +0 -0
  30. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/server/device_manager.py +0 -0
  31. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/server/reservation.py +0 -0
  32. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/server/rpc_manager.py +0 -0
  33. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/server/user_group_cli.py +0 -0
  34. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/server/user_group_handler.py +0 -0
  35. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/serverrf_cli.py +0 -0
  36. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/tools/__init__.py +0 -0
  37. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server/tools/gen_certs.py +0 -0
  38. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server.egg-info/SOURCES.txt +0 -0
  39. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server.egg-info/dependency_links.txt +0 -0
  40. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server.egg-info/entry_points.txt +0 -0
  41. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server.egg-info/requires.txt +0 -0
  42. {remoterf_server-0.1.62 → remoterf_server-0.1.64}/src/remoteRF_server.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoteRF-server
3
- Version: 0.1.62
3
+ Version: 0.1.64
4
4
  Description-Content-Type: text/markdown
5
5
  Requires-Dist: grpcio
6
6
  Requires-Dist: protobuf
@@ -283,13 +283,11 @@ Note that all device and server config parameters need a 'restart' to take affec
283
283
 
284
284
  After running `serverrf --serve`, you’ll enter the **RemoteRF Server Shell**. This interactive shell is used for runtime administration: managing users, devices, reservations, user groups, and enrollment codes.
285
285
 
286
- > Tip: Legacy commands (e.g. `show a`, `rm db`) may still work if enabled, but the commands below are the canonical interface.
287
-
288
286
  ---
289
287
 
290
288
  #### Server
291
289
 
292
- * `help` / `?` — Show help
290
+ * `help` / `h` — Show help
293
291
  * `clear` — Clear the screen
294
292
  * `status` — Show server status (uptime / bind info)
295
293
  * `quit` / `exit` — Exit the server shell
@@ -299,8 +297,8 @@ After running `serverrf --serve`, you’ll enter the **RemoteRF Server Shell**.
299
297
  #### Users
300
298
 
301
299
  * `users list` — List all accounts
302
- * `users manage` — Manage a specific user (permissions / deletion / reservations) *(interactive)*
303
- * `users purge` — Remove **all** users
300
+ * `users manage` — Manage a specific user
301
+ * `users purge` — Delete **all** users
304
302
 
305
303
  ---
306
304
 
@@ -313,7 +311,7 @@ After running `serverrf --serve`, you’ll enter the **RemoteRF Server Shell**.
313
311
  #### Reservations
314
312
 
315
313
  * `reservations list` — List all reservations
316
- * `reservations purge` — Remove **all** reservations
314
+ * `reservations purge` — Delete **all** reservations
317
315
 
318
316
  ---
319
317
 
@@ -323,6 +321,7 @@ After running `serverrf --serve`, you’ll enter the **RemoteRF Server Shell**.
323
321
  * `groups create` — Create a user group *(interactive)*
324
322
  * `groups edit` — Edit a user group *(interactive)*
325
323
  * `groups delete` — Delete a user group *(interactive)*
324
+ * `groups csv` - Export all groups as a CSV
326
325
 
327
326
  ---
328
327
 
@@ -331,12 +330,13 @@ After running `serverrf --serve`, you’ll enter the **RemoteRF Server Shell**.
331
330
  * `codes list` — List enrollment codes
332
331
  * `codes create` — Create enrollment codes *(interactive)*
333
332
  * `codes delete` — Delete an enrollment code *(interactive)*
333
+ * `codes csv` - Export all codes as a CSV
334
334
 
335
335
  ---
336
336
 
337
337
  #### Database
338
338
 
339
- * `db reset` — Remove all database entries
339
+ * `db purge` — Remove all database entries
340
340
 
341
341
  ---
342
342
 
@@ -344,17 +344,30 @@ After running `serverrf --serve`, you’ll enter the **RemoteRF Server Shell**.
344
344
  todo:
345
345
  <!-- Adding plutos via the CLI -->
346
346
  <!-- Testing functionality (disregarding Host pipeline) -->
347
- Clean up serverrf CLI + basic documentation for all commands, usergroups, export codes by CSV.
348
- Choice to choose usergroup/code by either int id or string name.
347
+ <!-- Clean up serverrf CLI + basic documentation for all commands, usergroups, export codes by CSV. -->
349
348
 
350
- Ability to remotely manage groups and codes as admin.
349
+ HostRF meta data -> push upon connection + retry connection on host side.
350
+
351
+ ServerRF display meta data as part of devices list / reservation + online/activity status (appended into device name) + (plus host side warning if the device id is NOT globally unique!)
352
+
353
+ set_device token (virtual devices) + routing/relay to proper host (multiplexed)
351
354
 
352
- HostRF integration -> Host directory, device routing, dynamic reservation pulling, with activity/status (online, offline, etc.)
355
+ User side testing + integration
356
+
357
+ hostrf side CLI/server (basically just device list)
358
+
359
+ edge case testing (disconnected hosts, disconnected server, many users using multiple devices, multi-host)
360
+
361
+ <!-- HostRF integration -> Host directory, device routing, dynamic reservation pulling, with activity/status (online, offline, etc.) -->
353
362
 
354
363
  Cleanup code comments.
355
364
 
356
365
  Update Ian with installation demo (try myself first on the NUC once!) for both server and host.
357
366
 
367
+ Nice to have (later):
368
+ Activity/stats?
369
+ Ability to remotely manage groups and codes as admin.
370
+ Choice to choose usergroup/code by either int id or string name. (too lazy)
358
371
 
359
372
  todo: Specify/Pull user side --config CLI pointing to this server + debug for connection sake (later)
360
373
 
@@ -272,13 +272,11 @@ Note that all device and server config parameters need a 'restart' to take affec
272
272
 
273
273
  After running `serverrf --serve`, you’ll enter the **RemoteRF Server Shell**. This interactive shell is used for runtime administration: managing users, devices, reservations, user groups, and enrollment codes.
274
274
 
275
- > Tip: Legacy commands (e.g. `show a`, `rm db`) may still work if enabled, but the commands below are the canonical interface.
276
-
277
275
  ---
278
276
 
279
277
  #### Server
280
278
 
281
- * `help` / `?` — Show help
279
+ * `help` / `h` — Show help
282
280
  * `clear` — Clear the screen
283
281
  * `status` — Show server status (uptime / bind info)
284
282
  * `quit` / `exit` — Exit the server shell
@@ -288,8 +286,8 @@ After running `serverrf --serve`, you’ll enter the **RemoteRF Server Shell**.
288
286
  #### Users
289
287
 
290
288
  * `users list` — List all accounts
291
- * `users manage` — Manage a specific user (permissions / deletion / reservations) *(interactive)*
292
- * `users purge` — Remove **all** users
289
+ * `users manage` — Manage a specific user
290
+ * `users purge` — Delete **all** users
293
291
 
294
292
  ---
295
293
 
@@ -302,7 +300,7 @@ After running `serverrf --serve`, you’ll enter the **RemoteRF Server Shell**.
302
300
  #### Reservations
303
301
 
304
302
  * `reservations list` — List all reservations
305
- * `reservations purge` — Remove **all** reservations
303
+ * `reservations purge` — Delete **all** reservations
306
304
 
307
305
  ---
308
306
 
@@ -312,6 +310,7 @@ After running `serverrf --serve`, you’ll enter the **RemoteRF Server Shell**.
312
310
  * `groups create` — Create a user group *(interactive)*
313
311
  * `groups edit` — Edit a user group *(interactive)*
314
312
  * `groups delete` — Delete a user group *(interactive)*
313
+ * `groups csv` - Export all groups as a CSV
315
314
 
316
315
  ---
317
316
 
@@ -320,12 +319,13 @@ After running `serverrf --serve`, you’ll enter the **RemoteRF Server Shell**.
320
319
  * `codes list` — List enrollment codes
321
320
  * `codes create` — Create enrollment codes *(interactive)*
322
321
  * `codes delete` — Delete an enrollment code *(interactive)*
322
+ * `codes csv` - Export all codes as a CSV
323
323
 
324
324
  ---
325
325
 
326
326
  #### Database
327
327
 
328
- * `db reset` — Remove all database entries
328
+ * `db purge` — Remove all database entries
329
329
 
330
330
  ---
331
331
 
@@ -333,17 +333,30 @@ After running `serverrf --serve`, you’ll enter the **RemoteRF Server Shell**.
333
333
  todo:
334
334
  <!-- Adding plutos via the CLI -->
335
335
  <!-- Testing functionality (disregarding Host pipeline) -->
336
- Clean up serverrf CLI + basic documentation for all commands, usergroups, export codes by CSV.
337
- Choice to choose usergroup/code by either int id or string name.
336
+ <!-- Clean up serverrf CLI + basic documentation for all commands, usergroups, export codes by CSV. -->
338
337
 
339
- Ability to remotely manage groups and codes as admin.
338
+ HostRF meta data -> push upon connection + retry connection on host side.
339
+
340
+ ServerRF display meta data as part of devices list / reservation + online/activity status (appended into device name) + (plus host side warning if the device id is NOT globally unique!)
341
+
342
+ set_device token (virtual devices) + routing/relay to proper host (multiplexed)
340
343
 
341
- HostRF integration -> Host directory, device routing, dynamic reservation pulling, with activity/status (online, offline, etc.)
344
+ User side testing + integration
345
+
346
+ hostrf side CLI/server (basically just device list)
347
+
348
+ edge case testing (disconnected hosts, disconnected server, many users using multiple devices, multi-host)
349
+
350
+ <!-- HostRF integration -> Host directory, device routing, dynamic reservation pulling, with activity/status (online, offline, etc.) -->
342
351
 
343
352
  Cleanup code comments.
344
353
 
345
354
  Update Ian with installation demo (try myself first on the NUC once!) for both server and host.
346
355
 
356
+ Nice to have (later):
357
+ Activity/stats?
358
+ Ability to remotely manage groups and codes as admin.
359
+ Choice to choose usergroup/code by either int id or string name. (too lazy)
347
360
 
348
361
  todo: Specify/Pull user side --config CLI pointing to this server + debug for connection sake (later)
349
362
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "remoteRF-server"
7
- version = "0.1.62"
7
+ version = "0.1.64"
8
8
  dependencies = [
9
9
  "grpcio",
10
10
  "protobuf",
@@ -32,6 +32,13 @@ from .host_directory_store import (
32
32
  cfg_dir_from_file,
33
33
  )
34
34
 
35
+ import hashlib
36
+
37
+
38
+ def host_key(host_id: str) -> str:
39
+ h = hashlib.sha256((host_id or "").encode("utf-8")).hexdigest()
40
+ return h[:16] # short stable key
41
+
35
42
 
36
43
  def _copy_argument_map(dst_map: Any, src_map: Any) -> None:
37
44
  if src_map is None:
@@ -103,6 +110,7 @@ class HostSession:
103
110
  fut.set_exception(RuntimeError(f"Host '{self.host_id}' disconnected"))
104
111
  self.inflight.clear()
105
112
 
113
+
106
114
  class HostTunnelRegistry:
107
115
  """
108
116
  Persistent host/device directory + live sessions.
@@ -188,16 +196,6 @@ class HostTunnelRegistry:
188
196
  known_hosts = csv_split(kv_lists.get("KNOWN_HOSTS", ""))
189
197
  known_devices = csv_split(kv_lists.get("KNOWN_DEVICES", ""))
190
198
 
191
- # also accept hosts referenced by HOST_*_DEVICES keys even if KNOWN_HOSTS drifted
192
- hosts_from_keys: Set[str] = set()
193
- for k, v in kv_lists.items():
194
- if k.startswith("HOST_") and k.endswith("_DEVICES"):
195
- # We don't know the original host_id from the sanitized key reliably.
196
- # That's fine; we still load routes primarily from DEVICE_*_HOST in meta.
197
- # But we DO want to retain the device list; we’ll stitch it via meta host ids.
198
- # So: just collect devices from these keys.
199
- pass
200
-
201
199
  # Reset directory + status (do not touch live sessions)
202
200
  with self._lock:
203
201
  self._routes.clear()
@@ -218,9 +216,6 @@ class HostTunnelRegistry:
218
216
  if k.startswith("HOST_") and k.endswith("_DEVICES"):
219
217
  devices_from_host_lists.update(csv_split(v))
220
218
 
221
- devices_from_meta: Set[str] = set()
222
- # any DEVICE_<X>_HOST implies a known device id (but we can’t reverse sanitize_env_key)
223
- # so we rely on KNOWN_DEVICES + HOST_*_DEVICES as primary device-id sources.
224
219
  # meta augments those devices; it can’t introduce new raw device ids safely.
225
220
  all_devices: List[str] = sorted(set(known_devices) | devices_from_host_lists)
226
221
 
@@ -242,15 +237,11 @@ class HostTunnelRegistry:
242
237
  kind = (kv_meta.get(f"DEVICE_{dk}_KIND", "") or "").strip()
243
238
 
244
239
  if host_id:
245
- # ensure host exists
246
240
  if host_id not in self._host_status:
247
241
  self._host_status[host_id] = HostStatus(host_id=host_id, online=False, last_seen_ms=0)
248
242
  self._host_devices.setdefault(host_id, set()).add(device_id)
249
-
250
- # route exists even while offline
251
243
  self._routes[device_id] = DeviceRoute(host_id=host_id, host_local_id=int(local_id))
252
244
 
253
- # keep info visible even when offline
254
245
  self._device_infos[device_id] = host_tunnel_pb2.DeviceInfo(
255
246
  device_id=str(device_id),
256
247
  local_id=int(local_id),
@@ -273,12 +264,36 @@ class HostTunnelRegistry:
273
264
  # persistence write helpers
274
265
  # ---------------------------
275
266
 
267
+ def _persist_host_meta(self, host_id: str, *, host_name: str = "", platform: str = "", version: str = "") -> None:
268
+ hk = host_key(host_id)
269
+ self._env_meta.set_kv_if_absent(f"HOST_{hk}_ID", host_id)
270
+ if host_name:
271
+ self._env_meta.upsert_kv(f"HOST_{hk}_NAME", host_name)
272
+ if platform:
273
+ self._env_meta.upsert_kv(f"HOST_{hk}_PLATFORM", platform)
274
+ if version:
275
+ self._env_meta.upsert_kv(f"HOST_{hk}_VERSION", version)
276
+
277
+ def persist_host_meta_from_hello(self, hello: Any) -> None:
278
+ """
279
+ Safe helper so HostTunnelServicer can persist from HostHello without needing to know field names.
280
+ """
281
+ try:
282
+ hid = str(getattr(hello, "host_id", "") or "").strip()
283
+ ver = str(getattr(hello, "version", "") or "").strip()
284
+ if hid:
285
+ self._persist_host_meta(hid, version=ver)
286
+ except Exception:
287
+ pass
288
+
276
289
  def _persist_host(self, host_id: str) -> None:
277
290
  self._env_lists.append_to_csv_list("KNOWN_HOSTS", host_id)
278
291
 
279
292
  def _persist_device(self, host_id: str, device_id: str, local_id: int, device_info: Any) -> None:
280
293
  self._env_lists.append_to_csv_list("KNOWN_DEVICES", device_id)
281
- self._env_lists.append_to_csv_list(f"HOST_{sanitize_env_key(host_id)}_DEVICES", device_id)
294
+ hk = host_key(host_id)
295
+ self._env_meta.set_kv_if_absent(f"HOST_{hk}_ID", host_id) # reverse map
296
+ self._env_lists.append_to_csv_list(f"HOST_{hk}_DEVICES", device_id)
282
297
 
283
298
  dk = sanitize_env_key(device_id)
284
299
  self._env_meta.upsert_kv(f"DEVICE_{dk}_HOST", host_id)
@@ -350,8 +365,6 @@ class HostTunnelRegistry:
350
365
  ds.online = True
351
366
  ds.last_seen_ms = now
352
367
 
353
- # no need to invalidate full cache; active-cache TTL handles this
354
- # but host status flips can matter; be conservative:
355
368
  self._invalidate_caches_locked()
356
369
 
357
370
  def announce_devices(self, host_id: str, devices: list) -> None:
@@ -382,17 +395,14 @@ class HostTunnelRegistry:
382
395
  if not device_id:
383
396
  continue
384
397
 
385
- # local_id is basically not used; keep for completeness
386
398
  try:
387
399
  local_id = int(getattr(d, "local_id", 0))
388
400
  except Exception:
389
401
  local_id = 0
390
402
 
391
- # route
392
403
  self._routes[device_id] = DeviceRoute(host_id=host_id, host_local_id=local_id)
393
404
  self._host_devices[host_id].add(device_id)
394
405
 
395
- # status
396
406
  ds = self._device_status.get(device_id) or DeviceStatus(
397
407
  device_id=device_id,
398
408
  host_id=host_id,
@@ -404,13 +414,11 @@ class HostTunnelRegistry:
404
414
  ds.last_seen_ms = now
405
415
  self._device_status[device_id] = ds
406
416
 
407
- # info (copy protobuf if possible)
408
417
  try:
409
418
  tmp = host_tunnel_pb2.DeviceInfo()
410
419
  tmp.CopyFrom(d)
411
420
  self._device_infos[device_id] = tmp
412
421
  except Exception:
413
- # fallback minimal
414
422
  self._device_infos[device_id] = host_tunnel_pb2.DeviceInfo(
415
423
  device_id=device_id,
416
424
  local_id=int(local_id),
@@ -428,10 +436,6 @@ class HostTunnelRegistry:
428
436
  # ---------------------------
429
437
 
430
438
  def is_host_device(self, device_id: str) -> bool:
431
- """
432
- O(1): does the directory route this device_id to a host?
433
- (Host may be offline.)
434
- """
435
439
  did = (device_id or "").strip()
436
440
  if not did:
437
441
  return False
@@ -439,10 +443,6 @@ class HostTunnelRegistry:
439
443
  return did in self._routes
440
444
 
441
445
  def is_host_device_active(self, device_id: str) -> bool:
442
- """
443
- O(1) route lookup + cached host-alive check (TTL).
444
- True iff device routes to a host AND that host's tunnel session is alive.
445
- """
446
446
  did = (device_id or "").strip()
447
447
  if not did:
448
448
  return False
@@ -468,12 +468,6 @@ class HostTunnelRegistry:
468
468
  # ---------------------------
469
469
 
470
470
  def device_directory_cached(self, *, ttl_ms: Optional[int] = None) -> Dict[str, Tuple[str, Any, bool]]:
471
- """
472
- Returns:
473
- { device_id: (host_id, device_info, is_active) }
474
-
475
- Cached rebuild at most once per ttl_ms.
476
- """
477
471
  now = now_ms()
478
472
  ttl = int(self._dir_cache_ttl_ms if ttl_ms is None else ttl_ms)
479
473
 
@@ -630,6 +624,7 @@ class HostTunnelRegistry:
630
624
  cancel_on_timeout=cancel_on_timeout,
631
625
  )
632
626
 
627
+
633
628
  def handle_host_device(
634
629
  registry: HostTunnelRegistry,
635
630
  *,
@@ -692,7 +687,25 @@ class HostTunnelServicer(host_tunnel_pb2_grpc.HostTunnelServicer):
692
687
  host_id = str(hello.host_id).strip() or "unknown-host"
693
688
  session = HostSession(host_id)
694
689
  self.registry.register_host(host_id, session)
690
+ self.registry.persist_host_meta_from_hello(hello)
691
+
692
+ # Ask host for metadata immediately on join (best-effort)
693
+ try:
694
+ mrid = secrets.token_hex(12)
695
+ session.send(
696
+ host_tunnel_pb2.HostFrame(
697
+ meta_request=host_tunnel_pb2.MetaRequest(
698
+ req_id=mrid,
699
+ include_platform=True,
700
+ include_env=True,
701
+ include_devices=True,
702
+ )
703
+ )
704
+ )
705
+ except Exception:
706
+ pass
695
707
 
708
+ # Also ping/ack
696
709
  try:
697
710
  session.send(
698
711
  host_tunnel_pb2.HostFrame(
@@ -735,6 +748,35 @@ class HostTunnelServicer(host_tunnel_pb2_grpc.HostTunnelServicer):
735
748
  if fut is not None and not fut.done():
736
749
  fut.set_result(resp)
737
750
 
751
+ elif which == "meta_response":
752
+ # Print host metadata (best-effort diagnostics)
753
+ try:
754
+ mr = frame.meta_response
755
+ if not mr.ok:
756
+ print(f"[HOST META] host_id={host_id or '?'} ERROR: {mr.error}")
757
+ continue
758
+
759
+ meta = mr.meta
760
+ print(f"\n[HOST META] host_id={meta.host_id} version={meta.version} unix_ms={meta.unix_ms}")
761
+
762
+ if meta.kv:
763
+ for k in sorted(meta.kv.keys()):
764
+ print(f" {k}={meta.kv[k]}")
765
+
766
+ if meta.devices:
767
+ print(" devices:")
768
+ for d in meta.devices:
769
+ print(
770
+ f" - local_id={d.local_id} "
771
+ f"label={d.label!r} "
772
+ f"device_id={d.device_id!r} "
773
+ f"serial={d.serial!r} "
774
+ f"kind={d.kind!r}"
775
+ )
776
+ print()
777
+ except Exception:
778
+ pass
779
+
738
780
  elif which == "heartbeat":
739
781
  if session is not None and host_id is not None:
740
782
  session.last_heartbeat_ms = int(frame.heartbeat.unix_ms) or now_ms()
@@ -826,4 +868,4 @@ def start_host_tunnel_server(
826
868
 
827
869
  th = threading.Thread(target=run, daemon=True)
828
870
  th.start()
829
- return server, th, reg
871
+ return server, th, reg
@@ -490,7 +490,7 @@ def help() -> None:
490
490
  print()
491
491
 
492
492
  printf("Database:", Sty.BOLD)
493
- printf(" db reset ", Sty.MAGENTA, "- Remove all database entries", Sty.DEFAULT)
493
+ printf(" db purge ", Sty.MAGENTA, "- Remove all database entries", Sty.DEFAULT)
494
494
  print()
495
495
 
496
496
  def server_input(inpu: str) -> bool:
@@ -546,7 +546,7 @@ def server_input(inpu: str) -> bool:
546
546
  "rm ar": lambda: _stay(reservation_handler.remove_all_reservations), # old
547
547
 
548
548
  # ---- db ----
549
- "db reset": lambda: _stay(reservation_handler.rm_db),
549
+ "db purge": lambda: _stay(reservation_handler.rm_db),
550
550
  "rm db": lambda: _stay(reservation_handler.rm_db), # old
551
551
 
552
552
  # ---- groups ----
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoteRF-server
3
- Version: 0.1.62
3
+ Version: 0.1.64
4
4
  Description-Content-Type: text/markdown
5
5
  Requires-Dist: grpcio
6
6
  Requires-Dist: protobuf
@@ -283,13 +283,11 @@ Note that all device and server config parameters need a 'restart' to take affec
283
283
 
284
284
  After running `serverrf --serve`, you’ll enter the **RemoteRF Server Shell**. This interactive shell is used for runtime administration: managing users, devices, reservations, user groups, and enrollment codes.
285
285
 
286
- > Tip: Legacy commands (e.g. `show a`, `rm db`) may still work if enabled, but the commands below are the canonical interface.
287
-
288
286
  ---
289
287
 
290
288
  #### Server
291
289
 
292
- * `help` / `?` — Show help
290
+ * `help` / `h` — Show help
293
291
  * `clear` — Clear the screen
294
292
  * `status` — Show server status (uptime / bind info)
295
293
  * `quit` / `exit` — Exit the server shell
@@ -299,8 +297,8 @@ After running `serverrf --serve`, you’ll enter the **RemoteRF Server Shell**.
299
297
  #### Users
300
298
 
301
299
  * `users list` — List all accounts
302
- * `users manage` — Manage a specific user (permissions / deletion / reservations) *(interactive)*
303
- * `users purge` — Remove **all** users
300
+ * `users manage` — Manage a specific user
301
+ * `users purge` — Delete **all** users
304
302
 
305
303
  ---
306
304
 
@@ -313,7 +311,7 @@ After running `serverrf --serve`, you’ll enter the **RemoteRF Server Shell**.
313
311
  #### Reservations
314
312
 
315
313
  * `reservations list` — List all reservations
316
- * `reservations purge` — Remove **all** reservations
314
+ * `reservations purge` — Delete **all** reservations
317
315
 
318
316
  ---
319
317
 
@@ -323,6 +321,7 @@ After running `serverrf --serve`, you’ll enter the **RemoteRF Server Shell**.
323
321
  * `groups create` — Create a user group *(interactive)*
324
322
  * `groups edit` — Edit a user group *(interactive)*
325
323
  * `groups delete` — Delete a user group *(interactive)*
324
+ * `groups csv` - Export all groups as a CSV
326
325
 
327
326
  ---
328
327
 
@@ -331,12 +330,13 @@ After running `serverrf --serve`, you’ll enter the **RemoteRF Server Shell**.
331
330
  * `codes list` — List enrollment codes
332
331
  * `codes create` — Create enrollment codes *(interactive)*
333
332
  * `codes delete` — Delete an enrollment code *(interactive)*
333
+ * `codes csv` - Export all codes as a CSV
334
334
 
335
335
  ---
336
336
 
337
337
  #### Database
338
338
 
339
- * `db reset` — Remove all database entries
339
+ * `db purge` — Remove all database entries
340
340
 
341
341
  ---
342
342
 
@@ -344,17 +344,30 @@ After running `serverrf --serve`, you’ll enter the **RemoteRF Server Shell**.
344
344
  todo:
345
345
  <!-- Adding plutos via the CLI -->
346
346
  <!-- Testing functionality (disregarding Host pipeline) -->
347
- Clean up serverrf CLI + basic documentation for all commands, usergroups, export codes by CSV.
348
- Choice to choose usergroup/code by either int id or string name.
347
+ <!-- Clean up serverrf CLI + basic documentation for all commands, usergroups, export codes by CSV. -->
349
348
 
350
- Ability to remotely manage groups and codes as admin.
349
+ HostRF meta data -> push upon connection + retry connection on host side.
350
+
351
+ ServerRF display meta data as part of devices list / reservation + online/activity status (appended into device name) + (plus host side warning if the device id is NOT globally unique!)
352
+
353
+ set_device token (virtual devices) + routing/relay to proper host (multiplexed)
351
354
 
352
- HostRF integration -> Host directory, device routing, dynamic reservation pulling, with activity/status (online, offline, etc.)
355
+ User side testing + integration
356
+
357
+ hostrf side CLI/server (basically just device list)
358
+
359
+ edge case testing (disconnected hosts, disconnected server, many users using multiple devices, multi-host)
360
+
361
+ <!-- HostRF integration -> Host directory, device routing, dynamic reservation pulling, with activity/status (online, offline, etc.) -->
353
362
 
354
363
  Cleanup code comments.
355
364
 
356
365
  Update Ian with installation demo (try myself first on the NUC once!) for both server and host.
357
366
 
367
+ Nice to have (later):
368
+ Activity/stats?
369
+ Ability to remotely manage groups and codes as admin.
370
+ Choice to choose usergroup/code by either int id or string name. (too lazy)
358
371
 
359
372
  todo: Specify/Pull user side --config CLI pointing to this server + debug for connection sake (later)
360
373