Skip to content

feat: relay Talk calls through Coturn - #126

Merged
MattDHill merged 1 commit into
masterfrom
feat/coturn-talk-turn
Aug 17, 2026
Merged

feat: relay Talk calls through Coturn#126
MattDHill merged 1 commit into
masterfrom
feat/coturn-talk-turn

Conversation

@MattDHill

Copy link
Copy Markdown
Member

Nextcloud Talk places calls directly between participants, which fails when both ends sit behind NAT or a strict firewall. This adds an optional Coturn dependency, gated on a new Relay Talk Calls Through Coturn toggle in the Configure action, that points Talk's STUN and TURN settings at the Coturn package.

Coturn authenticates with the TURN REST API shared secret rather than long-term accounts — exactly what Talk's TURN secret field expects — so the secret is handed over as-is and Talk mints its own ephemeral credentials.

Why two TURN entries

Talk stores a bare host:port per entry, and the plain and TLS endpoints sit on different ports. So they go in as two entries rather than one carrying turn,turns:

schemes server protocols
turn <domain>:3478 udp,tcp
turns <domain>:5349 tcp

Coturn serves TURN over TLS on TCP only, hence the asymmetry.

Only our own entries are ever touched

The talk-turn oneshot records what it applied in store.json and deletes exactly that before writing the new set, so Talk's default stun.nextcloud.com:443 and anything an admin added by hand survive. The deletes cover the incoming set too — without that, a partial failure re-adds a duplicate on retry. The secret is recorded as a hash rather than in the clear: enough for a rotated Coturn secret to re-apply, without a second copy of it on this volume.

Talk is user-installed from the Nextcloud app store, so the oneshot is a no-op until its files are present — logged as a wait rather than an error, and applied on a later start.

Notes

  • No health check on the dependency, deliberately: Coturn's own TURN Server check fails until a public domain is attached to it, which would leave Nextcloud showing a permanently unmet dependency even though Talk works fine without a relay.
  • 33.0.8:2 carries a migration, so it's spun off to v33.0.8_2.ts rather than bumped in place.
  • Requires no coordination with the Coturn PR — it works against released Coturn.

Verified on the dev box

Against Talk 23.0.10 and Coturn 4.17.2: enabling the toggle produced

STUN: ["stun.nextcloud.com:443", "coturn-test.start9.dev:3478"]
TURN: [{turn,  coturn-test.start9.dev:3478, secret, udp,tcp},
       {turns, coturn-test.start9.dev:5349, secret, tcp}]

with the secret a byte-for-byte match for Coturn's shared/turn-secret. Disabling removed exactly those three entries and left stun.nextcloud.com:443 standing. The oneshot ordered correctly behind nextcloud and finish-upgrade, and the dependency appeared and disappeared with the toggle.

🤖 Generated with Claude Code

@MattDHill
MattDHill force-pushed the feat/coturn-talk-turn branch from 287eb61 to 70a0f6a Compare August 17, 2026 22:46
Nextcloud Talk places calls directly between participants, which fails when
both ends are behind NAT or a strict firewall. Add an optional Coturn
dependency, gated on a new "Relay Talk Calls Through Coturn" toggle in the
Configure action, that points Talk's STUN and TURN settings at the Coturn
package.

Coturn authenticates with the TURN REST API shared-secret scheme rather than
long-term accounts, which is exactly what Talk's TURN secret field expects — so
the secret is handed over as-is and Talk mints its own ephemeral credentials.
The plain and TLS endpoints sit on different ports and Talk stores a bare
host:port per entry, so they go in as two entries (turn/udp,tcp and turns/tcp)
rather than one carrying `turn,turns`.

The `talk-turn` oneshot only ever touches entries this package added: it records
what it applied in store.json and deletes exactly that before writing the new
set, so Talk's default stun.nextcloud.com:443 and any hand-added entry survive.
Deletes cover the incoming set too, which is what keeps a retry after a partial
failure from leaving a duplicate. The secret is recorded as a hash, not in the
clear — enough for a rotated secret to re-apply without a second copy of it on
the volume.

No health check is declared on the dependency: Coturn's own TURN Server check
fails until a public domain is attached to it, which would surface here as a
permanently unmet dependency even though Talk degrades gracefully to direct
connections.

33.0.8:2 carries a migration, so it is spun off to v33.0.8_2.ts rather than
bumped in place.

Verified on the dev box against Talk 23.0.10 and Coturn 4.17.2: enabling the
toggle added Coturn's domain as a STUN entry plus the two TURN entries, each
carrying Coturn's own shared secret byte-for-byte; disabling removed exactly
those three and left Talk's stun.nextcloud.com:443 in place. The oneshot ordered
correctly behind nextcloud and finish-upgrade, and the dependency appeared and
disappeared with the toggle.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@MattDHill
MattDHill force-pushed the feat/coturn-talk-turn branch from 70a0f6a to 8102fbc Compare August 17, 2026 22:48
@MattDHill
MattDHill merged commit f1a5503 into master Aug 17, 2026
3 checks passed
@MattDHill
MattDHill deleted the feat/coturn-talk-turn branch August 17, 2026 23:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant