remoteRF-server 0.1.74__tar.gz → 0.1.76__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.74 → remoterf_server-0.1.76}/PKG-INFO +1 -1
  2. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/pyproject.toml +1 -1
  3. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/server/grpc_server.py +213 -69
  4. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server.egg-info/PKG-INFO +1 -1
  5. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/README.md +0 -0
  6. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/setup.cfg +0 -0
  7. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/__init__.py +0 -0
  8. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/common/__init__.py +0 -0
  9. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/common/grpc/__init__.py +0 -0
  10. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/common/grpc/grpc_host_pb2.py +0 -0
  11. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/common/grpc/grpc_host_pb2_grpc.py +0 -0
  12. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/common/grpc/grpc_pb2.py +0 -0
  13. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/common/grpc/grpc_pb2_grpc.py +0 -0
  14. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/common/utils/__init__.py +0 -0
  15. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/common/utils/ansi_codes.py +0 -0
  16. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/common/utils/api_token.py +0 -0
  17. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/common/utils/db_connection.py +0 -0
  18. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/common/utils/db_location.py +0 -0
  19. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/common/utils/list_string.py +0 -0
  20. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/common/utils/process_arg.py +0 -0
  21. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/drivers/__init__.py +0 -0
  22. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/drivers/adalm_pluto/__init__.py +0 -0
  23. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/drivers/adalm_pluto/pluto_remote_server.py +0 -0
  24. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/host/__init__.py +0 -0
  25. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/host/host_directory_store.py +0 -0
  26. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/host/host_tunnel_server.py +0 -0
  27. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/server/__init__.py +0 -0
  28. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/server/acc_perms.py +0 -0
  29. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/server/cert_provider.py +0 -0
  30. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/server/device_manager.py +0 -0
  31. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/server/reservation.py +0 -0
  32. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/server/rpc_manager.py +0 -0
  33. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/server/user_group_cli.py +0 -0
  34. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/server/user_group_handler.py +0 -0
  35. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/serverrf_cli.py +0 -0
  36. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/tools/__init__.py +0 -0
  37. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server/tools/gen_certs.py +0 -0
  38. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server.egg-info/SOURCES.txt +0 -0
  39. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server.egg-info/dependency_links.txt +0 -0
  40. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server.egg-info/entry_points.txt +0 -0
  41. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/src/remoteRF_server.egg-info/requires.txt +0 -0
  42. {remoterf_server-0.1.74 → remoterf_server-0.1.76}/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.74
3
+ Version: 0.1.76
4
4
  Description-Content-Type: text/markdown
5
5
  Requires-Dist: grpcio
