remoteRF-server 1.7.9__tar.gz → 1.7.11__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.
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/PKG-INFO +1 -1
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/pyproject.toml +1 -1
- remoterf_server-1.7.11/src/remoteRF_server/drivers/wifi/__init__.py +6 -0
- remoterf_server-1.7.9/src/remoteRF_server/drivers/wifi_dongle/wifi_dongle_schema.py → remoterf_server-1.7.11/src/remoteRF_server/drivers/wifi/wifi_schema.py +6 -7
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/remote_admin_shell.py +1 -1
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/commands.py +7 -1
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/device_manager.py +42 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/federation/cli.py +41 -9
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/global_onboarding.py +14 -1
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/grpc_server.py +113 -8
- remoterf_server-1.7.11/src/remoteRF_server/server/json_output.py +30 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/reservation.py +39 -1
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/serverrf_cli.py +21 -5
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/tools/__init__.py +5 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server.egg-info/PKG-INFO +1 -1
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server.egg-info/SOURCES.txt +6 -3
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_commands.py +7 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_contrib_drivers.py +38 -5
- remoterf_server-1.7.11/tests/test_default_wifi_schema.py +154 -0
- remoterf_server-1.7.11/tests/test_json_commands.py +230 -0
- remoterf_server-1.7.11/tests/test_reservation_cancel.py +146 -0
- remoterf_server-1.7.9/src/remoteRF_server/drivers/wifi_dongle/__init__.py +0 -1
- remoterf_server-1.7.9/tests/test_default_wifi_dongle_schema.py +0 -26
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/LICENSE +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/README.md +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/setup.cfg +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/__init__.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/__init__.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/grpc/__init__.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/grpc/deployment_capabilities.proto +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/grpc/deployment_capabilities_pb2.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/grpc/deployment_capabilities_pb2_grpc.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/grpc/federation.proto +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/grpc/federation_pb2.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/grpc/federation_pb2_grpc.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/grpc/grpc_host_pb2.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/grpc/grpc_host_pb2_grpc.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/grpc/grpc_pb2.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/grpc/grpc_pb2_grpc.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/grpc/v2_codec.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/idl/__init__.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/idl/device_schema.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/idl/pluto_mimo_schema.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/idl/pluto_schema.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/idl/schema.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/idl/v2.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/utils/__init__.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/utils/ansi_codes.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/utils/api_token.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/utils/db_connection.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/utils/db_location.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/utils/list_string.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/utils/panel.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/common/utils/process_arg.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/drivers/__init__.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/drivers/adalm_pluto/__init__.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/drivers/adalm_pluto/pluto_diag.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/drivers/adalm_pluto/pluto_mimo_schema.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/drivers/adalm_pluto/pluto_remote_server.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/drivers/adalm_pluto/pluto_schema.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/drivers/adalm_pluto/poe.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/drivers/bluetooth_diag.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/drivers/gps_diag.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/drivers/hackrf/__init__.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/drivers/hackrf/hackrf_schema.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/drivers/rtl_sdr/__init__.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/drivers/rtl_sdr/rtl_sdr_diag.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/drivers/rtl_sdr/rtl_sdr_schema.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/drivers/ti_mmwave/__init__.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/drivers/ti_mmwave/ti_mmwave_schema.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/drivers/usrp/__init__.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/drivers/usrp/policy.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/drivers/usrp/profiles.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/drivers/usrp/uhd_manifest.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/drivers/usrp/usrp_diag.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/drivers/usrp/usrp_schema.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/drivers/usrp_phaser/__init__.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/drivers/usrp_phaser/usrp_phaser_schema.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/host/__init__.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/host/host_auth_token.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/host/host_directory_store.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/host/host_tunnel_server.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/__init__.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/acc_perms.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/cert_provider.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/console.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/control.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/deployment/__init__.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/deployment/capabilities.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/diag.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/direct_path.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/dynamic_v2.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/dynamic_v2_bridge.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/federation/__init__.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/federation/admin.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/federation/cluster.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/federation/contracts.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/federation/destination.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/federation/devices.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/federation/enrollment.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/federation/global_home.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/federation/home.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/federation/home_devices.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/federation/identity.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/federation/legacy_state.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/federation/links.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/federation/migrations.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/federation/peers.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/federation/permissions.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/federation/principals.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/federation/runtime.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/federation/trust.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/host_direct.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/host_dynamic_v2.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/remote_admin.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/remote_admin_ssh.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/rpc_manager.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/schema_registry.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/startup_check.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/user_group_cli.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/user_group_handler.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/v2_errors.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/tools/gen_certs.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/tools/gist_status.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/tools/gist_status_testing.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/tools/introspect_uhd.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server.egg-info/dependency_links.txt +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server.egg-info/entry_points.txt +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server.egg-info/requires.txt +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server.egg-info/top_level.txt +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/rssi/__init__.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/rssi/cli.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/rssi/dongle.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/rssi/safety.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/rssi/scanner.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_account_hardening.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_account_names.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_account_removal.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_activity_log.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_availability.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_bind_host.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_cert_provider.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_client_sessions.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_cluster.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_console.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_default_rtl_sdr_schema.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_default_usrp_schema.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_deployment_capabilities.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_detached_device_rpc.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_device_core_aliases.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_device_handles.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_device_init.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_devices_add_wizard.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_devices_list.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_diag.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_direct_fixed_port.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_direct_path_server.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_dynamic_v2.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_federated_direct_path.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_federated_dynamic_v2.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_federation_edges.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_first_run.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_gate_d_contracts.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_gate_e_federation.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_gate_e_proto.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_gate_f_enrollment.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_gate_g1_logging.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_gate_g_permissions.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_gate_h_devices.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_gate_i_direct_path.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_gen_certs.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_global_accounts_migration.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_global_home.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_global_onboarding.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_global_v1_disposal.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_hackrf_schema.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_home_device_plane.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_host_direct.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_host_directory_store.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_host_dynamic_v2.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_idl_schema.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_lab_logins.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_panel.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_peer_transport.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_pluto_compat.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_pluto_poe.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_process_arg.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_proxy_lifecycle.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_remote_admin.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_reservation_handoff.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_reservation_policy_b.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_reservation_timestamps.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_rpc_manager_batch.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_rpc_manager_calln.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_rpc_manager_host_legacy.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_rpc_manager_rx_n.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_rtl_sdr_schema.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_schema_registry.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_soak_federation.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_startup_check.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_status_push.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_ti_mmwave_hardware.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_ti_mmwave_schema.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_user_group_cli.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_usrp_b205mini_uhd410_hardware.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_usrp_client_server_hardware.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_usrp_client_server_localhost.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_usrp_n210_client_server_hardware.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_usrp_preload.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_usrp_profiles.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_usrp_remoterf_rx_hardware.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_usrp_uhd410_hardware.py +0 -0
- {remoterf_server-1.7.9 → remoterf_server-1.7.11}/tests/test_wipe.py +0 -0
|
@@ -566,19 +566,18 @@ class WifiDongleDevice:
|
|
|
566
566
|
# RemoteRF Device Schema Class
|
|
567
567
|
# -----------------------------------------------------------------------------
|
|
568
568
|
|
|
569
|
-
@idl_register("
|
|
570
|
-
class
|
|
569
|
+
@idl_register("wifi")
|
|
570
|
+
class WifiSchema(DeviceSchema):
|
|
571
571
|
"""
|
|
572
572
|
RemoteRF Device Schema for USB Wi-Fi Dongles (RSSI Collector & RF Monitor).
|
|
573
573
|
|
|
574
574
|
Exposes live wireless telemetry, accurate linear-power RSSI statistics,
|
|
575
575
|
batch burst sampling, and deep 802.11 physical layer diagnostics to remote Python clients.
|
|
576
576
|
"""
|
|
577
|
-
device_type = "
|
|
577
|
+
device_type = "wifi"
|
|
578
578
|
client_class = "WifiDongle"
|
|
579
579
|
driver_version = "0.1.0"
|
|
580
580
|
|
|
581
|
-
client_modules = {"wifi_dongle": "remoteRF.drivers.support.wifi_dongle"}
|
|
582
581
|
|
|
583
582
|
@staticmethod
|
|
584
583
|
def make_device(**kwargs) -> Optional[WifiDongleDevice]:
|
|
@@ -599,16 +598,16 @@ class WifiDongleSchema(DeviceSchema):
|
|
|
599
598
|
max_history = int(kwargs.get("max_history", 100))
|
|
600
599
|
|
|
601
600
|
try:
|
|
602
|
-
print(f"[
|
|
601
|
+
print(f"[wifi] Initializing USB Wi-Fi dongle (interface={interface or 'auto'})...")
|
|
603
602
|
dev = WifiDongleDevice(
|
|
604
603
|
interface=interface,
|
|
605
604
|
auto_rescan=auto_rescan,
|
|
606
605
|
max_history=max_history
|
|
607
606
|
)
|
|
608
|
-
print(f"[
|
|
607
|
+
print(f"[wifi] Bound to interface '{dev.interface}'. Default route protected.")
|
|
609
608
|
return dev
|
|
610
609
|
except Exception as exc:
|
|
611
|
-
print(f"[
|
|
610
|
+
print(f"[wifi] Failed to initialize device: {exc}")
|
|
612
611
|
return None
|
|
613
612
|
|
|
614
613
|
# -------------------------------------------------------------------------
|
|
@@ -56,7 +56,7 @@ TIERS = {
|
|
|
56
56
|
"cluster create": WRITE, "cluster invite": WRITE, "cluster join": WRITE, "cluster kick": WRITE,
|
|
57
57
|
"cluster cap": WRITE, "cluster peers": WRITE, "restart": WRITE, "reload devices": WRITE,
|
|
58
58
|
"db export": WRITE, "cluster groups": WRITE, "cluster codes create": WRITE, "cluster accept": WRITE,
|
|
59
|
-
"schema download": WRITE,
|
|
59
|
+
"schema download": WRITE, "reservations cancel": WRITE,
|
|
60
60
|
# write, interactive in the daemon: through the control socket's prompt bridge
|
|
61
61
|
"users manage": WRITE, "diag": WRITE,
|
|
62
62
|
# destructive: password again, a typed word, an automatic backup, then -y
|
|
@@ -126,6 +126,7 @@ def _table() -> list[Command]:
|
|
|
126
126
|
Command(("users prune-proxies",), "Partner-deployment visitors whose home is gone: list them, -y removes [--stale DAYS]", _daemon("cmd_users_prune_proxies"), "Users", where=D),
|
|
127
127
|
# ---- reservations ----
|
|
128
128
|
Command(("reservations list",), "List reservations", _daemon("cmd_reservations_list"), "Reservations", where=D),
|
|
129
|
+
Command(("reservations cancel",), "Cancel one reservation: reservations cancel <res_id> [-y]", _daemon("cmd_reservations_cancel"), "Reservations", where=D),
|
|
129
130
|
Command(("reservations purge",), "Remove ALL reservations", _daemon("cmd_reservations_purge"), "Reservations", where=D),
|
|
130
131
|
# ---- groups ----
|
|
131
132
|
Command(("groups list",), "List user groups", _shell("cmd_groups_list"), "Groups"),
|
|
@@ -361,7 +362,12 @@ def _run_in_daemon(command: Command, rest: list[str]) -> int:
|
|
|
361
362
|
# for a remote admin: that would hand them the live console, past the policy.
|
|
362
363
|
return console.run_in_console([*command.names[0].split(), *rest])
|
|
363
364
|
# A prompt inside the daemon is asked here, over the socket (control.call).
|
|
364
|
-
|
|
365
|
+
timeout = (
|
|
366
|
+
3600.0 if command.interactive
|
|
367
|
+
else 120.0 if command.names[0] == "reload devices"
|
|
368
|
+
else 30.0
|
|
369
|
+
)
|
|
370
|
+
reply = control.call([*command.names[0].split(), *rest], timeout=timeout)
|
|
365
371
|
if reply is None:
|
|
366
372
|
print("The server stopped answering.", file=sys.stderr)
|
|
367
373
|
return 1
|
{remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/device_manager.py
RENAMED
|
@@ -395,6 +395,10 @@ def _connect_from_record(rec: Dict[str, Any], *, connect: bool = True):
|
|
|
395
395
|
# Keep those inventories usable while all new writes use the canonical type.
|
|
396
396
|
if dtype == "pluto":
|
|
397
397
|
dtype = "adalm_pluto"
|
|
398
|
+
# The built-in Wi-Fi driver's original public type was "wifi_dongle".
|
|
399
|
+
# Keep existing inventories usable while all new schemas use "wifi".
|
|
400
|
+
if dtype == "wifi_dongle":
|
|
401
|
+
dtype = "wifi"
|
|
398
402
|
ident = (rec.get("IDENT") or "").strip()
|
|
399
403
|
init_kwargs = rec.get("INIT") or {}
|
|
400
404
|
if not connect:
|
|
@@ -493,6 +497,13 @@ def _packaged_driver_schema_path(filename: str) -> Path:
|
|
|
493
497
|
for candidate in sorted(drivers_root.glob(f"*/{filename}")):
|
|
494
498
|
if candidate.exists():
|
|
495
499
|
return candidate
|
|
500
|
+
server_drivers_root = Path(__file__).resolve().parents[1] / "drivers"
|
|
501
|
+
direct = server_drivers_root / filename
|
|
502
|
+
if direct.exists():
|
|
503
|
+
return direct
|
|
504
|
+
for candidate in sorted(server_drivers_root.glob(f"*/{filename}")):
|
|
505
|
+
if candidate.exists():
|
|
506
|
+
return candidate
|
|
496
507
|
return drivers_root / "adalm_pluto" / filename
|
|
497
508
|
|
|
498
509
|
def _driver_registers_type(path: Path, device_type: str) -> bool:
|
|
@@ -555,6 +566,27 @@ def _refresh_managed_driver(
|
|
|
555
566
|
return True
|
|
556
567
|
|
|
557
568
|
|
|
569
|
+
def _remove_managed_driver(target: Path) -> bool:
|
|
570
|
+
"""Remove a schema only when its managed marker still matches its contents."""
|
|
571
|
+
marker = _managed_driver_marker(target)
|
|
572
|
+
if not target.is_file() or not marker.is_file():
|
|
573
|
+
return False
|
|
574
|
+
try:
|
|
575
|
+
marker_hash = marker.read_text(encoding="utf-8").strip()
|
|
576
|
+
target_hash = _driver_sha256(target)
|
|
577
|
+
normalized_hash = _driver_normalized_sha256(target)
|
|
578
|
+
except OSError:
|
|
579
|
+
return False
|
|
580
|
+
if marker_hash not in (target_hash, normalized_hash):
|
|
581
|
+
return False
|
|
582
|
+
try:
|
|
583
|
+
target.unlink()
|
|
584
|
+
marker.unlink()
|
|
585
|
+
except OSError:
|
|
586
|
+
return False
|
|
587
|
+
return True
|
|
588
|
+
|
|
589
|
+
|
|
558
590
|
def _seed_default_driver(
|
|
559
591
|
filename: str,
|
|
560
592
|
*device_types: str,
|
|
@@ -655,6 +687,14 @@ def _seed_default_usrp_phaser_driver() -> None:
|
|
|
655
687
|
_seed_default_driver("usrp_phaser_schema.py", "usrp_phaser")
|
|
656
688
|
|
|
657
689
|
|
|
690
|
+
def _seed_default_wifi_driver() -> None:
|
|
691
|
+
drivers_dir = get_drivers_dir()
|
|
692
|
+
legacy = drivers_dir / "wifi_dongle_schema.py"
|
|
693
|
+
if not _remove_managed_driver(legacy) and legacy.is_file():
|
|
694
|
+
print(f"[drivers] Kept {legacy}: not a managed copy. Remove it, or two Wi-Fi drivers register.")
|
|
695
|
+
_seed_default_driver("wifi_schema.py", "wifi")
|
|
696
|
+
|
|
697
|
+
|
|
658
698
|
# Only the C library from conda: pip already installed pyadi-iio, and conda's
|
|
659
699
|
# pulls a cryptography that breaks the pip-installed pyOpenSSL aioquic needs.
|
|
660
700
|
_LIBIIO_FIX = "conda install -c conda-forge libiio (in the server's conda env; not pyadi-iio, pip already has it)"
|
|
@@ -667,6 +707,7 @@ def _load_all_drivers() -> None:
|
|
|
667
707
|
_seed_default_hackrf_driver()
|
|
668
708
|
_seed_default_ti_mmwave_driver()
|
|
669
709
|
_seed_default_usrp_phaser_driver()
|
|
710
|
+
_seed_default_wifi_driver()
|
|
670
711
|
|
|
671
712
|
# The Pluto drivers need the native libiio behind pylibiio; without it
|
|
672
713
|
# every one of them fails the same way, which is worth one line, not four.
|
|
@@ -690,6 +731,7 @@ def _load_all_drivers() -> None:
|
|
|
690
731
|
("built-in hackrf driver", "..drivers.hackrf.hackrf_schema"),
|
|
691
732
|
("built-in TI mmWave driver", "..drivers.ti_mmwave.ti_mmwave_schema"),
|
|
692
733
|
("built-in USRP Phaser driver", "..drivers.usrp_phaser.usrp_phaser_schema"),
|
|
734
|
+
("built-in Wi-Fi driver", "..drivers.wifi.wifi_schema"),
|
|
693
735
|
):
|
|
694
736
|
try:
|
|
695
737
|
importlib.import_module(module, __package__)
|
{remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/federation/cli.py
RENAMED
|
@@ -47,7 +47,27 @@ def _day(epoch) -> str:
|
|
|
47
47
|
return datetime.datetime.fromtimestamp(int(epoch)).strftime("%Y-%m-%d")
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
def
|
|
50
|
+
def _cluster_json(item: dict) -> dict:
|
|
51
|
+
if item["role"] == "owner":
|
|
52
|
+
parties = item.get("members", [])
|
|
53
|
+
else:
|
|
54
|
+
parties = [{
|
|
55
|
+
"deployment_id": item["peer"],
|
|
56
|
+
"display_name": item.get("peer_name"),
|
|
57
|
+
"state": item.get("state"),
|
|
58
|
+
}, *item.get("peers", [])]
|
|
59
|
+
return {
|
|
60
|
+
"name": str(item["name"]),
|
|
61
|
+
"lab_cap": int(item["lab_cap"]) if item.get("lab_cap") else None,
|
|
62
|
+
"members": [{
|
|
63
|
+
"deployment_id": str(member["deployment_id"]),
|
|
64
|
+
"display_name": str(member.get("display_name") or member["deployment_id"]),
|
|
65
|
+
"state": str(member.get("state") or ""),
|
|
66
|
+
} for member in parties],
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def cmd_clusters(*, json_output: bool = False) -> int:
|
|
51
71
|
from ...common.utils.panel import marker, print_panel
|
|
52
72
|
from . import cluster
|
|
53
73
|
|
|
@@ -59,8 +79,13 @@ def cmd_clusters() -> int:
|
|
|
59
79
|
print(f"Federation is not enabled: {exc}")
|
|
60
80
|
return 1
|
|
61
81
|
state = load_state(Path(config["identity_dir"]))
|
|
62
|
-
rows = [f"{marker()} {config['display_name']}", ""]
|
|
63
82
|
items = cluster.list_clusters()
|
|
83
|
+
if json_output:
|
|
84
|
+
from ..json_output import print_json
|
|
85
|
+
|
|
86
|
+
print_json([_cluster_json(item) for item in items])
|
|
87
|
+
return 0
|
|
88
|
+
rows = [f"{marker()} {config['display_name']}", ""]
|
|
64
89
|
if not items:
|
|
65
90
|
rows.append("No clusters. Start one with `cluster create <name>`.")
|
|
66
91
|
me = {"display_name": config["display_name"], "deployment_id": state.deployment_id}
|
|
@@ -108,11 +133,8 @@ def cmd_clusters() -> int:
|
|
|
108
133
|
return 0
|
|
109
134
|
|
|
110
135
|
|
|
111
|
-
def cmd_cluster_info(name: str) -> int:
|
|
136
|
+
def cmd_cluster_info(name: str, *, json_output: bool = False) -> int:
|
|
112
137
|
"""Every lab in the cluster, each shared group with its limits, each device."""
|
|
113
|
-
from ...common.utils.panel import print_panel
|
|
114
|
-
from ..device_manager import describe_devices
|
|
115
|
-
from ..user_group_cli import _fmt_duration
|
|
116
138
|
from . import cluster
|
|
117
139
|
|
|
118
140
|
config = load_config()
|
|
@@ -120,6 +142,15 @@ def cmd_cluster_info(name: str) -> int:
|
|
|
120
142
|
item = {i["name"]: i for i in cluster.list_clusters()}.get(name)
|
|
121
143
|
if item is None:
|
|
122
144
|
raise cluster.ClusterError(f"no cluster named {name}")
|
|
145
|
+
if json_output:
|
|
146
|
+
from ..json_output import print_json
|
|
147
|
+
|
|
148
|
+
print_json(_cluster_json(item))
|
|
149
|
+
return 0
|
|
150
|
+
from ...common.utils.panel import print_panel
|
|
151
|
+
from ..device_manager import describe_devices
|
|
152
|
+
from ..user_group_cli import _fmt_duration
|
|
153
|
+
|
|
123
154
|
me = {"display_name": config["display_name"], "deployment_id": state.deployment_id}
|
|
124
155
|
mine = {"groups": len(item["groups"]), "detail": [
|
|
125
156
|
{"name": g["group_name"], "max_reservation_time_sec": g["max_reservation_time_sec"],
|
|
@@ -139,7 +170,8 @@ def cmd_cluster_info(name: str) -> int:
|
|
|
139
170
|
for index, (lab, role, sharing) in enumerate(labs):
|
|
140
171
|
if index:
|
|
141
172
|
rows.append("")
|
|
142
|
-
rows.append(f"{_lab(lab)} {role}
|
|
173
|
+
rows.append(f"{_lab(lab)} {role} {lab['deployment_id']}"
|
|
174
|
+
+ (" (you)" if lab["deployment_id"] == me["deployment_id"] else ""))
|
|
143
175
|
if not sharing.get("detail"):
|
|
144
176
|
rows.append(" shares nothing")
|
|
145
177
|
for group in sharing["detail"]:
|
|
@@ -310,7 +342,7 @@ def cmd_cluster(argv: list[str]) -> int:
|
|
|
310
342
|
flags = [a for a in argv[1:] if a.startswith("--")]
|
|
311
343
|
words = [a for a in argv[1:] if not a.startswith("--")]
|
|
312
344
|
if verb == "list" and not words:
|
|
313
|
-
return cmd_clusters()
|
|
345
|
+
return cmd_clusters(json_output="--json" in flags)
|
|
314
346
|
if verb == "codes" and (not words or words[0] not in ("create", "list")):
|
|
315
347
|
print("Usage: cluster codes create [<cluster>] | cluster codes list [<cluster>]", file=sys.stderr)
|
|
316
348
|
return 2
|
|
@@ -370,7 +402,7 @@ def cmd_cluster(argv: list[str]) -> int:
|
|
|
370
402
|
+ (_n(int(answer), "reservation") if int(answer) else "any number of reservations")
|
|
371
403
|
+ " on your devices at a time.", Sty.GRAY)
|
|
372
404
|
elif verb == "info" and arg:
|
|
373
|
-
return cmd_cluster_info(arg)
|
|
405
|
+
return cmd_cluster_info(arg, json_output="--json" in flags)
|
|
374
406
|
elif verb == "groups" and len(words) == 1:
|
|
375
407
|
cluster._find(cluster.load(), arg)
|
|
376
408
|
return _share_groups(arg)
|
{remoterf_server-1.7.9 → remoterf_server-1.7.11}/src/remoteRF_server/server/global_onboarding.py
RENAMED
|
@@ -1169,7 +1169,18 @@ _STATE_SUMMARY = {
|
|
|
1169
1169
|
}
|
|
1170
1170
|
|
|
1171
1171
|
|
|
1172
|
-
def cmd_status(refresh: bool = True) -> int:
|
|
1172
|
+
def cmd_status(refresh: bool = True, *, json_output: bool = False) -> int:
|
|
1173
|
+
if json_output:
|
|
1174
|
+
from .json_output import print_json
|
|
1175
|
+
|
|
1176
|
+
kv = load()
|
|
1177
|
+
print_json({
|
|
1178
|
+
"state": str(kv.get("GLOBAL_STATE") or ("unregistered" if kv else "unconfigured")),
|
|
1179
|
+
"slug": kv.get("GLOBAL_FINAL_NAME") or kv.get("GLOBAL_NAME") or None,
|
|
1180
|
+
"deployment_id": kv.get("GLOBAL_DEPLOYMENT_ID") or None,
|
|
1181
|
+
"relay": kv.get("GLOBAL_RELAY_HOSTNAME") or None,
|
|
1182
|
+
})
|
|
1183
|
+
return 0
|
|
1173
1184
|
from ..common.utils.panel import marker, print_panel
|
|
1174
1185
|
|
|
1175
1186
|
kv = load()
|
|
@@ -1310,6 +1321,8 @@ _COMMANDS = {"status": cmd_status, "join": cmd_join, "leave": cmd_leave}
|
|
|
1310
1321
|
|
|
1311
1322
|
|
|
1312
1323
|
def main(argv) -> int:
|
|
1324
|
+
if argv == ["status", "--json"]:
|
|
1325
|
+
return cmd_status(json_output=True)
|
|
1313
1326
|
if argv and argv[0] == "verify":
|
|
1314
1327
|
# A code pasted from email may arrive wrapped across lines.
|
|
1315
1328
|
return cmd_verify("".join(argv[1:]))
|
|
@@ -54,7 +54,8 @@ from .dynamic_v2 import (
|
|
|
54
54
|
from .host_dynamic_v2 import host_session_proxy
|
|
55
55
|
from .acc_perms import perms
|
|
56
56
|
from ..common.utils import *
|
|
57
|
-
from .device_manager import close_local_devices, reload_devices, get_registered_devices,
|
|
57
|
+
from .device_manager import (close_local_devices, reload_devices, get_registered_devices,
|
|
58
|
+
get_all_devices_str, handle_report, device_inventory_snapshot)
|
|
58
59
|
from .user_group_cli import make_user_group_local, edit_user_group_local, list_user_groups_local, remove_user_group_local, make_enrollment_codes_local, remove_enrollment_code_local, list_enrollment_codes_local, export_user_groups_csv_local, export_enrollment_codes_csv_local
|
|
59
60
|
from .user_group_handler import start_usergroup_cleanup_loop
|
|
60
61
|
from .global_onboarding import start_agent
|
|
@@ -681,7 +682,33 @@ def set_acc():
|
|
|
681
682
|
elif inpu == 'c':
|
|
682
683
|
print("Canceled. Exiting ...")
|
|
683
684
|
|
|
684
|
-
def
|
|
685
|
+
def _devices_json() -> list[dict]:
|
|
686
|
+
from .json_output import epoch_ms_iso
|
|
687
|
+
|
|
688
|
+
reg = hts.get_tunnel_registry(create=False)
|
|
689
|
+
live = reg.list_devices() if reg is not None else {}
|
|
690
|
+
rows = []
|
|
691
|
+
for device_id, item in sorted(device_inventory_snapshot().items()):
|
|
692
|
+
ds = live.get(str(device_id))
|
|
693
|
+
rows.append({
|
|
694
|
+
"device_id": int(device_id),
|
|
695
|
+
"name": str(item["name"]),
|
|
696
|
+
"handle": str(item["handle"]),
|
|
697
|
+
"type": str(item["device_type"] or ""),
|
|
698
|
+
"host": f"{ds.host_id} #{ds.host_local_id}" if ds else "local",
|
|
699
|
+
"online": bool(item["online"]),
|
|
700
|
+
"state": None,
|
|
701
|
+
"last_seen": epoch_ms_iso(ds.last_seen_ms) if ds else None,
|
|
702
|
+
})
|
|
703
|
+
return rows
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
def get_devices(*, json_output: bool = False):
|
|
707
|
+
if json_output:
|
|
708
|
+
from .json_output import print_json
|
|
709
|
+
|
|
710
|
+
print_json(_devices_json())
|
|
711
|
+
return
|
|
685
712
|
from ..common.utils.panel import print_table
|
|
686
713
|
|
|
687
714
|
reg = hts.get_tunnel_registry(create=False)
|
|
@@ -711,7 +738,33 @@ def _fmt_ms_ago(ms: int) -> str:
|
|
|
711
738
|
return f"{dt/1000:.1f}s"
|
|
712
739
|
return f"{dt/60_000:.1f}m"
|
|
713
740
|
|
|
714
|
-
def
|
|
741
|
+
def _hosts_json() -> list[dict]:
|
|
742
|
+
from .json_output import epoch_ms_iso
|
|
743
|
+
|
|
744
|
+
reg = hts.get_tunnel_registry()
|
|
745
|
+
assert reg is not None
|
|
746
|
+
hosts = reg.list_hosts()
|
|
747
|
+
by_host: dict[str, list[int]] = {}
|
|
748
|
+
for key, ds in reg.list_devices().items():
|
|
749
|
+
try:
|
|
750
|
+
device_id = int(key)
|
|
751
|
+
except (TypeError, ValueError):
|
|
752
|
+
continue
|
|
753
|
+
by_host.setdefault(ds.host_id or "-", []).append(device_id)
|
|
754
|
+
return [{
|
|
755
|
+
"host_id": str(host_id),
|
|
756
|
+
"online": bool(status.online),
|
|
757
|
+
"last_seen": epoch_ms_iso(status.last_seen_ms),
|
|
758
|
+
"devices": sorted(by_host.get(host_id, [])),
|
|
759
|
+
} for host_id, status in sorted(hosts.items())]
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
def _cmd_hosts_status(*, json_output: bool = False) -> None:
|
|
763
|
+
if json_output:
|
|
764
|
+
from .json_output import print_json
|
|
765
|
+
|
|
766
|
+
print_json(_hosts_json())
|
|
767
|
+
return
|
|
715
768
|
from ..common.utils.panel import print_table
|
|
716
769
|
|
|
717
770
|
reg = hts.get_tunnel_registry()
|
|
@@ -1139,7 +1192,32 @@ def _cmd_detach() -> None:
|
|
|
1139
1192
|
printf("Not in the console; the server runs in this terminal. Use quit to stop it.", Sty.GRAY)
|
|
1140
1193
|
|
|
1141
1194
|
|
|
1142
|
-
def
|
|
1195
|
+
def _status_json() -> dict:
|
|
1196
|
+
from . import global_onboarding as g
|
|
1197
|
+
|
|
1198
|
+
devices = device_inventory_snapshot()
|
|
1199
|
+
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
1200
|
+
active = g._count(
|
|
1201
|
+
reservation_handler.filepath,
|
|
1202
|
+
"SELECT COUNT(*) FROM Reservations WHERE start_time <= ? AND end_time > ?",
|
|
1203
|
+
(now, now),
|
|
1204
|
+
)
|
|
1205
|
+
return {
|
|
1206
|
+
"server_version": g.server_version(),
|
|
1207
|
+
"online": True,
|
|
1208
|
+
"uptime_s": max(0, int((datetime.now() - start_time).total_seconds())),
|
|
1209
|
+
"devices_total": len(devices),
|
|
1210
|
+
"devices_online": sum(bool(item["online"]) for item in devices.values()),
|
|
1211
|
+
"reservations_active": int(active),
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
|
|
1215
|
+
def _cmd_status(*, json_output: bool = False) -> None:
|
|
1216
|
+
if json_output:
|
|
1217
|
+
from .json_output import print_json
|
|
1218
|
+
|
|
1219
|
+
print_json(_status_json())
|
|
1220
|
+
return
|
|
1143
1221
|
from ..common.utils.panel import marker, print_panel
|
|
1144
1222
|
from . import global_onboarding as g
|
|
1145
1223
|
|
|
@@ -1278,16 +1356,43 @@ def _reexec() -> None:
|
|
|
1278
1356
|
|
|
1279
1357
|
|
|
1280
1358
|
# The shared command table calls these by name (commands.py).
|
|
1281
|
-
def cmd_status(argv): _cmd_status()
|
|
1359
|
+
def cmd_status(argv): _cmd_status(json_output="--json" in argv)
|
|
1282
1360
|
def cmd_detach(argv): _cmd_detach()
|
|
1283
1361
|
def cmd_clear(argv): clear()
|
|
1284
|
-
def cmd_hosts_status(argv): _cmd_hosts_status()
|
|
1362
|
+
def cmd_hosts_status(argv): _cmd_hosts_status(json_output="--json" in argv)
|
|
1285
1363
|
def cmd_hosts_wipe(argv): _cmd_hosts_wipe(yes=("-y" in argv or "--yes" in argv))
|
|
1286
1364
|
def cmd_users_list(argv): reservation_handler.print_all_accounts()
|
|
1287
1365
|
def cmd_users_manage(argv): set_acc()
|
|
1288
1366
|
def cmd_users_perms(argv): perms.print_perms()
|
|
1289
1367
|
def cmd_users_purge(argv): reservation_handler.remove_all_users()
|
|
1290
|
-
def cmd_reservations_list(argv): reservation_handler.print_all_reservations()
|
|
1368
|
+
def cmd_reservations_list(argv): reservation_handler.print_all_reservations(json_output="--json" in argv)
|
|
1369
|
+
|
|
1370
|
+
|
|
1371
|
+
def cmd_reservations_cancel(argv):
|
|
1372
|
+
args = [arg for arg in argv if arg not in ("-y", "--yes")]
|
|
1373
|
+
if len(args) != 1 or any(arg.startswith("-") for arg in args):
|
|
1374
|
+
print("Usage: reservations cancel <res_id> [-y]")
|
|
1375
|
+
return 2
|
|
1376
|
+
try:
|
|
1377
|
+
res_id = int(args[0])
|
|
1378
|
+
if not 0 < res_id < 2**63 or str(res_id) != args[0]:
|
|
1379
|
+
raise ValueError
|
|
1380
|
+
except ValueError:
|
|
1381
|
+
print("Reservation ID must be a positive integer.")
|
|
1382
|
+
return 2
|
|
1383
|
+
row = reservation_handler.cancel_reservation_admin(res_id)
|
|
1384
|
+
if row is None:
|
|
1385
|
+
print(f"Reservation {res_id} was not found or has already ended.")
|
|
1386
|
+
return 1
|
|
1387
|
+
if row is False:
|
|
1388
|
+
print(f"Reservation {res_id} is on a partner lab; the user cancels it from their client.")
|
|
1389
|
+
return 3
|
|
1390
|
+
print(" ".join(str(value) for value in row))
|
|
1391
|
+
try:
|
|
1392
|
+
reservation_handler.update_devices()
|
|
1393
|
+
except Exception as exc: # noqa: BLE001 - the delete is committed; report the pending release
|
|
1394
|
+
print(f"Warning: device release pending ({exc}); it completes on the next device update.")
|
|
1395
|
+
return 0
|
|
1291
1396
|
|
|
1292
1397
|
|
|
1293
1398
|
def cmd_users_prune_proxies(argv):
|
|
@@ -1353,7 +1458,7 @@ def cmd_stats_purge(argv):
|
|
|
1353
1458
|
printf("No activity log to purge.", Sty.GRAY)
|
|
1354
1459
|
return
|
|
1355
1460
|
printf(f"Purged activity log: {reservation.LOG_PATH}", Sty.GRAY)
|
|
1356
|
-
def cmd_devices_list(argv): get_devices()
|
|
1461
|
+
def cmd_devices_list(argv): get_devices(json_output="--json" in argv)
|
|
1357
1462
|
|
|
1358
1463
|
|
|
1359
1464
|
def cmd_diag(argv):
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Copyright (C) 2026 RemoteRF
|
|
2
|
+
#
|
|
3
|
+
# This program is free software: you can redistribute it and/or modify
|
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
6
|
+
# (at your option) any later version.
|
|
7
|
+
|
|
8
|
+
"""Small, shared pieces of the machine-readable serverrf output."""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def print_json(value) -> None:
|
|
17
|
+
print(json.dumps(value, separators=(",", ":")))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def local_iso(value) -> str:
|
|
21
|
+
"""Render a stored local datetime with the server's UTC offset."""
|
|
22
|
+
parsed = value if isinstance(value, datetime) else datetime.fromisoformat(str(value).strip())
|
|
23
|
+
return parsed.astimezone().isoformat()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def epoch_ms_iso(value) -> str | None:
|
|
27
|
+
milliseconds = int(value or 0)
|
|
28
|
+
if milliseconds <= 0:
|
|
29
|
+
return None
|
|
30
|
+
return datetime.fromtimestamp(milliseconds / 1000).astimezone().isoformat()
|
|
@@ -802,6 +802,26 @@ class ReservationHandler:
|
|
|
802
802
|
def remove_reservation(self, *, res_id):
|
|
803
803
|
self.cursor.execute('DELETE FROM Reservations WHERE res_id = ?', (res_id,))
|
|
804
804
|
return {"UC": map_arg(f'Removed reservation {res_id}')}
|
|
805
|
+
|
|
806
|
+
@db_connection
|
|
807
|
+
def cancel_reservation_admin(self, res_id: int):
|
|
808
|
+
row = self.cursor.execute(
|
|
809
|
+
"SELECT res_id, username, device_id, start_time, end_time "
|
|
810
|
+
"FROM Reservations WHERE res_id = ?",
|
|
811
|
+
(int(res_id),),
|
|
812
|
+
).fetchone()
|
|
813
|
+
if row is None or _parse_reservation_datetime(row[4]) <= datetime.now():
|
|
814
|
+
return None
|
|
815
|
+
# A booking our user holds on a partner lab is cancelled there first (home_devices.cancel).
|
|
816
|
+
if self.cursor.execute(
|
|
817
|
+
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='HomeFederatedReservations'"
|
|
818
|
+
).fetchone() and self.cursor.execute(
|
|
819
|
+
"SELECT 1 FROM HomeFederatedReservations WHERE res_id = ?", (int(res_id),)
|
|
820
|
+
).fetchone():
|
|
821
|
+
return False
|
|
822
|
+
self.cursor.execute("DELETE FROM Reservations WHERE res_id = ?", (int(res_id),))
|
|
823
|
+
self.cursor.execute("DELETE FROM FederatedReservations WHERE res_id = ?", (int(res_id),))
|
|
824
|
+
return row
|
|
805
825
|
|
|
806
826
|
@db_connection
|
|
807
827
|
def rm_db(self):
|
|
@@ -1336,7 +1356,25 @@ class ReservationHandler:
|
|
|
1336
1356
|
return [(_parse_reservation_datetime(a), _parse_reservation_datetime(b)) for a, b in rows]
|
|
1337
1357
|
|
|
1338
1358
|
@db_connection
|
|
1339
|
-
def print_all_reservations(self):
|
|
1359
|
+
def print_all_reservations(self, *, json_output: bool = False):
|
|
1360
|
+
if json_output:
|
|
1361
|
+
from .json_output import local_iso, print_json
|
|
1362
|
+
|
|
1363
|
+
rows = self.cursor.execute(
|
|
1364
|
+
"SELECT r.res_id, r.username, r.device_id, r.start_time, r.end_time, "
|
|
1365
|
+
"f.issuer_deployment_id FROM Reservations r LEFT JOIN FederatedReservations f "
|
|
1366
|
+
"ON f.res_id=r.res_id ORDER BY r.res_id"
|
|
1367
|
+
).fetchall()
|
|
1368
|
+
print_json([{
|
|
1369
|
+
"res_id": int(res_id),
|
|
1370
|
+
"username": str(username),
|
|
1371
|
+
"device_id": int(device_id),
|
|
1372
|
+
"start_time": local_iso(start),
|
|
1373
|
+
"end_time": local_iso(end),
|
|
1374
|
+
"federated": issuer is not None,
|
|
1375
|
+
"issuer_deployment_id": str(issuer) if issuer is not None else None,
|
|
1376
|
+
} for res_id, username, device_id, start, end, issuer in rows])
|
|
1377
|
+
return
|
|
1340
1378
|
from ..common.utils.panel import print_table
|
|
1341
1379
|
|
|
1342
1380
|
cursor = self.db.cursor()
|
|
@@ -28,6 +28,7 @@ from datetime import datetime, timezone
|
|
|
28
28
|
import base64
|
|
29
29
|
import secrets
|
|
30
30
|
import hashlib
|
|
31
|
+
import json
|
|
31
32
|
import getpass
|
|
32
33
|
import yaml
|
|
33
34
|
|
|
@@ -1485,11 +1486,14 @@ def _device_remove(global_id_str: str) -> int:
|
|
|
1485
1486
|
return 0
|
|
1486
1487
|
|
|
1487
1488
|
|
|
1488
|
-
def _device_show() -> int:
|
|
1489
|
+
def _device_show(*, json_output: bool = False) -> int:
|
|
1489
1490
|
from remoteRF_server.common.utils.panel import print_table
|
|
1490
1491
|
|
|
1491
1492
|
p = _devices_yaml_path()
|
|
1492
1493
|
if not p.exists() and not _devices_env_path().exists():
|
|
1494
|
+
if json_output:
|
|
1495
|
+
print("[]")
|
|
1496
|
+
return 0
|
|
1493
1497
|
print(f"No devices configured (missing {p}).")
|
|
1494
1498
|
return 0
|
|
1495
1499
|
|
|
@@ -1508,6 +1512,18 @@ def _device_show() -> int:
|
|
|
1508
1512
|
if is_device_handle(h):
|
|
1509
1513
|
handles[gid] = h if h not in seen else f"{h} ignored (duplicate name)"
|
|
1510
1514
|
seen.add(h)
|
|
1515
|
+
if json_output:
|
|
1516
|
+
print(json.dumps([{
|
|
1517
|
+
"device_id": int(gid),
|
|
1518
|
+
"name": str(record.get("NAME", "")),
|
|
1519
|
+
"handle": str(handles.get(gid, "")),
|
|
1520
|
+
"type": str(record.get("TYPE", "")),
|
|
1521
|
+
"host": "local",
|
|
1522
|
+
"online": False,
|
|
1523
|
+
"state": None,
|
|
1524
|
+
"last_seen": None,
|
|
1525
|
+
} for gid, record in sorted(recs.items())], separators=(",", ":")))
|
|
1526
|
+
return 0
|
|
1511
1527
|
rows = [
|
|
1512
1528
|
{"id": gid, "type": r.get("TYPE", ""), "name": r.get("NAME", ""),
|
|
1513
1529
|
"handle": handles.get(gid, "-"),
|
|
@@ -1641,13 +1657,13 @@ def cmd_devices_list(argv: List[str]) -> int:
|
|
|
1641
1657
|
if commands._IN_DAEMON:
|
|
1642
1658
|
from remoteRF_server.server.grpc_server import get_devices
|
|
1643
1659
|
|
|
1644
|
-
get_devices()
|
|
1660
|
+
get_devices(json_output="--json" in argv)
|
|
1645
1661
|
return 0
|
|
1646
|
-
reply = control.call(["devices", "list"])
|
|
1662
|
+
reply = control.call(["devices", "list", *(["--json"] if "--json" in argv else [])])
|
|
1647
1663
|
if reply is not None:
|
|
1648
1664
|
sys.stdout.write(reply[1])
|
|
1649
1665
|
return reply[0]
|
|
1650
|
-
return _device_show()
|
|
1666
|
+
return _device_show(json_output="--json" in argv)
|
|
1651
1667
|
|
|
1652
1668
|
|
|
1653
1669
|
# -----------------------------
|
|
@@ -2424,7 +2440,7 @@ def cmd_global(argv: List[str]) -> int:
|
|
|
2424
2440
|
from remoteRF_server.server.global_onboarding import main as global_main
|
|
2425
2441
|
|
|
2426
2442
|
rc = global_main(list(argv))
|
|
2427
|
-
if rc == 0:
|
|
2443
|
+
if rc == 0 and "--json" not in argv:
|
|
2428
2444
|
_ensure_certificate_names()
|
|
2429
2445
|
return rc
|
|
2430
2446
|
|
|
@@ -14,6 +14,11 @@
|
|
|
14
14
|
# You should have received a copy of the GNU General Public License
|
|
15
15
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
16
|
|
|
17
|
+
"""RRRFcerts: generate a local CA and a server TLS certificate/key with SANs.
|
|
18
|
+
|
|
19
|
+
Output goes to ~/.config/remoterf/certs by default. Clients should trust ca.crt (not server.crt) and must dial an address that matches one of the SANs.
|
|
20
|
+
"""
|
|
21
|
+
|
|
17
22
|
from __future__ import annotations
|
|
18
23
|
|
|
19
24
|
import argparse
|