Skip to content

SaltedByte/Blake-Bridge

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Blake Bridge

A self-hosted Docker stack that bridges PSTN phone calls into Discord voice channels. Call a phone number, enter a PIN, and you're in the voice chat - no internet required on the caller's end.

Built for a friend stationed on a ship with reliable cell service but no usable internet. Works with any SIP trunk provider, runs on any Docker host.

How It Works

Your Phone ──PSTN──> VoIP.ms DID ──SIP/RTP──> Asterisk Container
                                                    │
                                                    ├─ Caller ID whitelist check
                                                    ├─ PIN authentication (3 attempts)
                                                    └─ AudioSocket handoff
                                                           │
                                                           v
                                                   Bridge Container (Node.js)
                                                           │
                                                           ├─ 8kHz mono PCM ↔ 48kHz stereo PCM conversion
                                                           └─ Discord voice channel (join/leave/stream)
  1. You call the DID from a whitelisted phone number
  2. Asterisk checks your caller ID and prompts for a PIN
  3. On success, audio is handed off via AudioSocket to the bridge service
  4. The bridge joins a Discord voice channel and streams audio bidirectionally
  5. When you hang up, the bot leaves the voice channel

Requirements

  • Docker host - tested on Mac Mini M4 with Docker Desktop. Linux with Docker works too (and avoids some NAT quirks, see Troubleshooting).
  • VoIP.ms account (or any SIP trunk provider) - ~$3-5/month
  • Discord bot - free
  • Router with port forwarding - UDP ports 5060 and 10000-10100
  • Static/reserved LAN IP for the Docker host

Quick Start

git clone https://github.com/yourusername/blake-bridge.git
cd blake-bridge
cp .env.example .env
# Edit .env with your values (see setup guides below)
# Edit config/whitelist.txt with allowed phone numbers
docker compose up -d

Setup Guides

