Switch the lab reflector to jayReflector and harden local station use.

Build DJ1JAY’s fork with an optional Satellite/Trunk stub, allow -c/--config, open capture and playback as separate devices, and add a lab start helper.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-07 12:14:41 +02:00
co-authored by Cursor
parent f90b6150f3
commit f8cad55e9c
24 changed files with 847 additions and 225 deletions
+5 -4
View File
@@ -4,15 +4,16 @@ TZ=Europe/Berlin
LISTEN_PORT=5300
START_SVXLINK=0
START_REMOTETRX=0
START_SVXREFLECTOR=1
# 0 = local lab only (default). 1 = append config/svxreflector.link.conf
# (Satellite/Trunk from DJ1JAY). Satellite dials out — no public DNS needed.
ENABLE_FM_LINK=0
SVXREFLECTOR_CONF=/etc/svxlink/svxreflector.conf
SVXREFLECTOR_LINK_CONF=/etc/svxlink/svxreflector.link.conf
SVXREFLECTOR_ARGS=--logfile=/var/log/svxlink/svxreflector.log
FUNKNETZ_HOST=127.0.0.1
FUNKNETZ_PORT=5300
FUNKNETZ_CALLSIGN=DL0FN-1
FUNKNETZ_AUTH_KEY=local-lab-secret-change-me
FUNKNETZ_TALK_GROUP=9
FUNKNETZ_TALK_GROUP=20
+4
View File
@@ -4,3 +4,7 @@ data/*
!data/.gitkeep
.env
*.log
.funknetz.generated.json
# Optional: keep a private link file with real DJ1JAY secrets untracked.
# The committed config/svxreflector.link.conf is a commented stub only.
config/svxreflector.link.local.conf
+40 -45
View File
@@ -1,77 +1,72 @@
# Build and run SvxReflector for the Funknetz local lab.
# Provenance: patterned on f4hlv/svxlink-docker (community); builds from upstream sm0svx/svxlink.
# Build jayReflector (DJ1JAY / FM-Funknetz) for the Funknetz local lab.
# Binary name remains svxreflector; Trunk/Satellite are opt-in via config.
# Upstream pattern: dj1jay/jayReflector Dockerfile (clone + cmake, not stock sm0svx).
# =========================================================
# STAGE 1 — BUILD
# =========================================================
FROM debian:trixie-slim AS builder
FROM debian:bookworm AS builder
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get -y install --no-install-recommends \
git ca-certificates \
cmake g++ make dpkg-dev \
libsigc++-2.0-dev libgsm1-dev libpopt-dev tcl8.6-dev \
libgcrypt20-dev libspeex-dev libasound2-dev \
libopus-dev librtlsdr-dev libjsoncpp-dev \
libcurl4-openssl-dev libgpiod-dev libogg-dev \
ladspa-sdk libssl-dev \
build-essential cmake \
libsigc++-2.0-dev libssl-dev libjsoncpp-dev libpopt-dev \
libopus-dev libgsm1-dev \
&& rm -rf /var/lib/apt/lists/*
ARG GIT_URL=https://github.com/sm0svx/svxlink.git
ARG GIT_BRANCH=master
ARG GIT_URL=https://github.com/dj1jay/jayReflector.git
ARG GIT_BRANCH=main
ARG GIT_REF=
ARG NUM_CORES=4
WORKDIR /build
RUN git clone --recursive --depth 1 --branch "${GIT_BRANCH}" "${GIT_URL}" svxlink
WORKDIR /build/svxlink
RUN set -eux; \
SVX_VER="$(awk -F= '$1=="SVXSERVER"{gsub(/[[:space:]]/,"",$2); print $2}' src/versions)"; \
echo "SVXSERVER version from src/versions: ${SVX_VER}"; \
rm -rf build && mkdir -p build && cd build; \
cmake \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_INSTALL_SYSCONFDIR=/etc \
-DCMAKE_INSTALL_LOCALSTATEDIR=/var \
if [ -n "${GIT_REF}" ]; then \
git clone --recursive "${GIT_URL}" jayReflector; \
cd jayReflector && git checkout "${GIT_REF}"; \
else \
git clone --recursive --depth 1 --branch "${GIT_BRANCH}" "${GIT_URL}" jayReflector; \
fi
WORKDIR /build/jayReflector
RUN set -eux; \
cmake -S src -B build \
-DLOCAL_STATE_DIR=/var \
-DUSE_QT=OFF \
-DCMAKE_BUILD_TYPE=Release \
-DCPACK_GENERATOR=DEB \
-DCPACK_PACKAGE_VERSION="${SVX_VER}" \
-DCPACK_DEBIAN_PACKAGE_VERSION="${SVX_VER}" \
../src; \
make -j"${NUM_CORES}"; \
make package; \
ls -lh *.deb
-DUSE_ALSA=OFF \
-DCMAKE_BUILD_TYPE=Release; \
cmake --build build --target svxreflector -j"${NUM_CORES}"
# =========================================================
# STAGE 2 — RUNTIME
# =========================================================
FROM debian:trixie-slim
FROM debian:bookworm-slim
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get -y install --no-install-recommends \
adduser init-system-helpers ca-certificates openssl \
libasyncaudio1.6t64 libasynccore1.6t64 libasynccpp1.6t64 libcurl4t64 \
libgcrypt20 libgsm1 libjsoncpp26 libpopt0 librtlsdr0 \
libsigc++-2.0-0v5 libtcl8.6 libgcc-s1 libstdc++6 \
libasound2 libgpiod3 libogg0 libopus0 libspeex1 \
ca-certificates openssl \
libsigc++-2.0-0v5 libssl3 libjsoncpp25 libpopt0 \
libopus0 libgsm1 \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /build/svxlink/build/*.deb /tmp/svxlink.deb
COPY --from=builder /build/jayReflector/build/bin/svxreflector /usr/bin/svxreflector
COPY --from=builder /build/jayReflector/build/lib/lib*.so* /usr/lib/
RUN apt-get update && \
apt-get -y install --no-install-recommends /tmp/svxlink.deb && \
rm -f /tmp/svxlink.deb && \
rm -rf /var/lib/apt/lists/*
RUN mkdir -p /etc/svxlink /var/log/svxlink /var/lib/svxlink && \
RUN mkdir -p /etc/svxlink /etc/svxlink/pki /var/log/svxlink /var/lib/svxlink && \
ldconfig && \
useradd -r -u 1000 -m -d /home/svx svx || true
ENV SVXLINK_CONF=/etc/svxlink/svxlink.conf \
REMOTETRX_CONF=/etc/svxlink/remotetrx.conf \
SVXREFLECTOR_CONF=/etc/svxlink/svxreflector.conf
ENV SVXREFLECTOR_CONF=/etc/svxlink/svxreflector.conf \
SVXREFLECTOR_LINK_CONF=/etc/svxlink/svxreflector.link.conf \
ENABLE_FM_LINK=0
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# 5300: local Funknetz / SvxLink clients (TCP+UDP)
# Satellite mode dials out to parent :5303 — no inbound peer port required.
# Trunk mode would need 5302/tcp inbound; not published by default in compose.
EXPOSE 5300/tcp 5300/udp
ENTRYPOINT ["/entrypoint.sh"]
+11 -4
View File
@@ -1,6 +1,10 @@
###################################################################
# Local lab SvxReflector for Funknetz (protocol 2.0 / ReflectorV2)
# Local lab jayReflector for Funknetz (protocol 2.0 / ReflectorV2)
# Secrets here are placeholders — do not reuse production keys.
#
# Standalone by default: local clients only. FM-Funknetz Satellite/Trunk
# keys live in svxreflector.link.conf and are appended when
# ENABLE_FM_LINK=1 (see .env and docker-compose).
###################################################################
[GLOBAL]
@@ -8,10 +12,13 @@ TIMESTAMP_FORMAT="%c"
LISTEN_PORT=5300
TG_FOR_V1_CLIENTS=999
# CODECS=OPUS
COMMAND_PTY=/dev/shm/reflector_ctrl
COMMAND_PTY=/dev/shm/svxreflector_ctrl
CERT_PKI_DIR=/etc/svxlink/pki/
# Optional status JSON (publish 8080 in compose if you enable this):
# HTTP_SRV_PORT=8080
# Newer SvxReflector bootstraps a local PKI even for protocol 2.0 AUTH_KEY clients.
# COMMON_NAME is required so the server certificate can be generated on first start.
# jayReflector / modern SvxReflector bootstraps a local PKI even for
# protocol 2.0 AUTH_KEY clients. COMMON_NAME is required on first start.
[SERVER_CERT]
COMMON_NAME=funknetz-lab.local
SUBJECT_ALT_NAME=DNS:funknetz-lab.local,DNS:localhost,IP:127.0.0.1
@@ -0,0 +1,41 @@
###################################################################
# Optional FM-Funknetz link stub (jayReflector)
#
# Default: leave ENABLE_FM_LINK=0 in .env — this file is ignored.
# When DJ1JAY provides values:
# 1. Fill ONE of the blocks below (prefer Satellite for home/NAT labs).
# 2. Set ENABLE_FM_LINK=1 in .env
# 3. docker compose up -d
#
# Satellite (recommended): this reflector dials OUT to the parent on
# TCP 5303. No public DNS, no inbound peer port, no port-forward required
# for the link. Local Funknetz clients still use host :5300 only.
#
# Trunk: equal mesh peer; needs mutual reachability on TCP 5302 and a
# matching [TRUNK_x] section name + SECRET on both sides.
#
# Do not commit real secrets. Prefer a local override or keep secrets
# only in your private .env workflow if you copy this file.
# Docs: docs/research/2026-10-07-jayreflector-fm-funknetz.md
# README: https://github.com/dj1jay/jayReflector
###################################################################
# --- Satellite (fill from DJ1JAY; uncomment to use) ---
# SATELLITE_OF=parent.example.org:5303
# SATELLITE_ID=lab-yourcall
# SATELLITE_SECRET="replace-with-secret-from-dj1jay"
# Only these TGs cross to/from the parent (bidirectional). Omit for all TGs.
# SATELLITE_FILTER=2621,2629,20
# --- Trunk peer alternative (fill from DJ1JAY; usually leave commented) ---
# LOCAL_PREFIX=9
# TRUNK_LISTEN_PORT=5302
#
# [TRUNK_LAB_FM]
# HOST=fm-parent.example.org
# PORT=5302
# SECRET="replace-with-agreed-trunk-secret"
# REMOTE_PREFIX=262
# ALLOW_TGS=2621,2629,20
# # TG_MAP=1:2624100
# MQTT_NAME=Lab-YourCall
+19 -9
View File
@@ -1,6 +1,9 @@
# Local SvxReflector lab for Funknetz.
# Build once, then: docker compose up -d
# Local jayReflector lab for Funknetz.
# Build once, then: docker compose up -d --build
# Connect the app with host/port/callsign/authKey from .env (see FUNKNETZ_*).
#
# FM-Funknetz link (optional): fill config/svxreflector.link.conf from DJ1JAY,
# set ENABLE_FM_LINK=1, recreate. Satellite mode is outbound-only (no DNS/inbound).
services:
svxreflector:
@@ -8,9 +11,11 @@ services:
context: .
dockerfile: Dockerfile
args:
GIT_URL: https://github.com/sm0svx/svxlink.git
GIT_BRANCH: master
image: funknetz-svxreflector:local
GIT_URL: https://github.com/dj1jay/jayReflector.git
GIT_BRANCH: main
# Optional pin (overrides branch clone when set), e.g.:
# GIT_REF: 50997ed21a37c7c05c132eca5ebb07bab4b80fc7
image: funknetz-jayreflector:local
container_name: funknetz-svxreflector
restart: unless-stopped
env_file:
@@ -18,16 +23,21 @@ services:
ports:
# Host LISTEN_PORT → container 5300 (must match config/svxreflector.conf).
# Control is TCP; audio and UDP keepalives use the same number.
# Satellite uplink uses outbound TCP 5303 — not published here.
- "${LISTEN_PORT:-5300}:5300/tcp"
- "${LISTEN_PORT:-5300}:5300/udp"
# Optional HTTP /status when HTTP_SRV_PORT=8080 is set in conf:
# - "8080:8080/tcp"
# Trunk peer only (not needed for Satellite):
# - "5302:5302/tcp"
environment:
TZ: ${TZ:-Europe/Berlin}
START_SVXLINK: ${START_SVXLINK:-0}
START_REMOTETRX: ${START_REMOTETRX:-0}
START_SVXREFLECTOR: ${START_SVXREFLECTOR:-1}
ENABLE_FM_LINK: ${ENABLE_FM_LINK:-0}
SVXREFLECTOR_CONF: ${SVXREFLECTOR_CONF:-/etc/svxlink/svxreflector.conf}
SVXREFLECTOR_LINK_CONF: ${SVXREFLECTOR_LINK_CONF:-/etc/svxlink/svxreflector.link.conf}
SVXREFLECTOR_ARGS: ${SVXREFLECTOR_ARGS:-}
volumes:
- ./config/svxreflector.conf:/etc/svxlink/svxreflector.conf:ro
- ./config/svxreflector.link.conf:/etc/svxlink/svxreflector.link.conf:ro
- ./logs:/var/log/svxlink
- ./data:/var/lib/svxlink
- ./data/pki:/etc/svxlink/pki
+27 -45
View File
@@ -1,17 +1,11 @@
#!/bin/sh
set -eu
SVXLINK_CONF="${SVXLINK_CONF:-/etc/svxlink/svxlink.conf}"
REMOTETRX_CONF="${REMOTETRX_CONF:-/etc/svxlink/remotetrx.conf}"
SVXREFLECTOR_CONF="${SVXREFLECTOR_CONF:-/etc/svxlink/svxreflector.conf}"
START_SVXLINK="${START_SVXLINK:-0}"
START_REMOTETRX="${START_REMOTETRX:-0}"
START_SVXREFLECTOR="${START_SVXREFLECTOR:-1}"
SVXLINK_ARGS="${SVXLINK_ARGS:-}"
REMOTETRX_ARGS="${REMOTETRX_ARGS:-}"
SVXREFLECTOR_LINK_CONF="${SVXREFLECTOR_LINK_CONF:-/etc/svxlink/svxreflector.link.conf}"
SVXREFLECTOR_ARGS="${SVXREFLECTOR_ARGS:-}"
ENABLE_FM_LINK="${ENABLE_FM_LINK:-0}"
RUNTIME_CONF="/tmp/svxreflector.runtime.conf"
pids=""
@@ -24,15 +18,6 @@ die() {
exit 1
}
need_file_if_enabled() {
enabled="$1"
file="$2"
name="$3"
if [ "$enabled" = "1" ] && [ ! -f "$file" ]; then
die "Config for $name not found: $file (expected volume mount)"
fi
}
start_bg() {
name="$1"
shift
@@ -70,37 +55,34 @@ stop_all() {
trap 'stop_all; exit 0' INT TERM
if [ "$START_SVXLINK" = "1" ] && ! command -v svxlink >/dev/null 2>&1; then
die "svxlink binary not found"
fi
if [ "$START_REMOTETRX" = "1" ] && ! command -v remotetrx >/dev/null 2>&1; then
die "remotetrx binary not found"
fi
if [ "$START_SVXREFLECTOR" = "1" ] && ! command -v svxreflector >/dev/null 2>&1; then
die "svxreflector binary not found"
command -v svxreflector >/dev/null 2>&1 || die "svxreflector binary not found (jayReflector build)"
[ -f "$SVXREFLECTOR_CONF" ] || die "Config not found: $SVXREFLECTOR_CONF (expected volume mount)"
# Local-only by default. When ENABLE_FM_LINK=1, append the link stub
# (Satellite or Trunk keys from DJ1JAY) after the base lab conf.
if [ "$ENABLE_FM_LINK" = "1" ]; then
[ -f "$SVXREFLECTOR_LINK_CONF" ] || die "ENABLE_FM_LINK=1 but link config missing: $SVXREFLECTOR_LINK_CONF"
{
cat "$SVXREFLECTOR_CONF"
echo
echo "###################################################################"
echo "# Appended FM-Funknetz link stub (ENABLE_FM_LINK=1)"
echo "###################################################################"
cat "$SVXREFLECTOR_LINK_CONF"
} >"$RUNTIME_CONF"
conf_path="$RUNTIME_CONF"
log "FM link enabled: using $SVXREFLECTOR_CONF + $SVXREFLECTOR_LINK_CONF"
else
conf_path="$SVXREFLECTOR_CONF"
log "FM link disabled (ENABLE_FM_LINK=0): local lab reflector only"
fi
need_file_if_enabled "$START_SVXLINK" "$SVXLINK_CONF" "svxlink"
need_file_if_enabled "$START_REMOTETRX" "$REMOTETRX_CONF" "remotetrx"
need_file_if_enabled "$START_SVXREFLECTOR" "$SVXREFLECTOR_CONF" "svxreflector"
# Ensure PKI dir exists for first-start cert bootstrap.
mkdir -p /etc/svxlink/pki /var/lib/svxlink/pki
if [ "$START_SVXREFLECTOR" = "1" ]; then
start_bg "svxreflector" svxreflector --config "$SVXREFLECTOR_CONF" $SVXREFLECTOR_ARGS
fi
start_bg "svxreflector" svxreflector --config "$conf_path" $SVXREFLECTOR_ARGS
if [ "$START_REMOTETRX" = "1" ]; then
start_bg "remotetrx" remotetrx --config "$REMOTETRX_CONF" $REMOTETRX_ARGS
fi
if [ "$START_SVXLINK" = "1" ]; then
start_bg "svxlink" svxlink --config "$SVXLINK_CONF" $SVXLINK_ARGS
fi
if ! echo "$pids" | grep -q '[0-9]'; then
die "No service enabled. Set START_SVXREFLECTOR=1 (lab default) and/or START_SVXLINK / START_REMOTETRX"
fi
log "Requested services are running. Waiting…"
log "jayReflector (svxreflector) is running. Waiting…"
exit_code=0
for pid in $pids; do