#!/usr/bin/env bash
#
# ZyloVPN node provisioning — Ubuntu 24.04 LTS.
#
# Installs WireGuard, configures forwarding and NAT, installs the agent, and
# leaves it running. Idempotent: safe to re-run on a node that is already set up.
#
#   sudo ./install.sh --panel https://panel.zylovpn.com --token zylo_node_xxx
#
set -euo pipefail

PANEL_URL=""
AGENT_TOKEN=""
WG_INTERFACE="wg0"
WG_PORT="51820"
SSH_PORT="22"

usage() {
    cat <<'EOF'
Usage: sudo ./install.sh --panel <url> --token <token> [options]

Required:
  --panel <url>       Control plane base URL, e.g. https://panel.zylovpn.com
  --token <token>     Agent token from the admin panel

Options:
  --interface <name>  WireGuard interface name (default: wg0)
  --port <port>       WireGuard UDP port (default: 51820)
  --ssh-port <port>   SSH port to keep open in the firewall (default: 22)
  -h, --help          Show this message
EOF
}

while [[ $# -gt 0 ]]; do
    case "$1" in
        --panel)     PANEL_URL="$2"; shift 2 ;;
        --token)     AGENT_TOKEN="$2"; shift 2 ;;
        --interface) WG_INTERFACE="$2"; shift 2 ;;
        --port)      WG_PORT="$2"; shift 2 ;;
        --ssh-port)  SSH_PORT="$2"; shift 2 ;;
        -h|--help)   usage; exit 0 ;;
        *) echo "Unknown option: $1" >&2; usage; exit 1 ;;
    esac
done

[[ $EUID -eq 0 ]] || { echo "Run as root." >&2; exit 1; }
[[ -n "$PANEL_URL" ]] || { echo "--panel is required." >&2; exit 1; }
[[ -n "$AGENT_TOKEN" ]] || { echo "--token is required." >&2; exit 1; }

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
say() { printf '\n\033[1;34m==>\033[0m %s\n' "$1"; }

# ---------------------------------------------------------------- packages
say "Installing packages"
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq wireguard wireguard-tools python3 ufw iptables-persistent

# ------------------------------------------------------------- IP forwarding
say "Enabling IP forwarding"
# Written to a file rather than set live, so it survives reboot. Without this
# the tunnel establishes and then routes nothing — the single most common
# cause of "connected but no internet".
cat > /etc/sysctl.d/99-zylovpn.conf <<EOF
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
EOF
sysctl -q --system

# --------------------------------------------------------------------- NAT
say "Configuring NAT"
# Egress interface: the one carrying the default route. Hardcoding eth0 breaks
# on any host that names it ens3, enp1s0, or anything else.
EGRESS="$(ip -4 route show default | awk '/default/ {print $5; exit}')"
[[ -n "$EGRESS" ]] || { echo "Could not determine the default-route interface." >&2; exit 1; }
echo "    egress interface: $EGRESS"

# -C tests for the rule first, so re-running does not stack duplicates.
if ! iptables -t nat -C POSTROUTING -o "$EGRESS" -j MASQUERADE 2>/dev/null; then
    iptables -t nat -A POSTROUTING -o "$EGRESS" -j MASQUERADE
fi
if ! iptables -C FORWARD -i "$WG_INTERFACE" -j ACCEPT 2>/dev/null; then
    iptables -A FORWARD -i "$WG_INTERFACE" -j ACCEPT
fi
if ! iptables -C FORWARD -o "$WG_INTERFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null; then
    iptables -A FORWARD -o "$WG_INTERFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT
fi

netfilter-persistent save >/dev/null 2>&1 || true

# ---------------------------------------------------------------- firewall
say "Configuring firewall"
# Order matters: allow SSH before enabling, or this script locks itself out.
ufw allow "${SSH_PORT}/tcp" >/dev/null
ufw allow "${WG_PORT}/udp" >/dev/null
ufw default deny incoming >/dev/null
ufw default allow outgoing >/dev/null
ufw route allow in on "$WG_INTERFACE" >/dev/null 2>&1 || true
ufw --force enable >/dev/null
echo "    open: ${SSH_PORT}/tcp (ssh), ${WG_PORT}/udp (wireguard)"

# ------------------------------------------------------------------- agent
say "Installing zylo-agent"
install -m 0755 "$SCRIPT_DIR/zylo-agent" /usr/local/bin/zylo-agent
install -d -m 0700 /etc/zylovpn
install -d -m 0700 /etc/wireguard

# 0600 before the token is written into it.
umask 077
cat > /etc/zylovpn/agent.conf <<EOF
[agent]
panel_url = ${PANEL_URL}
token = ${AGENT_TOKEN}
interface = ${WG_INTERFACE}
poll_interval = 5
usage_interval = 60
EOF
chmod 600 /etc/zylovpn/agent.conf

install -m 0644 "$SCRIPT_DIR/zylo-agent.service" /etc/systemd/system/zylo-agent.service

# ------------------------------------------------------- verify before enabling
say "Testing connectivity to the control plane"
# One cycle in the foreground. Better to fail here with a readable error than
# to enable a unit that crash-loops in the background.
if ! /usr/local/bin/zylo-agent --once; then
    echo
    echo "The agent could not complete a cycle. Common causes:" >&2
    echo "  * wrong or rotated token          -> rotate it in the admin panel" >&2
    echo "  * panel URL unreachable from here -> check DNS and egress rules" >&2
    echo "  * no IP pool on this server yet   -> add one in the admin panel" >&2
    echo >&2
    echo "Config is at /etc/zylovpn/agent.conf. Nothing was enabled." >&2
    exit 1
fi

say "Starting service"
systemctl daemon-reload
systemctl enable --now zylo-agent >/dev/null
sleep 2
systemctl is-active --quiet zylo-agent && echo "    zylo-agent is running"

cat <<EOF

$(printf '\033[1;32m✓\033[0m') Node provisioned.

  Interface : ${WG_INTERFACE} on UDP ${WG_PORT}
  Egress    : ${EGRESS}
  Agent     : systemctl status zylo-agent
  Logs      : journalctl -u zylo-agent -f
  Peers     : wg show ${WG_INTERFACE}

The node should show Online in the admin panel within a few seconds.
EOF
