Fix security issues, breaking changes, and code cleanup for weather satellite PR

Co-authored-by: mitchross <6330506+mitchross@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-02-09 20:58:26 +00:00
parent 35cf01c11e
commit d41ba61aee
8 changed files with 54 additions and 31 deletions

12
app.py
View File

@@ -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:

View File

@@ -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')

View File

@@ -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:

View File

@@ -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/<category>')
@@ -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'})

View File

@@ -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()

View File

@@ -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([]);

View File

@@ -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'):

View File

@@ -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]: