From 10f03e151db9d048f6284526ec966cb35d37e928 Mon Sep 17 00:00:00 2001 From: florianfederspiel Date: Wed, 20 May 2026 22:18:58 +0200 Subject: [PATCH] =?UTF-8?q?KI-AGENT:=20Lokalen=20Asterisk-Teststack=20erg?= =?UTF-8?q?=C3=A4nzen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 17 ++ backend/src/index.ts | 2 + backend/src/routes/telephony.ts | 93 ++++++++ docker-compose.yml | 23 ++ frontend/components/MainNav.vue | 5 + frontend/pages/communication/phone.vue | 316 +++++++++++++++++++++++++ telephony/asterisk/acl.conf | 1 + telephony/asterisk/aeap.conf | 1 + telephony/asterisk/asterisk.conf | 15 ++ telephony/asterisk/ccss.conf | 2 + telephony/asterisk/cdr.conf | 2 + telephony/asterisk/cel.conf | 2 + telephony/asterisk/extensions.conf | 15 ++ telephony/asterisk/features.conf | 1 + telephony/asterisk/http.conf | 6 + telephony/asterisk/logger.conf | 6 + telephony/asterisk/manager.conf | 2 + telephony/asterisk/modules.conf | 74 ++++++ telephony/asterisk/pjproject.conf | 1 + telephony/asterisk/pjsip.conf | 64 +++++ telephony/asterisk/rtp.conf | 5 + telephony/asterisk/udptl.conf | 1 + 22 files changed, 654 insertions(+) create mode 100644 backend/src/routes/telephony.ts create mode 100644 frontend/pages/communication/phone.vue create mode 100644 telephony/asterisk/acl.conf create mode 100644 telephony/asterisk/aeap.conf create mode 100644 telephony/asterisk/asterisk.conf create mode 100644 telephony/asterisk/ccss.conf create mode 100644 telephony/asterisk/cdr.conf create mode 100644 telephony/asterisk/cel.conf create mode 100644 telephony/asterisk/extensions.conf create mode 100644 telephony/asterisk/features.conf create mode 100644 telephony/asterisk/http.conf create mode 100644 telephony/asterisk/logger.conf create mode 100644 telephony/asterisk/manager.conf create mode 100644 telephony/asterisk/modules.conf create mode 100644 telephony/asterisk/pjproject.conf create mode 100644 telephony/asterisk/pjsip.conf create mode 100644 telephony/asterisk/rtp.conf create mode 100644 telephony/asterisk/udptl.conf diff --git a/.env.example b/.env.example index 71f0c7a..1d3d611 100644 --- a/.env.example +++ b/.env.example @@ -56,6 +56,23 @@ NUXT_PUBLIC_PDF_LICENSE=replace-with-your-pdf-license # Interner Prometheus Node Exporter für die Admin-Systemstatusseite. NODE_EXPORTER_URL=http://node-exporter:9100 +# Lokaler Asterisk-Test für SIP/Voice. Aktivieren, wenn das Compose-Profil +# `telephony-dev` genutzt wird. +TELEPHONY_ENABLED=false +ASTERISK_IMAGE=andrius/asterisk:20 +TELEPHONY_ASTERISK_HTTP_URL=http://asterisk-dev:8088/ws +TELEPHONY_ASTERISK_WS_URL=ws://localhost:8088/ws +TELEPHONY_SIP_DOMAIN=localhost +TELEPHONY_TEST_EXTENSION=1001 +TELEPHONY_TEST_PASSWORD=fedeo-test-1001 +TELEPHONY_TEST_EXTENSION_2=1002 +TELEPHONY_TEST_PASSWORD_2=fedeo-test-1002 +TELEPHONY_ECHO_EXTENSION=600 +TELEPHONY_DEV_WS_PORT=8088 +TELEPHONY_DEV_SIP_PORT=5060 +TELEPHONY_DEV_RTP_MIN_PORT=10000 +TELEPHONY_DEV_RTP_MAX_PORT=10020 + # Optionaler Erststart-Bootstrap. Wenn gesetzt, werden Admin, Mandant, # Admin-Rolle und Grunddaten beim Backend-Start idempotent angelegt. FEDEO_BOOTSTRAP_ADMIN_EMAIL=admin@example.com diff --git a/backend/src/index.ts b/backend/src/index.ts index 628e111..b50306b 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -32,6 +32,7 @@ import wikiRoutes from "./routes/wiki"; import portalContractRoutes from "./routes/portal/contracts"; import mcpRoutes from "./routes/mcp"; import communicationRoutes from "./routes/communication"; +import telephonyRoutes from "./routes/telephony"; //Public Links import publiclinksNonAuthenticatedRoutes from "./routes/publiclinks/publiclinks-non-authenticated"; @@ -154,6 +155,7 @@ async function main() { await subApp.register(portalContractRoutes); await subApp.register(mcpRoutes); await subApp.register(communicationRoutes); + await subApp.register(telephonyRoutes); },{prefix: "/api"}) diff --git a/backend/src/routes/telephony.ts b/backend/src/routes/telephony.ts new file mode 100644 index 0000000..db94a30 --- /dev/null +++ b/backend/src/routes/telephony.ts @@ -0,0 +1,93 @@ +import { FastifyInstance } from "fastify" + +const envFlag = (value: string | undefined, fallback: boolean) => { + if (value === undefined || value === "") return fallback + return ["1", "true", "yes", "on"].includes(value.toLowerCase()) +} + +const telephonyEnabled = () => + envFlag(process.env.TELEPHONY_ENABLED, process.env.NODE_ENV !== "production") + +const asteriskHttpStatusUrl = () => + process.env.TELEPHONY_ASTERISK_HTTP_URL || "http://asterisk-dev:8088/ws" + +const publicAsteriskWsUrl = () => + process.env.TELEPHONY_ASTERISK_WS_URL || `ws://localhost:${process.env.TELEPHONY_DEV_WS_PORT || "8088"}/ws` + +const sipDomain = () => + process.env.TELEPHONY_SIP_DOMAIN || "localhost" + +const testAccounts = () => [ + { + extension: process.env.TELEPHONY_TEST_EXTENSION || "1001", + password: process.env.TELEPHONY_TEST_PASSWORD || "fedeo-test-1001", + displayName: "FEDEO Test 1001", + }, + { + extension: process.env.TELEPHONY_TEST_EXTENSION_2 || "1002", + password: process.env.TELEPHONY_TEST_PASSWORD_2 || "fedeo-test-1002", + displayName: "FEDEO Test 1002", + }, +] + +const fetchWithTimeout = async (url: string, timeoutMs = 2500) => { + const controller = new AbortController() + const timeout = setTimeout(() => controller.abort(), timeoutMs) + + try { + return await fetch(url, { signal: controller.signal }) + } finally { + clearTimeout(timeout) + } +} + +export default async function telephonyRoutes(server: FastifyInstance) { + server.get("/telephony/config", async () => ({ + enabled: telephonyEnabled(), + provider: "asterisk", + mode: "local-test", + sipDomain: sipDomain(), + sipWebSocketUrl: publicAsteriskWsUrl(), + echoExtension: process.env.TELEPHONY_ECHO_EXTENSION || "600", + testAccounts: testAccounts(), + })) + + server.get("/telephony/status", async () => { + const enabled = telephonyEnabled() + const url = asteriskHttpStatusUrl() + + if (!enabled) { + return { + enabled, + provider: "asterisk", + reachable: false, + statusUrl: url, + message: "Telefonie ist nicht aktiviert.", + } + } + + try { + const response = await fetchWithTimeout(url) + return { + enabled, + provider: "asterisk", + reachable: true, + statusCode: response.status, + statusUrl: url, + message: response.ok + ? "Asterisk ist erreichbar." + : `Asterisk-HTTP ist erreichbar (HTTP ${response.status}).`, + } + } catch (error: any) { + return { + enabled, + provider: "asterisk", + reachable: false, + statusUrl: url, + message: error?.name === "AbortError" + ? "Asterisk-Statusabfrage ist abgelaufen." + : (error?.message || "Asterisk ist nicht erreichbar."), + } + } + }) +} diff --git a/docker-compose.yml b/docker-compose.yml index 04b8f6a..fc33659 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -57,6 +57,15 @@ services: - WEB_PUSH_PRIVATE_KEY=${WEB_PUSH_PRIVATE_KEY:-} - WEB_PUSH_SUBJECT=${WEB_PUSH_SUBJECT:-mailto:admin@example.com} - NODE_EXPORTER_URL=${NODE_EXPORTER_URL:-http://node-exporter:9100} + - TELEPHONY_ENABLED=${TELEPHONY_ENABLED:-false} + - TELEPHONY_ASTERISK_HTTP_URL=${TELEPHONY_ASTERISK_HTTP_URL:-http://asterisk-dev:8088/ws} + - TELEPHONY_ASTERISK_WS_URL=${TELEPHONY_ASTERISK_WS_URL:-ws://localhost:8088/ws} + - TELEPHONY_SIP_DOMAIN=${TELEPHONY_SIP_DOMAIN:-localhost} + - TELEPHONY_TEST_EXTENSION=${TELEPHONY_TEST_EXTENSION:-1001} + - TELEPHONY_TEST_PASSWORD=${TELEPHONY_TEST_PASSWORD:-fedeo-test-1001} + - TELEPHONY_TEST_EXTENSION_2=${TELEPHONY_TEST_EXTENSION_2:-1002} + - TELEPHONY_TEST_PASSWORD_2=${TELEPHONY_TEST_PASSWORD_2:-fedeo-test-1002} + - TELEPHONY_ECHO_EXTENSION=${TELEPHONY_ECHO_EXTENSION:-600} networks: - traefik labels: @@ -92,6 +101,20 @@ services: networks: - traefik + asterisk-dev: + image: ${ASTERISK_IMAGE:-andrius/asterisk:20} + restart: unless-stopped + profiles: + - telephony-dev + volumes: + - ./telephony/asterisk:/etc/asterisk:ro + ports: + - "${TELEPHONY_DEV_WS_PORT:-8088}:8088" + - "${TELEPHONY_DEV_SIP_PORT:-5060}:5060/udp" + - "${TELEPHONY_DEV_RTP_MIN_PORT:-10000}-${TELEPHONY_DEV_RTP_MAX_PORT:-10020}:10000-10020/udp" + networks: + - traefik + matrix-db: image: postgres:16-alpine restart: unless-stopped diff --git a/frontend/components/MainNav.vue b/frontend/components/MainNav.vue index 6e14a4d..aaf3893 100644 --- a/frontend/components/MainNav.vue +++ b/frontend/components/MainNav.vue @@ -80,6 +80,11 @@ const links = computed(() => { to: "/communication/chat", icon: "i-heroicons-chat-bubble-left-right" }, + { + label: "Telefonie", + to: "/communication/phone", + icon: "i-heroicons-phone" + }, featureEnabled("helpdesk") ? { label: "Helpdesk", to: "/helpdesk", diff --git a/frontend/pages/communication/phone.vue b/frontend/pages/communication/phone.vue new file mode 100644 index 0000000..99bdce6 --- /dev/null +++ b/frontend/pages/communication/phone.vue @@ -0,0 +1,316 @@ + + + diff --git a/telephony/asterisk/acl.conf b/telephony/asterisk/acl.conf new file mode 100644 index 0000000..f26de27 --- /dev/null +++ b/telephony/asterisk/acl.conf @@ -0,0 +1 @@ +[general] diff --git a/telephony/asterisk/aeap.conf b/telephony/asterisk/aeap.conf new file mode 100644 index 0000000..f26de27 --- /dev/null +++ b/telephony/asterisk/aeap.conf @@ -0,0 +1 @@ +[general] diff --git a/telephony/asterisk/asterisk.conf b/telephony/asterisk/asterisk.conf new file mode 100644 index 0000000..67cbf31 --- /dev/null +++ b/telephony/asterisk/asterisk.conf @@ -0,0 +1,15 @@ +[directories] +astetcdir => /etc/asterisk +astmoddir => /usr/lib/asterisk/modules +astvarlibdir => /var/lib/asterisk +astdbdir => /var/lib/asterisk +astkeydir => /var/lib/asterisk +astdatadir => /var/lib/asterisk +astagidir => /var/lib/asterisk/agi-bin +astspooldir => /var/spool/asterisk +astrundir => /var/run/asterisk +astlogdir => /var/log/asterisk +astsbindir => /usr/sbin + +[options] +documentation_language => en_US diff --git a/telephony/asterisk/ccss.conf b/telephony/asterisk/ccss.conf new file mode 100644 index 0000000..d7a04a3 --- /dev/null +++ b/telephony/asterisk/ccss.conf @@ -0,0 +1,2 @@ +[general] +cc_max_requests=0 diff --git a/telephony/asterisk/cdr.conf b/telephony/asterisk/cdr.conf new file mode 100644 index 0000000..b54d235 --- /dev/null +++ b/telephony/asterisk/cdr.conf @@ -0,0 +1,2 @@ +[general] +enable=no diff --git a/telephony/asterisk/cel.conf b/telephony/asterisk/cel.conf new file mode 100644 index 0000000..b54d235 --- /dev/null +++ b/telephony/asterisk/cel.conf @@ -0,0 +1,2 @@ +[general] +enable=no diff --git a/telephony/asterisk/extensions.conf b/telephony/asterisk/extensions.conf new file mode 100644 index 0000000..054cefb --- /dev/null +++ b/telephony/asterisk/extensions.conf @@ -0,0 +1,15 @@ +[general] +static=yes +writeprotect=no +clearglobalvars=no + +[fedeo-local] +exten => 1001,1,Dial(PJSIP/1001,30) + same => n,Hangup() + +exten => 1002,1,Dial(PJSIP/1002,30) + same => n,Hangup() + +exten => 600,1,Answer() + same => n,Echo() + same => n,Hangup() diff --git a/telephony/asterisk/features.conf b/telephony/asterisk/features.conf new file mode 100644 index 0000000..f26de27 --- /dev/null +++ b/telephony/asterisk/features.conf @@ -0,0 +1 @@ +[general] diff --git a/telephony/asterisk/http.conf b/telephony/asterisk/http.conf new file mode 100644 index 0000000..265d46c --- /dev/null +++ b/telephony/asterisk/http.conf @@ -0,0 +1,6 @@ +[general] +enabled=yes +bindaddr=0.0.0.0 +bindport=8088 +prefix= +tlsenable=no diff --git a/telephony/asterisk/logger.conf b/telephony/asterisk/logger.conf new file mode 100644 index 0000000..846696f --- /dev/null +++ b/telephony/asterisk/logger.conf @@ -0,0 +1,6 @@ +[general] +dateformat=%F %T + +[logfiles] +console => notice,warning,error,verbose +messages => notice,warning,error diff --git a/telephony/asterisk/manager.conf b/telephony/asterisk/manager.conf new file mode 100644 index 0000000..1e54bc5 --- /dev/null +++ b/telephony/asterisk/manager.conf @@ -0,0 +1,2 @@ +[general] +enabled=no diff --git a/telephony/asterisk/modules.conf b/telephony/asterisk/modules.conf new file mode 100644 index 0000000..227a864 --- /dev/null +++ b/telephony/asterisk/modules.conf @@ -0,0 +1,74 @@ +[modules] +autoload=yes + +; Für den lokalen FEDEO-Test laden wir Asterisk bewusst schlank. +; Diese optionalen Module erzeugen ohne zusätzliche Konfiguration nur Lograuschen. +noload => app_agent_pool.so +noload => app_alarmreceiver.so +noload => app_amd.so +noload => app_confbridge.so +noload => app_followme.so +noload => app_minivm.so +noload => app_page.so +noload => app_queue.so +noload => app_stasis.so +noload => app_voicemail.so +noload => cdr_csv.so +noload => cdr_adaptive_odbc.so +noload => cdr_custom.so +noload => cdr_manager.so +noload => cdr_odbc.so +noload => cdr_pgsql.so +noload => cdr_sqlite3_custom.so +noload => cel_custom.so +noload => cel_manager.so +noload => cel_odbc.so +noload => cel_pgsql.so +noload => cel_sqlite3_custom.so +noload => chan_websocket.so +noload => chan_console.so +noload => chan_iax2.so +noload => chan_unistim.so +noload => format_ogg_vorbis.so +noload => func_odbc.so +noload => pbx_ael.so +noload => pbx_dundi.so +noload => res_calendar.so +noload => res_clialiases.so +noload => res_config_ldap.so +noload => res_config_odbc.so +noload => res_config_pgsql.so +noload => res_config_sqlite3.so +noload => res_fax.so +noload => res_fax_spandsp.so +noload => res_geolocation.so +noload => res_hep.so +noload => res_hep_pjsip.so +noload => res_hep_rtcp.so +noload => res_http_media_cache.so +noload => res_musiconhold.so +noload => res_odbc.so +noload => res_odbc_transaction.so +noload => res_parking.so +noload => res_ari.so +noload => res_ari_applications.so +noload => res_ari_asterisk.so +noload => res_ari_bridges.so +noload => res_ari_channels.so +noload => res_ari_device_states.so +noload => res_ari_endpoints.so +noload => res_ari_events.so +noload => res_ari_model.so +noload => res_ari_playbacks.so +noload => res_ari_recordings.so +noload => res_ari_sounds.so +noload => res_phoneprov.so +noload => res_pjsip_geolocation.so +noload => res_pjsip_config_wizard.so +noload => res_pjsip_notify.so +noload => res_pjsip_phoneprov_provider.so +noload => res_prometheus.so +noload => res_smdi.so +noload => res_statsd.so +noload => res_stun_monitor.so +noload => res_websocket_client.so diff --git a/telephony/asterisk/pjproject.conf b/telephony/asterisk/pjproject.conf new file mode 100644 index 0000000..f28f0ae --- /dev/null +++ b/telephony/asterisk/pjproject.conf @@ -0,0 +1 @@ +[startup] diff --git a/telephony/asterisk/pjsip.conf b/telephony/asterisk/pjsip.conf new file mode 100644 index 0000000..8762a17 --- /dev/null +++ b/telephony/asterisk/pjsip.conf @@ -0,0 +1,64 @@ +[global] +type=global +user_agent=FEDEO Local Asterisk + +[transport-udp] +type=transport +protocol=udp +bind=0.0.0.0:5060 + +[transport-ws] +type=transport +protocol=ws +bind=0.0.0.0 + +[fedeo-webrtc](!) +type=endpoint +context=fedeo-local +disallow=all +allow=opus,ulaw,alaw +webrtc=yes +ice_support=yes +use_avpf=yes +media_encryption=dtls +dtls_auto_generate_cert=yes +dtls_verify=fingerprint +dtls_setup=actpass +rtcp_mux=yes +direct_media=no +force_rport=yes +rewrite_contact=yes +rtp_symmetric=yes +transport=transport-ws + +[1001](fedeo-webrtc) +auth=1001-auth +aors=1001-aor +callerid=FEDEO Test 1001 <1001> + +[1001-auth] +type=auth +auth_type=userpass +username=1001 +password=fedeo-test-1001 + +[1001-aor] +type=aor +max_contacts=5 +remove_existing=yes + +[1002](fedeo-webrtc) +auth=1002-auth +aors=1002-aor +callerid=FEDEO Test 1002 <1002> + +[1002-auth] +type=auth +auth_type=userpass +username=1002 +password=fedeo-test-1002 + +[1002-aor] +type=aor +max_contacts=5 +remove_existing=yes diff --git a/telephony/asterisk/rtp.conf b/telephony/asterisk/rtp.conf new file mode 100644 index 0000000..1948e0e --- /dev/null +++ b/telephony/asterisk/rtp.conf @@ -0,0 +1,5 @@ +[general] +rtpstart=10000 +rtpend=10020 +icesupport=yes +strictrtp=no diff --git a/telephony/asterisk/udptl.conf b/telephony/asterisk/udptl.conf new file mode 100644 index 0000000..f26de27 --- /dev/null +++ b/telephony/asterisk/udptl.conf @@ -0,0 +1 @@ +[general]