remoteRF-server-testing 0.0.10__tar.gz → 0.0.12__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 (49) hide show
  1. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/PKG-INFO +1 -1
  2. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/pyproject.toml +1 -1
  3. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/server/device_manager.py +33 -15
  4. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/serverrf_cli.py +77 -15
  5. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server_testing.egg-info/PKG-INFO +1 -1
  6. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/README.md +0 -0
  7. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/setup.cfg +0 -0
  8. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/__init__.py +0 -0
  9. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/common/__init__.py +0 -0
  10. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/common/grpc/__init__.py +0 -0
  11. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/common/grpc/grpc_host_pb2.py +0 -0
  12. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/common/grpc/grpc_host_pb2_grpc.py +0 -0
  13. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/common/grpc/grpc_pb2.py +0 -0
  14. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/common/grpc/grpc_pb2_grpc.py +0 -0
  15. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/common/idl/__init__.py +0 -0
  16. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/common/idl/device_schema.py +0 -0
  17. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/common/idl/pluto_schema.py +0 -0
  18. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/common/idl/schema.py +0 -0
  19. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/common/utils/__init__.py +0 -0
  20. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/common/utils/ansi_codes.py +0 -0
  21. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/common/utils/api_token.py +0 -0
  22. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/common/utils/db_connection.py +0 -0
  23. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/common/utils/db_location.py +0 -0
  24. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/common/utils/list_string.py +0 -0
  25. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/common/utils/process_arg.py +0 -0
  26. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/drivers/__init__.py +0 -0
  27. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/drivers/adalm_pluto/__init__.py +0 -0
  28. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/drivers/adalm_pluto/pluto_remote_server.py +0 -0
  29. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/host/__init__.py +0 -0
  30. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/host/host_auth_token.py +0 -0
  31. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/host/host_directory_store.py +0 -0
  32. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/host/host_tunnel_server.py +0 -0
  33. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/server/__init__.py +0 -0
  34. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/server/acc_perms.py +0 -0
  35. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/server/cert_provider.py +0 -0
  36. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/server/grpc_server.py +0 -0
  37. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/server/reservation.py +0 -0
  38. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/server/rpc_manager.py +0 -0
  39. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/server/user_group_cli.py +0 -0
  40. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/server/user_group_handler.py +0 -0
  41. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/tools/__init__.py +0 -0
  42. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/tools/gen_certs.py +0 -0
  43. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/tools/gist_status.py +0 -0
  44. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server/tools/gist_status_testing.py +0 -0
  45. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server_testing.egg-info/SOURCES.txt +0 -0
  46. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server_testing.egg-info/dependency_links.txt +0 -0
  47. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server_testing.egg-info/entry_points.txt +0 -0
  48. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server_testing.egg-info/requires.txt +0 -0
  49. {remoterf_server_testing-0.0.10 → remoterf_server_testing-0.0.12}/src/remoteRF_server_testing.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoteRF-server-testing
3
- Version: 0.0.10
3
+ Version: 0.0.12
4
4
  Summary: RemoteRF server-side control package
5
5
  Requires-Python: >=3.8
6
6
  Description-Content-Type: text/markdown
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "remoteRF-server-testing"
7
- version = "0.0.10"
7
+ version = "0.0.12"
8
8
  description = "RemoteRF server-side control package"
9
9
  requires-python = ">=3.8"
10
10
  dependencies = [
@@ -229,6 +229,21 @@ def _host_devices_str_snapshot() -> Dict[int, str]:
229
229
  def _cfg_dir() -> Path:
230
230
  return Path(os.getenv("REMOTERF_CONFIG_DIR", get_remoterf_root()))
231
231
 
232
+ def _super_token_path() -> Path:
233
+ path = _cfg_dir() / "super_token.txt"
234
+ path.parent.mkdir(parents=True, exist_ok=True)
235
+ path.touch(exist_ok=True)
236
+ return path
237
+
238
+ def _get_master_token() -> str:
239
+ """Read the configured super token; blank file means the override is disabled."""
240
+ path = _super_token_path()
241
+ try:
242
+ return path.read_text(encoding="utf-8").strip()
243
+ except OSError as e:
244
+ print(f"Warning: could not read super token file {path}: {e}")
245
+ return ""
246
+
232
247
  def _devices_yaml_path() -> Path:
233
248
  p1 = _cfg_dir() / "devices.yml"
234
249
  if p1.exists():
@@ -316,9 +331,6 @@ def _connect_from_record(rec: Dict[str, Any]):
316
331
  devices_info: Dict[int, str] = {}
317
332
  device_serialization: Dict[int, str] = {}
318
333
 
319
- # master token (overrideable)
320
- master_token = os.getenv("REMOTERF_MASTER_TOKEN", "SuperCoolTokenForIan")
321
-
322
334
  def _init_from_env() -> None:
323
335
  records = _load_device_records()
324
336
 
@@ -378,13 +390,15 @@ def _load_all_drivers() -> None:
378
390
 
379
391
  _load_all_drivers()
380
392
  _init_from_env()
393
+ _super_token_path()
381
394
 
382
395
  # Legacy reservation helpers (master token parsing)
383
396
 
384
- def parse_mastertoken(token: str):
385
- if not token:
397
+ def parse_mastertoken(token: str, configured_token: Optional[str] = None):
398
+ configured_token = _get_master_token() if configured_token is None else configured_token
399
+ if not token or not configured_token:
386
400
  return None
387
- prefix = re.escape(master_token)
401
+ prefix = re.escape(configured_token)
388
402
  pattern = re.compile(rf"^{prefix}[_-](\d+)(?:_force)?$")
389
403
  m = pattern.match(token)
390
404
  if not m:
@@ -528,15 +542,16 @@ def get_schema_by_token(api_token: str):
528
542
 
529
543
  _sync_host_devices()
530
544
  gid: Optional[int] = None
545
+ configured_token = _get_master_token()
531
546
 
532
- if api_token == master_token:
547
+ if configured_token and api_token == configured_token:
533
548
  with _state_lock:
534
549
  for k, st in _devices.items():
535
550
  if st.origin == "local" and _is_connected(st) and st.salt == "" and st.hsh == "":
536
551
  gid = k
537
552
  break
538
553
  else:
539
- parsed = parse_mastertoken(api_token)
554
+ parsed = parse_mastertoken(api_token, configured_token=configured_token)
540
555
  if parsed:
541
556
  cand, _ = parsed
542
557
  with _state_lock:
@@ -577,9 +592,10 @@ def resolve_gid_for_token(api_token: str) -> Optional[int]:
577
592
  return None
578
593
 
579
594
  _sync_host_devices()
595
+ configured_token = _get_master_token()
580
596
 
581
597
  # Parsed master-token: resolves any device directly by gid (local or host).
582
- parsed = parse_mastertoken(api_token)
598
+ parsed = parse_mastertoken(api_token, configured_token=configured_token)
583
599
  if parsed:
584
600
  cand, _ = parsed
585
601
  with _state_lock:
@@ -589,7 +605,7 @@ def resolve_gid_for_token(api_token: str) -> Optional[int]:
589
605
  return None
590
606
 
591
607
  # Raw master token — prefer local, fall back to any connected device.
592
- if api_token == master_token:
608
+ if configured_token and api_token == configured_token:
593
609
  with _state_lock:
594
610
  # local first
595
611
  for k, st in _devices.items():
@@ -630,6 +646,7 @@ def get_device(*, api_token: str):
630
646
  return None
631
647
 
632
648
  _sync_host_devices()
649
+ configured_token = _get_master_token()
633
650
 
634
651
  with _state_lock:
635
652
  snapshot: List[Tuple[int, object, str, str, str]] = [
@@ -639,7 +656,7 @@ def get_device(*, api_token: str):
639
656
  ]
640
657
 
641
658
  # Preserve "master prefers local" behavior
642
- if api_token == master_token:
659
+ if configured_token and api_token == configured_token:
643
660
  # local first
644
661
  for _, dev, salt, hsh, origin in snapshot:
645
662
  if origin == "local" and dev is not None and salt == "" and hsh == "":
@@ -650,7 +667,7 @@ def get_device(*, api_token: str):
650
667
  return dev
651
668
  return None
652
669
 
653
- parsed = parse_mastertoken(api_token)
670
+ parsed = parse_mastertoken(api_token, configured_token=configured_token)
654
671
  if parsed:
655
672
  device_id, force = parsed
656
673
  with _state_lock:
@@ -679,8 +696,9 @@ def acquire_device(api_token: str) -> Iterator[Tuple[int, object]]:
679
696
  _sync_host_devices()
680
697
 
681
698
  gid: Optional[int] = None
699
+ configured_token = _get_master_token()
682
700
 
683
- if api_token == master_token:
701
+ if configured_token and api_token == configured_token:
684
702
  with _state_lock:
685
703
  # prefer local first
686
704
  for k, st in _devices.items():
@@ -693,7 +711,7 @@ def acquire_device(api_token: str) -> Iterator[Tuple[int, object]]:
693
711
  gid = k
694
712
  break
695
713
  else:
696
- parsed = parse_mastertoken(api_token)
714
+ parsed = parse_mastertoken(api_token, configured_token=configured_token)
697
715
  if parsed:
698
716
  cand, force = parsed
699
717
  with _state_lock:
@@ -790,4 +808,4 @@ def reload_devices() -> None:
790
808
 
791
809
  def set_pluto(ip: str = "192.168.2.1"):
792
810
  # kept for compatibility (no-op)
793
- pass
811
+ pass
@@ -5,6 +5,8 @@ import os
5
5
  import subprocess
6
6
  import shutil
7
7
  import re
8
+ import ssl
9
+ import ipaddress
8
10
  from pathlib import Path
9
11
  from typing import Optional, Dict, List, Tuple
10
12
  from datetime import datetime, timezone
@@ -53,6 +55,59 @@ def _fmt_remaining(na_utc: datetime) -> str:
53
55
  return f"{days}d {hours}h {mins}m"
54
56
 
55
57
 
58
+ _STATIC_IP_ENV_KEYS: Tuple[str, ...] = ("STATIC_IP", "SERVER_IP")
59
+
60
+
61
+ def _is_ip_address(value: str) -> bool:
62
+ try:
63
+ ipaddress.ip_address((value or "").strip())
64
+ return True
65
+ except ValueError:
66
+ return False
67
+
68
+
69
+ def _x509_static_ip(cert_path: Path) -> str:
70
+ if not cert_path.exists():
71
+ raise FileNotFoundError(f"server cert not found: {cert_path}")
72
+
73
+ try:
74
+ cert = ssl._ssl._test_decode_cert(str(cert_path))
75
+ except FileNotFoundError:
76
+ raise
77
+ except Exception as e:
78
+ raise ValueError(f"could not parse server cert {cert_path}: {e}") from e
79
+
80
+ for name, value in cert.get("subjectAltName", []):
81
+ if name.lower() == "ip address":
82
+ ip = (value or "").strip()
83
+ if ip:
84
+ return ip
85
+
86
+ for group in cert.get("subject", []):
87
+ for name, value in group:
88
+ if name.lower() != "commonname":
89
+ continue
90
+ cn = (value or "").strip()
91
+ if _is_ip_address(cn):
92
+ return cn
93
+
94
+ raise ValueError(f"server cert does not contain an IP SAN or IP common name: {cert_path}")
95
+
96
+
97
+ def _resolve_static_ip(kv: Dict[str, str]) -> Tuple[Optional[str], str, Optional[str]]:
98
+ env_path = _server_env_path()
99
+ for key in _STATIC_IP_ENV_KEYS:
100
+ value = (kv.get(key, "") or "").strip()
101
+ if value:
102
+ return value, f"{env_path} ({key})", None
103
+
104
+ cert_path = _certs_dir() / "server.crt"
105
+ try:
106
+ return _x509_static_ip(cert_path), str(cert_path), None
107
+ except (FileNotFoundError, ValueError) as e:
108
+ return None, str(cert_path), str(e)
109
+
110
+
56
111
  # -----------------------------
57
112
  # Repo-local server config locations
58
113
  # -----------------------------
@@ -126,7 +181,7 @@ def print_help() -> None:
126
181
  "Port options:\n"
127
182
  " --main-port <int> Set GRPC_PORT\n"
128
183
  " --cert-port <int> Set CERT_PORT\n"
129
- " --show | -s Show current ports\n"
184
+ " --show | -s Show current ports and static IP\n"
130
185
  " -w | --wipe [-y] Wipe ONLY port config (server.env)\n"
131
186
  "\n"
132
187
  "Hosts:\n"
@@ -186,13 +241,13 @@ def print_help() -> None:
186
241
  " serverrf --host --show\n"
187
242
  " serverrf --host --delete lab-host-01\n"
188
243
  " serverrf --host --wipe -y\n"
189
- "\n"
190
- " serverrf --device --add --pluto 0:pluto_aaa:123123\n"
191
- " serverrf --device --edit-name 0 \"New Pluto Name\"\n"
192
- " serverrf --device --remove 0\n"
193
- " serverrf --device --show\n"
194
- " serverrf --device --wipe -y\n"
195
- "\n"
244
+ # "\n"
245
+ # " serverrf --device --add --pluto 0:pluto_aaa:123123\n"
246
+ # " serverrf --device --edit-name 0 \"New Pluto Name\"\n"
247
+ # " serverrf --device --remove 0\n"
248
+ # " serverrf --device --show\n"
249
+ # " serverrf --device --wipe -y\n"
250
+ # "\n"
196
251
  " serverrf -s\n"
197
252
  " serverrf --serve\n"
198
253
  "\n"
@@ -525,20 +580,27 @@ def _gen_certs(
525
580
 
526
581
  def _config_show_ports() -> int:
527
582
  p = _server_env_path()
528
- if not p.exists():
529
- print(f"No port config found (missing {p}).")
530
- return 0
531
-
532
583
  kv = _read_env_kv(p)
533
- if not kv:
534
- print("Port config is empty.")
535
- return 0
536
584
 
537
585
  print(f"Port config: {p}")
586
+ if not p.exists():
587
+ print(" (missing)")
588
+ elif not kv:
589
+ print(" (empty)")
590
+
538
591
  if "GRPC_PORT" in kv:
539
592
  print(f" GRPC_PORT={kv['GRPC_PORT']}")
540
593
  if "CERT_PORT" in kv:
541
594
  print(f" CERT_PORT={kv['CERT_PORT']}")
595
+
596
+ static_ip, source, err = _resolve_static_ip(kv)
597
+ if static_ip:
598
+ print(f" STATIC_IP={static_ip}")
599
+ if source != f"{p} (STATIC_IP)":
600
+ print(f" STATIC_IP_SOURCE={source}")
601
+ else:
602
+ print(" STATIC_IP=(not set)")
603
+ print(f" STATIC_IP_ERROR={err or f'could not resolve from {source}'}")
542
604
  return 0
543
605
 
544
606
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoteRF-server-testing
3
- Version: 0.0.10
3
+ Version: 0.0.12
4
4
  Summary: RemoteRF server-side control package
5
5
  Requires-Python: >=3.8
6
6
  Description-Content-Type: text/markdown