#!/bin/bash
# QeroVPN node setup — turns a clean Ubuntu 24.04+/Debian 12+ server into a QeroVPN server.
#
#   curl -fsSL https://scripts.qero.me/setup.sh | bash -s -- <NODE_TOKEN>
#
# Installs: WireGuard (UDP 51820), IKEv2/IPsec (UDP 500/4500, Let's Encrypt certificate),
#           OpenVPN (UDP 1194 + TCP 8443), Xray VLESS + Reality (TCP 443),
#           the QeroVPN agent (monitoring + user sync).
# Safe to run again: it keeps existing keys and only fixes what is missing.
set -Eeuo pipefail

TOKEN="${1:-}"
BASE="${QERO_BASE:-https://api.qeramaq.com/agent}"
LOG=/var/log/qero-setup.log
WG_PORT=51820
WG_NET=10.8.0.0/16
WG_ADDR=10.8.0.1/16
IKE_POOL=10.9.0.0/16
OVPN_UDP_NET=10.10.0.0
OVPN_TCP_NET=10.11.0.0
REALITY_SNI="${REALITY_SNI:-www.microsoft.com}"
STATE=/etc/qero
export DEBIAN_FRONTEND=noninteractive

[ -n "$TOKEN" ] || { echo "usage: setup.sh <NODE_TOKEN>"; exit 1; }
[ "$(id -u)" = 0 ] || { echo "run as root"; exit 1; }
exec > >(tee -a "$LOG") 2>&1
step() { echo; echo "==> $*"; }
fail() { echo "ERROR: $*"; report_status "failed" "$*"; exit 1; }
trap 'fail "line $LINENO: $BASH_COMMAND"' ERR

api() { # api <method> <path> [json]
  curl -fsS -X "$1" "$BASE$2" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
    -H "User-Agent: qero-setup/1.0" ${3:+--data "$3"}
}
report_status() { api POST /node/status "{\"status\":\"$1\",\"message\":$(printf '%s' "${2:-}" | python3 -c 'import json,sys;print(json.dumps(sys.stdin.read()[:500]))' 2>/dev/null || echo '""')}" >/dev/null 2>&1 || true; }

mkdir -p "$STATE"; chmod 700 "$STATE"

step "Fetching node settings from the panel"
CFG=$(api GET /node/config) || { echo "Invalid or expired token"; exit 1; }
cfg() { printf '%s' "$CFG" | python3 -c "import json,sys;print(json.load(sys.stdin).get('$1') or '')"; }
NODE_HOST=$(cfg host)          # e.g. n71.qero.me (A record created by the panel)
ACME_EMAIL=$(cfg acme_email)
report_status "installing" "started"

step "System packages"
apt-get update -qq
apt-get install -y -qq --no-install-recommends \
  wireguard-tools charon-systemd strongswan-swanctl libcharon-extra-plugins libcharon-extauth-plugins libstrongswan-extra-plugins \
  certbot iptables curl unzip ca-certificates python3 openvpn openssl >/dev/null

step "Kernel networking (forwarding, BBR)"
cat > /etc/sysctl.d/90-qero.conf <<'EOF'
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.ip_no_pmtu_disc = 1
EOF
sysctl -q --system

IFACE=$(ip -4 route get 1.1.1.1 | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1); exit}')
PUBLIC_IP=$(ip -4 route get 1.1.1.1 | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1); exit}')

step "Firewall + NAT"
cat > /usr/local/sbin/qero-nat <<EOF
#!/bin/sh
# Re-applied at boot by qero-nat.service
iptables -t nat -C POSTROUTING -s $WG_NET -o $IFACE -j MASQUERADE 2>/dev/null || iptables -t nat -A POSTROUTING -s $WG_NET -o $IFACE -j MASQUERADE
for NET in $OVPN_UDP_NET/16 $OVPN_TCP_NET/16; do
  iptables -t nat -C POSTROUTING -s \$NET -o $IFACE -j MASQUERADE 2>/dev/null || iptables -t nat -A POSTROUTING -s \$NET -o $IFACE -j MASQUERADE
