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
|