1. VoIP.ms Account

  1. Create an account at voip.ms
  2. Add funds ($15 minimum - lasts months at low usage)
  3. Create a Sub Account:
    • Main menu > Sub Accounts > Create Sub Account
    • Pick a username and password
    • Protocol: SIP/UDP
    • Device Type: Asterisk/PBX
    • Allowed Codecs: G.711 u-law
    • DTMF Mode: RFC 2833
    • NAT: Yes
    • Note the Server field (e.g. chicago1.voip.ms)
  4. Purchase a DID:
    • DID Numbers > Order DID
    • Pick any US number (area code doesn't matter for inbound)
    • Route the DID to your sub account

You now have:

Value .env Variable
Sub account username (e.g. 123456_mysub) VOIPMS_USERNAME
Sub account password VOIPMS_PASSWORD
SIP server (e.g. chicago1.voip.ms) VOIPMS_SERVER

Cost estimate: ~$0.85/month DID + ~$0.009/min inbound = ~$3.55/month at 5 hours usage.

2. Discord Bot

  1. Go to Discord Developer Portal
  2. Click New Application - name it whatever you want (e.g. "Phone Bridge")
  3. Go to the Bot tab:
    • Click Reset Token and copy the token - this is DISCORD_BOT_TOKEN
    • Under Privileged Gateway Intents, enable Server Members Intent
  4. Go to OAuth2 > URL Generator:
    • Scopes: bot
    • Bot Permissions: Connect, Speak, Send Messages, Use Voice Activity
    • Copy the generated URL and open it to invite the bot to your server
  5. Enable Developer Mode in Discord: User Settings > Advanced > Developer Mode
  6. Get the IDs (right-click > Copy ID):
What to copy .env Variable
Server (guild) name DISCORD_GUILD_ID
Target voice channel DISCORD_VOICE_CHANNEL_ID
Text channel for notifications DISCORD_TEXT_CHANNEL_ID

Make sure the bot has permission to connect/speak in the voice channel and send messages in the text channel.

3. Network / Router Setup

  1. Assign a static LAN IP to your Docker host (via router DHCP reservation)
  2. Forward these ports on your router to that LAN IP:
Protocol Port(s) Purpose
UDP 5060 SIP signaling
UDP 10000-10100 RTP media (audio)
  1. Note your Docker host's LAN IP - this is MAC_MINI_LAN_IP

4. Configure .env

Copy .env.example to .env and fill in all values:

cp .env.example .env
# === VoIP.ms SIP Trunk ===
VOIPMS_SERVER=chicago1.voip.ms           # SIP server from sub account settings
VOIPMS_USERNAME=123456_mysubaccount      # Sub account username
VOIPMS_PASSWORD=your_password_here       # Sub account password

# === Bridge Security ===
BRIDGE_PIN=123456                        # 4-6 digit PIN callers must enter
CALLER_WHITELIST=+16715551234,+16715555678  # Comma-separated E.164 phone numbers

# === Discord ===
DISCORD_BOT_TOKEN=your-bot-token         # From Discord Developer Portal
DISCORD_GUILD_ID=000000000000000000      # Right-click server > Copy ID
DISCORD_VOICE_CHANNEL_ID=000000000000000000  # Right-click voice channel > Copy ID
DISCORD_TEXT_CHANNEL_ID=000000000000000000   # Right-click text channel > Copy ID

# === Network ===
MAC_MINI_LAN_IP=192.168.1.100           # Docker host's static LAN IP

# === Asterisk Ports (defaults are fine) ===
ASTERISK_SIP_PORT=5060
ASTERISK_RTP_START=10000
ASTERISK_RTP_END=10100

# === AudioSocket (defaults are fine) ===
AUDIOSOCKET_LISTEN_PORT=9092
AUDIOSOCKET_UUID=f47ac10b-58cc-4372-a567-0e02b2c3d479

5. Caller Whitelist

Edit config/whitelist.txt with one phone number per line in E.164 format:

+16715551234
+16715555678

Only calls from these numbers will be accepted. All others get an immediate rejection tone.

6. Launch

docker compose up -d

The first build takes several minutes (Asterisk compiles from source). Subsequent starts are fast.

Check that everything is running:

docker compose logs -f

You should see:

  • Asterisk: Asterisk Ready. and Endpoint voipms-endpoint is now Reachable
  • Bridge: Discord bot logged in as YourBot#1234 and AudioSocket server listening on port 9092

7. Test

  1. Call your VoIP.ms DID from a whitelisted number
  2. You'll hear "password" - enter your PIN followed by #
  3. You'll hear a beep - the bot joins Discord and audio flows both ways
  4. Hang up to disconnect - the bot leaves the voice channel

Architecture

Two Docker containers managed by docker compose:

Container Role
blake-asterisk Asterisk 20 PBX - SIP trunk, IVR, caller ID auth, PIN entry, AudioSocket handoff
blake-bridge Node.js/TypeScript - AudioSocket server, Discord voice bot, audio transcoding

Audio Pipeline

Phone (8kHz G.711) → Asterisk → slin 8kHz mono PCM → AudioSocket TCP
  → Bridge: upsample to 48kHz stereo → Discord voice (Opus/DAVE encrypted)

Discord voice → Opus decode → 48kHz stereo PCM
  → Bridge: downsample to 8kHz mono → AudioSocket TCP → Asterisk → G.711 → Phone

Audio quality is PSTN-grade (8kHz narrowband) - the caller sounds like a phone call to Discord users. Discord audio sent to the phone is downsampled but perfectly intelligible for voice chat.

Project Structure

blake-bridge/
├── docker-compose.yml          # Container orchestration
├── .env.example                # Environment variable template
├── config/
│   └── whitelist.txt           # Allowed caller IDs
├── asterisk/
│   ├── Dockerfile              # Builds Asterisk 20 from source
│   ├── entrypoint.sh           # Templates env vars into configs at startup
│   └── config/
│       ├── pjsip.conf          # SIP trunk configuration
│       ├── extensions.conf     # IVR dialplan (caller ID check → PIN → bridge)
│       ├── rtp.conf            # RTP port range
│       ├── modules.conf        # Asterisk module loading
│       └── agi-bin/
│           └── check_callerid.sh  # Caller ID whitelist AGI script
└── bridge/
    ├── Dockerfile
    ├── package.json
    ├── tsconfig.json
    └── src/
        ├── index.ts            # Entry point, graceful shutdown
        ├── audiosocket.ts      # AudioSocket TCP protocol parser
        ├── discord.ts          # Discord bot, voice channel management
        ├── bridge.ts           # Glue: AudioSocket ↔ Discord audio streams
        ├── audio.ts            # PCM sample rate conversion (8kHz ↔ 48kHz)
        ├── config.ts           # Environment variable validation
        └── logger.ts           # Structured JSON logger

Security

  • Caller ID whitelist - unknown numbers are immediately rejected with no indication a bridge exists
  • PIN authentication - 3 attempts max, then hangup
  • SIP endpoint identification - Asterisk only accepts SIP traffic from your VoIP.ms server IP
  • Discord bot - minimal permissions (Connect, Speak, Send Messages)
  • No exposed ports for the bridge container - it only makes outbound connections to Discord

Troubleshooting

Calls don't connect / "hanging" when dialing

This is usually a NAT issue between Docker and VoIP.ms. The qualify_frequency=30 and expiration=120 settings in pjsip.conf keep the NAT pinhole alive. If calls stop working:

# Force re-registration
docker exec blake-asterisk asterisk -rx "pjsip send unregister voipms"
sleep 5
docker exec blake-asterisk asterisk -rx "pjsip send register voipms"
# Wait 30-60 seconds before trying to call

Docker Desktop for Mac users

Docker Desktop runs containers in a Linux VM, which adds an extra NAT layer. This causes the SIP source port to differ from what's advertised. The config handles this with:

  • external_signaling_address / external_media_address set to your public IP (auto-detected at startup)
  • qualify_frequency=30 to keep the NAT mapping alive
  • expiration=120 for frequent re-registration

On native Linux Docker, these issues don't apply and you can simplify the config.

No audio / one-way audio

  • Verify UDP ports 10000-10100 are forwarded on your router
  • Check that external_media_address resolved to your public IP: docker logs blake-asterisk | grep "Detected public IP"
  • Ensure VoIP.ms sub account has NAT: Yes and DTMF Mode: RFC 2833

DTMF / PIN not recognized

  • Set DTMF Mode to RFC 2833 in your VoIP.ms sub account settings
  • The PIN is entered during the "password" prompt - press digits then #

Bot can't join voice channel

  • Ensure the bot has Connect and Speak permissions in the target voice channel
  • Discord now requires DAVE (end-to-end encryption) for voice - this project uses @discordjs/voice 1.0.0-dev with DAVE support via @snazzah/davey
  • Check: docker logs blake-bridge for voice connection state changes

Checking status

# Asterisk SIP registration
docker exec blake-asterisk asterisk -rx "pjsip show registrations"

# Active calls
docker exec blake-asterisk asterisk -rx "core show channels"

# Bridge logs
docker logs blake-bridge

# Full stack logs
docker compose logs -f

Alternative SIP Providers

Any SIP trunk provider works - only the .env SIP variables and VoIP.ms-specific settings change:

Provider DID Cost Inbound/min ~Monthly (5hr)
VoIP.ms $0.85 $0.009 $3.55
Telnyx $1.00 $0.01 $4.00
Twilio $1.15 $0.0085 $3.70

License

MIT

About

A self-hosted Docker stack that bridges PSTN phone calls into Discord voice channels. Call a phone number, enter a PIN, and you're in the voice chat - no internet required on the caller's end.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors