#!/usr/bin/env bash
#
# install-switch.sh - WebRTC softswitch: browser --WSS--> FreeSWITCH --> trunk
#
set -euo pipefail

# ============================== FILL THESE IN ==============================
DOMAIN="sip.milleniumboofer.com"          # subdomain A-record -> THIS droplet's IP
EMAIL="apapprovez@gmail.com"              # Let's Encrypt contact
SW_TOKEN="swpat_NX8Rx4tqr2i93QClrwFEyV9m9827mo0u9g3t"        # SignalWire Personal Access Token (free)

TRUNK_HOST="sip.telnyx.com"          # your trunk
TRUNK_USER="switch7dbc5fe7"
TRUNK_PASS="fa869e71a57339184cd882ed"

EXT_USER="1001"                      # the line the browser registers as
EXT_PASS="WorkDamnYou2w"

FAKE_CID="+18773800654"              # a number you do NOT own
FAKE_CID_NAME="Test Caller"
# ==========================================================================

log(){ printf '\n=== %s ===\n' "$*"; }
die(){ printf 'ERROR: %s\n' "$*" >&2; exit 1; }

[[ $EUID -eq 0 ]] || die "run as root"
[[ "$DOMAIN" == *.* ]] || die "set DOMAIN at the top of this script"
[[ -n "$SW_TOKEN" ]] || die "set SW_TOKEN (SignalWire Personal Access Token)"
[[ "$EXT_PASS" != change-me* ]] || die "pick a real EXT_PASS"

PUBLIC_IP="$(curl -4 -s --max-time 10 ifconfig.me || true)"
[[ -n "$PUBLIC_IP" ]] || die "could not detect public IP"
log "public IP: $PUBLIC_IP"

export DEBIAN_FRONTEND=noninteractive

log "installing prerequisites"
apt-get update -qq
apt-get install -y -qq gnupg2 wget lsb-release ca-certificates certbot ufw >/dev/null

log "adding the FreeSWITCH package repository"
wget -q --http-user=signalwire --http-password="$SW_TOKEN" \
  -O /usr/share/keyrings/signalwire-freeswitch-repo.gpg \
  https://freeswitch.signalwire.com/repo/deb/debian-release/signalwire-freeswitch-repo.gpg
echo "machine freeswitch.signalwire.com login signalwire password $SW_TOKEN" \
  > /etc/apt/auth.conf.d/freeswitch.conf
chmod 600 /etc/apt/auth.conf.d/freeswitch.conf
echo "deb [signed-by=/usr/share/keyrings/signalwire-freeswitch-repo.gpg] https://freeswitch.signalwire.com/repo/deb/debian-release/ bookworm main" \
  > /etc/apt/sources.list.d/freeswitch.list
apt-get update -qq
apt-get install -y -qq freeswitch-meta-vanilla freeswitch-sounds-en-us-callie freeswitch-music-default >/dev/null
[[ -d /etc/freeswitch ]] || cp -a /usr/share/freeswitch/conf/vanilla/. /etc/freeswitch/

log "requesting a TLS certificate for $DOMAIN"
systemctl stop freeswitch 2>/dev/null || true
certbot certonly --standalone --non-interactive --agree-tos -m "$EMAIL" -d "$DOMAIN"
mkdir -p /etc/freeswitch/tls
cat "/etc/letsencrypt/live/$DOMAIN/fullchain.pem" "/etc/letsencrypt/live/$DOMAIN/privkey.pem" \
  > /etc/freeswitch/tls/wss.pem