6
6
  Requires-Dist: protobuf
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "remoteRF-server"
7
- version = "0.1.74"
7
+ version = "0.1.76"
8
8
  dependencies = [
9
9
  "grpcio",
10
10
  "protobuf",
@@ -17,6 +17,13 @@ from datetime import datetime
17
17
  # os.environ.setdefault("GRPC_VERBOSITY", "ERROR")
18
18
  # os.environ.setdefault("GRPC_TRACE", "")
19
19
 
20
+ from __future__ import annotations
21
+ from pathlib import Path
22
+ import os
23
+ import re
24
+ import time
25
+ from typing import Any
26
+
20
27
  import grpc
21
28
 
22
29
  import traceback
@@ -461,24 +468,89 @@ def _repo_root_guess() -> Path:
461
468
  # fallback
462
469
  return p.parents[3] if len(p.parents) >= 4 else p.parent
463
470
 
471
+ # Optional: if you later add "kick" support (see section 2)
472
+ HOST_TUNNEL_EPOCH = 0
473
+
474
+ def _read_env_kv_file(path: Path) -> dict[str, str]:
475
+ out: dict[str, str] = {}
476
+ if not path.exists():
477
+ return out
478
+ for raw in path.read_text(encoding="utf-8").splitlines():
479
+ line = raw.strip()
480
+ if not line or line.startswith("#") or "=" not in line:
481
+ continue
482
+ k, v = line.split("=", 1)
483
+ out[k.strip()] = v.strip().strip('"').strip("'")
484
+ return out
485
+
486
+ def _env_values_as_paths(env_path: Path) -> list[Path]:
487
+ """
488
+ Parse an env file and treat values that look like filesystem paths as candidate dirs.
489
+ Relative paths are resolved relative to the env file's directory.
490
+ """
491
+ kv = _read_env_kv_file(env_path)
492
+ base = env_path.parent
493
+ out: list[Path] = []
494
+
495
+ for _, v in kv.items():
496
+ if not v:
497
+ continue
498
+ # heuristics: looks like a path
499
+ if ("/" not in v) and ("\\" not in v) and (not v.startswith(".")) and (not v.startswith("~")):
500
+ continue
501
+
502
+ p = Path(v).expanduser()
503
+ if not p.is_absolute():
504
+ p = (base / p).resolve()
505
+ out.append(p)
506
+
507
+ return out
508
+
464
509
  def _host_state_dirs() -> list[Path]:
465
510
  """
466
- Places host-directory persistence might live in your codebase:
467
- - User config dir: ~/.config/remoterf (REMOTERF_CONFIG_DIR override)
468
- - Repo-local: <repo>/.config
469
- - Server config dir you referenced: <repo>/src/remoteRF_server/config (because of .env.host_directory)
511
+ Aggressive candidate list:
512
+ - user config (~/.config/remoterf or REMOTERF_CONFIG_DIR)
513
+ - repo/.config
514
+ - repo/config (+ common subdirs)
515
+ - src/remoteRF_server/config
516
+ - plus env overrides + paths referenced by .env.host_directory (if present)
470
517
  """
471
- cands = [
518
+ repo = _repo_root_guess()
519
+
520
+ cands: list[Path] = [
472
521
  _cfg_dir(),
473
- _repo_root_guess() / ".config",
474
- (Path(__file__).resolve().parent.parent / "config"), # <repo>/src/remoteRF_server/config
522
+ repo / ".config",
523
+ repo / "config",
524
+ repo / "config" / "remoteRF_server",
525
+ repo / "config" / "remoteRF_host",
526
+ repo / "src" / "remoteRF_server" / "config",
527
+ (Path(__file__).resolve().parent.parent / "config"),
475
528
  ]
476
529
 
530
+ # Environment overrides (add any you might plausibly use)
531
+ for k in (
532
+ "REMOTERF_HOSTDIR_DIR",
533
+ "REMOTERF_HOST_DIRECTORY_DIR",
534
+ "REMOTERF_HOST_DIR",
535
+ "HOST_DIRECTORY_DIR",
536
+ "HOST_DIRECTORY_PATH",
537
+ "REMOTERF_STATE_DIR",
538
+ ):
539
+ v = os.getenv(k)
540
+ if v:
541
+ cands.append(Path(v).expanduser())
542
+
543
+ # If you have a .env.host_directory file, harvest any path-like values from it
544
+ env_host_dir = repo / "src" / "remoteRF_server" / "config" / ".env.host_directory"
545
+ if env_host_dir.exists():
546
+ cands.extend(_env_values_as_paths(env_host_dir))
547
+
548
+ # de-dupe + keep only dirs (or dirs that could exist)
477
549
  out: list[Path] = []
478
550
  seen: set[str] = set()
479
551
  for d in cands:
480
552
  try:
481
- rp = str(d.resolve())
553
+ rp = str(d.expanduser().resolve())
482
554
  except Exception:
483
555
  rp = str(d)
484
556
  if rp not in seen:
@@ -488,20 +560,42 @@ def _host_state_dirs() -> list[Path]:
488
560
 
489
561
  def _host_state_files() -> list[Path]:
490
562
  """
491
- Exact files your HostTunnelRegistry has historically used.
492
- Also includes a conservative glob for future additions.
563
+ Broader scan than just host_directory.env:
564
+ We only match filenames that clearly look like host/tunnel/registry state.
493
565
  """
566
+ patterns = [
567
+ "host_directory*.env",
568
+ "host_directory*.json",
569
+ "host_directory*.db",
570
+ "host_directory*.sqlite*",
571
+ "host_directory*.pkl",
572
+ "host_tunnel*.env",
573
+ "host_tunnel*.json",
574
+ "tunnel_registry*.env",
575
+ "tunnel_registry*.json",
576
+ "*host*registry*.json",
577
+ "*host*registry*.db",
578
+ "*host*registry*.sqlite*",
579
+ "*host*state*.json",
580
+ "*host*state*.db",
581
+ "*host*state*.sqlite*",
582
+ ]
583
+
494
584
  files: list[Path] = []
495
585
  for d in _host_state_dirs():
496
- files.append(d / "host_directory.env")
497
- files.append(d / "host_directory_meta.env")
498
- # If you later add more persisted host-dir files, keep them discoverable.
499
586
  try:
500
- for p in sorted(d.glob("host_directory*.env")):
501
- files.append(p)
587
+ if not d.exists() or not d.is_dir():
588
+ continue
502
589
  except Exception:
503
- pass
590
+ continue
591
+
592
+ for pat in patterns:
593
+ try:
594
+ files.extend(sorted(d.glob(pat)))
595
+ except Exception:
596
+ pass
504
597
 
598
+ # de-dupe
505
599
  out: list[Path] = []
506
600
  seen: set[str] = set()
507
601
  for p in files:
@@ -514,27 +608,67 @@ def _host_state_files() -> list[Path]:
514
608
  out.append(p)
515
609
  return out
516
610
 
517
- def _cmd_hosts_persisted() -> None:
518
- files = _host_state_files()
519
- found_any = False
520
- printf("Host directory (persisted) state files:", Sty.BOLD)
521
- for p in files:
522
- if p.exists():
523
- found_any = True
524
- try:
525
- size = p.stat().st_size
526
- except Exception:
527
- size = -1
528
- printf(" - ", Sty.DEFAULT, f"{p}", Sty.MAGENTA, f" ({size} bytes)", Sty.GRAY)
529
- else:
530
- printf(" - ", Sty.DEFAULT, f"{p}", Sty.GRAY, " (missing)", Sty.GRAY)
531
- if not found_any:
532
- printf(" (none found)", Sty.GRAY)
611
+ def _deep_clear_state(obj: Any) -> list[str]:
612
+ """
613
+ Best-effort: clear dict/list/set fields on an object, recursively,
614
+ but only for fields that look like state (host/device/route/session/cache/store/etc).
615
+ """
616
+ cleared: list[str] = []
617
+ seen: set[int] = set()
618
+
619
+ state_name = re.compile(r"(host|device|route|session|dir|store|cache|meta|seen|status)", re.I)
620
+
621
+ def walk(x: Any, prefix: str) -> None:
622
+ oid = id(x)
623
+ if oid in seen:
624
+ return
625
+ seen.add(oid)
626
+
627
+ # containers
628
+ if isinstance(x, dict):
629
+ x.clear()
630
+ cleared.append(prefix)
631
+ return
632
+ if isinstance(x, list):
633
+ x.clear()
634
+ cleared.append(prefix)
635
+ return
636
+ if isinstance(x, set):
637
+ x.clear()
638
+ cleared.append(prefix)
639
+ return
640
+
641
+ # objects with __dict__
642
+ d = getattr(x, "__dict__", None)
643
+ if not isinstance(d, dict):
644
+ return
645
+
646
+ for k, v in list(d.items()):
647
+ # never touch locks/threads/etc
648
+ if k in ("_lock", "lock", "_thread", "thread", "_executor", "executor"):
649
+ continue
650
+ if not state_name.search(k):
651
+ continue
652
+
653
+ if isinstance(v, (dict, list, set)):
654
+ try:
655
+ v.clear()
656
+ cleared.append(f"{prefix}.{k}")
657
+ except Exception:
658
+ pass
659
+ else:
660
+ # recurse into nested state objects (EnvStore, etc.)
661
+ walk(v, f"{prefix}.{k}")
662
+
663
+ walk(obj, obj.__class__.__name__)
664
+ return cleared
533
665
 
534
666
  def _try_clear_registry_in_memory(reg: object) -> list[str]:
535
667
  """
536
- Best-effort in-memory wipe without knowing exact internals.
537
- Uses registry lock if present.
668
+ Stronger in-memory clear:
669
+ - calls obvious reset/clear methods if present
670
+ - clears common containers
671
+ - recursively clears state-like fields (even if names differ)
538
672
  """
539
673
  cleared: list[str] = []
540
674
 
@@ -545,31 +679,18 @@ def _try_clear_registry_in_memory(reg: object) -> list[str]:
545
679
  lock.acquire()
546
680
  acquired = True
547
681
 
548
- # common private dict fields
549
- for attr in (
550
- "_hosts",
551
- "_devices",
552
- "_host_by_id",
553
- "_device_by_id",
554
- "_device_routes",
555
- "_routes",
556
- "_sessions",
557
- "_host_sessions",
682
+ # Prefer explicit APIs if you have them
683
+ for meth in (
684
+ "clear",
685
+ "reset",
686
+ "wipe",
687
+ "wipe_all",
688
+ "wipe_state",
689
+ "clear_state",
690
+ "reset_state",
691
+ "prune",
692
+ "gc",
558
693
  ):
559
- d = getattr(reg, attr, None)
560
- if isinstance(d, dict):
561
- d.clear()
562
- cleared.append(attr)
563
-
564
- # common private sets/lists
565
- for attr in ("_connected", "_online_hosts"):
566
- s = getattr(reg, attr, None)
567
- if isinstance(s, set):
568
- s.clear()
569
- cleared.append(attr)
570
-
571
- # if there is an explicit method, prefer it (won’t hurt if absent)
572
- for meth in ("clear", "reset", "wipe", "wipe_all", "wipe_state"):
573
694
  fn = getattr(reg, meth, None)
574
695
  if callable(fn):
575
696
  try:
@@ -578,6 +699,18 @@ def _try_clear_registry_in_memory(reg: object) -> list[str]:
578
699
  except Exception:
579
700
  pass
580
701
 
702
+ # Clear obvious containers by direct attribute scan
703
+ for attr, v in list(getattr(reg, "__dict__", {}).items()):
704
+ if isinstance(v, (dict, list, set)):
705
+ try:
706
+ v.clear()
707
+ cleared.append(attr)
708
+ except Exception:
709
+ pass
710
+
711
+ # Recursive targeted clear (for EnvStore etc.)
712
+ cleared.extend(_deep_clear_state(reg))
713
+
581
714
  finally:
582
715
  if acquired:
583
716
  try:
@@ -585,13 +718,19 @@ def _try_clear_registry_in_memory(reg: object) -> list[str]:
585
718
  except Exception:
586
719
  pass
587
720
 
588
- return cleared
721
+ # de-dupe
722
+ out: list[str] = []
723
+ seen: set[str] = set()
724
+ for x in cleared:
725
+ if x not in seen:
726
+ seen.add(x)
727
+ out.append(x)
728
+ return out
589
729
 
590
730
  def _cmd_hosts_wipe(*, yes: bool = False) -> None:
591
- # confirm
592
731
  if not yes:
593
732
  try:
594
- resp = session.prompt(stylize("Type 'wipe-hosts' to confirm wiping persisted host state: ", Sty.RED))
733
+ resp = session.prompt(stylize("Type 'wipe-hosts' to confirm wiping host state: ", Sty.RED))
595
734
  except KeyboardInterrupt:
596
735
  printf("Cancelled.", Sty.GRAY)
597
736
  return
@@ -599,10 +738,10 @@ def _cmd_hosts_wipe(*, yes: bool = False) -> None:
599
738
  printf("Wipe aborted.", Sty.GRAY)
600
739
  return
601
740
 
602
- # delete persisted files
741
+ # 1) delete persisted files (if any)
603
742
  files = [p for p in _host_state_files() if p.exists()]
604
743
  if not files:
605
- printf("No persisted host directory files found to wipe.", Sty.GRAY)
744
+ printf("No persisted host directory files found (searched common dirs/patterns).", Sty.YELLOW)
606
745
  else:
607
746
  for p in files:
608
747
  try:
@@ -612,20 +751,25 @@ def _cmd_hosts_wipe(*, yes: bool = False) -> None:
612
751
  printf("ERROR deleting: ", Sty.RED, f"{p} -> {e}", Sty.DEFAULT)
613
752
  return
614
753
 
615
- # clear in-memory registry (so you don’t have to restart for live state)
754
+ # 2) clear in-memory registry (THIS is what drives `hosts status`)
616
755
  global TUNNEL_REGISTRY
617
756
  reg = TUNNEL_REGISTRY
618
757
  if reg is None:
619
- printf("Host tunnel registry not initialized; persisted files wiped only.", Sty.GRAY)
758
+ printf("Host tunnel registry not initialized; only persisted wipe attempted.", Sty.GRAY)
620
759
  else:
621
760
  cleared = _try_clear_registry_in_memory(reg)
622
761
  if cleared:
623
- printf("Cleared in-memory registry fields: ", Sty.DEFAULT, ", ".join(cleared), Sty.GRAY)
762
+ printf("Cleared in-memory registry state:", Sty.GREEN)
763
+ # keep output short-ish
764
+ for item in cleared[:20]:
765
+ printf(" - ", Sty.DEFAULT, item, Sty.GRAY)
766
+ if len(cleared) > 20:
767
+ printf(" ...", Sty.GRAY)
624
768
  else:
625
- printf("WARNING: Could not confirm in-memory registry clear (unknown internals). Restart server if needed.", Sty.YELLOW)
769
+ printf("WARNING: in-memory wipe could not confirm any cleared fields.", Sty.YELLOW)
770
+ printf("If `hosts status` still shows data, it is almost certainly being repopulated by live connections.", Sty.YELLOW)
626
771
 
627
- printf("Host state wipe complete.", Sty.GREEN)
628
- printf("NOTE: If hosts reconnect, persisted files will be recreated.", Sty.GRAY)
772
+ printf("Host wipe complete.", Sty.GREEN)
629
773
 
630
774
  # def help():
631
775
  # printf("/------- HELP -------/", Sty.BOLD)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoteRF-server
3
- Version: 0.1.74
3
+ Version: 0.1.76
4
4
  Description-Content-Type: text/markdown
5
5
  Requires-Dist: grpcio
6
6
  Requires-Dist: protobuf