diff --git a/routes/radiosonde.py b/routes/radiosonde.py index fd1c765..0d4ef10 100644 --- a/routes/radiosonde.py +++ b/routes/radiosonde.py @@ -243,13 +243,40 @@ radius_temporary_block = False sonde_time_threshold = 3 """ - with open(cfg_path, 'w') as f: - f.write(cfg) + try: + with open(cfg_path, 'w') as f: + f.write(cfg) + except OSError as e: + logger.error(f"Cannot write station.cfg to {cfg_path}: {e}") + raise RuntimeError( + f"Cannot write radiosonde config to {cfg_path}: {e}. " + f"Fix permissions with: sudo chown -R $(whoami) {cfg_dir}" + ) from e + + # When running as root via sudo, fix ownership so next non-root run + # can still read/write these files. + _fix_data_ownership(cfg_dir) logger.info(f"Generated station.cfg at {cfg_path}") return cfg_path +def _fix_data_ownership(path: str) -> None: + """Recursively chown a path to the real (non-root) user when running via sudo.""" + uid = os.environ.get('INTERCEPT_SUDO_UID') + gid = os.environ.get('INTERCEPT_SUDO_GID') + if uid is None or gid is None: + return + try: + uid_int, gid_int = int(uid), int(gid) + for dirpath, dirnames, filenames in os.walk(path): + os.chown(dirpath, uid_int, gid_int) + for fname in filenames: + os.chown(os.path.join(dirpath, fname), uid_int, gid_int) + except OSError as e: + logger.warning(f"Could not fix ownership of {path}: {e}") + + def parse_radiosonde_udp(udp_port: int) -> None: """Thread function: listen for radiosonde_auto_rx UDP JSON telemetry.""" global radiosonde_running, _udp_socket @@ -532,17 +559,22 @@ def start_radiosonde(): }), 409 # Generate config - cfg_path = generate_station_cfg( - freq_min=freq_min, - freq_max=freq_max, - gain=gain, - device_index=device_int, - ppm=ppm, - bias_t=bias_t, - latitude=latitude, - longitude=longitude, - gpsd_enabled=gpsd_enabled, - ) + try: + cfg_path = generate_station_cfg( + freq_min=freq_min, + freq_max=freq_max, + gain=gain, + device_index=device_int, + ppm=ppm, + bias_t=bias_t, + latitude=latitude, + longitude=longitude, + gpsd_enabled=gpsd_enabled, + ) + except (OSError, RuntimeError) as e: + app_module.release_sdr_device(device_int, sdr_type_str) + logger.error(f"Failed to generate radiosonde config: {e}") + return jsonify({'status': 'error', 'message': str(e)}), 500 # Build command - auto_rx -c expects a config DIRECTORY containing station.cfg cfg_dir = os.path.dirname(os.path.abspath(cfg_path)) diff --git a/start.sh b/start.sh index a2da13c..be3c25c 100755 --- a/start.sh +++ b/start.sh @@ -79,12 +79,25 @@ export INTERCEPT_HOST="$HOST" export INTERCEPT_PORT="$PORT" # ── Fix ownership of user data dirs when run via sudo ──────────────────────── +# When invoked via sudo the server process runs as root, so every file it +# creates (configs, logs, database) ends up owned by root. On the *next* +# startup we fix that retroactively, and we also pre-create known runtime +# directories so they get correct ownership from the start. if [[ "$(id -u)" -eq 0 && -n "${SUDO_USER:-}" ]]; then - for dir in instance data; do + # Pre-create directories that routes may need at runtime + mkdir -p "$SCRIPT_DIR/instance" \ + "$SCRIPT_DIR/data/radiosonde/logs" \ + "$SCRIPT_DIR/data/weather_sat" + + for dir in instance data certs; do if [[ -d "$SCRIPT_DIR/$dir" ]]; then chown -R "$SUDO_USER" "$SCRIPT_DIR/$dir" fi done + + # Export real user identity so Python can chown runtime-created files + export INTERCEPT_SUDO_UID="$(id -u "$SUDO_USER")" + export INTERCEPT_SUDO_GID="$(id -g "$SUDO_USER")" fi # ── Dependency check (delegate to intercept.py) ─────────────────────────────