#!/bin/bash
# dialix-console - the server menu of a Dialix appliance.
#
#   sudo dialix-console
#
# Status, network, restarts, logs and updates, without having to know which
# service or file each one is.
set -u

[ "$(id -u)" = 0 ] || exec sudo "$0" "$@"

# The binary knows its version; VERSION is only what the disc installed.
VER="$(/opt/dialix/dialix --version 2>/dev/null | awk '{print $2}')"
TITLE="Dialix ${VER:-$(cat /opt/dialix/VERSION 2>/dev/null)}"
DX=/opt/dialix/dialix
ENV=/opt/dialix/.env
H=20; W=74

services() {
  for s in dialix asterisk dialixsipengine mariadb redis-server dialix-supercenter dialix-root-sql fail2ban ssh; do
    systemctl list-unit-files "$s.service" >/dev/null 2>&1 || continue
    systemctl list-unit-files "$s.service" 2>/dev/null | grep -q "^$s.service" || continue
    printf '  %-20s %s\n' "$s" "$(systemctl is-active "$s" 2>/dev/null)"
  done
}

# Fails when there is none yet, so `console_url || echo ...` shows the hint
# (sed | head always succeeded, and the menu showed a blank line instead).
console_url() {
  local u
  u="$(sed -n 's/^DIALIX_PUBLIC_BASE_URL=//p' "$ENV" 2>/dev/null | head -1)"
  [ -n "$u" ] && printf '%s\n' "$u"
}

# Which SIP engine the server runs (asked of the server itself).
engine_line() {
  [ -x "$DX" ] || { echo "not installed yet"; return; }
  "$DX" engine --dir /opt/dialix 2>/dev/null | sed -n 's/^SIP engine: //p' | head -1
}

status() {
  local t
  t="$(printf 'Console:   %s\nSIP:       %s\nHost:      %s\nAddresses: %s\nUptime:    %s\nDisk free: %s\n\nServices\n%s\n' \
      "$(console_url || echo 'not set up yet')" "$(engine_line)" "$(hostname -f 2>/dev/null || hostname)" \
      "$(hostname -I 2>/dev/null)" "$(uptime -p)" "$(df -h / | awk 'NR==2{print $4" of "$2}')" "$(services)")"
  whiptail --title "$TITLE - status" --msgbox "$t" $H $W
}

iface_default() { ip -4 route show default 2>/dev/null | awk '{print $5; exit}'; }

