Complete DDS training workflow and delivery package

This commit is contained in:
andreysk0304 2026-09-24 01:10:49 +03:00
commit 4c4b91064f
229 changed files with 11969 additions and 1024 deletions

2
sip/.dockerignore Normal file
View file

@ -0,0 +1,2 @@
*.log
*.wav

22
sip/Dockerfile Normal file
View file

@ -0,0 +1,22 @@
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get install -y --no-install-recommends asterisk ca-certificates gettext-base openssl \
&& rm -rf /var/lib/apt/lists/*
COPY pjsip.conf.template /opt/lct-sip/pjsip.conf.template
COPY extensions.conf /etc/asterisk/extensions.conf
COPY http.conf /etc/asterisk/http.conf
COPY modules.conf /etc/asterisk/modules.conf
COPY rtp.conf /etc/asterisk/rtp.conf
COPY entrypoint.sh /usr/local/bin/lct-sip-entrypoint
RUN chmod +x /usr/local/bin/lct-sip-entrypoint \
&& mkdir -p /recordings /var/lib/lct-sip /tls \
&& chown -R asterisk:asterisk /recordings /var/lib/lct-sip
EXPOSE 5060/udp 5060/tcp 5061/tcp 8088/tcp 10000-10099/udp
HEALTHCHECK --interval=10s --timeout=3s --retries=6 --start-period=15s \
CMD asterisk -rx "core show uptime" >/dev/null 2>&1 || exit 1
ENTRYPOINT ["/usr/local/bin/lct-sip-entrypoint"]

50
sip/entrypoint.sh Normal file
View file

@ -0,0 +1,50 @@
#!/bin/sh
set -eu
credentials=/var/lib/lct-sip/credentials.env
mkdir -p /var/lib/lct-sip /recordings /tls
umask 077
override_6001=${SIP_6001_PASSWORD:-}
override_6002=${SIP_6002_PASSWORD:-}
override_6003=${SIP_6003_PASSWORD:-}
override_6101=${SIP_6101_PASSWORD:-}
override_6102=${SIP_6102_PASSWORD:-}
if [ -s "$credentials" ]; then
# Файл содержит только сгенерированные hex-значения без shell-конструкций.
. "$credentials"
fi
SIP_6001_PASSWORD=${override_6001:-${SIP_6001_PASSWORD:-$(openssl rand -hex 16)}}
SIP_6002_PASSWORD=${override_6002:-${SIP_6002_PASSWORD:-$(openssl rand -hex 16)}}
SIP_6003_PASSWORD=${override_6003:-${SIP_6003_PASSWORD:-$(openssl rand -hex 16)}}
SIP_6101_PASSWORD=${override_6101:-${SIP_6101_PASSWORD:-$(openssl rand -hex 16)}}
SIP_6102_PASSWORD=${override_6102:-${SIP_6102_PASSWORD:-$(openssl rand -hex 16)}}
SIP_EXTERNAL_MEDIA_ADDRESS=${SIP_EXTERNAL_MEDIA_ADDRESS:-127.0.0.1}
cat > "$credentials" <<EOF
SIP_6001_PASSWORD=$SIP_6001_PASSWORD
SIP_6002_PASSWORD=$SIP_6002_PASSWORD
SIP_6003_PASSWORD=$SIP_6003_PASSWORD
SIP_6101_PASSWORD=$SIP_6101_PASSWORD
SIP_6102_PASSWORD=$SIP_6102_PASSWORD
EOF
chmod 600 "$credentials"
export SIP_6001_PASSWORD SIP_6002_PASSWORD SIP_6003_PASSWORD SIP_6101_PASSWORD SIP_6102_PASSWORD SIP_EXTERNAL_MEDIA_ADDRESS
envsubst '${SIP_6001_PASSWORD} ${SIP_6002_PASSWORD} ${SIP_6003_PASSWORD} ${SIP_6101_PASSWORD} ${SIP_6102_PASSWORD} ${SIP_EXTERNAL_MEDIA_ADDRESS}' \
< /opt/lct-sip/pjsip.conf.template > /etc/asterisk/pjsip.conf
chmod 640 /etc/asterisk/pjsip.conf
chown root:asterisk /etc/asterisk/pjsip.conf
if [ ! -s /tls/sip.crt ] || [ ! -s /tls/sip.key ]; then
openssl req -x509 -nodes -newkey rsa:3072 -sha256 -days 365 \
-keyout /tls/sip.key -out /tls/sip.crt \
-subj '/CN=localhost/O=LCT local SIP training stand' \
-addext 'subjectAltName=DNS:localhost,IP:127.0.0.1' \
-addext 'extendedKeyUsage=serverAuth' 2>/dev/null
fi
chmod 640 /tls/sip.key
chmod 644 /tls/sip.crt
chown root:asterisk /tls/sip.key /tls/sip.crt
chown -R asterisk:asterisk /recordings /var/lib/lct-sip
exec asterisk -f -U asterisk -G asterisk -vvv

28
sip/extensions.conf Normal file
View file

@ -0,0 +1,28 @@
[general]
static=yes
writeprotect=yes
clearglobalvars=no
[lct-training]
; 6001 — ДДС, 6002 — старший группы, 6003 — преподаватель.
exten => _600[1-3],1,NoOp(LCT training call ${CALLERID(num)} -> ${EXTEN})
same => n,Set(RECORDING_FILE=${STRFTIME(${EPOCH},UTC,%Y%m%d-%H%M%S)}-${CALLERID(num)}-${EXTEN}-${UNIQUEID})
same => n,MixMonitor(/recordings/${RECORDING_FILE}.wav,b)
same => n,Dial(PJSIP/${EXTEN},30)
same => n,StopMixMonitor()
same => n,Hangup()
; 6101/6102 — браузерные WebRTC-абоненты. Тот же учебный контекст и запись,
; но отдельные endpoints не ломают обычные UDP/TCP-софтфоны 6001–6003.
exten => _610[1-2],1,NoOp(LCT WebRTC training call ${CALLERID(num)} -> ${EXTEN})
same => n,Set(RECORDING_FILE=${STRFTIME(${EPOCH},UTC,%Y%m%d-%H%M%S)}-${CALLERID(num)}-${EXTEN}-${UNIQUEID})
same => n,MixMonitor(/recordings/${RECORDING_FILE}.wav,b)
same => n,Dial(PJSIP/${EXTEN},30)
same => n,StopMixMonitor()
same => n,Hangup()
; Эхо-номер нужен для проверки полного SIP/RTP-пути без второго устройства.
exten => 7000,1,NoOp(LCT SIP echo test)
same => n,Answer()
same => n,Echo()
same => n,Hangup()

8
sip/http.conf Normal file
View file

@ -0,0 +1,8 @@
[general]
enabled=yes
bindaddr=0.0.0.0
bindport=8088
tlsenable=no
; Порт доступен только локально/Docker-сети. Браузер приходит по WSS через
; Nginx; прямой HTTP нужен лишь для health/smoke на localhost.

7
sip/modules.conf Normal file
View file

@ -0,0 +1,7 @@
[modules]
autoload=yes
; Ubuntu всё ещё поставляет legacy chan_sip, который первым захватывает
; WebSocket subprotocol `sip`. Все endpoints стенда описаны в PJSIP.
noload => chan_sip.so
load => res_pjsip_transport_websocket.so

156
sip/pjsip.conf.template Normal file
View file

@ -0,0 +1,156 @@
[global]
type=global
user_agent=LCT-112-Training-SIP
endpoint_identifier_order=auth_username,username,ip,anonymous
[transport-udp]
type=transport
protocol=udp
bind=0.0.0.0:5060
[transport-tcp]
type=transport
protocol=tcp
bind=0.0.0.0:5060
[transport-tls]
type=transport
protocol=tls
bind=0.0.0.0:5061
cert_file=/tls/sip.crt
priv_key_file=/tls/sip.key
method=tlsv1_2
verify_client=no
verify_server=no
allow_reload=yes
; TLS для браузера завершает тот же Nginx, что обслуживает АРМ. До Asterisk
; SIP signaling идёт WebSocket внутри изолированной Docker-сети.
[transport-ws]
type=transport
protocol=ws
bind=0.0.0.0
external_media_address=${SIP_EXTERNAL_MEDIA_ADDRESS}
[training-endpoint](!)
type=endpoint
context=lct-training
disallow=all
allow=ulaw
allow=alaw
direct_media=no
rtp_symmetric=yes
force_rport=yes
rewrite_contact=yes
ice_support=yes
timers=yes
dtmf_mode=rfc4733
language=ru
[6001](training-endpoint)
auth=6001-auth
aors=6001
callerid=Диспетчер ДДС <6001>
[6001-auth]
type=auth
auth_type=userpass
username=6001
password=${SIP_6001_PASSWORD}
[6001]
type=aor
max_contacts=3
remove_existing=yes
qualify_frequency=30
[6002](training-endpoint)
auth=6002-auth
aors=6002
callerid=Старший группы <6002>
[6002-auth]
type=auth
auth_type=userpass
username=6002
password=${SIP_6002_PASSWORD}
[6002]
type=aor
max_contacts=3
remove_existing=yes
qualify_frequency=30
[6003](training-endpoint)
auth=6003-auth
aors=6003
callerid=Преподаватель <6003>
[6003-auth]
type=auth
auth_type=userpass
username=6003
password=${SIP_6003_PASSWORD}
[6003]
type=aor
max_contacts=3
remove_existing=yes
qualify_frequency=30
[webrtc-endpoint](!)
type=endpoint
context=lct-training
disallow=all
allow=ulaw
allow=alaw
direct_media=no
webrtc=yes
use_avpf=yes
media_encryption=dtls
dtls_auto_generate_cert=yes
dtls_verify=fingerprint
dtls_setup=actpass
ice_support=yes
media_use_received_transport=yes
rtcp_mux=yes
rtp_symmetric=yes
force_rport=yes
rewrite_contact=yes
timers=yes
dtmf_mode=rfc4733
language=ru
[6101](webrtc-endpoint)
auth=6101-auth
aors=6101
callerid=Веб ДДС <6101>
[6101-auth]
type=auth
auth_type=userpass
username=6101
password=${SIP_6101_PASSWORD}
[6101]
type=aor
max_contacts=2
remove_existing=yes
qualify_frequency=30
[6102](webrtc-endpoint)
auth=6102-auth
aors=6102
callerid=Веб старший группы <6102>
[6102-auth]
type=auth
auth_type=userpass
username=6102
password=${SIP_6102_PASSWORD}
[6102]
type=aor
max_contacts=2
remove_existing=yes
qualify_frequency=30

5
sip/rtp.conf Normal file
View file

@ -0,0 +1,5 @@
[general]
rtpstart=10000
rtpend=10099
strictrtp=yes
icesupport=yes