KI-AGENT: NAT Einstellungen für Telekom-Trunk ergänzen
This commit is contained in:
3
backend/db/migrations/0046_telephony_trunk_nat.sql
Normal file
3
backend/db/migrations/0046_telephony_trunk_nat.sql
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE "telephony_trunks" ADD COLUMN IF NOT EXISTS "external_signaling_address" text;
|
||||||
|
ALTER TABLE "telephony_trunks" ADD COLUMN IF NOT EXISTS "external_media_address" text;
|
||||||
|
ALTER TABLE "telephony_trunks" ADD COLUMN IF NOT EXISTS "local_networks" text;
|
||||||
@@ -323,6 +323,13 @@
|
|||||||
"when": 1780164000000,
|
"when": 1780164000000,
|
||||||
"tag": "0045_telephony_trunks",
|
"tag": "0045_telephony_trunks",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 46,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1780167600000,
|
||||||
|
"tag": "0046_telephony_trunk_nat",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ export const telephonyTrunks = pgTable(
|
|||||||
callerId: text("caller_id"),
|
callerId: text("caller_id"),
|
||||||
inboundExtension: text("inbound_extension").notNull().default("1001"),
|
inboundExtension: text("inbound_extension").notNull().default("1001"),
|
||||||
outboundPrefix: text("outbound_prefix").notNull().default("0"),
|
outboundPrefix: text("outbound_prefix").notNull().default("0"),
|
||||||
|
externalSignalingAddress: text("external_signaling_address"),
|
||||||
|
externalMediaAddress: text("external_media_address"),
|
||||||
|
localNetworks: text("local_networks"),
|
||||||
|
|
||||||
createdAt: timestamp("created_at", { withTimezone: true })
|
createdAt: timestamp("created_at", { withTimezone: true })
|
||||||
.notNull()
|
.notNull()
|
||||||
|
|||||||
@@ -75,6 +75,9 @@ const sanitizeTrunk = (trunk: any) => ({
|
|||||||
callerId: trunk?.callerId || "",
|
callerId: trunk?.callerId || "",
|
||||||
inboundExtension: trunk?.inboundExtension || "1001",
|
inboundExtension: trunk?.inboundExtension || "1001",
|
||||||
outboundPrefix: trunk?.outboundPrefix || "0",
|
outboundPrefix: trunk?.outboundPrefix || "0",
|
||||||
|
externalSignalingAddress: trunk?.externalSignalingAddress || "",
|
||||||
|
externalMediaAddress: trunk?.externalMediaAddress || "",
|
||||||
|
localNetworks: trunk?.localNetworks || "172.16.0.0/12,192.168.0.0/16,10.0.0.0/8",
|
||||||
})
|
})
|
||||||
|
|
||||||
const envExternalTelephonyConfig = () => {
|
const envExternalTelephonyConfig = () => {
|
||||||
@@ -157,6 +160,7 @@ const renderTelekomPjsipConfig = (trunk: any) => {
|
|||||||
const authUser = asteriskValue(trunk.authUser) || sipUser
|
const authUser = asteriskValue(trunk.authUser) || sipUser
|
||||||
const password = asteriskValue(trunk.password)
|
const password = asteriskValue(trunk.password)
|
||||||
const callerId = asteriskValue(trunk.callerId) || sipUser
|
const callerId = asteriskValue(trunk.callerId) || sipUser
|
||||||
|
const externalMediaAddress = asteriskValue(trunk.externalMediaAddress || trunk.externalSignalingAddress)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"; Von FEDEO generiert. Änderungen im Container können überschrieben werden.",
|
"; Von FEDEO generiert. Änderungen im Container können überschrieben werden.",
|
||||||
@@ -181,6 +185,7 @@ const renderTelekomPjsipConfig = (trunk: any) => {
|
|||||||
`from_user=${sipUser}`,
|
`from_user=${sipUser}`,
|
||||||
`from_domain=${registrar}`,
|
`from_domain=${registrar}`,
|
||||||
`callerid=Telekom <${callerId}>`,
|
`callerid=Telekom <${callerId}>`,
|
||||||
|
...(externalMediaAddress ? [`media_address=${externalMediaAddress}`] : []),
|
||||||
"direct_media=no",
|
"direct_media=no",
|
||||||
"force_rport=yes",
|
"force_rport=yes",
|
||||||
"rewrite_contact=yes",
|
"rewrite_contact=yes",
|
||||||
@@ -249,6 +254,35 @@ const renderTelekomExtensionsConfig = (trunk: any) => {
|
|||||||
].join("\n")
|
].join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const renderTelekomTransportConfig = (trunk: any) => {
|
||||||
|
const externalSignalingAddress = asteriskValue(trunk?.externalSignalingAddress)
|
||||||
|
const externalMediaAddress = asteriskValue(trunk?.externalMediaAddress || trunk?.externalSignalingAddress)
|
||||||
|
const localNetworks = asteriskValue(trunk?.localNetworks || "172.16.0.0/12,192.168.0.0/16,10.0.0.0/8")
|
||||||
|
.split(/[\s,;]+/)
|
||||||
|
.map((entry) => entry.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
|
||||||
|
if (!externalSignalingAddress && !externalMediaAddress) {
|
||||||
|
return [
|
||||||
|
"; Von FEDEO generiert.",
|
||||||
|
"; Kein externes Asterisk-NAT-Rewrite konfiguriert.",
|
||||||
|
"",
|
||||||
|
].join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
"; Von FEDEO generiert. Änderungen am Transport benötigen einen Asterisk-Neustart.",
|
||||||
|
"[transport-udp](+)",
|
||||||
|
...(externalSignalingAddress ? [
|
||||||
|
`external_signaling_address=${externalSignalingAddress}`,
|
||||||
|
"external_signaling_port=5060",
|
||||||
|
] : []),
|
||||||
|
...(externalMediaAddress ? [`external_media_address=${externalMediaAddress}`] : []),
|
||||||
|
...localNetworks.map((network) => `local_net=${network}`),
|
||||||
|
"",
|
||||||
|
].join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
const writeAsteriskTrunkConfig = async (trunk: any) => {
|
const writeAsteriskTrunkConfig = async (trunk: any) => {
|
||||||
const targetDir = asteriskGeneratedDir()
|
const targetDir = asteriskGeneratedDir()
|
||||||
await fs.mkdir(targetDir, { recursive: true })
|
await fs.mkdir(targetDir, { recursive: true })
|
||||||
@@ -262,6 +296,10 @@ const writeAsteriskTrunkConfig = async (trunk: any) => {
|
|||||||
name: "extensions.telekom.conf",
|
name: "extensions.telekom.conf",
|
||||||
content: renderTelekomExtensionsConfig(trunk),
|
content: renderTelekomExtensionsConfig(trunk),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "pjsip.transport.conf",
|
||||||
|
content: renderTelekomTransportConfig(trunk),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
await Promise.all(files.map(async (file) => {
|
await Promise.all(files.map(async (file) => {
|
||||||
@@ -364,10 +402,13 @@ export default async function telephonyRoutes(server: FastifyInstance) {
|
|||||||
return {
|
return {
|
||||||
enabled: trunk.enabled,
|
enabled: trunk.enabled,
|
||||||
provider: trunk.provider,
|
provider: trunk.provider,
|
||||||
inboundExtension: trunk.inboundExtension,
|
inboundExtension: trunk.inboundExtension,
|
||||||
outboundPrefix: trunk.outboundPrefix,
|
outboundPrefix: trunk.outboundPrefix,
|
||||||
registrar: trunk.registrar,
|
registrar: trunk.registrar,
|
||||||
sipUserConfigured: Boolean(trunk.sipUser),
|
externalSignalingAddress: trunk.externalSignalingAddress,
|
||||||
|
externalMediaAddress: trunk.externalMediaAddress,
|
||||||
|
localNetworks: trunk.localNetworks,
|
||||||
|
sipUserConfigured: Boolean(trunk.sipUser),
|
||||||
authUserConfigured: Boolean(trunk.authUser),
|
authUserConfigured: Boolean(trunk.authUser),
|
||||||
passwordConfigured: Boolean(trunk.password),
|
passwordConfigured: Boolean(trunk.password),
|
||||||
callerIdConfigured: Boolean(trunk.callerId),
|
callerIdConfigured: Boolean(trunk.callerId),
|
||||||
@@ -463,6 +504,9 @@ export default async function telephonyRoutes(server: FastifyInstance) {
|
|||||||
callerId: bodyString(body, "callerId"),
|
callerId: bodyString(body, "callerId"),
|
||||||
inboundExtension: bodyString(body, "inboundExtension") || "1001",
|
inboundExtension: bodyString(body, "inboundExtension") || "1001",
|
||||||
outboundPrefix: bodyString(body, "outboundPrefix") || "0",
|
outboundPrefix: bodyString(body, "outboundPrefix") || "0",
|
||||||
|
externalSignalingAddress: bodyString(body, "externalSignalingAddress"),
|
||||||
|
externalMediaAddress: bodyString(body, "externalMediaAddress"),
|
||||||
|
localNetworks: bodyString(body, "localNetworks") || "172.16.0.0/12,192.168.0.0/16,10.0.0.0/8",
|
||||||
password: clearPassword ? null : (password || existing?.password || null),
|
password: clearPassword ? null : (password || existing?.password || null),
|
||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
updatedBy: req.user.user_id,
|
updatedBy: req.user.user_id,
|
||||||
|
|||||||
@@ -22,11 +22,16 @@ TELEPHONY_TELEKOM_AUTH_USER=<anschlusskennung><zugangsnummer>#<mitbenutzernummer
|
|||||||
- `Absendernummer`: optional, meist identisch zur SIP-ID
|
- `Absendernummer`: optional, meist identisch zur SIP-ID
|
||||||
- `Eingehende Nebenstelle`: lokale FEDEO/Asterisk-Nebenstelle, z. B. `1001`
|
- `Eingehende Nebenstelle`: lokale FEDEO/Asterisk-Nebenstelle, z. B. `1001`
|
||||||
- `Ausgehender Prefix`: standardmäßig `0`
|
- `Ausgehender Prefix`: standardmäßig `0`
|
||||||
|
- `Öffentliche Signaling-Adresse`: öffentliche IP oder DNS-Name, unter dem Asterisk von Telekom erreichbar ist
|
||||||
|
- `Öffentliche Medien-Adresse`: öffentliche RTP-Adresse, leer nutzt die Signaling-Adresse
|
||||||
|
- `Lokale Netze`: interne Netze, für die Asterisk keine öffentliche Adresse einsetzen soll
|
||||||
|
|
||||||
Das Kennwort wird nicht wieder an das Frontend zurückgegeben. In der Oberfläche siehst du nur, ob ein Kennwort gespeichert ist.
|
Das Kennwort wird nicht wieder an das Frontend zurückgegeben. In der Oberfläche siehst du nur, ob ein Kennwort gespeichert ist.
|
||||||
|
|
||||||
Nach dem Speichern muss die Konfiguration mit **In Asterisk anwenden** in die Asterisk-Include-Dateien geschrieben werden. FEDEO lädt danach PJSIP und den Dialplan über AMI neu und fordert die Telekom-Registration an. Den Laufzeitstatus findest du unter **Kommunikation -> Telefonie Setup** im Bereich **Externe Telefonie**.
|
Nach dem Speichern muss die Konfiguration mit **In Asterisk anwenden** in die Asterisk-Include-Dateien geschrieben werden. FEDEO lädt danach PJSIP und den Dialplan über AMI neu und fordert die Telekom-Registration an. Den Laufzeitstatus findest du unter **Kommunikation -> Telefonie Setup** im Bereich **Externe Telefonie**.
|
||||||
|
|
||||||
|
Wenn du die öffentliche Signaling- oder Medien-Adresse änderst, muss Asterisk anschließend neu gestartet werden, weil PJSIP-Transporte diese Werte nicht vollständig im laufenden Betrieb neu laden.
|
||||||
|
|
||||||
## `.env` Fallback
|
## `.env` Fallback
|
||||||
|
|
||||||
Die `.env`-Werte bleiben nur als lokaler Fallback für den Asterisk-Teststack erhalten, falls keine mandantenbezogene Konfiguration gepflegt ist.
|
Die `.env`-Werte bleiben nur als lokaler Fallback für den Asterisk-Teststack erhalten, falls keine mandantenbezogene Konfiguration gepflegt ist.
|
||||||
|
|||||||
@@ -145,7 +145,10 @@ const telephonyTrunkForm = reactive({
|
|||||||
clearPassword: false,
|
clearPassword: false,
|
||||||
callerId: "",
|
callerId: "",
|
||||||
inboundExtension: "1001",
|
inboundExtension: "1001",
|
||||||
outboundPrefix: "0"
|
outboundPrefix: "0",
|
||||||
|
externalSignalingAddress: "",
|
||||||
|
externalMediaAddress: "",
|
||||||
|
localNetworks: "172.16.0.0/12,192.168.0.0/16,10.0.0.0/8"
|
||||||
})
|
})
|
||||||
|
|
||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
@@ -223,6 +226,9 @@ const loadTelephonyTrunk = async () => {
|
|||||||
telephonyTrunkForm.callerId = res?.callerId || ""
|
telephonyTrunkForm.callerId = res?.callerId || ""
|
||||||
telephonyTrunkForm.inboundExtension = res?.inboundExtension || "1001"
|
telephonyTrunkForm.inboundExtension = res?.inboundExtension || "1001"
|
||||||
telephonyTrunkForm.outboundPrefix = res?.outboundPrefix || "0"
|
telephonyTrunkForm.outboundPrefix = res?.outboundPrefix || "0"
|
||||||
|
telephonyTrunkForm.externalSignalingAddress = res?.externalSignalingAddress || ""
|
||||||
|
telephonyTrunkForm.externalMediaAddress = res?.externalMediaAddress || ""
|
||||||
|
telephonyTrunkForm.localNetworks = res?.localNetworks || "172.16.0.0/12,192.168.0.0/16,10.0.0.0/8"
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.add({ title: "Telefonie-Trunk konnte nicht geladen werden", color: "error" })
|
toast.add({ title: "Telefonie-Trunk konnte nicht geladen werden", color: "error" })
|
||||||
} finally {
|
} finally {
|
||||||
@@ -254,7 +260,10 @@ const saveTelephonyTrunk = async () => {
|
|||||||
clearPassword: telephonyTrunkForm.clearPassword,
|
clearPassword: telephonyTrunkForm.clearPassword,
|
||||||
callerId: telephonyTrunkForm.callerId,
|
callerId: telephonyTrunkForm.callerId,
|
||||||
inboundExtension: telephonyTrunkForm.inboundExtension,
|
inboundExtension: telephonyTrunkForm.inboundExtension,
|
||||||
outboundPrefix: telephonyTrunkForm.outboundPrefix
|
outboundPrefix: telephonyTrunkForm.outboundPrefix,
|
||||||
|
externalSignalingAddress: telephonyTrunkForm.externalSignalingAddress,
|
||||||
|
externalMediaAddress: telephonyTrunkForm.externalMediaAddress,
|
||||||
|
localNetworks: telephonyTrunkForm.localNetworks
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -523,6 +532,15 @@ onMounted(() => {
|
|||||||
<UFormField label="Ausgehender Prefix">
|
<UFormField label="Ausgehender Prefix">
|
||||||
<UInput v-model="telephonyTrunkForm.outboundPrefix" placeholder="0" />
|
<UInput v-model="telephonyTrunkForm.outboundPrefix" placeholder="0" />
|
||||||
</UFormField>
|
</UFormField>
|
||||||
|
<UFormField label="Öffentliche Signaling-Adresse">
|
||||||
|
<UInput v-model="telephonyTrunkForm.externalSignalingAddress" placeholder="Öffentliche IP oder DNS-Name" />
|
||||||
|
</UFormField>
|
||||||
|
<UFormField label="Öffentliche Medien-Adresse">
|
||||||
|
<UInput v-model="telephonyTrunkForm.externalMediaAddress" placeholder="Leer = Signaling-Adresse" />
|
||||||
|
</UFormField>
|
||||||
|
<UFormField label="Lokale Netze">
|
||||||
|
<UInput v-model="telephonyTrunkForm.localNetworks" placeholder="172.16.0.0/12,192.168.0.0/16,10.0.0.0/8" />
|
||||||
|
</UFormField>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ user_agent=FEDEO Local Asterisk
|
|||||||
type=transport
|
type=transport
|
||||||
protocol=udp
|
protocol=udp
|
||||||
bind=0.0.0.0:5060
|
bind=0.0.0.0:5060
|
||||||
|
#tryinclude generated/pjsip.transport.conf
|
||||||
|
|
||||||
[transport-ws]
|
[transport-ws]
|
||||||
type=transport
|
type=transport
|
||||||
|
|||||||
Reference in New Issue
Block a user