network() {
  local ifc mode addr gw dns cur
  ifc="$(iface_default)"; ifc="${ifc:-$(ls /sys/class/net | grep -v '^lo$' | head -1)}"
  cur="$(ip -4 -o addr show dev "$ifc" 2>/dev/null | awk '{print $4; exit}')"
  mode=$(whiptail --title "$TITLE - network" --menu "Network card $ifc, now $cur" $H $W 3 \
    dhcp "Automatic address (DHCP)" static "Fixed address" back "Back" 3>&1 1>&2 2>&3) || return
  case "$mode" in
    dhcp)
      write_interfaces "$ifc" "iface $ifc inet dhcp" ;;
    static)
      addr=$(whiptail --inputbox "Address with prefix (e.g. 192.168.1.20/24)" 9 $W "$cur" 3>&1 1>&2 2>&3) || return
      gw=$(whiptail --inputbox "Gateway" 9 $W "$(ip -4 route show default | awk '{print $3; exit}')" 3>&1 1>&2 2>&3) || return
      dns=$(whiptail --inputbox "DNS servers, separated by spaces" 9 $W "$(awk '/^nameserver/{printf "%s ", $2}' /etc/resolv.conf)" 3>&1 1>&2 2>&3) || return
      case "$addr" in */*) ;; *) whiptail --msgbox "Give the prefix too, like /24." 8 $W; return ;; esac
      write_interfaces "$ifc" "iface $ifc inet static
    address $addr
    gateway $gw"
      { echo "# Written by dialix-console."; for d in $dns; do echo "nameserver $d"; done; } > /etc/resolv.conf ;;
    *) return ;;
  esac
  if ! whiptail --yesno "Apply the new network settings now? A connection over SSH may drop." 9 $W; then
    mv -f /etc/network/interfaces.new /etc/network/interfaces
    whiptail --msgbox "Saved. The new settings apply when the server restarts." 8 $W
    return
  fi
  # Down with the settings it came up with, up with the new ones. Restarting
  # `networking` after rewriting the file took the card down using the NEW
  # file, so a DHCP client left running kept the old lease next to the fixed
  # address, and rewrote resolv.conf behind it.
  ifdown --force "$ifc" 2>/dev/null
  mv -f /etc/network/interfaces.new /etc/network/interfaces
  ifup "$ifc" 2>/dev/null || systemctl restart networking
  whiptail --msgbox "Now: $(ip -4 -o addr show dev "$ifc" | awk '{print $4}')\n\nIf the console address changed, update it under Settings > General in Dialix." 10 $W
}

write_interfaces() {
  cp -n /etc/network/interfaces /etc/network/interfaces.orig 2>/dev/null || true
  # Staged: applied by network() once the old configuration is taken down.
  printf '# Written by dialix-console.\nsource /etc/network/interfaces.d/*\n\nauto lo\niface lo inet loopback\n\nallow-hotplug %s\n%s\n' "$1" "$2" > /etc/network/interfaces.new
}

restart() {
  local s
  s=$(whiptail --title "$TITLE - restart" --menu "Restart which service?" $H $W 6 \
    dialix "Dialix console and call engine" asterisk "Asterisk (SIP) - drops calls in progress" \
    dialixsipengine "DialixSipEngine (SIP) - drops calls in progress" \
    all "Everything Dialix uses" back "Back" 3>&1 1>&2 2>&3) || return
  case "$s" in
    # try-restart: only the engine that is in use; the other one stays off.
    dialix) systemctl restart dialix ;;
    asterisk|dialixsipengine)
      if ! systemctl is-active --quiet "$s"; then
        whiptail --msgbox "$s is not running - it is not the SIP engine in use ($(engine_line)).\n\nChoose the engine under 'SIP engine'." 10 $W; return
      fi
      systemctl restart "$s" ;;
    all) for x in mariadb redis-server asterisk dialixsipengine dialix dialix-supercenter dialix-root-sql; do systemctl try-restart "$x" 2>/dev/null; done ;;
    *) return ;;
  esac
  whiptail --msgbox "Restarted.\n\n$(services)" $H $W
}

logs() {
  local s
  s=$(whiptail --title "$TITLE - logs" --menu "Show the log of" $H $W 6 \
    dialix "Dialix" asterisk "Asterisk" dialixsipengine "DialixSipEngine" live "Dialix, live (new lines as they come)" \
    setup "First-boot setup" install "Installer (post-install)" 3>&1 1>&2 2>&3) || return
  if [ "$s" = live ]; then
    clear
    echo "Following the Dialix log. Press Ctrl+C to go back to the menu."
    echo
    # Ctrl+C ends journalctl only; the menu (the parent) carries on.
    trap ':' INT
    journalctl -u dialix -n 50 -f --no-pager
    trap - INT
    return 0
  fi
  # -K: Ctrl+C leaves the log as well as q. The prompt says so, because a
  # pager that ignores Ctrl+C looks like a hung screen.
  local pager=(less -K -R +G -P "Log of $s  -  q or Ctrl+C\: back to the menu  -  arrows/PgUp\: scroll  -  /\: search")
  case "$s" in
    install) "${pager[@]}" /var/log/dialix-install.log ;;
    # Run by Dialix itself (not systemd), the engine logs to a file.
    dialixsipengine)
      if [ -s /opt/dialix/sipengine/engine.log ] && ! systemctl is-active --quiet dialixsipengine; then
        "${pager[@]}" /opt/dialix/sipengine/engine.log
      else
        journalctl -u dialixsipengine --no-pager -n 400 | "${pager[@]}"
      fi ;;
    setup) journalctl -u dialix-setup --no-pager -n 400 | "${pager[@]}" ;;
    *) journalctl -u "$s" --no-pager -n 400 | "${pager[@]}" ;;
  esac
  return 0
}

doctor() {
  clear
  if [ ! -x /opt/dialix/dialix ]; then
    echo "Dialix is not installed yet: finish the setup screen first."
  else
    /opt/dialix/dialix doctor --dir /opt/dialix
    echo
    if read -r -p "Do the safe repairs now? [y/N] " a && [ "${a,,}" = y ]; then
      echo; /opt/dialix/dialix doctor --fix --dir /opt/dialix
    fi
  fi
  echo; read -r -p "Press Enter to return to the menu. " _
}

update() {
  whiptail --yesno "Install Debian security and bug-fix updates now?\n\nThis needs internet. Dialix itself is updated under 'Update Dialix' in this menu, or in the console (Settings > Updates)." 12 $W || return
  clear
  apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y upgrade
  echo; read -r -p "Press Enter to return to the menu. " _
}

# Dialix itself, the way Settings > Updates does it: the server downloads and
# checks the release, swaps it in (this menu, the command line and the other
# Dialix programs too) and rolls back by itself if it does not come up.
update_dialix() {
  local out
  [ -x "$DX" ] || { whiptail --msgbox "Dialix is not installed yet: finish the setup screen first." 8 $W; return; }
  out="$("$DX" update --dir /opt/dialix 2>&1)"
  case "$out" in
    *"is available"*) ;;
    *) whiptail --title "$TITLE - update" --msgbox "$out" 10 $W; return ;;
  esac
  whiptail --title "$TITLE - update" --yesno "$out\n\nInstall it now? Dialix restarts for a moment. It will not start while calls are up." 12 $W || return
  clear
  echo "Downloading and checking the update - this can take a few minutes."
  local res log
  # Shown as it runs, and kept to see why it stopped: only calls in progress
  # are a reason to offer forcing it - after a checksum or network error,
  # dropping the calls would not help.
  log="$(mktemp)"
  "$DX" update --install --dir /opt/dialix 2>&1 | tee "$log" || true
  res="$(cat "$log")"; rm -f "$log"
  case "$res" in
    *"call(s) in progress"*)
      echo
      if read -r -p "Install anyway, dropping the calls in progress? [y/N] " a && [ "${a,,}" = y ]; then
        "$DX" update --install --force --dir /opt/dialix
      fi ;;
  esac
  echo; read -r -p "Press Enter to return to the menu. " _
}

# Asterisk or DialixSipEngine (or neither). The server does the switching:
# the other engine is stopped (both use port 5060) and the ARI bridge follows.
engine() {
  local cur to
  [ -x "$DX" ] || { whiptail --msgbox "Dialix is not installed yet: finish the setup screen first." 8 $W; return; }
  cur="$(engine_line)"
  to=$(whiptail --title "$TITLE - SIP engine" --menu "The SIP engine in use: ${cur:-unknown}" $H $W 4 \
    asterisk "Asterisk - desk phones, trunks, IVR, voicemail, recording" \
    dialixengine "DialixSipEngine (experimental) - phones, queues, trunks" \
    none "No SIP engine - browser, app and carrier calling only" \
    back "Back" 3>&1 1>&2 2>&3) || return
  [ "$to" = back ] && return
  whiptail --yesno "Switch the SIP engine to $to?\n\nCalls on the engine that stops are dropped." 10 $W || return
  whiptail --title "$TITLE - SIP engine" --msgbox "$("$DX" engine "$to" --dir /opt/dialix 2>&1)" 12 $W
}

while true; do
  c=$(whiptail --title "$TITLE" --menu "$(console_url || echo 'Setup has not finished: open the setup screen.')" $H $W 12 \
    status "Status and addresses" \
    doctor "Check and repair (dialix doctor)" \
    dxupdate "Update Dialix" \
    engine "SIP engine (Asterisk or DialixSipEngine)" \
    network "Network (DHCP or fixed address)" \
    restart "Restart services" \
    logs "Logs" \
    update "Install Debian updates" \
    shell "Command line (type exit to return)" \
    reboot "Restart the server" \
    poweroff "Shut down" \
    quit "Leave this menu" 3>&1 1>&2 2>&3) || exit 0
  case "$c" in
    status) status ;;
    doctor) doctor ;;
    dxupdate) update_dialix ;;
    engine) engine ;;
    network) network ;;
    restart) restart ;;
    logs) logs ;;
    update) update ;;
    shell) clear; bash -l ;;
    reboot) whiptail --yesno "Restart the server now? Calls in progress are dropped." 8 $W && systemctl reboot ;;
    poweroff) whiptail --yesno "Shut the server down now?" 8 $W && systemctl poweroff ;;
    quit) clear; exit 0 ;;
  esac
done
