#!/usr/bin/env bash
#
# Puts the PointDesk relay on a Linux server.
# Safe to run twice. Running it again rebuilds and restarts.

set -euo pipefail

PORT=7788
MAX_SESSIONS=64
UNINSTALL=0

BIN=/usr/local/bin/pointdesk-relay
UNIT=/etc/systemd/system/pointdesk-relay.service
SERVICE=pointdesk-relay
USER_NAME=pointdesk

usage() {
    cat <<'EOF'
Usage:
  sudo ./install.sh                  install or upgrade, port 7788
  sudo ./install.sh --port 9000      use a different port
  sudo ./install.sh --uninstall      take it off again
EOF
}

while [ $# -gt 0 ]; do
    case "$1" in
        --port)         PORT="$2"; shift 2 ;;
        --max-sessions) MAX_SESSIONS="$2"; shift 2 ;;
        --uninstall)    UNINSTALL=1; shift ;;
        -h|--help)      usage; exit 0 ;;
        *) echo "unknown option: $1" >&2; usage >&2; exit 1 ;;
    esac
done

if [ "$(id -u)" -ne 0 ]; then
    echo "Run this with sudo." >&2
    exit 1
fi

say() { printf '\n\033[1;32m==>\033[0m %s\n' "$*"; }


# Uninstall

if [ "$UNINSTALL" -eq 1 ]; then
    say "Removing the relay"
    systemctl disable --now "$SERVICE" 2>/dev/null || true
    rm -f "$UNIT" "$BIN"
    systemctl daemon-reload
    userdel "$USER_NAME" 2>/dev/null || true
    echo "Done. If you opened port $PORT in a firewall, that rule is still there."
    exit 0
fi


# Compiler

SRC_DIR="$(cd "$(dirname "$0")" && pwd)"
SRC="$SRC_DIR/pointdesk-relay.cpp"

if [ ! -f "$SRC" ]; then
    echo "pointdesk-relay.cpp is not next to this script." >&2
    exit 1
fi

if ! command -v g++ >/dev/null 2>&1; then
    say "Installing a compiler"
    if command -v apt-get >/dev/null 2>&1; then
        export DEBIAN_FRONTEND=noninteractive
        apt-get update -qq
        apt-get install -y -qq build-essential
    elif command -v dnf >/dev/null 2>&1; then
        dnf install -y -q gcc-c++
    elif command -v yum >/dev/null 2>&1; then
        yum install -y -q gcc-c++
    elif command -v apk >/dev/null 2>&1; then
        apk add --no-cache g++
    else
        echo "No package manager I know. Install g++ and run this again." >&2
        exit 1
    fi
fi


# Build

say "Building"
g++ -O2 -std=c++20 -Wall -Wextra -o "$BIN.new" "$SRC"
mv -f "$BIN.new" "$BIN"
chmod 755 "$BIN"
echo "$BIN"


# Service
#
# Its own account with no shell and no home. The relay never touches the disk
# and never needs to be anybody.

if ! id -u "$USER_NAME" >/dev/null 2>&1; then
    useradd --system --no-create-home --shell /usr/sbin/nologin "$USER_NAME" 2>/dev/null \
        || useradd --system --no-create-home --shell /sbin/nologin "$USER_NAME"
fi

# Ports below 1024 need a capability. Above that it needs none at all.
if [ "$PORT" -lt 1024 ]; then
    CAPS="AmbientCapabilities=CAP_NET_BIND_SERVICE"
else
    CAPS="CapabilityBoundingSet="
fi

say "Writing the service"
cat > "$UNIT" <<EOF
[Unit]
Description=PointDesk relay
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=$BIN --port $PORT --max-sessions $MAX_SESSIONS
User=$USER_NAME
Restart=always
RestartSec=2

# It only needs a socket. Everything else is closed off.
NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictAddressFamilies=AF_INET AF_INET6
RestrictNamespaces=yes
LockPersonality=yes
MemoryDenyWriteExecute=yes
SystemCallArchitectures=native
$CAPS

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable "$SERVICE" >/dev/null 2>&1 || true
systemctl restart "$SERVICE"

sleep 1
if ! systemctl is-active --quiet "$SERVICE"; then
    echo "The service did not start. What it said:" >&2
    journalctl -u "$SERVICE" -n 20 --no-pager >&2
    exit 1
fi


# Firewall

OPENED=""
if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q "^Status: active"; then
    ufw allow "$PORT"/tcp >/dev/null
    OPENED="ufw"
elif command -v firewall-cmd >/dev/null 2>&1 && firewall-cmd --state >/dev/null 2>&1; then
    firewall-cmd --permanent --add-port="$PORT"/tcp >/dev/null
    firewall-cmd --reload >/dev/null
    OPENED="firewalld"
fi


# Done

IP="$(curl -fsS --max-time 5 https://api.ipify.org 2>/dev/null || true)"
[ -z "$IP" ] && IP="$(hostname -I 2>/dev/null | awk '{print $1}')"
[ -z "$IP" ] && IP="your.server.ip"

printf '\n\033[1;32mPointDesk relay is running.\033[0m\n\n'

cat <<EOF
  Server   $IP
  Port     $PORT

To share a screen
  Set "Share this screen" to "Relay server".
  Put in the server and port above, keep the code on, press Start sharing.
  Your ID appears, nine digits. Read out the ID and the code.

To watch one
  Set "Connect to another PC" to "By ID".
  Type their ID, the same server, port and code, then press Connect.

The ID says which machine to reach. The code says you are allowed in.
This server only ever sees the ID.

Only this server needs a port forward. The two PCs do not.
EOF

if [ -n "$OPENED" ]; then
    printf '\nPort %s is open in %s.\n' "$PORT" "$OPENED"
else
    printf '\nNo active firewall here. If your provider has one in its panel, allow TCP %s.\n' "$PORT"
fi

cat <<EOF

  Watch it    journalctl -u $SERVICE -f
  Stop it     systemctl stop $SERVICE
  Remove it   sudo ./install.sh --uninstall

EOF
