migrate cc-config to chezmoi: add all skills, agents, and openclaw installer
- Add 4 missing skills: chromedp-alpine-container, claude-memory-api, openclaw-custom-model-provider, webrtc-turn-shared-secret - Add 9 custom agents: sre, dba, devops-engineer, platform-engineer, security-engineer, network-engineer, observability-engineer, home-automation-engineer, cluster-health-checker - Add openclaw-install.sh: standalone script to clone dotfiles and install skills/agents/hooks/settings to OpenClaw's home directory Replaces the cc-config NFS volume + sync.sh approach
This commit is contained in:
parent
ba3ec6ced5
commit
c95ffa03c5
16 changed files with 1013 additions and 2 deletions
105
dot_claude/skills/webrtc-turn-shared-secret/SKILL.md
Normal file
105
dot_claude/skills/webrtc-turn-shared-secret/SKILL.md
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
---
|
||||
name: webrtc-turn-shared-secret
|
||||
description: |
|
||||
Generate ephemeral TURN credentials from a shared secret for coturn (--use-auth-secret mode).
|
||||
Use when: (1) WebRTC ICE connection state goes to "failed" or stays at "checking",
|
||||
(2) STUN-only config can't establish media path through NAT/k8s,
|
||||
(3) coturn is configured with --use-auth-secret and you need time-limited credentials,
|
||||
(4) need to pass TURN credentials to both server-side (pion/webrtc) and client-side
|
||||
(browser RTCPeerConnection). Covers credential generation, Go implementation, and
|
||||
client-side WebRTC configuration.
|
||||
author: Claude Code
|
||||
version: 1.0.0
|
||||
date: 2026-02-21
|
||||
---
|
||||
|
||||
# WebRTC TURN Server with Shared Secret Credentials
|
||||
|
||||
## Problem
|
||||
WebRTC connections fail with `ICE connection state: failed` when peers are behind NAT
|
||||
(especially in Kubernetes pods). STUN alone can't establish a media path through
|
||||
symmetric NAT. A TURN server is needed, and coturn's shared secret mode requires
|
||||
generating ephemeral credentials.
|
||||
|
||||
## Context / Trigger Conditions
|
||||
- `webrtc: ICE connection state: failed` in server logs
|
||||
- `ICE connection state: failed` in browser console
|
||||
- WebRTC signaling (offer/answer) succeeds but no media flows
|
||||
- Server is in a k8s pod with private IP, client is behind NAT
|
||||
- coturn configured with `--use-auth-secret` or `use-auth-secret` in turnserver.conf
|
||||
|
||||
## Solution
|
||||
|
||||
### Credential Generation (TURN REST API)
|
||||
|
||||
```
|
||||
username = Unix timestamp of expiry (e.g., "1740200000")
|
||||
password = Base64(HMAC-SHA1(username, shared_secret))
|
||||
```
|
||||
|
||||
### Go Implementation
|
||||
|
||||
```go
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GenerateTURNCredentials(turnURL, sharedSecret string, ttl time.Duration) (urls []string, username, credential string) {
|
||||
expiry := time.Now().Add(ttl).Unix()
|
||||
username = fmt.Sprintf("%d", expiry)
|
||||
mac := hmac.New(sha1.New, []byte(sharedSecret))
|
||||
mac.Write([]byte(username))
|
||||
credential = base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
||||
return []string{turnURL}, username, credential
|
||||
}
|
||||
```
|
||||
|
||||
### Server-side (pion/webrtc)
|
||||
|
||||
```go
|
||||
iceServers := []webrtc.ICEServer{
|
||||
{URLs: []string{"stun:stun.l.google.com:19302"}},
|
||||
{
|
||||
URLs: []string{"turn:your-turn-server:3478"},
|
||||
Username: username,
|
||||
Credential: credential,
|
||||
CredentialType: webrtc.ICECredentialTypePassword,
|
||||
},
|
||||
}
|
||||
pc, _ := webrtc.NewPeerConnection(webrtc.Configuration{ICEServers: iceServers})
|
||||
```
|
||||
|
||||
### Client-side (browser)
|
||||
|
||||
Send ICE config from server to client via signaling channel (WebSocket),
|
||||
then create RTCPeerConnection with it:
|
||||
|
||||
```javascript
|
||||
// Server sends: { type: "iceServers", iceServers: [...] }
|
||||
socket.onmessage = (e) => {
|
||||
const msg = JSON.parse(e.data);
|
||||
if (msg.type === 'iceServers') {
|
||||
pc = new RTCPeerConnection({ iceServers: msg.iceServers });
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
1. Server logs should show `ICE connection state: connected` (not `failed`)
|
||||
2. Browser console should show `ICE connection state: connected`
|
||||
3. Test TURN connectivity: `turnutils_uclient -u username -w credential turn-server-ip`
|
||||
|
||||
## Notes
|
||||
- Both server and client need the TURN credentials — the server uses them for its
|
||||
PeerConnection, and the client needs them for its RTCPeerConnection
|
||||
- Credentials are time-limited (TTL); generate fresh ones per session
|
||||
- If TURN server hostname doesn't resolve from k8s pods (CoreDNS custom zones),
|
||||
use the IP address directly: `turn:1.2.3.4:3478`
|
||||
- STUN is still useful as a fallback for direct connections; keep it in the ICE
|
||||
servers list alongside TURN
|
||||
- The shared secret must match coturn's `static-auth-secret` config
|
||||
Loading…
Add table
Add a link
Reference in a new issue