Initial import with GitLab CI/CD and registry deploy flow

This commit is contained in:
Yera All
2026-04-02 17:05:45 +05:00
commit 5374c202d9
338 changed files with 81297 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
asterisk \
ca-certificates \
curl \
openssl \
python3 \
procps \
uuid-runtime \
&& rm -rf /var/lib/apt/lists/*
COPY deployment/asterisk/entrypoint.sh /usr/local/bin/mvpcc-asterisk-entrypoint.sh
RUN chmod +x /usr/local/bin/mvpcc-asterisk-entrypoint.sh \
&& mkdir -p /config-template /assets /etc/asterisk/keys /var/spool/asterisk/monitor/mvpcc
ENTRYPOINT ["/usr/local/bin/mvpcc-asterisk-entrypoint.sh"]
+74
View File
@@ -0,0 +1,74 @@
# Asterisk Lab Config
This folder contains the lab-grade configuration set for Track 9, Track 11, and Track 14:
- `pjsip.conf`
- `extensions.conf`
- `manager.conf`
- `http.conf`
- `rtp.conf`
These files are templates for a dedicated Linux VM running Asterisk 20 LTS.
Before using them:
1. Replace default passwords and secrets.
2. Confirm the softphone source network.
3. Create the recording directory:
- `/var/spool/asterisk/monitor/mvpcc`
4. Point `ASTERISK_QUEUE_MAP_JSON` in the platform to the matching queue code:
- `voice_lab`
- `voice_lab_ai` for the dedicated AI-first staging entrypoint
- `voice_lab_ivr` for the dedicated IVR staging entrypoint
5. Configure operator extension mapping in bridge env for Track 11 call control:
- `ASTERISK_OPERATOR_EXTENSION_MAP_JSON={"operator_a":"2001","operator_b":"2002"}`
6. Optional transfer map for queue-code based blind transfer:
- `ASTERISK_TRANSFER_TARGET_MAP_JSON={"voice_lab":"2001","voice_lab_ai":"2001","voice_lab_ivr":"7200"}`
7. For IVR-over-Asterisk staging:
- enable `ASTERISK_IVR_FASTAGI_ENABLED=1`
- set `ASTERISK_IVR_FASTAGI_HOST=<bridge-host>`
- set `ASTERISK_IVR_FASTAGI_PORT=4573`
- set `IVR_RUNTIME_TRUSTED_SERVICE_SUBJECTS=svc:asterisk-bridge`
- provision the referenced `prompt_audio_key` files under the Asterisk sounds directory
- if you want a higher-quality prompt from OpenAI TTS, generate it once and keep it on the server:
- `python scripts/materialize_tts_prompt.py --text "Здравствуйте! Добро пожаловать в контакт-центр. Для русского языка нажмите 1. Қазақ тілі үшін 2 басыңыз." --output /usr/share/asterisk/sounds/en/mvpcc_ivr_language.wav --language ru`
- `python scripts/materialize_tts_prompt.py --text "Для отдела продаж нажмите 1. Для службы поддержки нажмите 2." --output /usr/share/asterisk/sounds/en/mvpcc_ivr_menu_ru.wav --language ru`
- `python scripts/materialize_tts_prompt.py --text "Сату бөлімі үшін 1 басыңыз. Қолдау қызметі үшін 2 басыңыз." --output /usr/share/asterisk/sounds/en/mvpcc_ivr_menu_kz.wav --language kk`
- repeated runs reuse the server cache from `AI_VOICE_TTS_CACHE_DIR` instead of calling OpenAI again for the same text
8. For Track 14 browser softphone QA:
- enable `ASTERISK_WEBRTC_ENABLED=1`
- set `ASTERISK_WEBRTC_WS_URL=wss://<asterisk-host>:8089/ws`
- configure `ASTERISK_BROWSER_SIP_MAP_JSON`
- replace TLS placeholders in `http.conf` with a trusted cert/key pair
9. For Voice AI V1 lab wiring:
- enable `AI_VOICE_ENABLED=1`
- set `AI_VOICE_QUEUE_CONFIG_JSON={"voice_lab_ai":{"mode":"ai_first","agent_profile":"voice_support","handoff_queue_code":"voice_lab","language":"ru"}}`
- set `AI_VOICE_RUNTIME_SERVICE_URL=http://ai-voice-runtime-service:8000`
- keep `AI_VOICE_RUNTIME_TRUSTED_SERVICE_SUBJECTS=svc:ai-voice-runtime`
- load `app_audiosocket.so` on the Asterisk VM before enabling the AI media path
- set `MVPCC_AI_AUDIOSOCKET_SERVICE=<media-host>:<port>` in `extensions.conf` only after the AudioSocket listener is reachable
Entrypoints in `extensions.conf`:
- `7000`: human-only baseline. This path preserves the original Track 9/11 behavior.
- `7100`: AI-first staging path. It emits the normal `MVPCCCallStarted` event with `QueueCode=voice_lab_ai`, then:
- falls back to human dialing immediately if `MVPCC_AI_AUDIOSOCKET_SERVICE` is blank
- otherwise starts `AudioSocket(<uuid>,<host>:<port>)` for the media bridge
- `7200`: IVR staging path. It emits the normal `MVPCCCallStarted` event with `QueueCode=voice_lab_ivr`, then:
- hands control to FastAGI at `MVPCC_IVR_FASTAGI_HOSTPORT`
- routes completed IVR sessions through `mvpcc-transfer`
- falls back to the human baseline if FastAGI does not return a transfer target
Important Voice AI note:
- `AI_VOICE_RUNTIME_SERVICE_URL` is the HTTP control-plane endpoint for platform services.
- `MVPCC_AI_AUDIOSOCKET_SERVICE` is a separate raw AudioSocket address for Asterisk media.
- Do not point `MVPCC_AI_AUDIOSOCKET_SERVICE` at the runtime HTTP port.
Default lab SIP accounts from `pjsip.conf`:
- `1001` (caller)
- `2001` (operator A)
- `2002` (operator B)
Track 14 note:
- `2001` and `2002` are allowed to keep multiple contacts so browser registration can coexist with `MicroSIP/Zoiper` in QA.
+104
View File
@@ -0,0 +1,104 @@
#!/usr/bin/env bash
set -euo pipefail
template_dir="/config-template"
monitor_dir="/var/spool/asterisk/monitor/mvpcc"
primary_sounds_dir="/usr/share/asterisk/sounds"
legacy_sounds_dir="/var/lib/asterisk/sounds"
keys_dir="/etc/asterisk/keys"
password_1001="${ASTERISK_ENDPOINT_1001_PASSWORD:-${ASTERISK_AMI_SECRET:-ChangeMe-1001}}"
password_2001="${ASTERISK_ENDPOINT_2001_PASSWORD:-${ASTERISK_AMI_SECRET:-ChangeMe-2001}}"
password_2002="${ASTERISK_ENDPOINT_2002_PASSWORD:-${ASTERISK_AMI_SECRET:-ChangeMe-2002}}"
ami_secret="${ASTERISK_AMI_SECRET:-ChangeMe-AmiSecret}"
external_address="${ASTERISK_EXTERNAL_ADDRESS:-92.38.48.166}"
mkdir -p /etc/asterisk "$monitor_dir" "$primary_sounds_dir" "$legacy_sounds_dir" "$keys_dir"
for file in extensions.conf pjsip.conf manager.conf http.conf rtp.conf; do
cp "${template_dir}/${file}" "/etc/asterisk/${file}"
done
export ASTERISK_PASSWORD_1001="$password_1001"
export ASTERISK_PASSWORD_2001="$password_2001"
export ASTERISK_PASSWORD_2002="$password_2002"
export ASTERISK_MANAGER_SECRET="$ami_secret"
export ASTERISK_EXTERNAL_ADDRESS_VALUE="$external_address"
python3 - <<'PY'
from pathlib import Path
import os
replacements = {
"ChangeMe-1001": os.environ["ASTERISK_PASSWORD_1001"],
"ChangeMe-2001": os.environ["ASTERISK_PASSWORD_2001"],
"ChangeMe-2002": os.environ["ASTERISK_PASSWORD_2002"],
"ChangeMe-AmiSecret": os.environ["ASTERISK_MANAGER_SECRET"],
"92.38.48.166": os.environ["ASTERISK_EXTERNAL_ADDRESS_VALUE"],
}
for path in (
Path("/etc/asterisk/pjsip.conf"),
Path("/etc/asterisk/manager.conf"),
):
text = path.read_text(encoding="utf-8")
for old, new in replacements.items():
text = text.replace(old, new)
path.write_text(text, encoding="utf-8")
modules_path = Path("/etc/asterisk/modules.conf")
if modules_path.exists():
text = modules_path.read_text(encoding="utf-8")
if "noload => chan_sip.so" not in text:
stanza = (
"; MVPCC uses PJSIP/WebRTC for browser softphones.\n"
"noload => chan_sip.so\n\n"
)
if "[global]" in text:
text = text.replace("[global]", f"{stanza}[global]", 1)
elif "[modules]" in text:
text = text.rstrip() + "\n\n" + stanza
else:
text = "[modules]\nautoload=yes\n\n" + stanza + text
modules_path.write_text(text, encoding="utf-8")
PY
if [ ! -f "${keys_dir}/asterisk.pem" ] || [ ! -f "${keys_dir}/asterisk.key" ]; then
openssl req \
-x509 \
-nodes \
-newkey rsa:2048 \
-keyout "${keys_dir}/asterisk.key" \
-out "${keys_dir}/asterisk.pem" \
-days 3650 \
-subj "/CN=${external_address}"
fi
if [ -f /assets/mvpcc_ivr_prompt.wav ]; then
for sounds_dir in "$primary_sounds_dir" "$legacy_sounds_dir"; do
cp /assets/mvpcc_ivr_prompt.wav "${sounds_dir}/mvpcc_ivr_prompt.wav"
mkdir -p "${sounds_dir}/en" "${sounds_dir}/ru" "${sounds_dir}/ru_RU"
cp /assets/mvpcc_ivr_prompt.wav "${sounds_dir}/en/mvpcc_ivr_prompt.wav"
cp /assets/mvpcc_ivr_prompt.wav "${sounds_dir}/ru/mvpcc_ivr_prompt.wav"
cp /assets/mvpcc_ivr_prompt.wav "${sounds_dir}/ru_RU/mvpcc_ivr_prompt.wav"
done
fi
if [ -d /assets/ivr ]; then
for sounds_dir in "$primary_sounds_dir" "$legacy_sounds_dir"; do
mkdir -p "${sounds_dir}/ivr"
cp -a /assets/ivr/. "${sounds_dir}/ivr/"
done
fi
chown -R asterisk:asterisk /etc/asterisk "$keys_dir" /var/spool/asterisk/monitor
for sounds_dir in "$primary_sounds_dir" "$legacy_sounds_dir"; do
if [ -w "$sounds_dir" ]; then
chown -R asterisk:asterisk "$sounds_dir"
fi
done
chmod 0755 /var/spool/asterisk /var/spool/asterisk/monitor "$monitor_dir"
exec /usr/sbin/asterisk -f -U asterisk -G asterisk -vvv
+119
View File
@@ -0,0 +1,119 @@
[globals]
MVPCC_QUEUE_CODE=voice_lab
MVPCC_AI_QUEUE_CODE=voice_lab_ai
MVPCC_IVR_QUEUE_CODE=voice_lab_ivr
MVPCC_IVR_FALLBACK_QUEUE_CODE=voice_lab
MVPCC_MONITOR_DIR=/var/spool/asterisk/monitor/mvpcc
MVPCC_HUMAN_DIAL_TARGETS=PJSIP/2001&PJSIP/2002
MVPCC_AI_AUDIOSOCKET_SERVICE=127.0.0.1:9019
MVPCC_IVR_FASTAGI_HOSTPORT=127.0.0.1:4573
[from-softphones]
exten => 7000,1,NoOp(MVPCC human baseline entrypoint)
same => n,Answer()
same => n,Set(MVPCC_QUEUE=${MVPCC_QUEUE_CODE})
same => n,Gosub(mvpcc-call-init,s,1(${EXTEN}))
same => n,Dial(${MVPCC_HUMAN_DIAL_TARGETS},40,U(mvpcc-connected^${MVPCC_CALL_ID}^${MVPCC_LINKED_ID}))
same => n,Hangup()
exten => 7100,1,NoOp(MVPCC AI-first voice lab entrypoint)
same => n,Set(MVPCC_QUEUE=${IF($["${MVPCC_AI_QUEUE_OVERRIDE}" != ""]?${MVPCC_AI_QUEUE_OVERRIDE}:${MVPCC_AI_QUEUE_CODE})})
same => n,GotoIf($["${MVPCC_AI_REUSE_CALL}" = "1"]?ai-reuse,1)
same => n,Answer()
same => n,Gosub(mvpcc-call-init,s,1(${EXTEN}))
same => n,Goto(ai-audiosocket,1)
exten => ai-reuse,1,NoOp(MVPCC AI handoff reusing active IVR call)
same => n,Goto(ai-audiosocket,1)
exten => ai-audiosocket,1,NoOp(MVPCC AI audio bridge start)
same => n,GotoIf($["${MVPCC_AI_AUDIOSOCKET_SERVICE}" = ""]?ai-human-fallback,1)
same => n,Set(MVPCC_MEDIA_UUID=${SHELL(uuidgen | tr -d '\r\n')})
same => n,UserEvent(MVPCCAIAudioBridgeRequested,CallID:${MVPCC_CALL_ID},LinkedID:${MVPCC_LINKED_ID},QueueCode:${MVPCC_QUEUE},Extension:${MVPCC_ENTRY_EXTENSION},Context:${CONTEXT},MediaUUID:${MVPCC_MEDIA_UUID},UuidLen:${LEN(${MVPCC_MEDIA_UUID})},AudioSocketService:${MVPCC_AI_AUDIOSOCKET_SERVICE},Channel:${CHANNEL})
same => n,UserEvent(MVPCCAIAudioSocketAttempt,CallID:${MVPCC_CALL_ID},LinkedID:${MVPCC_LINKED_ID},QueueCode:${MVPCC_QUEUE},Extension:${MVPCC_ENTRY_EXTENSION},Context:${CONTEXT},MediaUUID:${MVPCC_MEDIA_UUID},UuidLen:${LEN(${MVPCC_MEDIA_UUID})},AudioSocketService:${MVPCC_AI_AUDIOSOCKET_SERVICE},Channel:${CHANNEL})
same => n,TryExec(AudioSocket(${MVPCC_MEDIA_UUID},${MVPCC_AI_AUDIOSOCKET_SERVICE}))
same => n,UserEvent(MVPCCAIAudioSocketResult,CallID:${MVPCC_CALL_ID},LinkedID:${MVPCC_LINKED_ID},QueueCode:${MVPCC_QUEUE},Extension:${MVPCC_ENTRY_EXTENSION},Context:${CONTEXT},MediaUUID:${MVPCC_MEDIA_UUID},UuidLen:${LEN(${MVPCC_MEDIA_UUID})},AudioSocketService:${MVPCC_AI_AUDIOSOCKET_SERVICE},TryExecStatus:${TRYSTATUS},Channel:${CHANNEL})
same => n,GotoIf($["${TRYSTATUS}" = "SUCCESS"]?ai-bridge-ended,1)
same => n,UserEvent(MVPCCAIFallbackToHuman,CallID:${MVPCC_CALL_ID},LinkedID:${MVPCC_LINKED_ID},QueueCode:${MVPCC_QUEUE},Extension:${MVPCC_ENTRY_EXTENSION},Context:${CONTEXT},Reason:audiosocket_failed,TryExecStatus:${TRYSTATUS},Channel:${CHANNEL})
same => n,Dial(${MVPCC_HUMAN_DIAL_TARGETS},40,U(mvpcc-connected^${MVPCC_CALL_ID}^${MVPCC_LINKED_ID}))
same => n,Hangup()
exten => ai-bridge-ended,1,UserEvent(MVPCCAIAudioBridgeEnded,CallID:${MVPCC_CALL_ID},LinkedID:${MVPCC_LINKED_ID},QueueCode:${MVPCC_QUEUE},Extension:${MVPCC_ENTRY_EXTENSION},Context:${CONTEXT},MediaUUID:${MVPCC_MEDIA_UUID},AudioSocketService:${MVPCC_AI_AUDIOSOCKET_SERVICE},Channel:${CHANNEL})
same => n,Hangup()
exten => ai-human-fallback,1,UserEvent(MVPCCAIFallbackToHuman,CallID:${MVPCC_CALL_ID},LinkedID:${MVPCC_LINKED_ID},QueueCode:${MVPCC_QUEUE},Extension:${MVPCC_ENTRY_EXTENSION},Context:${CONTEXT},Reason:audiosocket_service_missing,Channel:${CHANNEL})
same => n,Dial(${MVPCC_HUMAN_DIAL_TARGETS},40,U(mvpcc-connected^${MVPCC_CALL_ID}^${MVPCC_LINKED_ID}))
same => n,Hangup()
exten => 7200,1,NoOp(MVPCC IVR staging entrypoint)
same => n,Answer()
same => n,Set(MVPCC_QUEUE=${MVPCC_IVR_QUEUE_CODE})
same => n,Set(MVPCC_IVR_FALLBACK_QUEUE_CODE=${GLOBAL(MVPCC_IVR_FALLBACK_QUEUE_CODE)})
same => n,Gosub(mvpcc-call-init,s,1(${EXTEN}))
same => n,Set(MVPCC_IVR_RESULT=started)
same => n,Set(MVPCC_IVR_TARGET_QUEUE_CODE=)
same => n,Set(MVPCC_IVR_TARGET_EXTENSION=)
same => n,Set(MVPCC_IVR_SESSION_ID=)
same => n,GotoIf($["${MVPCC_IVR_FASTAGI_HOSTPORT}" = ""]?ivr-human-fallback,1)
same => n,AGI(agi://${MVPCC_IVR_FASTAGI_HOSTPORT}/ivr,${MVPCC_CALL_ID},${MVPCC_LINKED_ID},${MVPCC_QUEUE},${MVPCC_IVR_FALLBACK_QUEUE_CODE},${EXTEN})
same => n,GotoIf($["${MVPCC_IVR_TARGET_EXTENSION}" = ""]?ivr-human-fallback,1)
same => n,Goto(mvpcc-transfer,${MVPCC_IVR_TARGET_EXTENSION},1)
exten => ivr-human-fallback,1,UserEvent(MVPCCIVRFallbackToHuman,CallID:${MVPCC_CALL_ID},LinkedID:${MVPCC_LINKED_ID},QueueCode:${MVPCC_QUEUE},FallbackQueueCode:${MVPCC_IVR_FALLBACK_QUEUE_CODE},Extension:${MVPCC_ENTRY_EXTENSION},Result:${MVPCC_IVR_RESULT},TargetQueueCode:${MVPCC_IVR_TARGET_QUEUE_CODE},TargetExtension:${MVPCC_IVR_TARGET_EXTENSION},Channel:${CHANNEL})
same => n,Dial(${MVPCC_HUMAN_DIAL_TARGETS},40,U(mvpcc-connected^${MVPCC_CALL_ID}^${MVPCC_LINKED_ID}))
same => n,Hangup()
exten => h,1,StopMixMonitor()
same => n,UserEvent(MVPCCCallEnded,CallID:${MVPCC_CALL_ID},LinkedID:${MVPCC_LINKED_ID},HangupCause:${HANGUPCAUSE},DurationSeconds:${CDR(duration)})
same => n,UserEvent(MVPCCRecordingReady,CallID:${MVPCC_CALL_ID},LinkedID:${MVPCC_LINKED_ID},RemotePath:${MONITOR_FILENAME},FileName:${MVPCC_RECORD_FILE},MimeType:audio/wav,DurationSeconds:${CDR(duration)})
[mvpcc-call-init]
exten => s,1,Set(MVPCC_ENTRY_EXTENSION=${ARG1})
same => n,Set(MVPCC_CALL_ID=${UNIQUEID})
same => n,Set(MVPCC_LINKED_ID=${CHANNEL(linkedid)})
same => n,Set(MVPCC_RECORD_FILE=${STRFTIME(${EPOCH},,%Y%m%d-%H%M%S)}-${UNIQUEID}.wav)
same => n,Set(MONITOR_FILENAME=${MVPCC_MONITOR_DIR}/${MVPCC_RECORD_FILE})
same => n,UserEvent(MVPCCCallStarted,CallID:${MVPCC_CALL_ID},LinkedID:${MVPCC_LINKED_ID},CallerNumber:${CALLERID(num)},CallerName:${CALLERID(name)},QueueCode:${MVPCC_QUEUE},Extension:${MVPCC_ENTRY_EXTENSION},Context:${CONTEXT},Direction:inbound,Channel:${CHANNEL})
same => n,MixMonitor(${MONITOR_FILENAME},a)
same => n,Return()
[mvpcc-connected]
exten => s,1,Set(MVPCC_OPERATOR=${IF($["${ARG3}" != ""]?${ARG3}:${CHANNEL(peername)})})
same => n,Set(MVPCC_OPERATOR=${IF($["${MVPCC_OPERATOR}" = ""]?${EXTEN}:${MVPCC_OPERATOR})})
same => n,UserEvent(MVPCCOperatorConnected,CallID:${ARG1},LinkedID:${ARG2},OperatorExtension:${MVPCC_OPERATOR},Context:${CONTEXT},Channel:${CHANNEL},ClaimedByUser:${MVPCC_OPERATOR})
same => n,Return()
[mvpcc-claim]
exten => _X.,1,NoOp(Claimed call routed to operator ${EXTEN})
same => n,Set(MVPCC_CALL_ID=${CHANNEL(linkedid)})
same => n,Set(MVPCC_LINKED_ID=${CHANNEL(linkedid)})
same => n,Dial(PJSIP/${EXTEN},40,U(mvpcc-connected^${MVPCC_CALL_ID}^${MVPCC_LINKED_ID}^${EXTEN}))
same => n,Hangup()
[mvpcc-transfer]
exten => 7000,1,NoOp(Queue transfer target ${EXTEN})
same => n,Dial(${MVPCC_HUMAN_DIAL_TARGETS},40)
same => n,Hangup()
exten => 7100,1,NoOp(Queue transfer target ${EXTEN})
same => n,Goto(from-softphones,7100,1)
exten => 7200,1,NoOp(Queue transfer target ${EXTEN})
same => n,Goto(from-softphones,7200,1)
exten => _X.,1,NoOp(Blind transfer target ${EXTEN})
same => n,Dial(PJSIP/${EXTEN},40,U(mvpcc-connected^${MVPCC_CALL_ID}^${MVPCC_LINKED_ID}^${EXTEN}))
same => n,GotoIf($["${DIALSTATUS}" = "ANSWER"]?transfer-finished,1)
same => n,GotoIf($["${MVPCC_IVR_FALLBACK_QUEUE_CODE}" = ""]?transfer-finished,1)
same => n,Set(MVPCC_IVR_RESULT=fallback_transfer_unavailable)
same => n,Goto(from-softphones,ivr-human-fallback,1)
exten => transfer-finished,1,Hangup()
exten => h,1,StopMixMonitor()
same => n,UserEvent(MVPCCCallEnded,CallID:${MVPCC_CALL_ID},LinkedID:${MVPCC_LINKED_ID},HangupCause:${HANGUPCAUSE},DurationSeconds:${CDR(duration)})
same => n,UserEvent(MVPCCRecordingReady,CallID:${MVPCC_CALL_ID},LinkedID:${MVPCC_LINKED_ID},RemotePath:${MONITOR_FILENAME},FileName:${MVPCC_RECORD_FILE},MimeType:audio/wav,DurationSeconds:${CDR(duration)})
[mvpcc-operators]
exten => _X.,1,Dial(PJSIP/${EXTEN},30)
same => n,Hangup()
+12
View File
@@ -0,0 +1,12 @@
[general]
enabled=yes
enablestatic=yes
bindaddr=0.0.0.0
bindport=8088
; QA-only TLS placeholders for direct browser SIP over WebSocket.
; Replace with your actual trusted certificate and key paths before enabling WSS.
tlsenable=yes
tlsbindaddr=0.0.0.0:8089
tlscertfile=/etc/asterisk/keys/asterisk.pem
tlsprivatekey=/etc/asterisk/keys/asterisk.key
+11
View File
@@ -0,0 +1,11 @@
[general]
enabled = yes
port = 5038
bindaddr = 0.0.0.0
[mvpcc]
secret = ChangeMe-AmiSecret
deny = 0.0.0.0/0.0.0.0
permit = 0.0.0.0/0.0.0.0
read = system,call,log,verbose,command,agent,user,dtmf,reporting,cdr,dialplan
write = command,call,originate,agent,user
+84
View File
@@ -0,0 +1,84 @@
[transport-udp]
type=transport
protocol=udp
bind=0.0.0.0
external_signaling_address=92.38.48.166
external_media_address=92.38.48.166
[transport-wss]
type=transport
protocol=wss
bind=0.0.0.0
external_signaling_address=92.38.48.166
external_media_address=92.38.48.166
[softphone-endpoint](!)
type=endpoint
disallow=all
allow=opus,ulaw,alaw
dtmf_mode=auto_info
direct_media=no
rtp_symmetric=yes
force_rport=yes
rewrite_contact=yes
webrtc=yes
ice_support=yes
rtcp_mux=yes
use_avpf=yes
media_encryption=dtls
dtls_auto_generate_cert=yes
dtls_setup=actpass
media_use_received_transport=yes
media_address=92.38.48.166
[caller-endpoint](!,softphone-endpoint)
context=from-softphones
[operator-endpoint](!,softphone-endpoint)
context=mvpcc-operators
[1001](caller-endpoint)
auth=1001-auth
aors=1001
[1001-auth]
type=auth
auth_type=userpass
username=1001
password=ChangeMe-1001
[1001]
type=aor
max_contacts=2
[2001](operator-endpoint)
auth=2001-auth
aors=2001
[2001-auth]
type=auth
auth_type=userpass
username=2001
password=ChangeMe-2001
[2001]
type=aor
max_contacts=1
remove_existing=yes
support_path=yes
[2002](operator-endpoint)
auth=2002-auth
aors=2002
[2002-auth]
type=auth
auth_type=userpass
username=2002
password=ChangeMe-2002
[2002]
type=aor
max_contacts=1
remove_existing=yes
support_path=yes
+5
View File
@@ -0,0 +1,5 @@
[general]
rtpstart=10000
rtpend=20000
icesupport=true
stunaddr=stun.l.google.com:19302