Files
abyssVpn/deploy/remote/setup_vpn_admin_access.sh
T
arthur_chen f69efb67ad Initial commit: abyssVpn project
- Tauri desktop app (apps/desktop)
- Python Django API service (services/api)
- Deployment scripts and docs
2026-05-15 09:06:10 +08:00

228 lines
6.1 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
BASE_DOMAIN="${NETMAKER_BASE_DOMAIN:-vpn.abyssinfo.com}"
NETWORK_ID="${NETMAKER_NETWORK_ID:-homevpn}"
ENV_FILE="${NETMAKER_ENV_FILE:-/root/netmaker/netmaker.env}"
INSTALL_DIR="${NETCLIENT_INSTALL_DIR:-/usr/local/bin}"
NETCLIENT_VERSION="${NETCLIENT_VERSION:-v1.5.1}"
ADMIN_PORT="${VPN_ADMIN_PORT:-9090}"
NETMAKER_DIR="${NETMAKER_DIR:-/root/netmaker}"
CADDY_FILE="${NETMAKER_DIR}/Caddyfile"
COMPOSE_FILE="${NETMAKER_DIR}/docker-compose.yml"
if [[ "$(id -u)" != "0" ]]; then
echo "[AbyssInfo] Please run as root." >&2
exit 1
fi
if [[ ! -f "${ENV_FILE}" ]]; then
echo "[AbyssInfo] Missing ${ENV_FILE}" >&2
exit 1
fi
if [[ ! -f "${CADDY_FILE}" || ! -f "${COMPOSE_FILE}" ]]; then
echo "[AbyssInfo] Missing Netmaker compose files in ${NETMAKER_DIR}" >&2
exit 1
fi
set -a
# shellcheck source=/dev/null
. "${ENV_FILE}"
set +a
if [[ -z "${MASTER_KEY:-}" ]]; then
echo "[AbyssInfo] MASTER_KEY is missing in ${ENV_FILE}" >&2
exit 1
fi
api_url="https://api.${BASE_DOMAIN}"
auth_header="Authorization: Bearer ${MASTER_KEY}"
install_netclient() {
if command -v netclient >/dev/null 2>&1; then
return 0
fi
echo "[AbyssInfo] Installing netclient ${NETCLIENT_VERSION} ..."
dnf install -y wget jq wireguard-tools >/dev/null
local arch
arch="$(uname -m)"
case "${arch}" in
x86_64|amd64) arch="amd64" ;;
aarch64|arm64) arch="arm64" ;;
*)
echo "[AbyssInfo] Unsupported architecture: ${arch}" >&2
exit 1
;;
esac
mkdir -p "${INSTALL_DIR}"
wget -qO "${INSTALL_DIR}/netclient" "https://github.com/gravitl/netclient/releases/download/${NETCLIENT_VERSION}/netclient-linux-${arch}"
chmod +x "${INSTALL_DIR}/netclient"
"${INSTALL_DIR}/netclient" install
}
current_vpn_ip() {
if [[ -f /etc/netclient/nodes.json ]]; then
jq -r --arg network "${NETWORK_ID}" '.[$network].address.IP // empty' /etc/netclient/nodes.json 2>/dev/null |
awk 'NF && $1 !~ /^169\.254\./ { print; exit }'
fi
}
create_server_join_token() {
local key_name response_file code token
key_name="abyssinfo-server-admin-$(date +%Y%m%d%H%M%S)"
response_file="$(mktemp)"
local payload
payload="$(jq -n \
--arg name "${key_name}" \
--arg network "${NETWORK_ID}" \
'{
name: $name,
tags: ["abyssinfo-server-admin"],
type: 2,
uses_remaining: 1,
unlimited: false,
expiration: 0,
networks: [$network],
groups: ["homevpn", "server", "admin"],
auto_egress: false,
auto_assign_gw: false
}')"
code="$(curl -ksS -o "${response_file}" -w "%{http_code}" \
-X POST "${api_url}/api/v1/enrollment-keys" \
-H "${auth_header}" \
-H "Content-Type: application/json" \
--data "${payload}" || true)"
if [[ "${code}" -lt 200 || "${code}" -ge 300 ]]; then
echo "[AbyssInfo] Failed to create server enrollment token. HTTP ${code}" >&2
head -c 500 "${response_file}" >&2 || true
echo >&2
rm -f "${response_file}"
exit 1
fi
token="$(jq -r '.token // .data.token // empty' "${response_file}")"
rm -f "${response_file}"
if [[ -z "${token}" || "${token}" == "null" ]]; then
echo "[AbyssInfo] Netmaker did not return an enrollment token." >&2
exit 1
fi
printf "%s" "${token}"
}
join_server_to_vpn() {
local ip token
ip="$(current_vpn_ip || true)"
if [[ -n "${ip}" ]]; then
echo "[AbyssInfo] Server is already in ${NETWORK_ID}: ${ip}"
systemctl enable --now netclient >/dev/null 2>&1 || true
return 0
fi
echo "[AbyssInfo] Joining server to Netmaker network ${NETWORK_ID} ..."
token="$(create_server_join_token)"
netclient join -t "${token}" >/tmp/abyssinfo-server-netclient-join.log 2>&1 || {
echo "[AbyssInfo] netclient join failed. Last log lines:" >&2
tail -n 80 /tmp/abyssinfo-server-netclient-join.log >&2 || true
exit 1
}
systemctl enable --now netclient >/dev/null 2>&1 || true
for _ in $(seq 1 90); do
ip="$(current_vpn_ip || true)"
if [[ -n "${ip}" ]]; then
echo "[AbyssInfo] Server VPN IP: ${ip}"
return 0
fi
sleep 2
done
echo "[AbyssInfo] Server joined, but VPN IP was not detected." >&2
exit 1
}
configure_caddy_vpn_admin() {
local vpn_ip="$1"
echo "[AbyssInfo] Configuring Caddy VPN-only admin entry on ${vpn_ip}:${ADMIN_PORT} ..."
cp -a "${CADDY_FILE}" "${CADDY_FILE}.bak.$(date +%Y%m%d%H%M%S)"
cp -a "${COMPOSE_FILE}" "${COMPOSE_FILE}.bak.$(date +%Y%m%d%H%M%S)"
VPN_IP="${vpn_ip}" VPN_ADMIN_PORT="${ADMIN_PORT}" python3 - <<'PY'
from pathlib import Path
import os
import re
caddy_path = Path("/root/netmaker/Caddyfile")
compose_path = Path("/root/netmaker/docker-compose.yml")
vpn_ip = os.environ["VPN_IP"]
port = os.environ["VPN_ADMIN_PORT"]
block = f"""
# AbyssInfo VPN-only Netmaker Dashboard
:{port} {{
\theader {{
\t\tX-Robots-Tag "none"
\t\t-Server
\t}}
\treverse_proxy http://netmaker-ui
}}
"""
caddy = caddy_path.read_text(encoding="utf-8")
caddy = re.sub(
r"\n# AbyssInfo VPN-only Netmaker Dashboard\n:\d+ \{.*?\n\}\n?",
"\n",
caddy,
flags=re.S,
).rstrip() + block
caddy_path.write_text(caddy, encoding="utf-8")
compose = compose_path.read_text(encoding="utf-8")
mapping = f' - "{vpn_ip}:{port}:{port}/tcp"'
lines = []
inserted = False
for line in compose.splitlines():
if f":{port}:{port}/tcp" in line:
if not inserted:
lines.append(mapping)
inserted = True
continue
lines.append(line)
if '"50051:50051"' in line and not inserted:
lines.append(mapping)
inserted = True
if not inserted:
raise SystemExit("failed to insert caddy port mapping after 50051")
compose_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
PY
(cd "${NETMAKER_DIR}" && docker compose up -d caddy)
sleep 2
curl -fsSI --connect-timeout 10 "http://${vpn_ip}:${ADMIN_PORT}" >/dev/null
}
main() {
install_netclient
join_server_to_vpn
local vpn_ip
vpn_ip="$(current_vpn_ip)"
configure_caddy_vpn_admin "${vpn_ip}"
echo "[AbyssInfo] VPN-only Netmaker dashboard:"
echo " http://${vpn_ip}:${ADMIN_PORT}"
echo "[AbyssInfo] Only VPN clients with route to ${NETWORK_ID} should be able to reach this address."
}
main "$@"