sueta / deploy / install-server.sh
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#!/usr/bin/env bash
# One-shot server installer for the sueta p2p stack: coturn (STUN/TURN) +
# sueta-signal (blind relay, TURN credential minter, built-in Let's Encrypt).
#
# Works on any apt-based box (Ubuntu 20.04+, Debian 11+), fresh or not.
# Idempotent: keeps existing secrets/config on re-runs (use --force to
# re-render configs). Run as root ON the server:
#
#   sudo ./install-server.sh signal.example.com [turn.example.com]
#
# One hostname is enough — STUN/TURN (:3478) and the relay (:443) can share it.
# Point DNS A record(s) at this box BEFORE running (Let's Encrypt needs it).
set -euo pipefail

GO_VERSION=1.26.0
REPO_URL="${REPO_URL:-https://git-chat.ardegazu.ro/sueta.git}"
SRC_DIR=/opt/sueta

SIGNAL_HOST="${1:-}"
TURN_HOST="${2:-$SIGNAL_HOST}"
FORCE=0
[ "${3:-}" = "--force" ] && FORCE=1

say() { printf '\n\033[1;32m== %s\033[0m\n' "$*"; }
die() { printf '\033[1;31mERROR: %s\033[0m\n' "$*" >&2; exit 1; }

[ -n "$SIGNAL_HOST" ] || die "usage: $0 <signal-hostname> [turn-hostname] [--force]"
[ "$(id -u)" = 0 ] || die "run as root (sudo)"
command -v apt-get >/dev/null || die "apt-based systems only (Ubuntu/Debian)"

say "checking DNS for $SIGNAL_HOST"
MYIP=$(curl -4 -s --max-time 10 https://ifconfig.me || true)
RESOLVED=$(getent ahostsv4 "$SIGNAL_HOST" | awk '{print $1; exit}' || true)
if [ -z "$RESOLVED" ]; then
  echo "WARNING: $SIGNAL_HOST does not resolve yet — TLS certificates will fail until DNS points here ($MYIP)."
elif [ -n "$MYIP" ] && [ "$RESOLVED" != "$MYIP" ]; then
  echo "WARNING: $SIGNAL_HOST resolves to $RESOLVED but this box looks like $MYIP."
fi

say "installing packages (coturn, git, curl)"
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq coturn git curl ca-certificates >/dev/null

say "installing Go $GO_VERSION toolchain"
ARCH=$(dpkg --print-architecture) # amd64 | arm64
if ! /usr/local/go/bin/go version 2>/dev/null | grep -q "go$GO_VERSION"; then
  curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-${ARCH}.tar.gz" -o /tmp/go.tgz
  rm -rf /usr/local/go
  tar -C /usr/local -xzf /tmp/go.tgz
  rm /tmp/go.tgz
fi

say "fetching sueta source"
if [ -d "$(dirname "$0")/../server" ]; then
  SRC_DIR=$(cd "$(dirname "$0")/.." && pwd) # running from a checkout
  echo "using local checkout at $SRC_DIR"
elif [ -d "$SRC_DIR/.git" ]; then
  git -C "$SRC_DIR" pull --ff-only || true
else
  git clone "$REPO_URL" "$SRC_DIR"
fi

say "building sueta-signal"
(cd "$SRC_DIR/server" && /usr/local/go/bin/go build -trimpath -ldflags="-s -w" -o /usr/local/bin/sueta-signal .)

say "generating shared TURN secret (kept across re-runs)"
if [ ! -f /etc/sueta-signal.secret ]; then
  (umask 077 && openssl rand -hex 32 > /etc/sueta-signal.secret)
fi
SECRET=$(cat /etc/sueta-signal.secret)

say "writing /etc/turnserver.conf"
if [ ! -f /etc/turnserver.conf.sueta ] || [ "$FORCE" = 1 ]; then
  sed -e "s/__STATIC_AUTH_SECRET__/$SECRET/" \
      -e "s/^realm=.*/realm=$TURN_HOST/" \
      "$SRC_DIR/deploy/turnserver.conf" > /etc/turnserver.conf
  touch /etc/turnserver.conf.sueta # marker: managed by this installer
else
  echo "kept existing config (rerun with --force to re-render)"
fi
# some Ubuntu releases ship coturn disabled by default
[ -f /etc/default/coturn ] && sed -i 's/^#*TURNSERVER_ENABLED=.*/TURNSERVER_ENABLED=1/' /etc/default/coturn || true

say "writing /etc/sueta-signal.env"
if [ ! -f /etc/sueta-signal.env ] || [ "$FORCE" = 1 ]; then
  cat > /etc/sueta-signal.env <<EOF
ALLOWED_HOSTS=$SIGNAL_HOST
# tighten to your app origins once deployed, e.g. https://chat.example.com
ORIGINS=
STATIC_AUTH_SECRET=$SECRET
TURN_URLS=stun:$TURN_HOST:3478,turn:$TURN_HOST:3478?transport=udp,turn:$TURN_HOST:3478?transport=tcp
EOF
  chmod 600 /etc/sueta-signal.env
else
  echo "kept existing env (rerun with --force to re-render)"
fi

say "installing systemd unit"
cp "$SRC_DIR/deploy/sueta-signal.service" /etc/systemd/system/sueta-signal.service
systemctl daemon-reload
systemctl enable --now coturn sueta-signal >/dev/null 2>&1 || true
systemctl restart coturn sueta-signal

say "firewall"
if command -v ufw >/dev/null && ufw status | grep -q "Status: active"; then
  ufw allow 80/tcp && ufw allow 443/tcp && ufw allow 3478/tcp && ufw allow 3478/udp && ufw allow 49152:65535/udp
  echo "ufw rules added"
else
  echo "no active ufw — ensure 80/tcp 443/tcp 3478/tcp+udp 49152-65535/udp are reachable"
fi

sleep 2
systemctl --no-pager --quiet is-active coturn || die "coturn failed to start (journalctl -u coturn)"
systemctl --no-pager --quiet is-active sueta-signal || die "sueta-signal failed to start (journalctl -u sueta-signal)"

say "done — verify"
cat <<EOF
  curl https://$SIGNAL_HOST/healthz            # after DNS + first cert (~30s)
  curl https://$SIGNAL_HOST/turn-credentials

client build for this server:
  VITE_SIGNAL_URL=wss://$SIGNAL_HOST/ws npm run build

services: coturn (STUN/TURN :3478, relay ports 49152-65535/udp)
          sueta-signal (:443 TLS via Let's Encrypt, :80 ACME)
secret:   /etc/sueta-signal.secret   env: /etc/sueta-signal.env
EOF