Initial commit: abyssVpn project
- Tauri desktop app (apps/desktop) - Python Django API service (services/api) - Deployment scripts and docs
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
#!/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 "$@"
|
||||
Reference in New Issue
Block a user