cp /etc/freeswitch/tls/wss.pem /etc/freeswitch/tls/tls.pem
chown -R freeswitch:freeswitch /etc/freeswitch/tls
chmod 640 /etc/freeswitch/tls/*.pem

log "setting domain and external IP"
sed -i -E "s#(data=\"domain=)[^\"]*#\1${DOMAIN}#"            /etc/freeswitch/vars.xml
sed -i -E "s#(data=\"external_rtp_ip=)[^\"]*#\1${PUBLIC_IP}#" /etc/freeswitch/vars.xml
sed -i -E "s#(data=\"external_sip_ip=)[^\"]*#\1${PUBLIC_IP}#" /etc/freeswitch/vars.xml

log "writing the webrtc SIP profile (WSS on 7443)"
cat > /etc/freeswitch/sip_profiles/webrtc.xml <<'EOF'
<profile name="webrtc">
  <settings>
    <param name="context" value="default"/>
    <param name="dialplan" value="XML"/>
    <param name="sip-port" value="5061"/>
    <param name="tls" value="true"/>
    <param name="tls-only" value="true"/>
    <param name="tls-cert-dir" value="/etc/freeswitch/tls"/>
    <param name="wss-binding" value=":7443"/>
    <param name="rtp-ip" value="$${local_ip_v4}"/>
    <param name="sip-ip" value="$${local_ip_v4}"/>
    <param name="ext-rtp-ip" value="$${external_rtp_ip}"/>
    <param name="ext-sip-ip" value="$${external_sip_ip}"/>
    <param name="rtp-secure-media" value="mandatory:AEAD_AES_256_GCM_8"/>
    <param name="apply-candidate-acl" value="wan_v4.auto"/>
    <param name="inbound-late-negotiation" value="true"/>
    <param name="inbound-zrtp-passthru" value="true"/>
    <param name="auth-calls" value="true"/>
    <param name="challenge-realm" value="auto_from"/>
    <param name="accept-blind-reg" value="false"/>
    <param name="rtp-timeout-sec" value="300"/>
    <param name="rtp-hold-timeout-sec" value="1800"/>
  </settings>
</profile>
EOF

log "creating line $EXT_USER with caller ID $FAKE_CID"
cat > /etc/freeswitch/directory/default/${EXT_USER}.xml <<'EOF'
<include>
  <user id="__EXT_USER__">
    <params>
      <param name="password" value="__EXT_PASS__"/>
      <param name="max-registrations" value="2"/>
    </params>
    <variables>
      <variable name="effective_caller_id_number" value="__FAKE_CID__"/>
      <variable name="effective_caller_id_name" value="__FAKE_CID_NAME__"/>
      <variable name="outbound_caller_id_number" value="__FAKE_CID__"/>
      <variable name="outbound_caller_id_name" value="__FAKE_CID_NAME__"/>
      <variable name="user_context" value="default"/>
    </variables>
  </user>
</include>
EOF
sed -i "s/__EXT_USER__/${EXT_USER}/g; s/__EXT_PASS__/${EXT_PASS}/g; \
        s/__FAKE_CID_NAME__/${FAKE_CID_NAME}/g; s/__FAKE_CID__/${FAKE_CID}/g" \
  /etc/freeswitch/directory/default/${EXT_USER}.xml

log "writing the outbound dialplan"
cat > /etc/freeswitch/dialplan/default/00_outbound.xml <<'EOF'
<include>
  <extension name="outbound_trunk">
    <condition field="destination_number" expression="^(\+?[0-9]{7,15})$">
      <action application="set" data="hangup_after_bridge=true"/>
      <action application="set" data="continue_on_fail=true"/>
      <action application="bridge" data="sofia/gateway/trunk/$1"/>
    </condition>
  </extension>
</include>
EOF

log "writing the trunk gateway"
cat > /etc/freeswitch/sip_profiles/external/trunk.xml <<'EOF'
<include>
  <gateway name="trunk">
    <param name="proxy" value="__TRUNK_HOST__"/>
    <param name="realm" value="__TRUNK_HOST__"/>
    <param name="username" value="__TRUNK_USER__"/>
    <param name="password" value="__TRUNK_PASS__"/>
    <param name="from-domain" value="__TRUNK_HOST__"/>
    <param name="register" value="true"/>
    <param name="caller-id-in-from" value="true"/>
    <param name="retry-seconds" value="30"/>
  </gateway>
</include>
EOF
sed -i "s/__TRUNK_HOST__/${TRUNK_HOST}/g; s/__TRUNK_USER__/${TRUNK_USER}/g; \
        s/__TRUNK_PASS__/${TRUNK_PASS}/g" /etc/freeswitch/sip_profiles/external/trunk.xml

log "opening the firewall"
ufw allow 22/tcp    >/dev/null
ufw allow 80/tcp    >/dev/null
ufw allow 5060/udp  >/dev/null
ufw allow 5060/tcp  >/dev/null
ufw allow 5061/tcp  >/dev/null
ufw allow 7443/tcp  >/dev/null
ufw allow 16384:32768/udp >/dev/null
ufw --force enable  >/dev/null

log "starting freeswitch"
systemctl enable --now freeswitch
sleep 6

cat > /root/sip-test.html <<'EOF'
<!doctype html><html><head><meta charset="utf-8"><title>SIP WSS test</title>
<style>body{font-family:system-ui;max-width:620px;margin:40px auto;padding:0 16px}
input{width:100%;padding:8px;margin:4px 0;box-sizing:border-box}
button{padding:10px 18px;margin:6px 6px 6px 0;cursor:pointer}
pre{background:#f4f4f4;padding:12px;height:240px;overflow:auto;font-size:12px}</style></head>
<body><h2>WSS registration test</h2>
<input id="wss"   value="wss://__DOMAIN__:7443">
<input id="user"  value="__EXT_USER__">
<input id="pass"  value="__EXT_PASS__">
<input id="dest"  value="9196" placeholder="9196 = echo test">
<button onclick="reg()">Register</button>
<button onclick="call()">Call</button>
<button onclick="hang()">Hang up</button>
<pre id="log"></pre>
<script src="https://cdn.jsdelivr.net/npm/sip.js@0.21.1/lib/bundle.min.js"></script>
<script>
const g=id=>document.getElementById(id), L=m=>{g('log').textContent+=m+"\n";g('log').scrollTop=1e6;};
let ua=null,sess=null;
function reg(){
  const host=g('wss').value.split("/")[2].split(":")[0];
  const uri=SIP.UserAgent.makeURI("sip:"+g('user').value+"@"+host);
  ua=new SIP.UserAgent({uri,transportOptions:{server:g('wss').value},
    authorizationUsername:g('user').value,authorizationPassword:g('pass').value,
    delegate:{onConnect:()=>L("registered"),onDisconnect:e=>L("disconnected: "+e)}});
  ua.start().then(()=>L("connecting...")).catch(e=>L("ERR "+e));
}
function call(){
  if(!ua||!ua.isConnected())return L("not registered");
  const host=g('wss').value.split("/")[2].split(":")[0];
  const t=SIP.UserAgent.makeURI("sip:"+g('dest').value+"@"+host);
  sess=new SIP.Inviter(ua,t,{sessionDescriptionHandlerOptions:{constraints:{audio:true,video:false}}});
  sess.stateChange.addListener(s=>L("call: "+s));
  sess.invite().catch(e=>L("ERR "+e));
}
function hang(){ if(sess) sess.bye().catch(()=>{}); }
</script></body></html>
EOF
sed -i "s/__DOMAIN__/${DOMAIN}/g; s/__EXT_USER__/${EXT_USER}/g; s/__EXT_PASS__/${EXT_PASS}/g" /root/sip-test.html

log "VERIFY"
fs_cli -x "status"                     || true
fs_cli -x "sofia status"               || true
fs_cli -x "sofia status gateway trunk" || true

cat <<EOF

============================================================
Switch built. Now prove it, in this order.

1) TRUNK UP    - look for "State: REGED" above. FAILED = bad credentials.
2) WSS LISTEN  - look for the webrtc profile, state RUNNING.
3) BROWSER     - open sip-test.html, Register, then Call (echo test).
4) FAKE NUMBER - change dest to your cell and call. Your cell must show
                 $FAKE_CID

Trunk host : $TRUNK_HOST
WSS URL    : wss://$DOMAIN:7443
Line       : $EXT_USER / $EXT_PASS
Caller ID  : $FAKE_CID
============================================================
EOF
