From d41ba61aee7a7ea0dd1bafe110ede4edaab12b7c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Feb 2026 20:58:26 +0000 Subject: [PATCH] Fix security issues, breaking changes, and code cleanup for weather satellite PR Co-authored-by: mitchross <6330506+mitchross@users.noreply.github.com> --- app.py | 12 ++++++++-- config.py | 2 +- docker-compose.yml | 9 +++----- routes/satellite.py | 34 +++++++++++++++------------- routes/weather_sat.py | 4 ++-- static/js/modes/weather-satellite.js | 13 +++++++++-- utils/weather_sat.py | 5 ++++ utils/weather_sat_scheduler.py | 6 +++-- 8 files changed, 54 insertions(+), 31 deletions(-) diff --git a/app.py b/app.py index df2b1a3..7a82bd5 100644 --- a/app.py +++ b/app.py @@ -692,8 +692,7 @@ def kill_all() -> Response: 'airodump-ng', 'aireplay-ng', 'airmon-ng', 'dump1090', 'acarsdec', 'direwolf', 'AIS-catcher', 'hcitool', 'bluetoothctl', 'satdump', 'dsd', - 'rtl_tcp', 'rtl_power', 'rtlamr', 'ffmpeg', - 'grgsm_scanner', 'grgsm_livemon', 'tshark' + 'rtl_tcp', 'rtl_power', 'rtlamr', 'ffmpeg' ] for proc in processes_to_kill: @@ -870,6 +869,15 @@ def main() -> None: from routes import register_blueprints register_blueprints(app) + # Initialize TLE auto-refresh (must be after blueprint registration) + try: + from routes.satellite import init_tle_auto_refresh + import os + if not os.environ.get('TESTING'): + init_tle_auto_refresh() + except Exception as e: + logger.warning(f"Failed to initialize TLE auto-refresh: {e}") + # Update TLE data in background thread (non-blocking) def update_tle_background(): try: diff --git a/config.py b/config.py index 67e7d3e..b7758cb 100644 --- a/config.py +++ b/config.py @@ -152,7 +152,7 @@ def _get_env_bool(key: str, default: bool) -> bool: # Logging configuration -_log_level_str = _get_env('LOG_LEVEL', 'INFO').upper() +_log_level_str = _get_env('LOG_LEVEL', 'WARNING').upper() LOG_LEVEL = getattr(logging, _log_level_str, logging.WARNING) LOG_FORMAT = _get_env('LOG_FORMAT', '%(asctime)s - %(levelname)s - %(message)s') diff --git a/docker-compose.yml b/docker-compose.yml index 0211fb4..b6318ba 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,13 +12,10 @@ services: intercept: - # When INTERCEPT_IMAGE is set, use that pre-built image; when empty/unset, - # the empty string causes Docker Compose to fall through to the build: directive. - image: ${INTERCEPT_IMAGE:-} + # When INTERCEPT_IMAGE is set, use that pre-built image; otherwise build locally + image: ${INTERCEPT_IMAGE:-intercept:latest} build: . container_name: intercept - profiles: - - basic ports: - "5050:5050" # Privileged mode required for USB SDR device access @@ -64,7 +61,7 @@ services: # Enable with: docker compose --profile history up -d intercept-history: # Same image/build fallback pattern as above - image: ${INTERCEPT_IMAGE:-} + image: ${INTERCEPT_IMAGE:-intercept:latest} build: . container_name: intercept-history profiles: diff --git a/routes/satellite.py b/routes/satellite.py index 3a8f078..e3dbefa 100644 --- a/routes/satellite.py +++ b/routes/satellite.py @@ -30,22 +30,22 @@ ALLOWED_TLE_HOSTS = ['celestrak.org', 'celestrak.com', 'www.celestrak.org', 'www # Local TLE cache (can be updated via API) _tle_cache = dict(TLE_SATELLITES) -# Auto-refresh TLEs from CelesTrak on startup (non-blocking) -import os -import threading -def _auto_refresh_tle(): - try: - updated = refresh_tle_data() - if updated: - logger.info(f"Auto-refreshed TLE data for: {', '.join(updated)}") - except Exception as e: - logger.warning(f"Auto TLE refresh failed: {e}") - -# Delay import — refresh_tle_data is defined later in this module -# Guard to avoid firing during tests -if not os.environ.get('TESTING'): +def init_tle_auto_refresh(): + """Initialize TLE auto-refresh. Called by app.py after initialization.""" + import threading + + def _auto_refresh_tle(): + try: + updated = refresh_tle_data() + if updated: + logger.info(f"Auto-refreshed TLE data for: {', '.join(updated)}") + except Exception as e: + logger.warning(f"Auto TLE refresh failed: {e}") + + # Start auto-refresh in background threading.Timer(2.0, _auto_refresh_tle).start() + logger.info("TLE auto-refresh scheduled") def _fetch_iss_realtime(observer_lat: Optional[float] = None, observer_lon: Optional[float] = None) -> Optional[dict]: @@ -498,7 +498,8 @@ def update_tle(): 'updated': updated }) except Exception as e: - return jsonify({'status': 'error', 'message': str(e)}) + logger.error(f"Error updating TLE data: {e}") + return jsonify({'status': 'error', 'message': 'TLE update failed'}) @satellite_bp.route('/celestrak/') @@ -552,4 +553,5 @@ def fetch_celestrak(category): }) except Exception as e: - return jsonify({'status': 'error', 'message': str(e)}) + logger.error(f"Error fetching CelesTrak data: {e}") + return jsonify({'status': 'error', 'message': 'Failed to fetch satellite data'}) diff --git a/routes/weather_sat.py b/routes/weather_sat.py index 728d7be..0caa5ad 100644 --- a/routes/weather_sat.py +++ b/routes/weather_sat.py @@ -253,7 +253,7 @@ def test_decode(): }), 400 if not input_path.is_file(): - logger.warning(f"Test-decode file not found: {input_file}") + logger.warning("Test-decode file not found") return jsonify({ 'status': 'error', 'message': 'File not found' @@ -313,7 +313,7 @@ def stop_capture(): JSON confirmation. """ decoder = get_weather_sat_decoder() - device_index = decoder._device_index + device_index = decoder.device_index decoder.stop() diff --git a/static/js/modes/weather-satellite.js b/static/js/modes/weather-satellite.js index c53a2cd..51ecaa9 100644 --- a/static/js/modes/weather-satellite.js +++ b/static/js/modes/weather-satellite.js @@ -446,8 +446,17 @@ const WeatherSat = (function() { * Load pass predictions (with trajectory + ground track) */ async function loadPasses() { - const storedLat = localStorage.getItem('observerLat'); - const storedLon = localStorage.getItem('observerLon'); + let storedLat, storedLon; + + // Use ObserverLocation if available, otherwise fall back to localStorage + if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) { + const shared = ObserverLocation.getShared(); + storedLat = shared?.lat?.toString(); + storedLon = shared?.lon?.toString(); + } else { + storedLat = localStorage.getItem('observerLat'); + storedLon = localStorage.getItem('observerLon'); + } if (!storedLat || !storedLon) { renderPasses([]); diff --git a/utils/weather_sat.py b/utils/weather_sat.py index e91442a..347c0a9 100644 --- a/utils/weather_sat.py +++ b/utils/weather_sat.py @@ -185,6 +185,11 @@ class WeatherSatDecoder: def current_frequency(self) -> float: return self._current_frequency + @property + def device_index(self) -> int: + """Return current device index.""" + return self._device_index + def _detect_decoder(self) -> str | None: """Detect which weather satellite decoder is available.""" if shutil.which('satdump'): diff --git a/utils/weather_sat_scheduler.py b/utils/weather_sat_scheduler.py index 48aea6f..6f16a54 100644 --- a/utils/weather_sat_scheduler.py +++ b/utils/weather_sat_scheduler.py @@ -51,14 +51,16 @@ class ScheduledPass: def start_dt(self) -> datetime: dt = datetime.fromisoformat(self.start_time) if dt.tzinfo is None: - return dt.replace(tzinfo=timezone.utc) + # Naive datetime - assume UTC + dt = dt.replace(tzinfo=timezone.utc) return dt.astimezone(timezone.utc) @property def end_dt(self) -> datetime: dt = datetime.fromisoformat(self.end_time) if dt.tzinfo is None: - return dt.replace(tzinfo=timezone.utc) + # Naive datetime - assume UTC + dt = dt.replace(tzinfo=timezone.utc) return dt.astimezone(timezone.utc) def to_dict(self) -> dict[str, Any]: