Add a local SvxReflector Docker lab for headset testing.
Funknetz needs a protocol 2.0 reflector without production credentials; the compose stack and smoke test prove the station can log in and select a talk group. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
logs/
|
||||
data/
|
||||
.git
|
||||
.gitignore
|
||||
*.md
|
||||
*.log
|
||||
.DS_Store
|
||||
@@ -0,0 +1,18 @@
|
||||
# Copy to .env and adjust. Auth secrets must match config/svxreflector.conf.
|
||||
|
||||
TZ=Europe/Berlin
|
||||
|
||||
LISTEN_PORT=5300
|
||||
|
||||
START_SVXLINK=0
|
||||
START_REMOTETRX=0
|
||||
START_SVXREFLECTOR=1
|
||||
|
||||
SVXREFLECTOR_CONF=/etc/svxlink/svxreflector.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
|
||||
@@ -0,0 +1,6 @@
|
||||
logs/*
|
||||
!logs/.gitkeep
|
||||
data/*
|
||||
!data/.gitkeep
|
||||
.env
|
||||
*.log
|
||||
@@ -0,0 +1,77 @@
|
||||
# Build and run SvxReflector for the Funknetz local lab.
|
||||
# Provenance: patterned on f4hlv/svxlink-docker (community); builds from upstream sm0svx/svxlink.
|
||||
|
||||
# =========================================================
|
||||
# STAGE 1 — BUILD
|
||||
# =========================================================
|
||||
FROM debian:trixie-slim 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 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ARG GIT_URL=https://github.com/sm0svx/svxlink.git
|
||||
ARG GIT_BRANCH=master
|
||||
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 \
|
||||
-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
|
||||
|
||||
# =========================================================
|
||||
# STAGE 2 — RUNTIME
|
||||
# =========================================================
|
||||
FROM debian:trixie-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 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=builder /build/svxlink/build/*.deb /tmp/svxlink.deb
|
||||
|
||||
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 && \
|
||||
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
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
@@ -0,0 +1,24 @@
|
||||
###################################################################
|
||||
# Local lab SvxReflector for Funknetz (protocol 2.0 / ReflectorV2)
|
||||
# Secrets here are placeholders — do not reuse production keys.
|
||||
###################################################################
|
||||
|
||||
[GLOBAL]
|
||||
TIMESTAMP_FORMAT="%c"
|
||||
LISTEN_PORT=5300
|
||||
TG_FOR_V1_CLIENTS=999
|
||||
# CODECS=OPUS
|
||||
COMMAND_PTY=/dev/shm/reflector_ctrl
|
||||
|
||||
# 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.
|
||||
[SERVER_CERT]
|
||||
COMMON_NAME=funknetz-lab.local
|
||||
SUBJECT_ALT_NAME=DNS:funknetz-lab.local,DNS:localhost,IP:127.0.0.1
|
||||
|
||||
[USERS]
|
||||
DL0FN-1=LocalTest
|
||||
DL0FN-2=LocalTest
|
||||
|
||||
[PASSWORDS]
|
||||
LocalTest="local-lab-secret-change-me"
|
||||
@@ -0,0 +1,33 @@
|
||||
# Local SvxReflector lab for Funknetz.
|
||||
# Build once, then: docker compose up -d
|
||||
# Connect the app with host/port/callsign/authKey from .env (see FUNKNETZ_*).
|
||||
|
||||
services:
|
||||
svxreflector:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
GIT_URL: https://github.com/sm0svx/svxlink.git
|
||||
GIT_BRANCH: master
|
||||
image: funknetz-svxreflector:local
|
||||
container_name: funknetz-svxreflector
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
# Host LISTEN_PORT → container 5300 (must match config/svxreflector.conf).
|
||||
# Control is TCP; audio and UDP keepalives use the same number.
|
||||
- "${LISTEN_PORT:-5300}:5300/tcp"
|
||||
- "${LISTEN_PORT:-5300}:5300/udp"
|
||||
environment:
|
||||
TZ: ${TZ:-Europe/Berlin}
|
||||
START_SVXLINK: ${START_SVXLINK:-0}
|
||||
START_REMOTETRX: ${START_REMOTETRX:-0}
|
||||
START_SVXREFLECTOR: ${START_SVXREFLECTOR:-1}
|
||||
SVXREFLECTOR_CONF: ${SVXREFLECTOR_CONF:-/etc/svxlink/svxreflector.conf}
|
||||
SVXREFLECTOR_ARGS: ${SVXREFLECTOR_ARGS:-}
|
||||
volumes:
|
||||
- ./config/svxreflector.conf:/etc/svxlink/svxreflector.conf:ro
|
||||
- ./logs:/var/log/svxlink
|
||||
- ./data:/var/lib/svxlink
|
||||
Executable
+113
@@ -0,0 +1,113 @@
|
||||
#!/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_ARGS="${SVXREFLECTOR_ARGS:-}"
|
||||
|
||||
pids=""
|
||||
|
||||
log() {
|
||||
echo "[$(date -Iseconds)] $*"
|
||||
}
|
||||
|
||||
die() {
|
||||
log "ERROR: $*"
|
||||
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
|
||||
log "Starting: $name -> $*"
|
||||
"$@" &
|
||||
pid="$!"
|
||||
pids="$pids $pid"
|
||||
log "$name started (pid=$pid)"
|
||||
}
|
||||
|
||||
stop_all() {
|
||||
log "Stop requested; stopping: $pids"
|
||||
for pid in $pids; do
|
||||
kill -TERM "$pid" 2>/dev/null || true
|
||||
done
|
||||
|
||||
i=0
|
||||
while [ $i -lt 10 ]; do
|
||||
alive=0
|
||||
for pid in $pids; do
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
alive=1
|
||||
fi
|
||||
done
|
||||
[ "$alive" -eq 0 ] && break
|
||||
i=$((i + 1))
|
||||
sleep 1
|
||||
done
|
||||
|
||||
for pid in $pids; do
|
||||
kill -KILL "$pid" 2>/dev/null || true
|
||||
done
|
||||
log "Stop complete."
|
||||
}
|
||||
|
||||
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"
|
||||
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"
|
||||
|
||||
if [ "$START_SVXREFLECTOR" = "1" ]; then
|
||||
start_bg "svxreflector" svxreflector --config "$SVXREFLECTOR_CONF" $SVXREFLECTOR_ARGS
|
||||
fi
|
||||
|
||||
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…"
|
||||
|
||||
exit_code=0
|
||||
for pid in $pids; do
|
||||
if ! wait "$pid"; then
|
||||
exit_code=1
|
||||
fi
|
||||
done
|
||||
|
||||
log "Exit (exit_code=$exit_code)"
|
||||
exit "$exit_code"
|
||||
Reference in New Issue
Block a user