Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ INTERCEPT_SHARED_OBSERVER_LOCATION=true
# INTERCEPT_DEFAULT_LAT=40.7128
# INTERCEPT_DEFAULT_LON=-74.0060

# Unraid / Docker install (set by the Community Applications template)
# INTERCEPT_UNRAID=true
# INTERCEPT_DOCKER=true
# INTERCEPT_INSTANCE_DIR=/app/instance
# INTERCEPT_ENV_FILE=/config/.env
# INTERCEPT_SQLITE_JOURNAL=DELETE

# =============================================================================
# AGENT SETTINGS (for docker-compose.agent.yml on remote Pis)
# =============================================================================
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

All notable changes to iNTERCEPT will be documented in this file.

## [Unreleased]

### Added
- **Unraid app + setup wizard** — Community Applications template in `unraid/intercept.xml`, persistent `instance/` and `/config` volumes, FUSE-safe SQLite journal mode on Unraid user shares, and a first-run wizard that reports USB passthrough via `GET /setup/status`.

---

## [2.32.0] - 2026-07-07

### Added
Expand Down
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ COPY . .
RUN find . -name '*.sh' -exec sed -i 's/\r$//' {} +

# Create data directory for persistence
RUN mkdir -p /app/data /app/data/weather_sat /app/data/radiosonde/logs
RUN mkdir -p /app/data /app/data/weather_sat /app/data/radiosonde/logs /app/instance /config

# Expose web interface port
EXPOSE 5050
Expand All @@ -302,6 +302,7 @@ EXPOSE 5443
ENV INTERCEPT_HOST=0.0.0.0 \
INTERCEPT_PORT=5050 \
INTERCEPT_LOG_LEVEL=INFO \
INTERCEPT_INSTANCE_DIR=/app/instance \
PYTHONUNBUFFERED=1

# Health check using the new endpoint
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,17 @@ docker compose --profile basic up -d --build

> **Note:** Docker requires privileged mode for USB SDR access. SDR devices are passed through via `/dev/bus/usb`.

### Unraid

Install INTERCEPT as a Community Applications Docker app, then finish setup in the browser wizard.

1. Load `unraid/intercept.xml` (Docker → Add Container → Advanced View), or add the raw template URL from that file.
2. Set a real admin password. Keep privileged mode and `/dev/bus/usb`.
3. Map the SQLite directory to a **cache/pool** path (`/mnt/cache/appdata/intercept/instance`). Unraid user shares (`/mnt/user`) are FUSE; INTERCEPT falls back to DELETE journal mode there.
4. Open `http://TOWER:5050/`, sign in, and complete Quick Setup. After login, `GET /setup/status` reports USB passthrough, storage journal mode, and whether setup is complete.

Full steps: [unraid/README.md](unraid/README.md).

For multi-architecture builds (amd64 + arm64 for Raspberry Pi), see `build-multiarch.sh` — it handles cross-compilation and registry push in one step.

### Environment Configuration
Expand Down
4 changes: 4 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ services:
volumes:
# Persist runtime output directories across container rebuilds.
# Mount subdirectories individually so Python modules in /app/data are not shadowed.
- ./instance:/app/instance
- ./data/weather_sat:/app/data/weather_sat
- ./data/radiosonde:/app/data/radiosonde
- ./data/subghz:/app/data/subghz
Expand All @@ -39,6 +40,7 @@ services:
- INTERCEPT_HOST=0.0.0.0
- INTERCEPT_PORT=5050
- INTERCEPT_LOG_LEVEL=INFO
- INTERCEPT_INSTANCE_DIR=/app/instance
# HTTPS support (auto-generates self-signed cert)
# - INTERCEPT_HTTPS=true
# - INTERCEPT_PORT=5443
Expand Down Expand Up @@ -89,6 +91,7 @@ services:
devices:
- /dev/bus/usb:/dev/bus/usb
volumes:
- ./instance:/app/instance
- ./data/weather_sat:/app/data/weather_sat
- ./data/radiosonde:/app/data/radiosonde
- ./data/subghz:/app/data/subghz
Expand All @@ -98,6 +101,7 @@ services:
- INTERCEPT_HOST=0.0.0.0
- INTERCEPT_PORT=5050
- INTERCEPT_LOG_LEVEL=INFO
- INTERCEPT_INSTANCE_DIR=/app/instance
# HTTPS support (auto-generates self-signed cert)
# - INTERCEPT_HTTPS=true
# - INTERCEPT_PORT=5443
Expand Down
2 changes: 2 additions & 0 deletions routes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def register_blueprints(app):
from .satellite import satellite_bp
from .sensor import sensor_bp
from .settings import settings_bp
from .setup import setup_bp
from .signalid import signalid_bp
from .space_weather import space_weather_bp
from .spy_stations import spy_stations_bp
Expand Down Expand Up @@ -68,6 +69,7 @@ def register_blueprints(app):
app.register_blueprint(satellite_bp)
app.register_blueprint(gps_bp)
app.register_blueprint(settings_bp)
app.register_blueprint(setup_bp)
app.register_blueprint(correlation_bp)
app.register_blueprint(receiver_bp)
app.register_blueprint(meshtastic_bp)
Expand Down
5 changes: 4 additions & 1 deletion routes/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@


