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
+2
View File
@@ -0,0 +1,2 @@
APP_IMAGE=registry.example.com/group/project/app:latest
ASTERISK_IMAGE=registry.example.com/group/project/asterisk:latest
+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
@@ -0,0 +1,24 @@
services:
asterisk:
image: ${ASTERISK_IMAGE:?Set ASTERISK_IMAGE in deployment/.env.images or shell env}
container_name: call-center-asterisk
restart: unless-stopped
env_file:
- ../.env.production
environment:
ASTERISK_EXTERNAL_ADDRESS: 92.38.48.166
network_mode: host
volumes:
- ./asterisk:/config-template:ro
- ../.asterisk_assets:/assets
- ../.data_local/generated_ivr_yandex/ivr:/usr/share/asterisk/sounds/ivr
- ../.data_local/generated_ivr_yandex/ivr:/var/lib/asterisk/sounds/ivr
- /var/spool/asterisk/monitor:/var/spool/asterisk/monitor
healthcheck:
test:
- CMD-SHELL
- asterisk -rx "core show version" >/dev/null 2>&1
interval: 15s
timeout: 5s
retries: 10
start_period: 20s
@@ -0,0 +1,27 @@
services:
asterisk:
build:
context: ..
dockerfile: deployment/asterisk/Dockerfile
image: call-center-asterisk:local
container_name: call-center-asterisk
restart: unless-stopped
env_file:
- ../.env.production
environment:
ASTERISK_EXTERNAL_ADDRESS: 92.38.48.166
network_mode: host
volumes:
- ./asterisk:/config-template:ro
- ../.codex_tmp:/assets
- ../.data_local/generated_ivr_yandex/ivr:/usr/share/asterisk/sounds/ivr
- ../.data_local/generated_ivr_yandex/ivr:/var/lib/asterisk/sounds/ivr
- /var/spool/asterisk/monitor:/var/spool/asterisk/monitor
healthcheck:
test:
- CMD-SHELL
- asterisk -rx "core show version" >/dev/null 2>&1
interval: 15s
timeout: 5s
retries: 10
start_period: 20s
@@ -0,0 +1,207 @@
name: call-center-pg
x-app-env: &app_env
CC_DATA_DIR: /app/.data_pg_parallel
SCHEMA_MANAGEMENT_MODE: migrations
AUTH_SERVICE_URL: http://auth-service:8000
AUDIT_SERVICE_URL: http://audit-service:8000
CUSTOMER_SERVICE_URL: http://customer-service:8000
INTERACTION_SERVICE_URL: http://interaction-service:8000
ROUTING_SERVICE_URL: http://routing-service:8000
VOICE_ADAPTER_SERVICE_URL: http://voice-adapter-service:8000
RECORDING_SERVICE_URL: http://recording-service:8000
IVR_SERVICE_URL: http://ivr-service:8000
EVENT_BUS_SERVICE_URL: http://event-bus-service:8000
ASTERISK_BRIDGE_SERVICE_URL: http://asterisk-bridge-service:8000
TELEGRAM_ADAPTER_SERVICE_URL: http://telegram-adapter-service:8000
WHATSAPP_ADAPTER_SERVICE_URL: http://whatsapp-adapter-service:8000
AI_ORCHESTRATOR_SERVICE_URL: http://ai-orchestrator-service:8000
AI_VOICE_RUNTIME_SERVICE_URL: http://ai-voice-runtime-service:8000
WEBCHAT_ADAPTER_SERVICE_URL: http://webchat-adapter-service:8000
EMAIL_ADAPTER_SERVICE_URL: http://email-adapter-service:8000
KB_SERVICE_URL: http://kb-service:8000
REPORTING_SERVICE_URL: http://reporting-service:8000
SUPERVISOR_SERVICE_URL: http://supervisor-service:8000
x-service-defaults: &service_defaults
build:
context: ..
image: call-center-app:pg-parallel
restart: unless-stopped
env_file:
- ../.env.production
- ../.env.postgres.parallel.server
environment:
<<: *app_env
volumes:
- ../.data_pg_parallel:/app/.data_pg_parallel
networks:
- default
- postgres
extra_hosts:
- host.docker.internal:host-gateway
healthcheck:
test:
- CMD
- python
- -c
- |
from urllib.request import urlopen
import sys
sys.exit(0 if urlopen("http://127.0.0.1:8000/health", timeout=2).getcode() == 200 else 1)
interval: 15s
timeout: 5s
retries: 10
start_period: 10s
services:
auth-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.auth_service.app:app
audit-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.audit_service.app:app
customer-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.customer_service.app:app
interaction-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.interaction_service.app:app
routing-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.routing_service.app:app
voice-adapter-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.voice_adapter_service.app:app
recording-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.recording_service.app:app
CC_RECORDINGS_DIR: /app/.data_pg_parallel/recordings
RECORDING_MAX_BYTES: 26214400
ivr-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.ivr_service.app:app
telegram-adapter-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.telegram_adapter_service.app:app
whatsapp-adapter-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.whatsapp_adapter_service.app:app
ai-orchestrator-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.ai_orchestrator_service.app:app
ai-voice-runtime-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.ai_voice_runtime_service.app:app
webchat-adapter-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.webchat_adapter_service.app:app
email-adapter-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.email_adapter_service.app:app
kb-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.kb_service.app:app
reporting-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.reporting_service.app:app
supervisor-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.supervisor_service.app:app
event-bus-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.event_bus_service.app:app
asterisk-bridge-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.asterisk_bridge_service.app:app
ASTERISK_IVR_FASTAGI_HOST: 0.0.0.0
ASTERISK_IVR_FASTAGI_PORT: 4573
api-gateway:
<<: *service_defaults
depends_on:
- auth-service
- audit-service
- customer-service
- interaction-service
- routing-service
- voice-adapter-service
- recording-service
- ivr-service
- telegram-adapter-service
- whatsapp-adapter-service
- ai-orchestrator-service
- ai-voice-runtime-service
- webchat-adapter-service
- email-adapter-service
- kb-service
- reporting-service
- supervisor-service
- event-bus-service
- asterisk-bridge-service
environment:
<<: *app_env
APP_MODULE: gateway.app:app
ports:
- "18080:8000"
networks:
postgres:
external: true
name: call-center-postgres_default
@@ -0,0 +1,21 @@
name: call-center-postgres
services:
postgres:
container_name: call-center-postgres
image: postgres:16-alpine
restart: unless-stopped
env_file:
- ../.env.postgres.server
ports:
- "127.0.0.1:5434:5432"
volumes:
- ../.data_pg:/var/lib/postgresql/data
healthcheck:
test:
- CMD-SHELL
- pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB"
interval: 10s
timeout: 5s
retries: 12
start_period: 15s
@@ -0,0 +1,201 @@
name: call-center
x-app-env: &app_env
CC_DATA_DIR: /app/.data_local
DATABASE_URL: sqlite:////app/.data_local/mvp_cc.db
AUTH_SERVICE_URL: http://auth-service:8000
AUDIT_SERVICE_URL: http://audit-service:8000
CUSTOMER_SERVICE_URL: http://customer-service:8000
INTERACTION_SERVICE_URL: http://interaction-service:8000
ROUTING_SERVICE_URL: http://routing-service:8000
VOICE_ADAPTER_SERVICE_URL: http://voice-adapter-service:8000
RECORDING_SERVICE_URL: http://recording-service:8000
IVR_SERVICE_URL: http://ivr-service:8000
EVENT_BUS_SERVICE_URL: http://event-bus-service:8000
ASTERISK_BRIDGE_SERVICE_URL: http://asterisk-bridge-service:8000
TELEGRAM_ADAPTER_SERVICE_URL: http://telegram-adapter-service:8000
WHATSAPP_ADAPTER_SERVICE_URL: http://whatsapp-adapter-service:8000
AI_ORCHESTRATOR_SERVICE_URL: http://ai-orchestrator-service:8000
AI_VOICE_RUNTIME_SERVICE_URL: http://ai-voice-runtime-service:8000
WEBCHAT_ADAPTER_SERVICE_URL: http://webchat-adapter-service:8000
EMAIL_ADAPTER_SERVICE_URL: http://email-adapter-service:8000
KB_SERVICE_URL: http://kb-service:8000
REPORTING_SERVICE_URL: http://reporting-service:8000
SUPERVISOR_SERVICE_URL: http://supervisor-service:8000
x-service-defaults: &service_defaults
image: ${APP_IMAGE:?Set APP_IMAGE in deployment/.env.images or shell env}
restart: unless-stopped
env_file:
- ../.env.production
environment:
<<: *app_env
volumes:
- ../.data_local:/app/.data_local
extra_hosts:
- host.docker.internal:host-gateway
healthcheck:
test:
- CMD
- python
- -c
- |
from urllib.request import urlopen
import sys
sys.exit(0 if urlopen("http://127.0.0.1:8000/health", timeout=2).getcode() == 200 else 1)
interval: 15s
timeout: 5s
retries: 10
start_period: 10s
services:
auth-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.auth_service.app:app
audit-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.audit_service.app:app
customer-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.customer_service.app:app
interaction-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.interaction_service.app:app
routing-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.routing_service.app:app
voice-adapter-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.voice_adapter_service.app:app
recording-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.recording_service.app:app
CC_RECORDINGS_DIR: /app/.data_local/recordings
RECORDING_MAX_BYTES: 26214400
ivr-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.ivr_service.app:app
telegram-adapter-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.telegram_adapter_service.app:app
whatsapp-adapter-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.whatsapp_adapter_service.app:app
ai-orchestrator-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.ai_orchestrator_service.app:app
ai-voice-runtime-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.ai_voice_runtime_service.app:app
ports:
- "9019:9019"
webchat-adapter-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.webchat_adapter_service.app:app
email-adapter-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.email_adapter_service.app:app
kb-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.kb_service.app:app
reporting-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.reporting_service.app:app
supervisor-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.supervisor_service.app:app
event-bus-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.event_bus_service.app:app
asterisk-bridge-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.asterisk_bridge_service.app:app
ASTERISK_IVR_FASTAGI_HOST: 0.0.0.0
ASTERISK_IVR_FASTAGI_PORT: 4573
ports:
- "4573:4573"
api-gateway:
<<: *service_defaults
container_name: call-center-api-gateway
depends_on:
- auth-service
- audit-service
- customer-service
- interaction-service
- routing-service
- voice-adapter-service
- recording-service
- ivr-service
- telegram-adapter-service
- whatsapp-adapter-service
- ai-orchestrator-service
- ai-voice-runtime-service
- webchat-adapter-service
- email-adapter-service
- kb-service
- reporting-service
- supervisor-service
- event-bus-service
- asterisk-bridge-service
environment:
<<: *app_env
APP_MODULE: gateway.app:app
ports:
- "8080:8000"
+203
View File
@@ -0,0 +1,203 @@
name: call-center
x-app-env: &app_env
CC_DATA_DIR: /app/.data_local
DATABASE_URL: sqlite:////app/.data_local/mvp_cc.db
AUTH_SERVICE_URL: http://auth-service:8000
AUDIT_SERVICE_URL: http://audit-service:8000
CUSTOMER_SERVICE_URL: http://customer-service:8000
INTERACTION_SERVICE_URL: http://interaction-service:8000
ROUTING_SERVICE_URL: http://routing-service:8000
VOICE_ADAPTER_SERVICE_URL: http://voice-adapter-service:8000
RECORDING_SERVICE_URL: http://recording-service:8000
IVR_SERVICE_URL: http://ivr-service:8000
EVENT_BUS_SERVICE_URL: http://event-bus-service:8000
ASTERISK_BRIDGE_SERVICE_URL: http://asterisk-bridge-service:8000
TELEGRAM_ADAPTER_SERVICE_URL: http://telegram-adapter-service:8000
WHATSAPP_ADAPTER_SERVICE_URL: http://whatsapp-adapter-service:8000
AI_ORCHESTRATOR_SERVICE_URL: http://ai-orchestrator-service:8000
AI_VOICE_RUNTIME_SERVICE_URL: http://ai-voice-runtime-service:8000
WEBCHAT_ADAPTER_SERVICE_URL: http://webchat-adapter-service:8000
EMAIL_ADAPTER_SERVICE_URL: http://email-adapter-service:8000
KB_SERVICE_URL: http://kb-service:8000
REPORTING_SERVICE_URL: http://reporting-service:8000
SUPERVISOR_SERVICE_URL: http://supervisor-service:8000
x-service-defaults: &service_defaults
build:
context: ..
image: call-center-app:local
restart: unless-stopped
env_file:
- ../.env.production
environment:
<<: *app_env
volumes:
- ../.data_local:/app/.data_local
extra_hosts:
- host.docker.internal:host-gateway
healthcheck:
test:
- CMD
- python
- -c
- |
from urllib.request import urlopen
import sys
sys.exit(0 if urlopen("http://127.0.0.1:8000/health", timeout=2).getcode() == 200 else 1)
interval: 15s
timeout: 5s
retries: 10
start_period: 10s
services:
auth-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.auth_service.app:app
audit-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.audit_service.app:app
customer-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.customer_service.app:app
interaction-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.interaction_service.app:app
routing-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.routing_service.app:app
voice-adapter-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.voice_adapter_service.app:app
recording-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.recording_service.app:app
CC_RECORDINGS_DIR: /app/.data_local/recordings
RECORDING_MAX_BYTES: 26214400
ivr-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.ivr_service.app:app
telegram-adapter-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.telegram_adapter_service.app:app
whatsapp-adapter-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.whatsapp_adapter_service.app:app
ai-orchestrator-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.ai_orchestrator_service.app:app
ai-voice-runtime-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.ai_voice_runtime_service.app:app
ports:
- "9019:9019"
webchat-adapter-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.webchat_adapter_service.app:app
email-adapter-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.email_adapter_service.app:app
kb-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.kb_service.app:app
reporting-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.reporting_service.app:app
supervisor-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.supervisor_service.app:app
event-bus-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.event_bus_service.app:app
asterisk-bridge-service:
<<: *service_defaults
environment:
<<: *app_env
APP_MODULE: services.asterisk_bridge_service.app:app
ASTERISK_IVR_FASTAGI_HOST: 0.0.0.0
ASTERISK_IVR_FASTAGI_PORT: 4573
ports:
- "4573:4573"
api-gateway:
<<: *service_defaults
container_name: call-center-api-gateway
depends_on:
- auth-service
- audit-service
- customer-service
- interaction-service
- routing-service
- voice-adapter-service
- recording-service
- ivr-service
- telegram-adapter-service
- whatsapp-adapter-service
- ai-orchestrator-service
- ai-voice-runtime-service
- webchat-adapter-service
- email-adapter-service
- kb-service
- reporting-service
- supervisor-service
- event-bus-service
- asterisk-bridge-service
environment:
<<: *app_env
APP_MODULE: gateway.app:app
ports:
- "8080:8000"
+325
View File
@@ -0,0 +1,325 @@
x-app-env: &app_env
CC_DATA_DIR: /app/.data
DATABASE_URL: postgresql://mvp:mvp@postgres:5432/mvpcc
SCHEMA_MANAGEMENT_MODE: migrations
EVENT_BUS_ENABLED: "1"
EVENT_BUS_URL: amqp://guest:guest@rabbitmq:5672/
EVENT_BUS_EXCHANGE: mvpcc.domain.events
EVENT_BUS_DISPATCH_BATCH_SIZE: "50"
EVENT_BUS_MAX_ATTEMPTS: "5"
EVENT_BUS_POLL_INTERVAL_SECONDS: "2"
EVENT_BUS_CONSUMER_ENABLED: "1"
EVENT_BUS_AUDIT_QUEUE: mvpcc.audit.events
EVENT_BUS_REPORTING_QUEUE: mvpcc.reporting.events
ASTERISK_BRIDGE_ENABLED: "0"
ASTERISK_AMI_EVENT_PREFIX: MVPCC
ASTERISK_QUEUE_MAP_JSON: "{}"
AI_VOICE_RUNTIME_SERVICE_URL: http://ai-voice-runtime-service:8000
AI_VOICE_ENABLED: "0"
AI_VOICE_QUEUE_CONFIG_JSON: "{}"
AI_VOICE_ASR_PROVIDER: openai
AI_VOICE_TTS_PROVIDER: openai
AI_VOICE_AUDIOSOCKET_ENABLED: "1"
AI_VOICE_AUDIOSOCKET_HOST: 0.0.0.0
AI_VOICE_AUDIOSOCKET_PORT: "9019"
AI_VOICE_ASR_MODEL: gpt-4o-mini-transcribe
AI_VOICE_TTS_MODEL: gpt-4o-mini-tts
AI_VOICE_TTS_VOICE: alloy
AI_VOICE_TTS_CACHE_ENABLED: "1"
AI_VOICE_TTS_CACHE_DIR: /app/.data/ai_voice_tts_cache
AI_VOICE_VAD_MIN_SPEECH_MS: "300"
AI_VOICE_VAD_TRAILING_SILENCE_MS: "700"
AI_VOICE_TURN_MAX_MS: "10000"
AI_VOICE_MEDIA_IDLE_TIMEOUT_SECONDS: "15"
AI_VOICE_MAX_CONTEXT_SEGMENTS: "8"
AI_VOICE_HANDOFF_TIMEOUT_SECONDS: "8"
AI_VOICE_RUNTIME_TRUSTED_SERVICE_SUBJECTS: svc:ai-voice-runtime
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: mvpcc
POSTGRES_USER: mvp
POSTGRES_PASSWORD: mvp
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
rabbitmq:
image: rabbitmq:3-management
ports:
- "5672:5672"
- "15672:15672"
auth-service:
build: ..
depends_on: [postgres, rabbitmq]
environment:
<<: *app_env
APP_MODULE: services.auth_service.app:app
volumes:
- ../.data:/app/.data
ports:
- "8001:8000"
audit-service:
build: ..
depends_on: [postgres, rabbitmq]
environment:
<<: *app_env
APP_MODULE: services.audit_service.app:app
volumes:
- ../.data:/app/.data
ports:
- "8002:8000"
customer-service:
build: ..
depends_on: [postgres, rabbitmq]
environment:
<<: *app_env
APP_MODULE: services.customer_service.app:app
volumes:
- ../.data:/app/.data
ports:
- "8003:8000"
interaction-service:
build: ..
depends_on: [postgres, rabbitmq]
environment:
<<: *app_env
APP_MODULE: services.interaction_service.app:app
volumes:
- ../.data:/app/.data
ports:
- "8004:8000"
routing-service:
build: ..
depends_on: [postgres, rabbitmq]
environment:
<<: *app_env
APP_MODULE: services.routing_service.app:app
volumes:
- ../.data:/app/.data
ports:
- "8005:8000"
voice-adapter-service:
build: ..
depends_on: [postgres, rabbitmq]
environment:
<<: *app_env
APP_MODULE: services.voice_adapter_service.app:app
volumes:
- ../.data:/app/.data
ports:
- "8006:8000"
recording-service:
build: ..
depends_on: [postgres, rabbitmq]
environment:
<<: *app_env
APP_MODULE: services.recording_service.app:app
CC_RECORDINGS_DIR: /app/.data/recordings
RECORDING_MAX_BYTES: 26214400
volumes:
- ../.data:/app/.data
ports:
- "8013:8000"
ivr-service:
build: ..
depends_on: [postgres, rabbitmq]
environment:
<<: *app_env
APP_MODULE: services.ivr_service.app:app
volumes:
- ../.data:/app/.data
ports:
- "8014:8000"
telegram-adapter-service:
build: ..
depends_on: [postgres, rabbitmq]
environment:
<<: *app_env
APP_MODULE: services.telegram_adapter_service.app:app
volumes:
- ../.data:/app/.data
ports:
- "8007:8000"
whatsapp-adapter-service:
build: ..
depends_on: [postgres, rabbitmq]
environment:
<<: *app_env
APP_MODULE: services.whatsapp_adapter_service.app:app
volumes:
- ../.data:/app/.data
ports:
- "8019:8000"
ai-orchestrator-service:
build: ..
depends_on: [postgres, rabbitmq]
environment:
<<: *app_env
APP_MODULE: services.ai_orchestrator_service.app:app
TELEGRAM_ADAPTER_SERVICE_URL: http://telegram-adapter-service:8000
WHATSAPP_ADAPTER_SERVICE_URL: http://whatsapp-adapter-service:8000
INTERACTION_SERVICE_URL: http://interaction-service:8000
volumes:
- ../.data:/app/.data
ports:
- "8017:8000"
ai-voice-runtime-service:
build: ..
depends_on: [postgres, rabbitmq]
environment:
<<: *app_env
APP_MODULE: services.ai_voice_runtime_service.app:app
AI_ORCHESTRATOR_SERVICE_URL: http://ai-orchestrator-service:8000
ASTERISK_BRIDGE_SERVICE_URL: http://asterisk-bridge-service:8000
volumes:
- ../.data:/app/.data
ports:
- "8018:8000"
- "9019:9019"
webchat-adapter-service:
build: ..
depends_on: [postgres, rabbitmq]
environment:
<<: *app_env
APP_MODULE: services.webchat_adapter_service.app:app
volumes:
- ../.data:/app/.data
ports:
- "8011:8000"
email-adapter-service:
build: ..
depends_on: [postgres, rabbitmq]
environment:
<<: *app_env
APP_MODULE: services.email_adapter_service.app:app
volumes:
- ../.data:/app/.data
ports:
- "8012:8000"
kb-service:
build: ..
depends_on: [postgres, rabbitmq]
environment:
<<: *app_env
APP_MODULE: services.kb_service.app:app
volumes:
- ../.data:/app/.data
ports:
- "8008:8000"
reporting-service:
build: ..
depends_on: [postgres, rabbitmq]
environment:
<<: *app_env
APP_MODULE: services.reporting_service.app:app
volumes:
- ../.data:/app/.data
ports:
- "8009:8000"
supervisor-service:
build: ..
depends_on: [postgres, rabbitmq]
environment:
<<: *app_env
APP_MODULE: services.supervisor_service.app:app
volumes:
- ../.data:/app/.data
ports:
- "8010:8000"
event-bus-service:
build: ..
depends_on: [postgres, rabbitmq]
environment:
<<: *app_env
APP_MODULE: services.event_bus_service.app:app
volumes:
- ../.data:/app/.data
ports:
- "8015:8000"
asterisk-bridge-service:
build: ..
depends_on: [postgres, rabbitmq]
environment:
<<: *app_env
APP_MODULE: services.asterisk_bridge_service.app:app
INTERACTION_SERVICE_URL: http://interaction-service:8000
VOICE_ADAPTER_SERVICE_URL: http://voice-adapter-service:8000
RECORDING_SERVICE_URL: http://recording-service:8000
IVR_SERVICE_URL: http://ivr-service:8000
volumes:
- ../.data:/app/.data
ports:
- "8016:8000"
- "4573:4573"
api-gateway:
build: ..
depends_on:
- auth-service
- audit-service
- customer-service
- interaction-service
- routing-service
- voice-adapter-service
- recording-service
- ivr-service
- telegram-adapter-service
- whatsapp-adapter-service
- ai-orchestrator-service
- ai-voice-runtime-service
- webchat-adapter-service
- email-adapter-service
- kb-service
- reporting-service
- supervisor-service
- event-bus-service
- asterisk-bridge-service
environment:
APP_MODULE: gateway.app:app
AUTH_SERVICE_URL: http://auth-service:8000
AUDIT_SERVICE_URL: http://audit-service:8000
CUSTOMER_SERVICE_URL: http://customer-service:8000
INTERACTION_SERVICE_URL: http://interaction-service:8000
ROUTING_SERVICE_URL: http://routing-service:8000
VOICE_ADAPTER_SERVICE_URL: http://voice-adapter-service:8000
RECORDING_SERVICE_URL: http://recording-service:8000
IVR_SERVICE_URL: http://ivr-service:8000
EVENT_BUS_SERVICE_URL: http://event-bus-service:8000
ASTERISK_BRIDGE_SERVICE_URL: http://asterisk-bridge-service:8000
TELEGRAM_ADAPTER_SERVICE_URL: http://telegram-adapter-service:8000
WHATSAPP_ADAPTER_SERVICE_URL: http://whatsapp-adapter-service:8000
AI_ORCHESTRATOR_SERVICE_URL: http://ai-orchestrator-service:8000
WEBCHAT_ADAPTER_SERVICE_URL: http://webchat-adapter-service:8000
EMAIL_ADAPTER_SERVICE_URL: http://email-adapter-service:8000
KB_SERVICE_URL: http://kb-service:8000
REPORTING_SERVICE_URL: http://reporting-service:8000
SUPERVISOR_SERVICE_URL: http://supervisor-service:8000
ports:
- "8080:8000"
volumes:
pgdata:
+6
View File
@@ -0,0 +1,6 @@
apiVersion: v2
name: mvp-cc-platform
description: Helm chart for MVP Contact Center microservices
type: application
version: 0.1.0
appVersion: "1.0.0"
@@ -0,0 +1,48 @@
{{- $databaseUrl := $.Values.global.databaseUrl | default "" }}
{{- $schemaManagementMode := $.Values.global.schemaManagementMode | default "legacy" }}
{{- $runMigrations := and $.Values.migrations.enabled (eq $schemaManagementMode "migrations") (or $.Values.profiles.scale500.enabled $databaseUrl) }}
{{- if $runMigrations }}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ .Release.Name }}-db-migrate
namespace: {{ $.Values.namespace }}
annotations:
"helm.sh/hook": pre-install,pre-upgrade
"helm.sh/hook-weight": "-10"
"helm.sh/hook-delete-policy": "{{ $.Values.migrations.hookDeletePolicy }}"
spec:
backoffLimit: {{ $.Values.migrations.backoffLimit }}
activeDeadlineSeconds: {{ $.Values.migrations.activeDeadlineSeconds }}
ttlSecondsAfterFinished: {{ $.Values.migrations.ttlSecondsAfterFinished }}
template:
metadata:
labels:
app: {{ .Release.Name }}-db-migrate
spec:
restartPolicy: Never
{{- if $.Values.global.runtimeClass }}
runtimeClassName: {{ $.Values.global.runtimeClass }}
{{- end }}
containers:
- name: migrate
image: "{{ $.Values.image.repository }}:{{ $.Values.image.tag }}"
imagePullPolicy: {{ $.Values.global.imagePullPolicy }}
command:
- python
- scripts/migrate_core_db.py
env:
- name: CC_DATA_DIR
value: /var/lib/mvp-cc/data
- name: DATABASE_URL
value: "{{ if $.Values.profiles.scale500.enabled }}{{ required "global.databaseUrl is required when profiles.scale500.enabled=true" $.Values.global.databaseUrl }}{{ else }}{{ $databaseUrl }}{{ end }}"
- name: SCHEMA_MANAGEMENT_MODE
value: "{{ $schemaManagementMode }}"
resources:
requests:
cpu: {{ $.Values.migrations.resources.requests.cpu }}
memory: {{ $.Values.migrations.resources.requests.memory }}
limits:
cpu: {{ $.Values.migrations.resources.limits.cpu }}
memory: {{ $.Values.migrations.resources.limits.memory }}
{{- end }}
+4
View File
@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: {{ .Values.namespace }}
+46
View File
@@ -0,0 +1,46 @@
{{- if .Values.rabbitmq.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: rabbitmq
namespace: {{ .Values.namespace }}
spec:
replicas: {{ .Values.rabbitmq.replicas }}
selector:
matchLabels:
app: rabbitmq
template:
metadata:
labels:
app: rabbitmq
spec:
containers:
- name: rabbitmq
image: {{ .Values.rabbitmq.image }}
env:
- name: RABBITMQ_DEFAULT_USER
value: "{{ .Values.rabbitmq.user }}"
- name: RABBITMQ_DEFAULT_PASS
value: "{{ .Values.rabbitmq.password }}"
ports:
- name: amqp
containerPort: {{ .Values.rabbitmq.servicePort }}
- name: management
containerPort: {{ .Values.rabbitmq.managementPort }}
---
apiVersion: v1
kind: Service
metadata:
name: rabbitmq
namespace: {{ .Values.namespace }}
spec:
selector:
app: rabbitmq
ports:
- name: amqp
port: {{ .Values.rabbitmq.servicePort }}
targetPort: {{ .Values.rabbitmq.servicePort }}
- name: management
port: {{ .Values.rabbitmq.managementPort }}
targetPort: {{ .Values.rabbitmq.managementPort }}
{{- end }}
+404
View File
@@ -0,0 +1,404 @@
{{- range $name, $svc := .Values.services }}
{{- $databaseUrl := $.Values.global.databaseUrl | default "" }}
{{- $schemaManagementMode := $.Values.global.schemaManagementMode | default "legacy" }}
{{- $isGateway := default false $svc.gateway }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $name }}
namespace: {{ $.Values.namespace }}
spec:
replicas: {{ $svc.replicas }}
selector:
matchLabels:
app: {{ $name }}
template:
metadata:
labels:
app: {{ $name }}
spec:
terminationGracePeriodSeconds: 30
{{- if $.Values.global.runtimeClass }}
runtimeClassName: {{ $.Values.global.runtimeClass }}
{{- end }}
{{- if gt (int $svc.replicas) 1 }}
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: {{ $name }}
{{- end }}
containers:
- name: app
image: "{{ $.Values.image.repository }}:{{ $.Values.image.tag }}"
imagePullPolicy: {{ $.Values.global.imagePullPolicy }}
env:
- name: APP_MODULE
value: "{{ $svc.module }}"
- name: CC_DATA_DIR
value: /var/lib/mvp-cc/data
- name: APP_TOKEN_SECRET
value: "{{ $.Values.auth.appTokenSecret }}"
- name: APP_TOKEN_TTL_SECONDS
value: "{{ $.Values.auth.appTokenTtlSeconds }}"
- name: ALLOW_LEGACY_HEADER_AUTH
value: "{{ $.Values.auth.allowLegacyHeaderAuth }}"
{{- if or $.Values.profiles.scale500.enabled $databaseUrl }}
- name: DATABASE_URL
value: "{{ if $.Values.profiles.scale500.enabled }}{{ required "global.databaseUrl is required when profiles.scale500.enabled=true" $.Values.global.databaseUrl }}{{ else }}{{ $databaseUrl }}{{ end }}"
{{- end }}
- name: SCHEMA_MANAGEMENT_MODE
value: "{{ $schemaManagementMode }}"
- name: DB_POOL_SIZE
value: "{{ $.Values.db.poolSize }}"
- name: DB_MAX_OVERFLOW
value: "{{ $.Values.db.maxOverflow }}"
- name: DB_POOL_TIMEOUT_SECONDS
value: "{{ $.Values.db.poolTimeoutSeconds }}"
- name: DB_POOL_RECYCLE_SECONDS
value: "{{ $.Values.db.poolRecycleSeconds }}"
- name: EVENT_BUS_ENABLED
value: "{{ $.Values.eventBus.enabled }}"
- name: EVENT_BUS_URL
value: "{{ $.Values.eventBus.url }}"
- name: EVENT_BUS_EXCHANGE
value: "{{ $.Values.eventBus.exchange }}"
- name: EVENT_BUS_DISPATCH_BATCH_SIZE
value: "{{ $.Values.eventBus.dispatchBatchSize }}"
- name: EVENT_BUS_MAX_ATTEMPTS
value: "{{ $.Values.eventBus.maxAttempts }}"
- name: EVENT_BUS_POLL_INTERVAL_SECONDS
value: "{{ $.Values.eventBus.pollIntervalSeconds }}"
- name: EVENT_BUS_CONSUMER_ENABLED
value: "{{ $.Values.eventBus.consumerEnabled }}"
- name: EVENT_BUS_AUDIT_QUEUE
value: "{{ $.Values.eventBus.auditQueue }}"
- name: EVENT_BUS_REPORTING_QUEUE
value: "{{ $.Values.eventBus.reportingQueue }}"
{{- if eq $name "asterisk-bridge-service" }}
- name: ASTERISK_BRIDGE_ENABLED
value: "{{ $.Values.asteriskBridge.enabled }}"
- name: ASTERISK_AMI_HOST
value: "{{ $.Values.asteriskBridge.amiHost }}"
- name: ASTERISK_AMI_PORT
value: "{{ $.Values.asteriskBridge.amiPort }}"
- name: ASTERISK_AMI_USERNAME
value: "{{ $.Values.asteriskBridge.amiUsername }}"
- name: ASTERISK_AMI_SECRET
value: "{{ $.Values.asteriskBridge.amiSecret }}"
- name: ASTERISK_AMI_EVENT_PREFIX
value: "{{ $.Values.asteriskBridge.amiEventPrefix }}"
- name: ASTERISK_QUEUE_MAP_JSON
value: '{{ $.Values.asteriskBridge.queueMapJson }}'
- name: ASTERISK_SFTP_HOST
value: "{{ $.Values.asteriskBridge.sftpHost }}"
- name: ASTERISK_SFTP_PORT
value: "{{ $.Values.asteriskBridge.sftpPort }}"
- name: ASTERISK_SFTP_USERNAME
value: "{{ $.Values.asteriskBridge.sftpUsername }}"
- name: ASTERISK_SFTP_PASSWORD
value: "{{ $.Values.asteriskBridge.sftpPassword }}"
- name: ASTERISK_SFTP_BASE_PATH
value: "{{ $.Values.asteriskBridge.sftpBasePath }}"
- name: ASTERISK_BRIDGE_POLL_INTERVAL_SECONDS
value: "{{ $.Values.asteriskBridge.pollIntervalSeconds }}"
- name: ASTERISK_BRIDGE_AUTH_MODE
value: "{{ $.Values.asteriskBridge.authMode }}"
- name: ASTERISK_BRIDGE_AUTH_FALLBACK_LEGACY
value: "{{ $.Values.asteriskBridge.authFallbackLegacy }}"
- name: ASTERISK_BRIDGE_AUTH_SUBJECT
value: "{{ $.Values.asteriskBridge.authSubject }}"
- name: ASTERISK_BRIDGE_AUTH_USER
value: "{{ $.Values.asteriskBridge.authUser }}"
- name: ASTERISK_BRIDGE_AUTH_ROLE
value: "{{ $.Values.asteriskBridge.authRole }}"
- name: ASTERISK_BRIDGE_AUTH_TOKEN_TTL_SECONDS
value: "{{ $.Values.asteriskBridge.authTokenTtlSeconds }}"
- name: ASTERISK_IVR_FASTAGI_ENABLED
value: "{{ $.Values.asteriskBridge.ivrFastagiEnabled }}"
- name: ASTERISK_IVR_FASTAGI_HOST
value: "{{ $.Values.asteriskBridge.ivrFastagiHost }}"
- name: ASTERISK_IVR_FASTAGI_PORT
value: "{{ $.Values.asteriskBridge.ivrFastagiPort }}"
- name: ASTERISK_IVR_DTMF_TIMEOUT_SECONDS
value: "{{ $.Values.asteriskBridge.ivrDtmfTimeoutSeconds }}"
- name: ASTERISK_IVR_MAX_NO_INPUT_RETRIES
value: "{{ $.Values.asteriskBridge.ivrMaxNoInputRetries }}"
- name: ASTERISK_IVR_MAX_INVALID_RETRIES
value: "{{ $.Values.asteriskBridge.ivrMaxInvalidRetries }}"
- name: ASTERISK_IVR_CALL_LINK_WAIT_SECONDS
value: "{{ $.Values.asteriskBridge.ivrCallLinkWaitSeconds }}"
- name: ASTERISK_CALLCONTROL_ENABLED
value: "{{ $.Values.asteriskBridge.callcontrolEnabled }}"
- name: ASTERISK_CALLCONTROL_ACTION_TIMEOUT_SECONDS
value: "{{ $.Values.asteriskBridge.callcontrolActionTimeoutSeconds }}"
- name: ASTERISK_OPERATOR_EXTENSION_MAP_JSON
value: '{{ $.Values.asteriskBridge.operatorExtensionMapJson }}'
- name: ASTERISK_TRANSFER_TARGET_MAP_JSON
value: '{{ $.Values.asteriskBridge.transferTargetMapJson }}'
- name: ASTERISK_CALLCONTROL_CLAIM_CONTEXT
value: "{{ $.Values.asteriskBridge.claimContext }}"
- name: ASTERISK_CALLCONTROL_TRANSFER_CONTEXT
value: "{{ $.Values.asteriskBridge.transferContext }}"
- name: AI_VOICE_RUNTIME_SERVICE_URL
value: http://ai-voice-runtime-service:8000
- name: AI_VOICE_ENABLED
value: "{{ $.Values.voiceAI.enabled }}"
- name: AI_VOICE_QUEUE_CONFIG_JSON
value: '{{ $.Values.voiceAI.queueConfigJson }}'
- name: AI_VOICE_RUNTIME_TRUSTED_SERVICE_SUBJECTS
value: "{{ $.Values.voiceAI.runtimeTrustedServiceSubjects }}"
- name: INTERACTION_SERVICE_URL
value: http://interaction-service:8000
- name: VOICE_ADAPTER_SERVICE_URL
value: http://voice-adapter-service:8000
- name: RECORDING_SERVICE_URL
value: http://recording-service:8000
- name: IVR_SERVICE_URL
value: http://ivr-service:8000
{{- end }}
{{- if eq $name "ivr-service" }}
- name: IVR_RUNTIME_TRUSTED_SERVICE_SUBJECTS
value: "{{ $.Values.ivrRuntime.trustedServiceSubjects }}"
{{- end }}
{{- if eq $name "ai-orchestrator-service" }}
- name: INTERACTION_SERVICE_URL
value: http://interaction-service:8000
- name: TELEGRAM_ADAPTER_SERVICE_URL
value: http://telegram-adapter-service:8000
- name: AI_VOICE_MAX_CONTEXT_SEGMENTS
value: "{{ $.Values.voiceAI.maxContextSegments }}"
{{- end }}
{{- if eq $name "ai-voice-runtime-service" }}
- name: AI_ORCHESTRATOR_SERVICE_URL
value: http://ai-orchestrator-service:8000
- name: ASTERISK_BRIDGE_SERVICE_URL
value: http://asterisk-bridge-service:8000
- name: AI_VOICE_ASR_PROVIDER
value: "{{ $.Values.voiceAI.asrProvider }}"
- name: AI_VOICE_TTS_PROVIDER
value: "{{ $.Values.voiceAI.ttsProvider }}"
- name: AI_VOICE_AUDIOSOCKET_ENABLED
value: "{{ $.Values.voiceAI.audioSocketEnabled }}"
- name: AI_VOICE_AUDIOSOCKET_HOST
value: "{{ $.Values.voiceAI.audioSocketHost }}"
- name: AI_VOICE_AUDIOSOCKET_PORT
value: "{{ $.Values.voiceAI.audioSocketPort }}"
- name: AI_VOICE_ASR_MODEL
value: "{{ $.Values.voiceAI.asrModel }}"
- name: AI_VOICE_TTS_MODEL
value: "{{ $.Values.voiceAI.ttsModel }}"
- name: AI_VOICE_TTS_VOICE
value: "{{ $.Values.voiceAI.ttsVoice }}"
- name: AI_VOICE_TTS_CACHE_ENABLED
value: "{{ $.Values.voiceAI.ttsCacheEnabled }}"
- name: AI_VOICE_TTS_CACHE_DIR
value: "{{ $.Values.voiceAI.ttsCacheDir }}"
- name: AI_VOICE_VAD_MIN_SPEECH_MS
value: "{{ $.Values.voiceAI.vadMinSpeechMs }}"
- name: AI_VOICE_VAD_TRAILING_SILENCE_MS
value: "{{ $.Values.voiceAI.vadTrailingSilenceMs }}"
- name: AI_VOICE_TURN_MAX_MS
value: "{{ $.Values.voiceAI.turnMaxMs }}"
- name: AI_VOICE_MEDIA_IDLE_TIMEOUT_SECONDS
value: "{{ $.Values.voiceAI.mediaIdleTimeoutSeconds }}"
- name: AI_VOICE_HANDOFF_TIMEOUT_SECONDS
value: "{{ $.Values.voiceAI.handoffTimeoutSeconds }}"
{{- end }}
{{- if eq $name "recording-service" }}
- name: CC_RECORDINGS_DIR
value: /var/lib/mvp-cc/data/recordings
- name: RECORDING_MAX_BYTES
value: "26214400"
- name: RECORDING_IMPORT_TRUSTED_SERVICE_SUBJECTS
value: "{{ $.Values.recording.importTrustedServiceSubjects }}"
- name: RECORDING_IMPORT_ALLOW_ADMIN
value: "{{ $.Values.recording.importAllowAdmin }}"
{{- end }}
{{- if eq $name "voice-adapter-service" }}
- name: VOICE_ADAPTER_TRUSTED_SERVICE_SUBJECTS
value: "{{ $.Values.voiceAdapter.trustedServiceSubjects }}"
{{- end }}
{{- if eq $name "auth-service" }}
- name: OIDC_ENABLED
value: "{{ $.Values.auth.oidc.enabled }}"
- name: OIDC_PROVIDER
value: "{{ $.Values.auth.oidc.provider }}"
- name: OIDC_ISSUER_URL
value: "{{ $.Values.auth.oidc.issuerUrl }}"
- name: OIDC_CLIENT_ID
value: "{{ $.Values.auth.oidc.clientId }}"
- name: OIDC_CLIENT_SECRET
value: "{{ $.Values.auth.oidc.clientSecret }}"
- name: OIDC_REDIRECT_URI
value: "{{ $.Values.auth.oidc.redirectUri }}"
- name: OIDC_SCOPES
value: "{{ $.Values.auth.oidc.scopes }}"
- name: OIDC_ROLE_CLAIM
value: "{{ $.Values.auth.oidc.roleClaim }}"
- name: OIDC_ROLE_MAP_JSON
value: '{{ $.Values.auth.oidc.roleMapJson }}'
{{- end }}
{{- if $isGateway }}
- name: AUTH_SERVICE_URL
value: http://auth-service:8000
- name: AUDIT_SERVICE_URL
value: http://audit-service:8000
- name: CUSTOMER_SERVICE_URL
value: http://customer-service:8000
- name: INTERACTION_SERVICE_URL
value: http://interaction-service:8000
- name: ROUTING_SERVICE_URL
value: http://routing-service:8000
- name: VOICE_ADAPTER_SERVICE_URL
value: http://voice-adapter-service:8000
- name: RECORDING_SERVICE_URL
value: http://recording-service:8000
- name: IVR_SERVICE_URL
value: http://ivr-service:8000
- name: EVENT_BUS_SERVICE_URL
value: http://event-bus-service:8000
- name: ASTERISK_BRIDGE_SERVICE_URL
value: http://asterisk-bridge-service:8000
- name: TELEGRAM_ADAPTER_SERVICE_URL
value: http://telegram-adapter-service:8000
- name: AI_ORCHESTRATOR_SERVICE_URL
value: http://ai-orchestrator-service:8000
- name: AI_VOICE_RUNTIME_SERVICE_URL
value: http://ai-voice-runtime-service:8000
- name: WEBCHAT_ADAPTER_SERVICE_URL
value: http://webchat-adapter-service:8000
- name: EMAIL_ADAPTER_SERVICE_URL
value: http://email-adapter-service:8000
- name: KB_SERVICE_URL
value: http://kb-service:8000
- name: REPORTING_SERVICE_URL
value: http://reporting-service:8000
- name: SUPERVISOR_SERVICE_URL
value: http://supervisor-service:8000
{{- end }}
ports:
- containerPort: 8000
{{- if eq $name "ai-voice-runtime-service" }}
- containerPort: 9019
{{- end }}
{{- if eq $name "asterisk-bridge-service" }}
- containerPort: 4573
{{- end }}
resources:
requests:
cpu: {{ $svc.resources.requests.cpu }}
memory: {{ $svc.resources.requests.memory }}
limits:
cpu: {{ $svc.resources.limits.cpu }}
memory: {{ $svc.resources.limits.memory }}
startupProbe:
httpGet:
path: /health
port: 8000
failureThreshold: 30
periodSeconds: 5
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 2
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 20
periodSeconds: 15
timeoutSeconds: 2
{{- if $svc.persistence.enabled }}
volumeMounts:
- name: app-data
mountPath: /var/lib/mvp-cc/data
{{- end }}
{{- if $svc.persistence.enabled }}
volumes:
- name: app-data
persistentVolumeClaim:
claimName: {{ $name }}-data
{{- end }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ $name }}
namespace: {{ $.Values.namespace }}
spec:
selector:
app: {{ $name }}
ports:
- name: http
port: 8000
targetPort: 8000
{{- if eq $name "ai-voice-runtime-service" }}
- name: audiosocket
port: 9019
targetPort: 9019
{{- end }}
{{- if eq $name "asterisk-bridge-service" }}
- name: fastagi
port: 4573
targetPort: 4573
{{- end }}
type: {{ if $isGateway }}LoadBalancer{{ else }}ClusterIP{{ end }}
---
{{- if $svc.persistence.enabled }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ $name }}-data
namespace: {{ $.Values.namespace }}
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ $svc.persistence.size }}
---
{{- end }}
{{- if and $svc.podDisruptionBudget.enabled (gt (int $svc.replicas) 1) }}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ $name }}-pdb
namespace: {{ $.Values.namespace }}
spec:
minAvailable: {{ $svc.podDisruptionBudget.minAvailable }}
selector:
matchLabels:
app: {{ $name }}
---
{{- end }}
{{- if $svc.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ $name }}-hpa
namespace: {{ $.Values.namespace }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ $name }}
minReplicas: {{ $svc.autoscaling.minReplicas }}
maxReplicas: {{ $svc.autoscaling.maxReplicas }}
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ $svc.autoscaling.targetCPUUtilizationPercentage }}
---
{{- end }}
{{- end }}
+12
View File
@@ -0,0 +1,12 @@
profiles:
scale500:
enabled: true
auth:
allowLegacyHeaderAuth: "0"
db:
poolSize: "15"
maxOverflow: "30"
poolTimeoutSeconds: "30"
poolRecycleSeconds: "1800"
+77
View File
@@ -0,0 +1,77 @@
auth:
allowLegacyHeaderAuth: "0"
eventBus:
enabled: "0"
url: amqp://guest:guest@rabbitmq:5672/
exchange: mvpcc.domain.events
dispatchBatchSize: "50"
maxAttempts: "5"
pollIntervalSeconds: "2"
consumerEnabled: "1"
auditQueue: mvpcc.audit.events
reportingQueue: mvpcc.reporting.events
rabbitmq:
enabled: false
image: rabbitmq:3-management
replicas: 1
user: guest
password: guest
servicePort: 5672
managementPort: 15672
asteriskBridge:
enabled: "1"
amiEventPrefix: MVPCC
queueMapJson: '{"voice_lab":"<queue_id>"}'
authMode: bearer
authFallbackLegacy: "0"
authSubject: svc:asterisk-bridge
authUser: asterisk-bridge
authRole: admin
authTokenTtlSeconds: "300"
callcontrolEnabled: "0"
callcontrolActionTimeoutSeconds: "10"
operatorExtensionMapJson: "{}"
transferTargetMapJson: "{}"
claimContext: mvpcc-claim
transferContext: mvpcc-transfer
pollIntervalSeconds: "1"
voiceAdapter:
trustedServiceSubjects: svc:asterisk-bridge,svc:ivr-service
recording:
importTrustedServiceSubjects: svc:asterisk-bridge
importAllowAdmin: "0"
services:
auth-service:
replicas: 1
voice-adapter-service:
replicas: 1
recording-service:
replicas: 1
asterisk-bridge-service:
module: services.asterisk_bridge_service.app:app
# Must stay singleton: background AMI consumption and retry/reconcile loops are not multi-replica safe.
replicas: 1
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 1
targetCPUUtilizationPercentage: 70
podDisruptionBudget:
enabled: false
minAvailable: 1
persistence:
enabled: false
size: 1Gi
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 750m
memory: 512Mi
+18
View File
@@ -0,0 +1,18 @@
auth:
allowLegacyHeaderAuth: "0"
asteriskBridge:
enabled: "1"
authMode: bearer
authFallbackLegacy: "0"
authSubject: svc:asterisk-bridge
authUser: asterisk-bridge
authRole: admin
authTokenTtlSeconds: "300"
voiceAdapter:
trustedServiceSubjects: svc:asterisk-bridge,svc:ivr-service
recording:
importTrustedServiceSubjects: svc:asterisk-bridge
importAllowAdmin: "0"
+538
View File
@@ -0,0 +1,538 @@
namespace: mvp-cc
image:
repository: mvp-cc-platform
tag: latest
global:
databaseUrl: postgresql://mvp:mvp@postgresql.mvp-cc.svc.cluster.local:5432/mvpcc
schemaManagementMode: migrations
runtimeClass: ""
imagePullPolicy: IfNotPresent
migrations:
enabled: true
backoffLimit: 1
activeDeadlineSeconds: 300
ttlSecondsAfterFinished: 600
hookDeletePolicy: before-hook-creation,hook-succeeded
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
profiles:
scale500:
enabled: false
db:
poolSize: "15"
maxOverflow: "30"
poolTimeoutSeconds: "30"
poolRecycleSeconds: "1800"
voiceAdapter:
trustedServiceSubjects: svc:asterisk-bridge,svc:ivr-service
ivrRuntime:
trustedServiceSubjects: svc:asterisk-bridge
recording:
importTrustedServiceSubjects: svc:asterisk-bridge
importAllowAdmin: "1"
voiceAI:
enabled: "0"
queueConfigJson: "{}"
asrProvider: openai
ttsProvider: openai
audioSocketEnabled: "1"
audioSocketHost: 0.0.0.0
audioSocketPort: "9019"
asrModel: gpt-4o-mini-transcribe
ttsModel: gpt-4o-mini-tts
ttsVoice: alloy
ttsCacheEnabled: "1"
ttsCacheDir: /var/lib/mvp-cc/data/ai_voice_tts_cache
vadMinSpeechMs: "300"
vadTrailingSilenceMs: "700"
turnMaxMs: "10000"
mediaIdleTimeoutSeconds: "15"
maxContextSegments: "8"
handoffTimeoutSeconds: "8"
runtimeTrustedServiceSubjects: svc:ai-voice-runtime
eventBus:
enabled: "0"
url: amqp://guest:guest@rabbitmq:5672/
exchange: mvpcc.domain.events
dispatchBatchSize: "50"
maxAttempts: "5"
pollIntervalSeconds: "2"
consumerEnabled: "1"
auditQueue: mvpcc.audit.events
reportingQueue: mvpcc.reporting.events
asteriskBridge:
enabled: "0"
amiHost: ""
amiPort: "5038"
amiUsername: ""
amiSecret: ""
amiEventPrefix: MVPCC
queueMapJson: "{}"
authMode: legacy_headers
authFallbackLegacy: "1"
authSubject: svc:asterisk-bridge
authUser: asterisk-bridge
authRole: admin
authTokenTtlSeconds: "300"
ivrFastagiEnabled: "0"
ivrFastagiHost: 0.0.0.0
ivrFastagiPort: "4573"
ivrDtmfTimeoutSeconds: "5"
ivrMaxNoInputRetries: "2"
ivrMaxInvalidRetries: "2"
ivrCallLinkWaitSeconds: "5"
callcontrolEnabled: "0"
callcontrolActionTimeoutSeconds: "10"
operatorExtensionMapJson: "{}"
transferTargetMapJson: "{}"
claimContext: mvpcc-claim
transferContext: mvpcc-transfer
sftpHost: ""
sftpPort: "22"
sftpUsername: ""
sftpPassword: ""
sftpBasePath: /var/spool/asterisk/monitor
pollIntervalSeconds: "1"
rabbitmq:
enabled: false
image: rabbitmq:3-management
replicas: 1
user: guest
password: guest
servicePort: 5672
managementPort: 15672
auth:
appTokenSecret: change-me-app-token-secret
appTokenTtlSeconds: "3600"
allowLegacyHeaderAuth: "1"
oidc:
enabled: "0"
provider: keycloak
issuerUrl: https://keycloak.example.local/realms/mvp-cc
clientId: mvp-cc
clientSecret: change-me-keycloak-client-secret
redirectUri: https://cc.example.local/proxy/auth/auth/oidc/callback
scopes: openid profile email
roleClaim: groups
roleMapJson: '{"kc_admin":"admin","kc_supervisor":"supervisor","kc_operator":"operator","kc_analyst":"analyst"}'
services:
auth-service:
module: services.auth_service.app:app
replicas: 2
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 750m
memory: 512Mi
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 2
targetCPUUtilizationPercentage: 70
podDisruptionBudget:
enabled: true
minAvailable: 1
persistence:
enabled: false
size: 1Gi
audit-service:
module: services.audit_service.app:app
replicas: 2
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 750m
memory: 512Mi
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 2
targetCPUUtilizationPercentage: 70
podDisruptionBudget:
enabled: true
minAvailable: 1
persistence:
enabled: false
size: 1Gi
customer-service:
module: services.customer_service.app:app
replicas: 2
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 750m
memory: 512Mi
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 2
targetCPUUtilizationPercentage: 70
podDisruptionBudget:
enabled: true
minAvailable: 1
persistence:
enabled: false
size: 1Gi
interaction-service:
module: services.interaction_service.app:app
replicas: 4
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 1500m
memory: 1Gi
autoscaling:
enabled: true
minReplicas: 4
maxReplicas: 8
targetCPUUtilizationPercentage: 70
podDisruptionBudget:
enabled: true
minAvailable: 1
persistence:
enabled: false
size: 1Gi
routing-service:
module: services.routing_service.app:app
replicas: 3
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 1500m
memory: 1Gi
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 6
targetCPUUtilizationPercentage: 70
podDisruptionBudget:
enabled: true
minAvailable: 1
persistence:
enabled: false
size: 1Gi
voice-adapter-service:
module: services.voice_adapter_service.app:app
replicas: 2
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 1000m
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 4
targetCPUUtilizationPercentage: 70
podDisruptionBudget:
enabled: true
minAvailable: 1
persistence:
enabled: false
size: 1Gi
recording-service:
module: services.recording_service.app:app
replicas: 1
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 1
targetCPUUtilizationPercentage: 70
podDisruptionBudget:
enabled: false
minAvailable: 1
persistence:
enabled: true
size: 10Gi
ivr-service:
module: services.ivr_service.app:app
replicas: 2
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 750m
memory: 512Mi
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 2
targetCPUUtilizationPercentage: 70
podDisruptionBudget:
enabled: true
minAvailable: 1
persistence:
enabled: false
size: 1Gi
event-bus-service:
module: services.event_bus_service.app:app
replicas: 1
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 750m
memory: 512Mi
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 1
targetCPUUtilizationPercentage: 70
podDisruptionBudget:
enabled: false
minAvailable: 1
persistence:
enabled: false
size: 1Gi
asterisk-bridge-service:
module: services.asterisk_bridge_service.app:app
# Must stay singleton: background AMI consumption and retry/reconcile loops are not multi-replica safe.
replicas: 1
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 750m
memory: 512Mi
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 1
targetCPUUtilizationPercentage: 70
podDisruptionBudget:
enabled: false
minAvailable: 1
persistence:
enabled: false
size: 1Gi
telegram-adapter-service:
module: services.telegram_adapter_service.app:app
replicas: 2
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 1000m
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 4
targetCPUUtilizationPercentage: 70
podDisruptionBudget:
enabled: true
minAvailable: 1
persistence:
enabled: false
size: 1Gi
ai-orchestrator-service:
module: services.ai_orchestrator_service.app:app
replicas: 2
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 1000m
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 4
targetCPUUtilizationPercentage: 70
podDisruptionBudget:
enabled: true
minAvailable: 1
persistence:
enabled: false
size: 1Gi
ai-voice-runtime-service:
module: services.ai_voice_runtime_service.app:app
replicas: 1
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 1000m
memory: 512Mi
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 1
targetCPUUtilizationPercentage: 70
podDisruptionBudget:
enabled: false
minAvailable: 1
persistence:
enabled: false
size: 1Gi
webchat-adapter-service:
module: services.webchat_adapter_service.app:app
replicas: 2
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 1000m
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 4
targetCPUUtilizationPercentage: 70
podDisruptionBudget:
enabled: true
minAvailable: 1
persistence:
enabled: false
size: 1Gi
email-adapter-service:
module: services.email_adapter_service.app:app
replicas: 2
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 1000m
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 4
targetCPUUtilizationPercentage: 70
podDisruptionBudget:
enabled: true
minAvailable: 1
persistence:
enabled: false
size: 1Gi
kb-service:
module: services.kb_service.app:app
replicas: 2
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 750m
memory: 512Mi
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 2
targetCPUUtilizationPercentage: 70
podDisruptionBudget:
enabled: true
minAvailable: 1
persistence:
enabled: false
size: 1Gi
reporting-service:
module: services.reporting_service.app:app
replicas: 2
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 750m
memory: 512Mi
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 2
targetCPUUtilizationPercentage: 70
podDisruptionBudget:
enabled: true
minAvailable: 1
persistence:
enabled: false
size: 1Gi
supervisor-service:
module: services.supervisor_service.app:app
replicas: 2
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 750m
memory: 512Mi
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 2
targetCPUUtilizationPercentage: 70
podDisruptionBudget:
enabled: true
minAvailable: 1
persistence:
enabled: false
size: 1Gi
api-gateway:
module: gateway.app:app
gateway: true
replicas: 4
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 1500m
memory: 1Gi
autoscaling:
enabled: true
minReplicas: 4
maxReplicas: 8
targetCPUUtilizationPercentage: 70
podDisruptionBudget:
enabled: true
minAvailable: 1
persistence:
enabled: false
size: 1Gi
File diff suppressed because it is too large Load Diff
+67
View File
@@ -0,0 +1,67 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-config
namespace: mvp-cc
data:
prometheus.yml: |
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'mvp-services'
metrics_path: /health
static_configs:
- targets:
- auth-service:8000
- audit-service:8000
- customer-service:8000
- interaction-service:8000
- routing-service:8000
- voice-adapter-service:8000
- telegram-adapter-service:8000
- kb-service:8000
- reporting-service:8000
- supervisor-service:8000
- api-gateway:8000
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: prometheus
namespace: mvp-cc
spec:
replicas: 1
selector:
matchLabels:
app: prometheus
template:
metadata:
labels:
app: prometheus
spec:
containers:
- name: prometheus
image: prom/prometheus:v2.55.1
args:
- --config.file=/etc/prometheus/prometheus.yml
volumeMounts:
- name: config
mountPath: /etc/prometheus
volumes:
- name: config
configMap:
name: prometheus-config
---
apiVersion: v1
kind: Service
metadata:
name: prometheus
namespace: mvp-cc
spec:
selector:
app: prometheus
ports:
- name: http
port: 9090
targetPort: 9090
type: ClusterIP