f69efb67ad
- Tauri desktop app (apps/desktop) - Python Django API service (services/api) - Deployment scripts and docs
228 lines
6.1 KiB
Bash
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 "$@"
|