def _get_env_file_path() -> Path:
"""Return the project .env path."""
"""Return the project .env path (INTERCEPT_ENV_FILE on Unraid)."""
override = os.environ.get("INTERCEPT_ENV_FILE", "").strip()
if override:
return Path(override)
return Path(__file__).resolve().parent.parent / ".env"


Expand Down
109 changes: 109 additions & 0 deletions routes/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""First-run / Unraid installation wizard API."""

from __future__ import annotations

from flask import Blueprint, Response

from utils.database import get_setting, set_setting
from utils.logging import get_logger
from utils.responses import api_error, api_success
from utils.unraid import (
running_in_docker,
running_on_unraid,
storage_advice,
usb_passthrough_present,
)

logger = get_logger("intercept.setup")

setup_bp = Blueprint("setup", __name__, url_prefix="/setup")

SETUP_COMPLETE_KEY = "setup.complete.v1"


def _observer_configured() -> tuple[bool, float, float]:
try:
from config import DEFAULT_LATITUDE, DEFAULT_LONGITUDE
except Exception:
return False, 0.0, 0.0
configured = DEFAULT_LATITUDE != 0.0 or DEFAULT_LONGITUDE != 0.0
return configured, float(DEFAULT_LATITUDE), float(DEFAULT_LONGITUDE)


def _cached_sdr_summary() -> list[dict[str, object]]:
try:
from utils.sdr.detection import get_cached_devices
except Exception:
return []
devices = get_cached_devices()
if not devices:
return []
summary: list[dict[str, object]] = []
for device in devices:
sdr_type = device.sdr_type.value if hasattr(device.sdr_type, "value") else str(device.sdr_type)
summary.append(
{
"type": sdr_type,
"index": device.index,
"name": device.name,
"serial": device.serial or "",
}
)
return summary


def build_setup_status() -> dict[str, object]:
"""Return wizard state without probing USB hardware."""
from utils.database import get_instance_dir

complete = bool(get_setting(SETUP_COMPLETE_KEY, False))
observer_ok, lat, lon = _observer_configured()
instance_dir = get_instance_dir()
try:
from config import ADMIN_PASSWORD, ADMIN_USERNAME
except Exception:
admin_username, default_admin = "admin", True
else:
admin_username = ADMIN_USERNAME
default_admin = ADMIN_PASSWORD == "admin"

return {
"complete": complete,
"platform": {
"unraid": running_on_unraid(),
"docker": running_in_docker(),
"usb_passthrough": usb_passthrough_present(),
},
"storage": storage_advice(instance_dir),
"observer": {
"configured": observer_ok,
"lat": lat,
"lon": lon,
},
"admin": {
"username": admin_username,
"using_default_password": default_admin,
},
"sdr_devices": _cached_sdr_summary(),
}


@setup_bp.route("/status", methods=["GET"])
def setup_status() -> Response:
"""Current first-run / Unraid wizard status."""
try:
return api_success(data=build_setup_status())
except Exception as exc:
logger.error("Error building setup status: %s", exc)
return api_error(str(exc), 500)


@setup_bp.route("/complete", methods=["POST"])
def mark_setup_complete() -> Response:
"""Persist setup completion in SQLite so it survives browser/localStorage."""
try:
set_setting(SETUP_COMPLETE_KEY, True)
return api_success(data={"complete": True}, message="Setup marked complete")
except Exception as exc:
logger.error("Error marking setup complete: %s", exc)
return api_error(str(exc), 500)
7 changes: 7 additions & 0 deletions start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,16 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# ── Load .env if present ──────────────────────────────────────────────────────
if [[ -f "$SCRIPT_DIR/.env" ]]; then
set -a
# shellcheck disable=SC1091
source "$SCRIPT_DIR/.env"
set +a
fi
if [[ -n "${INTERCEPT_ENV_FILE:-}" && -f "$INTERCEPT_ENV_FILE" && "$INTERCEPT_ENV_FILE" != "$SCRIPT_DIR/.env" ]]; then
set -a
# shellcheck disable=SC1090
source "$INTERCEPT_ENV_FILE"
set +a
fi

if [[ -x "$SCRIPT_DIR/venv/bin/python" ]]; then
PYTHON="$SCRIPT_DIR/venv/bin/python"
Expand Down
Loading