done
iptables -t nat -C POSTROUTING -s $IKE_POOL -o $IFACE -m policy --dir out --pol none -j MASQUERADE 2>/dev/null || iptables -t nat -A POSTROUTING -s $IKE_POOL -o $IFACE -m policy --dir out --pol none -j MASQUERADE
# Clamp MSS for IPsec clients (avoids stalls on some mobile networks)
iptables -t mangle -C FORWARD -s $IKE_POOL -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1360 2>/dev/null || iptables -t mangle -A FORWARD -s $IKE_POOL -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1360
# Users may not reach each other or the server's private services
iptables -C FORWARD -s $WG_NET -d $WG_NET -j DROP 2>/dev/null || iptables -I FORWARD -s $WG_NET -d $WG_NET -j DROP
exit 0
EOF
chmod 755 /usr/local/sbin/qero-nat
cat > /etc/systemd/system/qero-nat.service <<'EOF'
[Unit]
Description=QeroVPN NAT rules
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/qero-nat
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload; systemctl enable --now qero-nat >/dev/null 2>&1; /usr/local/sbin/qero-nat
if command -v ufw >/dev/null && ufw status | grep -q "Status: active"; then
  ufw allow 22/tcp; ufw allow 80/tcp; ufw allow 443/tcp; ufw allow $WG_PORT/udp; ufw allow 500/udp; ufw allow 4500/udp; ufw allow 1194/udp; ufw allow 8443/tcp
fi

step "WireGuard"
if [ ! -f "$STATE/wg.key" ]; then umask 077; wg genkey > "$STATE/wg.key"; fi
WG_PRIV=$(cat "$STATE/wg.key"); WG_PUB=$(printf '%s' "$WG_PRIV" | wg pubkey)
umask 077
cat > /etc/wireguard/wg0.conf <<EOF
[Interface]
Address = $WG_ADDR
ListenPort = $WG_PORT
PrivateKey = $WG_PRIV
# Peers are added live by the QeroVPN agent (wg set), not stored here.
EOF
umask 022
systemctl enable wg-quick@wg0 >/dev/null 2>&1
systemctl restart wg-quick@wg0

step "TLS certificate for IKEv2 ($NODE_HOST)"
if [ -n "$NODE_HOST" ]; then
  for i in $(seq 1 30); do
    [ "$(getent ahostsv4 "$NODE_HOST" | awk 'NR==1{print $1}')" = "$PUBLIC_IP" ] && break
    echo "waiting for DNS $NODE_HOST -> $PUBLIC_IP ($i)"; sleep 10
  done
  if [ ! -f "/etc/letsencrypt/live/$NODE_HOST/fullchain.pem" ]; then
    certbot certonly --standalone --non-interactive --agree-tos -m "${ACME_EMAIL:-admin@qero.me}" -d "$NODE_HOST" --key-type rsa --rsa-key-size 2048
  fi
  mkdir -p /etc/letsencrypt/renewal-hooks/deploy
  cat > /etc/letsencrypt/renewal-hooks/deploy/qero-ikev2 <<EOF
#!/bin/sh
cp /etc/letsencrypt/live/$NODE_HOST/cert.pem /etc/swanctl/x509/qero.pem
cp /etc/letsencrypt/live/$NODE_HOST/privkey.pem /etc/swanctl/private/qero.pem
# strongSwan reads one certificate per file: split the chain so phones get every intermediate
# (Let's Encrypt YR2 -> Root YR cross-signed by ISRG Root X1).
rm -f /etc/swanctl/x509ca/qero-chain*.pem
awk 'BEGIN{n=0} /BEGIN CERTIFICATE/{n++} n>0{print > ("/etc/swanctl/x509ca/qero-chain-" n ".pem")}' /etc/letsencrypt/live/$NODE_HOST/chain.pem
[ -f /etc/ssl/certs/ISRG_Root_X1.pem ] && cp /etc/ssl/certs/ISRG_Root_X1.pem /etc/swanctl/x509ca/qero-root-x1.pem
swanctl --load-creds >/dev/null 2>&1 || true
EOF
  chmod 755 /etc/letsencrypt/renewal-hooks/deploy/qero-ikev2
  /etc/letsencrypt/renewal-hooks/deploy/qero-ikev2
else
  echo "No hostname from the panel: IKEv2 skipped"
fi

