# DJ7NTs QO100 Webconsole > **Disclaimer:** This is a proof-of-concept project. Use at your own risk — no guarantees or warranties of any kind. The source code is not fully open-source at this time. ## Prerequisites - Docker (any recent version) and Docker Compose - **Analog Devices PlutoSDR** (original rev.B/C - 2(!) Channel-Version (one TX, one RX)) or **AD9363-based clone** (e.g. LibreSDR) — device type is selected during setup - **USB-to-Ethernet adapter** (100 Mbit) connected to the Pluto's USB OTG port — Gigabit adapters are not supported and may cause issues - LNB connected to the PlutoSDR - Original(!) 2channel-Pluto should be GPS-DO stabilized. Currently there's only a XIT to compensate it. As an additional Feature there's a global TX-Offset-Setting. - The Pluto and the host running Docker must be on the same network The prebuilt image is available for **x86_64** (PCs/servers) and **arm64** (Raspberry Pi 4+, Rock 5 ITX). Docker automatically pulls the correct variant for your hardware. ## Quick Start with Docker Compose Create a `docker-compose.yml`: ```yaml services: sdrc: image: git.dj7nt.de/dj7nt/qo100wc:latest ports: - "3004:3000" volumes: - ./data:/app/data environment: SESSION_SECRET: ${SESSION_SECRET} DATABASE_PATH: /app/data/qo100.db PLUTO_CONNECTED: "1" restart: unless-stopped ``` Generate a session secret and create a `.env` file: ```bash echo "SESSION_SECRET=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c 32)" > .env ``` Create the data directory before starting (Docker would create it as root otherwise): ```bash mkdir -p data ``` Start: ```bash docker compose up -d ``` ## First-Time Setup 1. Open `http://:3004` in a browser 2. Log in with **admin** / **admin** (you will be forced to change the password) 3. You will be redirected to the **Setup** wizard (`/setup`) 4. Configure: - **Device Type** — select your SDR hardware: ADALM Pluto (AD9364) or AD9363 Clone (LibreSDR) - **Operating Mode** — `qo100` for satellite operation or `simple` for general-purpose SDR use - **PlutoSDR IP** — the IP address of your Pluto (default: `192.168.6.122`) - **LNB LO Frequency** — the actual local oscillator frequency of your LNB in Hz. This is *not* the nominal 9750 MHz — e.g. my Bullseye TCXO LNB runs at `9749971700` Hz (9750 MHz minus ~28.3 kHz offset). If you use a different LNB, measure or look up its exact LO frequency. (QO-100 mode only) - **TX Calibration** — frequency calibration offset in Hz for transmitter accuracy - **TX Offset** — fixed frequency offset applied to TX (e.g. for transverter configurations) 5. Click **Save and Connect** To change these later, go to **Setup** as admin (via the sidebar or `/setup`). ## Admin Page (`/admin`) The admin page provides full management of the webconsole. Only users with the **admin** role can access it. ### User Management - View all users with their role, status, last seen, and last TX timestamps - Create, edit, disable, or delete users - Reset user passwords - Three roles: **admin** (full access), **user** (TX/RX), **guest** (RX only) - Safety: cannot delete yourself or the last remaining admin ### SDR Settings - **Device Type** — select SDR hardware profile (Pluto or AD9363 clone) - **Operating Mode** — `qo100` (satellite) or `simple` (general-purpose) - **PlutoSDR IP** — IP address of the Pluto (default: `192.168.6.122`) - **LNB LO Frequency** — exact local oscillator frequency in Hz (e.g. `9749971700` for a Bullseye TCXO) - **S-Meter Offset** — calibration offset from dBFS to dBm - **TX Calibration** — transmitter frequency calibration in Hz - **TX Offset** — fixed TX frequency offset in Hz Changes trigger a reconnect to the SDR bridge. ### TX Lock A global transmit lock that, when enabled, blocks all users from transmitting (PTT, two-tone, etc.) and stops any active transmissions. ### Activity Log - Paginated log (200 entries per page) of all user activity - Filterable by user and event type (login, logout, TX start/stop, disconnect, etc.) ## Networking: Reaching the Pluto By default Docker uses bridge networking. If the Pluto is on a separate Ethernet interface the container can't route to, use host networking (Linux only): ```yaml services: sdrc: image: git.dj7nt.de/dj7nt/qo100wc:latest network_mode: host # no ports: block needed — app listens on 3000 directly volumes: - ./data:/app/data environment: SESSION_SECRET: ${SESSION_SECRET} DATABASE_PATH: /app/data/qo100.db PLUTO_CONNECTED: "1" restart: unless-stopped ``` ## HTTPS / Microphone Access The browser's `getUserMedia()` API (used for TX microphone capture) requires a **secure context**. This means one of: - Access via `http://localhost` (works for local testing only) - Access via **HTTPS** (required for remote access) The Docker image does **not** include HTTPS. You need a reverse proxy. ### HAProxy Example Put HAProxy in front to terminate TLS (e.g. with Let's Encrypt via certbot): ```yaml services: sdrc: image: git.dj7nt.de/dj7nt/qo100wc:latest # no ports exposed publicly — only haproxy talks to it volumes: - ./data:/app/data environment: SESSION_SECRET: ${SESSION_SECRET} DATABASE_PATH: /app/data/qo100.db PLUTO_CONNECTED: "1" restart: unless-stopped networks: - internal haproxy: image: haproxy:3 ports: - "80:80" - "443:443" volumes: - ./haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro - ./certs:/usr/local/etc/haproxy/certs:ro restart: unless-stopped networks: - internal networks: internal: ``` `haproxy/haproxy.cfg`: ``` frontend http bind *:80 http-request redirect scheme https unless { ssl_fc } frontend https bind *:443 ssl crt /usr/local/etc/haproxy/certs/sdrc.pem default_backend sdrc backend sdrc server sdrc sdrc:3000 ``` Place your fullchain + privkey as `certs/sdrc.pem` (concatenated PEM). With Let's Encrypt: ```bash certbot certonly --standalone -d sdrc.example.com cat /etc/letsencrypt/live/sdrc.example.com/fullchain.pem \ /etc/letsencrypt/live/sdrc.example.com/privkey.pem > certs/sdrc.pem ``` After this setup, open `https://sdrc.example.com` — the browser will allow microphone access for TX. ### Alternative: Chrome Flag (Not Recommended for Production) For testing without HTTPS, launch Chrome with: ``` chrome --unsafely-treat-insecure-origin-as-secure=http://:3004 ``` This grants media permissions on that HTTP origin. Only use for development.