#!/bin/bash
# ============================================================
# ip-sync — in-container public-IP auto-sync (zero-touch deploys).
#
# On boxes with a DYNAMIC public IP (on-prem behind a router), an IP change
# silently breaks call audio: Asterisk keeps announcing the OLD IP in SDP
# (external_media_address / external_signaling_address / the public
# local_net=<ip>/32 hairpin entry). The dashboard's "Server public IP" card
# fixes it manually; this loop makes it automatic — same repair the card does.
#
# Runs forever in the background (started by start.sh). Every CHECK_EVERY
# seconds: detect real public IP → compare with configured → if different,
# rewrite the vault base pjsip.conf, rebuild, and restart Asterisk in-place
# (transports are not reloadable). Static-IP boxes simply never trigger it.
#
# Safety (mirrors the proven host-side wan-ip-sync.sh):
#   - never writes a blank/malformed/private/CGNAT address
#   - refuses to touch the config if the EXISTING value looks wrong
#   - defers while any call is active (restart drops live calls)
#   - single rotating backup + verifies sed left no stale refs before restart
#
# Opt-out: create file  <CONFIG_DIR>/ip-autosync-off  (e.g. the owner box,
# where the host-side cron already owns this job).
# ============================================================

CONF_DIR="${CONFIG_DIR:-/host-config}"
PJSIP="$CONF_DIR/pjsip.conf"
OFF_FILE="$CONF_DIR/ip-autosync-off"
CHECK_EVERY=300
FIRST_DELAY=120

log() { echo "[ip-sync] $*"; }

valid_public_ip() {
  local ip=$1
  [[ $ip =~ ^([0-9]{1,3})\.([0-9]{1,3})\.[0-9]{1,3}\.[0-9]{1,3}$ ]] || return 1
  local a=${BASH_REMATCH[1]} b=${BASH_REMATCH[2]}
  (( a > 0 && a < 224 )) || return 1
  [[ $a == 10 || $a == 127 ]] && return 1
  [[ $a == 192 && $b == 168 ]] && return 1
  [[ $a == 172 && $b -ge 16 && $b -le 31 ]] && return 1
  [[ $a == 169 && $b == 254 ]] && return 1
  [[ $a == 100 && $b -ge 64 && $b -le 127 ]] && return 1
  return 0
}

detect_ip() {
  local ip
  for url in https://api.ipify.org https://ifconfig.me https://icanhazip.com; do
    ip=$(timeout 15 curl -4 -s "$url" 2>/dev/null | tr -d ' \r\n')
    valid_public_ip "$ip" && { echo "$ip"; return 0; }
  done
  return 1
}

sync_once() {
  [ -f "$OFF_FILE" ] && return 0
  [ -f "$PJSIP" ] || return 0

  local OLD
  OLD=$(grep -m1 '^external_media_address=' "$PJSIP" | cut -d= -f2 | tr -d ' \r')
  # Not configured yet (fresh box before dashboard setup) — nothing to sync.
  [ -n "$OLD" ] || return 0
  valid_public_ip "$OLD" || return 0

  local WAN
  WAN=$(detect_ip) || return 0
  [ "$WAN" = "$OLD" ] && return 0

  # Restarting Asterisk drops every live call — defer to the next cycle.
  local ACTIVE
  ACTIVE=$(asterisk -rx "core show channels" 2>/dev/null | awk '/active channels/{print $1}')
  if [ -z "$ACTIVE" ] || [ "$ACTIVE" != "0" ]; then
    log "public IP changed $OLD -> $WAN but ${ACTIVE:-unknown} active channel(s); deferring"
    return 0
  fi

  cp "$PJSIP" "$PJSIP.pre-ipsync.bak" 2>/dev/null
  sed -i "s/\b${OLD//./\\.}\b/$WAN/g" "$PJSIP"
  if grep -q "$OLD" "$PJSIP"; then
    log "ERROR: stale refs to $OLD remain after rewrite - restoring backup, NOT restarting"
    cp "$PJSIP.pre-ipsync.bak" "$PJSIP" 2>/dev/null
    return 0
  fi

  manage-extensions rebuild >/dev/null 2>&1
  asterisk -rx "core restart now" >/dev/null 2>&1
  log "public IP updated $OLD -> $WAN (config rewritten, Asterisk restarted)"
}

sleep "$FIRST_DELAY"
if [ -f "$OFF_FILE" ]; then
  log "disabled by $OFF_FILE"
else
  log "watching public IP (every ${CHECK_EVERY}s)"
fi
while true; do
  sync_once
  sleep "$CHECK_EVERY"
done