step "IKEv2 (strongSwan)"
if [ -n "$NODE_HOST" ]; then
cat > /etc/swanctl/conf.d/qero.conf <<EOF
connections {
  qero-ikev2 {
    version = 2
    local_addrs = %any
    proposals = aes256gcm16-prfsha384-ecp384,aes256-sha256-modp2048,aes256-sha256-ecp256,aes128-sha256-modp2048,default
    unique = never
    send_cert = always
    dpd_delay = 30s
    rekey_time = 0
    pools = qero-pool
    local {
      auth = pubkey
      certs = qero.pem
      id = $NODE_HOST
    }
    remote {
      auth = eap-mschapv2
      eap_id = %any
    }
    children {
      net {
        local_ts = 0.0.0.0/0
        esp_proposals = aes256gcm16,aes256-sha256,aes128-sha256,default
        dpd_action = clear
        rekey_time = 0
      }
    }
  }
}
pools {
  qero-pool {
    addrs = $IKE_POOL
    dns = 1.1.1.1, 8.8.8.8
  }
}
include /etc/swanctl/qero-users.conf
EOF
  [ -f /etc/swanctl/qero-users.conf ] || printf 'secrets {\n}\n' > /etc/swanctl/qero-users.conf
  chmod 600 /etc/swanctl/qero-users.conf
  systemctl enable strongswan >/dev/null 2>&1 || true
  systemctl restart strongswan
  sleep 2; swanctl --load-all >/dev/null
fi

step "OpenVPN (UDP 1194 + TCP 8443, username/password per user)"
OVPN=/etc/openvpn/server
mkdir -p $OVPN /run/openvpn-server
if [ ! -f $OVPN/qero-ca.crt ]; then
  cd $OVPN
  openssl ecparam -name prime256v1 -genkey -noout -out qero-ca.key
  openssl req -x509 -new -key qero-ca.key -sha256 -days 3650 -subj "/CN=QeroVPN CA" -out qero-ca.crt
  openssl ecparam -name prime256v1 -genkey -noout -out qero-server.key
  openssl req -new -key qero-server.key -subj "/CN=qero-server" -out qero-server.csr
  printf 'basicConstraints=CA:FALSE
keyUsage=digitalSignature,keyAgreement
extendedKeyUsage=serverAuth
' > ext.cnf
  openssl x509 -req -in qero-server.csr -CA qero-ca.crt -CAkey qero-ca.key -CAcreateserial -days 3650 -sha256 -extfile ext.cnf -out qero-server.crt
  rm -f qero-server.csr ext.cnf
  openvpn --genkey tls-crypt qero-tc.key
  chmod 600 qero-ca.key qero-server.key qero-tc.key
  cd - >/dev/null
fi
# Login check: compares against the user list the agent keeps in sync with the panel.
cat > /usr/local/sbin/qero-ovpn-auth <<'EOF'
#!/usr/bin/python3
import hashlib, hmac, json, sys
try:
    user, pw = open(sys.argv[1]).read().splitlines()[:2]
    users = json.load(open('/etc/openvpn/server/qero-users.json'))
    want = users.get(user.strip())
    ok = want and hmac.compare_digest(want, hashlib.sha256(pw.strip().encode()).hexdigest())
    sys.exit(0 if ok else 1)
except Exception:
    sys.exit(1)
EOF
chmod 755 /usr/local/sbin/qero-ovpn-auth
[ -f $OVPN/qero-users.json ] || echo '{}' > $OVPN/qero-users.json
chown root:nogroup $OVPN/qero-users.json; chmod 640 $OVPN/qero-users.json
for PROTO in udp tcp; do
  if [ $PROTO = udp ]; then PORT=1194; NET=$OVPN_UDP_NET; else PORT=8443; NET=$OVPN_TCP_NET; fi
  cat > $OVPN/qero-$PROTO.conf <<EOF
port $PORT
proto $PROTO
dev tun-$PROTO
dev-type tun
topology subnet
server $NET 255.255.0.0
ca $OVPN/qero-ca.crt
cert $OVPN/qero-server.crt
key $OVPN/qero-server.key
dh none
tls-crypt $OVPN/qero-tc.key
tls-version-min 1.2
data-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305
verify-client-cert none
username-as-common-name
script-security 2
auth-user-pass-verify /usr/local/sbin/qero-ovpn-auth via-file
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 1.1.1.1"
push "dhcp-option DNS 8.8.8.8"
push "block-outside-dns"
keepalive 10 60
duplicate-cn
max-clients 2000
user nobody
group nogroup
persist-key
persist-tun
status /run/openvpn-server/status-qero-$PROTO.log 10
status-version 2
verb 2
EOF
  # UDP: reply from the same address the client contacted (servers with several IPs / NAT).
  [ $PROTO = udp ] && printf 'explicit-exit-notify 1\nmultihome\n' >> $OVPN/qero-$PROTO.conf
  systemctl enable openvpn-server@qero-$PROTO >/dev/null 2>&1
  systemctl restart openvpn-server@qero-$PROTO
done

step "Xray (VLESS + Reality)"
if ! command -v xray >/dev/null; then
  bash -c "$(curl -fsSL https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install >/dev/null
