remoteRF-server 0.1.73__tar.gz → 0.1.75__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.73 → remoterf_server-0.1.75}/PKG-INFO +1 -1
  2. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/pyproject.toml +1 -1
  3. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/server/grpc_server.py +332 -1
  4. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server.egg-info/PKG-INFO +1 -1
  5. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/README.md +0 -0
  6. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/setup.cfg +0 -0
  7. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/__init__.py +0 -0
  8. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/common/__init__.py +0 -0
  9. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/common/grpc/__init__.py +0 -0
  10. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/common/grpc/grpc_host_pb2.py +0 -0
  11. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/common/grpc/grpc_host_pb2_grpc.py +0 -0
  12. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/common/grpc/grpc_pb2.py +0 -0
  13. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/common/grpc/grpc_pb2_grpc.py +0 -0
  14. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/common/utils/__init__.py +0 -0
  15. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/common/utils/ansi_codes.py +0 -0
  16. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/common/utils/api_token.py +0 -0
  17. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/common/utils/db_connection.py +0 -0
  18. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/common/utils/db_location.py +0 -0
  19. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/common/utils/list_string.py +0 -0
  20. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/common/utils/process_arg.py +0 -0
  21. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/drivers/__init__.py +0 -0
  22. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/drivers/adalm_pluto/__init__.py +0 -0
  23. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/drivers/adalm_pluto/pluto_remote_server.py +0 -0
  24. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/host/__init__.py +0 -0
  25. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/host/host_directory_store.py +0 -0
  26. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/host/host_tunnel_server.py +0 -0
  27. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/server/__init__.py +0 -0
  28. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/server/acc_perms.py +0 -0
  29. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/server/cert_provider.py +0 -0
  30. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/server/device_manager.py +0 -0
  31. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/server/reservation.py +0 -0
  32. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/server/rpc_manager.py +0 -0
  33. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/server/user_group_cli.py +0 -0
  34. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/server/user_group_handler.py +0 -0
  35. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/serverrf_cli.py +0 -0
  36. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/tools/__init__.py +0 -0
  37. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server/tools/gen_certs.py +0 -0
  38. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server.egg-info/SOURCES.txt +0 -0
  39. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server.egg-info/dependency_links.txt +0 -0
  40. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server.egg-info/entry_points.txt +0 -0
  41. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/src/remoteRF_server.egg-info/requires.txt +0 -0
  42. {remoterf_server-0.1.73 → remoterf_server-0.1.75}/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.73
3
+ Version: 0.1.75
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.73"
7
+ version = "0.1.75"
8
8
  dependencies = [
9
9
  "grpcio",
10
10
  "protobuf",
@@ -445,6 +445,331 @@ def _cmd_devices_status() -> None:
445
445
  " online=", Sty.DEFAULT, f"{ds.online}", Sty.GREEN if ds.online else Sty.RED,
446
446
  " last_seen=", Sty.DEFAULT, f"{_fmt_ms_ago(ds.last_seen_ms)} ago", Sty.BLUE
447
447
  )
448
+
449
+ from typing import Iterable
450
+ import contextlib
451
+
452
+ def _repo_root_guess() -> Path:
453
+ """
454
+ Best-effort: find <repo>/src/... and return <repo>.
455
+ Fallback: walk up a few levels from this file.
456
+ """
457
+ p = Path(__file__).resolve()
458
+ for parent in p.parents:
459
+ if parent.name == "src":
460
+ return parent.parent
461
+ # fallback
462
+ return p.parents[3] if len(p.parents) >= 4 else p.parent
463
+
464
+ from __future__ import annotations
465
+ from pathlib import Path
466
+ import os
467
+ import re
468
+ import time
469
+ from typing import Any
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
+
509
+ def _host_state_dirs() -> list[Path]:
510
+ """
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)
517
+ """
518
+ repo = _repo_root_guess()
519
+
520
+ cands: list[Path] = [
521
+ _cfg_dir(),
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"),
528
+ ]
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)
549
+ out: list[Path] = []
550
+ seen: set[str] = set()
551
+ for d in cands:
552
+ try:
553
+ rp = str(d.expanduser().resolve())
554
+ except Exception:
555
+ rp = str(d)
556
+ if rp not in seen:
557
+ seen.add(rp)
558
+ out.append(d)
559
+ return out
560
+
561
+ def _host_state_files() -> list[Path]:
562
+ """
563
+ Broader scan than just host_directory.env:
564
+ We only match filenames that clearly look like host/tunnel/registry state.
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
+
584
+ files: list[Path] = []
585
+ for d in _host_state_dirs():
586
+ try:
587
+ if not d.exists() or not d.is_dir():
588
+ continue
589
+ except Exception:
590
+ continue
591
+
592
+ for pat in patterns:
593
+ try:
594
+ files.extend(sorted(d.glob(pat)))
595
+ except Exception:
596
+ pass
597
+
598
+ # de-dupe
599
+ out: list[Path] = []
600
+ seen: set[str] = set()
601
+ for p in files:
602
+ try:
603
+ rp = str(p.resolve())
604
+ except Exception:
605
+ rp = str(p)
606
+ if rp not in seen:
607
+ seen.add(rp)
608
+ out.append(p)
609
+ return out
610
+
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
665
+
666
+ def _try_clear_registry_in_memory(reg: object) -> list[str]:
667
+ """
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)
672
+ """
673
+ cleared: list[str] = []
674
+
675
+ lock = getattr(reg, "_lock", None)
676
+ acquired = False
677
+ try:
678
+ if lock is not None and hasattr(lock, "acquire") and hasattr(lock, "release"):
679
+ lock.acquire()
680
+ acquired = True
681
+
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",
693
+ ):
694
+ fn = getattr(reg, meth, None)
695
+ if callable(fn):
696
+ try:
697
+ fn()
698
+ cleared.append(f"{meth}()")
699
+ except Exception:
700
+ pass
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
+
714
+ finally:
715
+ if acquired:
716
+ try:
717
+ lock.release()
718
+ except Exception:
719
+ pass
720
+
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
729
+
730
+ def _cmd_hosts_wipe(*, yes: bool = False) -> None:
731
+ if not yes:
732
+ try:
733
+ resp = session.prompt(stylize("Type 'wipe-hosts' to confirm wiping host state: ", Sty.RED))
734
+ except KeyboardInterrupt:
735
+ printf("Cancelled.", Sty.GRAY)
736
+ return
737
+ if (resp or "").strip().lower() != "wipe-hosts":
738
+ printf("Wipe aborted.", Sty.GRAY)
739
+ return
740
+
741
+ # 1) delete persisted files (if any)
742
+ files = [p for p in _host_state_files() if p.exists()]
743
+ if not files:
744
+ printf("No persisted host directory files found (searched common dirs/patterns).", Sty.YELLOW)
745
+ else:
746
+ for p in files:
747
+ try:
748
+ p.unlink()
749
+ printf("Deleted: ", Sty.DEFAULT, f"{p}", Sty.MAGENTA)
750
+ except Exception as e:
751
+ printf("ERROR deleting: ", Sty.RED, f"{p} -> {e}", Sty.DEFAULT)
752
+ return
753
+
754
+ # 2) clear in-memory registry (THIS is what drives `hosts status`)
755
+ global TUNNEL_REGISTRY
756
+ reg = TUNNEL_REGISTRY
757
+ if reg is None:
758
+ printf("Host tunnel registry not initialized; only persisted wipe attempted.", Sty.GRAY)
759
+ else:
760
+ cleared = _try_clear_registry_in_memory(reg)
761
+ if cleared:
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)
768
+ else:
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)
771
+
772
+ printf("Host wipe complete.", Sty.GREEN)
448
773
 
449
774
  # def help():
450
775
  # printf("/------- HELP -------/", Sty.BOLD)
@@ -571,6 +896,7 @@ def help() -> None:
571
896
 
572
897
  printf("Host Tunnel (live):", Sty.BOLD)
573
898
  printf(" hosts status ", Sty.MAGENTA, "- Live host online/last_seen/devices", Sty.DEFAULT)
899
+ printf(" hosts wipe ", Sty.MAGENTA, "- Wipe persisted host directory state (and clear in-memory)", Sty.DEFAULT)
574
900
  printf(" devices status ", Sty.MAGENTA, "- Live device online/last_seen/route", Sty.DEFAULT)
575
901
  print()
576
902
 
@@ -669,7 +995,6 @@ def server_input(inpu: str) -> bool:
669
995
  # ---- host tunnel ----
670
996
  "hosts status": lambda: _stay(_cmd_hosts_status),
671
997
  "host status": lambda: _stay(_cmd_hosts_status),
672
- "tunnel hosts": lambda: _stay(_cmd_hosts_status),
673
998
 
674
999
  "devices status": lambda: _stay(_cmd_devices_status),
675
1000
  "tunnel devices": lambda: _stay(_cmd_devices_status),
@@ -679,6 +1004,12 @@ def server_input(inpu: str) -> bool:
679
1004
 
680
1005
  fn = DISPATCH.get(cmd)
681
1006
  if fn is None:
1007
+ # allow: "hosts wipe" and "hosts wipe -y"
1008
+ if cmd.startswith("hosts wipe"):
1009
+ yes = ("-y" in cmd.split()) or ("--yes" in cmd.split())
1010
+ _cmd_hosts_wipe(yes=yes)
1011
+ return True
1012
+
682
1013
  printf("Unknown command: ", Sty.RED, f"{inpu}", Sty.DEFAULT)
683
1014
  printf("Type ", Sty.DEFAULT, "help", Sty.MAGENTA, " to see commands.", Sty.DEFAULT)
684
1015
  return True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoteRF-server
3
- Version: 0.1.73
3
+ Version: 0.1.75
4
4
  Description-Content-Type: text/markdown
5
5
  Requires-Dist: grpcio
6
6
  Requires-Dist: protobuf