fi
if [ ! -f "$STATE/reality.json" ]; then
  KEYS=$(xray x25519)
  R_PRIV=$(printf '%s\n' "$KEYS" | awk -F': *' 'tolower($1) ~ /private/ {print $2; exit}')
  R_PUB=$(printf '%s\n' "$KEYS" | awk -F': *' 'tolower($1) ~ /public|password/ {print $2; exit}')
  R_SID=$(openssl rand -hex 8)
  printf '{"private":"%s","public":"%s","short_id":"%s","sni":"%s"}\n' "$R_PRIV" "$R_PUB" "$R_SID" "$REALITY_SNI" > "$STATE/reality.json"
  chmod 600 "$STATE/reality.json"
fi
R_PRIV=$(python3 -c "import json;print(json.load(open('$STATE/reality.json'))['private'])")
R_PUB=$(python3 -c "import json;print(json.load(open('$STATE/reality.json'))['public'])")
R_SID=$(python3 -c "import json;print(json.load(open('$STATE/reality.json'))['short_id'])")
R_SNI=$(python3 -c "import json;print(json.load(open('$STATE/reality.json'))['sni'])")
# Users are written into clients[] by the agent; keep existing ones on re-runs.
CLIENTS='[]'
[ -f /usr/local/etc/xray/config.json ] && CLIENTS=$(python3 -c "import json;c=json.load(open('/usr/local/etc/xray/config.json'));print(json.dumps(c['inbounds'][0]['settings']['clients']))" 2>/dev/null || echo '[]')
cat > /usr/local/etc/xray/config.json <<EOF
{
  "log": { "loglevel": "warning" },
  "inbounds": [{
    "tag": "vless-reality",
    "listen": "0.0.0.0",
    "port": 443,
    "protocol": "vless",
    "settings": { "clients": $CLIENTS, "decryption": "none" },
    "streamSettings": {
      "network": "tcp",
      "security": "reality",
      "realitySettings": {
        "dest": "$R_SNI:443",
        "serverNames": ["$R_SNI"],
        "privateKey": "$R_PRIV",
        "shortIds": ["$R_SID"]
      }
    },
    "sniffing": { "enabled": true, "destOverride": ["http", "tls", "quic"] }
  }],
  "outbounds": [
    { "protocol": "freedom", "tag": "direct" },
    { "protocol": "blackhole", "tag": "block" }
  ],
  "routing": { "rules": [ { "type": "field", "ip": ["geoip:private"], "outboundTag": "block" } ] }
}
EOF
xray run -test -config /usr/local/etc/xray/config.json >/dev/null
systemctl enable xray >/dev/null 2>&1; systemctl restart xray

step "QeroVPN agent"
mkdir -p /opt/qero-agent /var/lib/qero-agent
curl -fsSL "$BASE/agent.py" -o /opt/qero-agent/agent.py
chmod 755 /opt/qero-agent/agent.py
umask 077; printf 'URL=%s/report\nTOKEN=%s\nSYNC_URL=%s/node/sync\n' "$BASE" "$TOKEN" "$BASE" > /etc/qero-agent.conf; umask 022
cat > /etc/systemd/system/qero-agent.service <<'EOF'
[Unit]
Description=QeroVPN agent (monitoring + user sync)
After=network-online.target wg-quick@wg0.service strongswan.service xray.service
Wants=network-online.target
[Service]
ExecStart=/usr/bin/python3 /opt/qero-agent/agent.py
Restart=always
RestartSec=10
MemoryMax=96M
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload; systemctl enable qero-agent >/dev/null 2>&1; systemctl restart qero-agent

step "Registering with the panel"
api POST /node/ready "$(python3 - <<PY
import json
print(json.dumps({
  "public_ip": "$PUBLIC_IP", "host": "$NODE_HOST",
  "wireguard": {"public_key": "$WG_PUB", "port": $WG_PORT, "address": "$WG_ADDR"},
  "reality": {"public_key": "$R_PUB", "short_id": "$R_SID", "sni": "$R_SNI", "port": 443},
  "ikev2": {"enabled": bool("$NODE_HOST")},
  "openvpn": {"udp_port": 1194, "tcp_port": 8443,
              "ca": open("/etc/openvpn/server/qero-ca.crt").read(),
              "tls_crypt": open("/etc/openvpn/server/qero-tc.key").read()},
}))
PY
)" >/dev/null
trap - ERR
echo
echo "QeroVPN node is ready: WireGuard :$WG_PORT/udp, IKEv2 ${NODE_HOST:-(off)}, OpenVPN :1194/udp :8443/tcp, VLESS-Reality :443/tcp"
