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,14 @@
|
||||
@echo off
|
||||
setlocal
|
||||
call "%~dp0config.bat"
|
||||
|
||||
echo [AbyssInfo] Checking server %SERVER_USER%@%SERVER_HOST% ...
|
||||
%SSH_BASE% "whoami && hostname && cat /etc/os-release && echo --- tools --- && (command -v docker || true) && (docker --version || true) && (docker compose version || true) && (command -v git || true) && (command -v nginx || true)"
|
||||
|
||||
if errorlevel 1 (
|
||||
echo [AbyssInfo] Server check failed.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [AbyssInfo] Server check completed.
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
@echo off
|
||||
setlocal
|
||||
call "%~dp0config.bat"
|
||||
|
||||
echo [AbyssInfo] Uploading environment installer ...
|
||||
%SCP_BASE% "%~dp0remote\install_env.sh" %SERVER_USER%@%SERVER_HOST%:/tmp/abyssinfo_install_env.sh
|
||||
if errorlevel 1 (
|
||||
echo [AbyssInfo] Upload failed.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [AbyssInfo] Installing server environment ...
|
||||
%SSH_BASE% "chmod +x /tmp/abyssinfo_install_env.sh && /tmp/abyssinfo_install_env.sh"
|
||||
if errorlevel 1 (
|
||||
echo [AbyssInfo] Environment installation failed.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [AbyssInfo] Environment installation completed.
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
@echo off
|
||||
setlocal
|
||||
call "%~dp0config.bat"
|
||||
|
||||
echo [AbyssInfo] Uploading Netmaker installer and cached assets ...
|
||||
%SCP_BASE% "%~dp0vendor\nm-quick.sh" %SERVER_USER%@%SERVER_HOST%:/tmp/nm-quick.sh
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
%SCP_BASE% -r "%~dp0vendor\netmaker-assets" %SERVER_USER%@%SERVER_HOST%:/tmp/netmaker-assets
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
%SCP_BASE% "%~dp0remote\deploy_netmaker.sh" %SERVER_USER%@%SERVER_HOST%:/tmp/abyssinfo_deploy_netmaker.sh
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
echo [AbyssInfo] Deploying Netmaker ...
|
||||
%SSH_BASE% "chmod +x /tmp/abyssinfo_deploy_netmaker.sh && /tmp/abyssinfo_deploy_netmaker.sh"
|
||||
if errorlevel 1 (
|
||||
echo [AbyssInfo] Netmaker deployment failed.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [AbyssInfo] Netmaker deployment completed.
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
@echo off
|
||||
setlocal
|
||||
call "%~dp0config.bat"
|
||||
|
||||
echo [AbyssInfo] Checking Netmaker containers and endpoints ...
|
||||
%SSH_BASE% "cd /root/netmaker 2>/dev/null || cd /root; docker compose ps || true; echo --- containers ---; docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'; echo --- health ---; curl -kIs --connect-timeout 10 https://api.nm.101-96-214-81.nip.io | head || true; curl -kIs --connect-timeout 10 https://dashboard.nm.101-96-214-81.nip.io | head || true"
|
||||
|
||||
if errorlevel 1 (
|
||||
echo [AbyssInfo] Netmaker check failed.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [AbyssInfo] Netmaker check completed.
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
@echo off
|
||||
setlocal
|
||||
call "%~dp0config.bat"
|
||||
|
||||
echo [AbyssInfo] Uploading Netmaker API probe ...
|
||||
%SCP_BASE% "%~dp0remote\netmaker_api_probe.sh" %SERVER_USER%@%SERVER_HOST%:/tmp/abyssinfo_netmaker_api_probe.sh
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
echo [AbyssInfo] Probing Netmaker API ...
|
||||
%SSH_BASE% "chmod +x /tmp/abyssinfo_netmaker_api_probe.sh && /tmp/abyssinfo_netmaker_api_probe.sh"
|
||||
if errorlevel 1 (
|
||||
echo [AbyssInfo] Netmaker API probe failed.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [AbyssInfo] Netmaker API probe completed.
|
||||
@@ -0,0 +1,16 @@
|
||||
@echo off
|
||||
setlocal
|
||||
call "%~dp0config.bat"
|
||||
|
||||
echo [AbyssInfo] Uploading Netmaker network initializer ...
|
||||
%SCP_BASE% "%~dp0remote\init_netmaker_network.sh" %SERVER_USER%@%SERVER_HOST%:/tmp/abyssinfo_init_netmaker_network.sh
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
echo [AbyssInfo] Initializing Netmaker network ...
|
||||
%SSH_BASE% "chmod +x /tmp/abyssinfo_init_netmaker_network.sh && /tmp/abyssinfo_init_netmaker_network.sh"
|
||||
if errorlevel 1 (
|
||||
echo [AbyssInfo] Netmaker network initialization failed.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [AbyssInfo] Netmaker network initialization completed.
|
||||
@@ -0,0 +1,17 @@
|
||||
@echo off
|
||||
setlocal
|
||||
call "%~dp0config.bat"
|
||||
|
||||
echo [AbyssInfo] Configuring Django API .env ...
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\configure_api_env.ps1" ^
|
||||
-ServerHost "%SERVER_HOST%" ^
|
||||
-ServerUser "%SERVER_USER%" ^
|
||||
-SshKey "%SSH_KEY%" ^
|
||||
-ProjectRoot "%~dp0.."
|
||||
|
||||
if errorlevel 1 (
|
||||
echo [AbyssInfo] Django API .env configuration failed.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [AbyssInfo] Django API .env configured.
|
||||
@@ -0,0 +1,27 @@
|
||||
@echo off
|
||||
setlocal
|
||||
call "%~dp0config.bat"
|
||||
|
||||
set "API_ZIP=%TEMP%\abyssinfo-api.zip"
|
||||
|
||||
echo [AbyssInfo] Packaging Django API ...
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\package_api.ps1" ^
|
||||
-ProjectRoot "%~dp0.." ^
|
||||
-OutputZip "%API_ZIP%"
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
echo [AbyssInfo] Uploading Django API package and deploy script ...
|
||||
%SCP_BASE% "%API_ZIP%" %SERVER_USER%@%SERVER_HOST%:/tmp/abyssinfo-api.zip
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
%SCP_BASE% "%~dp0remote\deploy_api.sh" %SERVER_USER%@%SERVER_HOST%:/tmp/abyssinfo_deploy_api.sh
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
echo [AbyssInfo] Deploying Django API ...
|
||||
%SSH_BASE% "chmod +x /tmp/abyssinfo_deploy_api.sh && REMOTE_APP_DIR=%REMOTE_APP_DIR% API_DOMAIN=%API_DOMAIN% API_LEGACY_DOMAIN=%API_LEGACY_DOMAIN% NETMAKER_BASE_DOMAIN=%NETMAKER_BASE_DOMAIN% /tmp/abyssinfo_deploy_api.sh"
|
||||
if errorlevel 1 (
|
||||
echo [AbyssInfo] Django API deployment failed.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [AbyssInfo] Django API deployment completed.
|
||||
@@ -0,0 +1,13 @@
|
||||
@echo off
|
||||
setlocal
|
||||
call "%~dp0config.bat"
|
||||
|
||||
echo [AbyssInfo] Checking Django API service and endpoint ...
|
||||
%SSH_BASE% "systemctl is-active --quiet abyssinfo-api && curl -fsS http://127.0.0.1:8000/health/ && echo && curl -kfsS https://%API_DOMAIN%/health/"
|
||||
if errorlevel 1 (
|
||||
echo [AbyssInfo] Django API check failed.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo [AbyssInfo] Django API check completed.
|
||||
@@ -0,0 +1,20 @@
|
||||
@echo off
|
||||
setlocal
|
||||
chcp 65001 >nul
|
||||
|
||||
call "%~dp0config.bat"
|
||||
|
||||
if not exist "%SSH_KEY%" (
|
||||
echo [AbyssInfo] SSH key not found: %SSH_KEY%
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [AbyssInfo] Uploading VPN-only Netmaker admin setup script...
|
||||
%SCP_BASE% "%~dp0remote\setup_vpn_admin_access.sh" "%SERVER_USER%@%SERVER_HOST%:/tmp/abyssinfo_setup_vpn_admin_access.sh"
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
echo [AbyssInfo] Configuring VPN-only Netmaker admin entry...
|
||||
%SSH_BASE% "chmod +x /tmp/abyssinfo_setup_vpn_admin_access.sh && NETMAKER_BASE_DOMAIN=%NETMAKER_BASE_DOMAIN% NETMAKER_NETWORK_ID=homevpn VPN_ADMIN_PORT=9090 /tmp/abyssinfo_setup_vpn_admin_access.sh"
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
echo [AbyssInfo] Done.
|
||||
@@ -0,0 +1,19 @@
|
||||
@echo off
|
||||
setlocal
|
||||
call "%~dp0config.bat"
|
||||
|
||||
echo [AbyssInfo] Uploading Gitea public access setup script ...
|
||||
%SCP_BASE% "%~dp0remote\setup_gitea_public_access.sh" %SERVER_USER%@%SERVER_HOST%:/tmp/abyssinfo_setup_gitea_public_access.sh
|
||||
if errorlevel 1 (
|
||||
echo [AbyssInfo] Upload failed.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [AbyssInfo] Configuring Gitea public access ...
|
||||
%SSH_BASE% "chmod +x /tmp/abyssinfo_setup_gitea_public_access.sh && GITEA_DOMAIN=%GITEA_DOMAIN% GITEA_WEB_UPSTREAM=%GITEA_WEB_UPSTREAM% GITEA_OIDC_UPSTREAM=%GITEA_OIDC_UPSTREAM% /tmp/abyssinfo_setup_gitea_public_access.sh"
|
||||
if errorlevel 1 (
|
||||
echo [AbyssInfo] Gitea public access setup failed.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [AbyssInfo] Gitea public access setup completed.
|
||||
@@ -0,0 +1,13 @@
|
||||
@echo off
|
||||
set "SERVER_HOST=101.96.214.81"
|
||||
set "SERVER_USER=root"
|
||||
set "SSH_KEY=%~dp0..\codex.pem"
|
||||
set "REMOTE_APP_DIR=/opt/abyssinfo-home-vpn"
|
||||
set "API_DOMAIN=vpn.abyssinfo.com"
|
||||
set "API_LEGACY_DOMAIN=vpn-api.nm.101-96-214-81.nip.io"
|
||||
set "NETMAKER_BASE_DOMAIN=vpn.abyssinfo.com"
|
||||
set "GITEA_DOMAIN=gitea.abyssinfo.com"
|
||||
set "GITEA_WEB_UPSTREAM=100.88.0.1:3001"
|
||||
set "GITEA_OIDC_UPSTREAM=100.88.0.1:3080"
|
||||
set "SSH_BASE=ssh -o StrictHostKeyChecking=accept-new -i %SSH_KEY% %SERVER_USER%@%SERVER_HOST%"
|
||||
set "SCP_BASE=scp -o StrictHostKeyChecking=accept-new -i %SSH_KEY%"
|
||||
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
APP_DIR="${REMOTE_APP_DIR:-/opt/abyssinfo-home-vpn}"
|
||||
API_DIR="${APP_DIR}/api"
|
||||
VENV_DIR="${APP_DIR}/.venv-api"
|
||||
ZIP_FILE="${API_ZIP_FILE:-/tmp/abyssinfo-api.zip}"
|
||||
API_DOMAIN="${API_DOMAIN:-vpn-api.nm.101-96-214-81.sslip.io}"
|
||||
API_LEGACY_DOMAIN="${API_LEGACY_DOMAIN:-vpn-api.nm.101-96-214-81.nip.io}"
|
||||
API_PORT="${API_PORT:-8000}"
|
||||
NETMAKER_DOMAIN="${NETMAKER_BASE_DOMAIN:-nm.101-96-214-81.nip.io}"
|
||||
|
||||
echo "[AbyssInfo] Deploying Django API"
|
||||
echo "[AbyssInfo] API domain: ${API_DOMAIN}"
|
||||
|
||||
if [[ "$(id -u)" != "0" ]]; then
|
||||
echo "[AbyssInfo] Please run as root." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "${ZIP_FILE}" ]]; then
|
||||
echo "[AbyssInfo] Missing ${ZIP_FILE}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[AbyssInfo] Installing Python runtime ..."
|
||||
dnf install -y python3.12 python3.12-pip unzip
|
||||
|
||||
mkdir -p "${API_DIR}"
|
||||
PREVIOUS_ENV="/tmp/abyssinfo-api.previous.env"
|
||||
if [[ -f "${API_DIR}/.env" ]]; then
|
||||
cp "${API_DIR}/.env" "${PREVIOUS_ENV}"
|
||||
else
|
||||
rm -f "${PREVIOUS_ENV}"
|
||||
fi
|
||||
|
||||
find "${API_DIR}" -mindepth 1 \
|
||||
! -name db.sqlite3 \
|
||||
! -path "${API_DIR}/db.sqlite3" \
|
||||
-exec rm -rf {} +
|
||||
|
||||
echo "[AbyssInfo] Extracting API package ..."
|
||||
set +e
|
||||
unzip -q "${ZIP_FILE}" -d "${API_DIR}"
|
||||
UNZIP_EXIT=$?
|
||||
set -e
|
||||
if [[ "${UNZIP_EXIT}" -gt 1 ]]; then
|
||||
echo "[AbyssInfo] Failed to unzip API package. Exit code ${UNZIP_EXIT}" >&2
|
||||
exit "${UNZIP_EXIT}"
|
||||
fi
|
||||
|
||||
if [[ ! -f "${API_DIR}/.env" ]]; then
|
||||
echo "[AbyssInfo] Missing ${API_DIR}/.env" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[AbyssInfo] Normalizing production env ..."
|
||||
python3.12 - <<PY
|
||||
from pathlib import Path
|
||||
import secrets
|
||||
|
||||
env_path = Path("${API_DIR}/.env")
|
||||
previous_env_path = Path("${PREVIOUS_ENV}")
|
||||
values = {}
|
||||
for candidate in [previous_env_path, env_path]:
|
||||
if not candidate.exists():
|
||||
continue
|
||||
for line in candidate.read_text(encoding="utf-8").splitlines():
|
||||
if not line or line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
key, value = line.split("=", 1)
|
||||
key = key.lstrip("\\ufeff")
|
||||
if value or key not in values:
|
||||
values[key] = value
|
||||
|
||||
if not values.get("DJANGO_SECRET_KEY"):
|
||||
values["DJANGO_SECRET_KEY"] = secrets.token_urlsafe(48)
|
||||
if not values.get("JWT_SECRET"):
|
||||
values["JWT_SECRET"] = secrets.token_urlsafe(48)
|
||||
|
||||
values["DJANGO_DEBUG"] = "false"
|
||||
values["DJANGO_ALLOWED_HOSTS"] = "127.0.0.1,localhost,${API_DOMAIN},${API_LEGACY_DOMAIN}"
|
||||
values["CORS_ALLOWED_ORIGINS"] = "http://localhost:5173,http://127.0.0.1:5173,http://tauri.localhost,https://tauri.localhost,tauri://localhost,https://${API_DOMAIN}"
|
||||
values["FEISHU_REDIRECT_URI"] = "https://${API_DOMAIN}/auth/feishu/callback"
|
||||
|
||||
ordered = [
|
||||
"DJANGO_SECRET_KEY", "DJANGO_DEBUG", "DJANGO_ALLOWED_HOSTS", "CORS_ALLOWED_ORIGINS",
|
||||
"FEISHU_APP_ID", "FEISHU_APP_SECRET", "FEISHU_REDIRECT_URI", "FEISHU_EVENT_VERIFICATION_TOKEN", "TAURI_DEEP_LINK_SCHEME",
|
||||
"JWT_SECRET", "JWT_EXPIRES_SECONDS", "OAUTH_STATE_EXPIRES_SECONDS", "LOGIN_CODE_EXPIRES_SECONDS",
|
||||
"NETMAKER_BASE_URL", "NETMAKER_API_TOKEN", "NETMAKER_NETWORK_ID",
|
||||
"NETMAKER_ENROLLMENT_TAGS", "NETMAKER_ENROLLMENT_TTL_SECONDS",
|
||||
]
|
||||
|
||||
env_path.write_text("\\n".join(f"{key}={values.get(key, '')}" for key in ordered) + "\\n", encoding="utf-8")
|
||||
PY
|
||||
|
||||
echo "[AbyssInfo] Creating virtual environment ..."
|
||||
if [[ ! -x "${VENV_DIR}/bin/python" ]]; then
|
||||
python3.12 -m venv "${VENV_DIR}"
|
||||
fi
|
||||
"${VENV_DIR}/bin/python" -m pip install --upgrade pip
|
||||
"${VENV_DIR}/bin/pip" install -r "${API_DIR}/requirements.txt"
|
||||
|
||||
echo "[AbyssInfo] Running Django checks and migrations ..."
|
||||
cd "${API_DIR}"
|
||||
"${VENV_DIR}/bin/python" manage.py check
|
||||
"${VENV_DIR}/bin/python" manage.py migrate --noinput
|
||||
"${VENV_DIR}/bin/python" manage.py collectstatic --noinput
|
||||
|
||||
echo "[AbyssInfo] Installing systemd service ..."
|
||||
cat >/etc/systemd/system/abyssinfo-api.service <<SERVICE
|
||||
[Unit]
|
||||
Description=AbyssInfo Home VPN Django API
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=${API_DIR}
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
ExecStart=${VENV_DIR}/bin/gunicorn config.wsgi:application --bind 0.0.0.0:${API_PORT} --workers 2 --timeout 60
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
SERVICE
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now abyssinfo-api
|
||||
systemctl restart abyssinfo-api
|
||||
|
||||
echo "[AbyssInfo] Updating Netmaker Caddy reverse proxy ..."
|
||||
CADDYFILE="/root/netmaker/Caddyfile"
|
||||
if [[ ! -f "${CADDYFILE}" ]]; then
|
||||
echo "[AbyssInfo] Missing ${CADDYFILE}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! grep -q "https://${API_DOMAIN}" "${CADDYFILE}"; then
|
||||
cat >>"${CADDYFILE}" <<CADDY
|
||||
|
||||
# AbyssInfo Django API
|
||||
https://${API_DOMAIN} {
|
||||
reverse_proxy http://host.docker.internal:${API_PORT}
|
||||
}
|
||||
CADDY
|
||||
fi
|
||||
|
||||
(cd /root/netmaker && docker compose exec -T caddy caddy reload --config /etc/caddy/Caddyfile)
|
||||
|
||||
echo "[AbyssInfo] Verifying API health ..."
|
||||
curl -fsS "http://127.0.0.1:${API_PORT}/health/" >/dev/null
|
||||
curl -kfsS "https://${API_DOMAIN}/health/" >/dev/null
|
||||
|
||||
echo "[AbyssInfo] Django API deployed:"
|
||||
echo " https://${API_DOMAIN}/health/"
|
||||
@@ -0,0 +1,168 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
BASE_DOMAIN="${NETMAKER_BASE_DOMAIN:-nm.101-96-214-81.nip.io}"
|
||||
ADMIN_EMAIL="${NETMAKER_ADMIN_EMAIL:-admin@abyssinfo.local}"
|
||||
ASSET_PORT="${NETMAKER_ASSET_PORT:-18080}"
|
||||
ASSET_DIR="/tmp/netmaker-assets"
|
||||
QUICK_SCRIPT="/tmp/nm-quick.sh"
|
||||
|
||||
echo "[AbyssInfo] Deploying Netmaker CE"
|
||||
echo "[AbyssInfo] Base domain: ${BASE_DOMAIN}"
|
||||
|
||||
if [[ "$(id -u)" != "0" ]]; then
|
||||
echo "[AbyssInfo] Please run as root." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "${QUICK_SCRIPT}" ]]; then
|
||||
echo "[AbyssInfo] Missing ${QUICK_SCRIPT}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -d "${ASSET_DIR}" ]]; then
|
||||
echo "[AbyssInfo] Missing ${ASSET_DIR}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[AbyssInfo] Installing Netmaker quick-install dependencies ..."
|
||||
dnf install -y wget jq bind-utils gawk wireguard-tools python3
|
||||
|
||||
echo "[AbyssInfo] Ensuring Docker is running ..."
|
||||
mkdir -p /etc/docker
|
||||
cat >/etc/docker/daemon.json <<'JSON'
|
||||
{
|
||||
"registry-mirrors": [
|
||||
"https://docker.1ms.run",
|
||||
"https://docker.m.daocloud.io",
|
||||
"https://hub-mirror.c.163.com"
|
||||
]
|
||||
}
|
||||
JSON
|
||||
systemctl daemon-reload
|
||||
systemctl restart docker
|
||||
systemctl enable --now docker
|
||||
|
||||
echo "[AbyssInfo] Stopping Nginx to free ports 80/443 for Netmaker Caddy ..."
|
||||
systemctl disable --now nginx 2>/dev/null || true
|
||||
|
||||
health_check() {
|
||||
local dashboard_url="https://dashboard.${BASE_DOMAIN}"
|
||||
|
||||
if [[ ! -f /root/netmaker/docker-compose.yml ]]; then
|
||||
echo "[AbyssInfo] Missing /root/netmaker/docker-compose.yml" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "[AbyssInfo] Netmaker containers:"
|
||||
(cd /root/netmaker && docker compose ps)
|
||||
|
||||
echo "[AbyssInfo] Checking dashboard endpoint: ${dashboard_url}"
|
||||
curl -kfsSI --connect-timeout 15 "${dashboard_url}" >/dev/null
|
||||
}
|
||||
|
||||
apply_internal_only_defaults() {
|
||||
local env_file="/root/netmaker/netmaker.env"
|
||||
if [[ ! -f "${env_file}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "[AbyssInfo] Applying internal-only Netmaker client defaults ..."
|
||||
python3 - <<'PY'
|
||||
from pathlib import Path
|
||||
|
||||
env_path = Path("/root/netmaker/netmaker.env")
|
||||
values = {}
|
||||
order = []
|
||||
for line in env_path.read_text(encoding="utf-8").splitlines():
|
||||
if not line or line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
key, value = line.split("=", 1)
|
||||
if key not in values:
|
||||
order.append(key)
|
||||
values[key] = value
|
||||
|
||||
for key, value in {
|
||||
"DNS_MODE": "off",
|
||||
"MANAGE_DNS": "false",
|
||||
}.items():
|
||||
if key not in values:
|
||||
order.append(key)
|
||||
values[key] = value
|
||||
|
||||
env_path.write_text("\n".join(f"{key}={values[key]}" for key in order) + "\n", encoding="utf-8")
|
||||
PY
|
||||
}
|
||||
|
||||
if [[ "${NETMAKER_FORCE_REINSTALL:-0}" == "1" ]]; then
|
||||
echo "[AbyssInfo] Force reinstall requested. Removing previous Netmaker deployment ..."
|
||||
if [[ -f /root/netmaker/docker-compose.yml ]]; then
|
||||
(cd /root/netmaker && docker compose down --volumes || true)
|
||||
fi
|
||||
rm -rf /root/netmaker /etc/netmaker /etc/netclient
|
||||
elif [[ -f /root/netmaker/docker-compose.yml ]]; then
|
||||
echo "[AbyssInfo] Existing Netmaker deployment found. Starting services and running health check ..."
|
||||
apply_internal_only_defaults
|
||||
(cd /root/netmaker && docker compose up -d)
|
||||
if health_check; then
|
||||
echo "[AbyssInfo] Existing Netmaker deployment is healthy."
|
||||
echo "[AbyssInfo] URLs:"
|
||||
echo " Dashboard: https://dashboard.${BASE_DOMAIN}"
|
||||
echo " API: https://api.${BASE_DOMAIN}"
|
||||
echo " Broker: wss://broker.${BASE_DOMAIN}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "[AbyssInfo] Existing deployment is not healthy. Set NETMAKER_FORCE_REINSTALL=1 to rebuild it." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[AbyssInfo] Preparing quick installer ..."
|
||||
mkdir -p /root
|
||||
cp "${QUICK_SCRIPT}" /root/nm-quick.sh
|
||||
chmod +x /root/nm-quick.sh
|
||||
|
||||
# Use uploaded cached assets instead of pulling compose files from GitHub on the server.
|
||||
sed -i "s|local BASE_URL=\"https://raw.githubusercontent.com/gravitl/netmaker/\$BRANCH\"|local BASE_URL=\"http://127.0.0.1:${ASSET_PORT}\"|g" /root/nm-quick.sh
|
||||
|
||||
echo "[AbyssInfo] Starting temporary local asset server ..."
|
||||
pkill -f "python3 -m http.server ${ASSET_PORT}" 2>/dev/null || true
|
||||
cd "${ASSET_DIR}"
|
||||
python3 -m http.server "${ASSET_PORT}" --bind 127.0.0.1 >/tmp/abyssinfo-netmaker-assets.log 2>&1 &
|
||||
ASSET_PID=$!
|
||||
trap 'kill ${ASSET_PID} 2>/dev/null || true' EXIT
|
||||
sleep 2
|
||||
curl -fsS "http://127.0.0.1:${ASSET_PORT}/compose/docker-compose.yml" >/dev/null
|
||||
|
||||
echo "[AbyssInfo] Running Netmaker quick installer ..."
|
||||
cd /root
|
||||
export NM_DOMAIN="${BASE_DOMAIN}"
|
||||
export NM_EMAIL="${ADMIN_EMAIL}"
|
||||
export NM_SKIP_DEPS=1
|
||||
|
||||
# Input sequence for nm-quick CE:
|
||||
# 1 = use auto/generated domain, email, y = confirm settings.
|
||||
set +e
|
||||
printf "1\n%s\ny\n" "${ADMIN_EMAIL}" | /root/nm-quick.sh -c
|
||||
QUICK_EXIT=$?
|
||||
set -e
|
||||
|
||||
echo "[AbyssInfo] Netmaker files:"
|
||||
ls -la /root/netmaker
|
||||
|
||||
if ! health_check; then
|
||||
echo "[AbyssInfo] Netmaker health check failed after quick installer exit code ${QUICK_EXIT}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${QUICK_EXIT}" != "0" ]]; then
|
||||
echo "[AbyssInfo] Quick installer exited with ${QUICK_EXIT}, but Netmaker services are healthy. Continuing."
|
||||
fi
|
||||
|
||||
apply_internal_only_defaults
|
||||
(cd /root/netmaker && docker compose up -d)
|
||||
|
||||
echo "[AbyssInfo] URLs:"
|
||||
echo " Dashboard: https://dashboard.${BASE_DOMAIN}"
|
||||
echo " API: https://api.${BASE_DOMAIN}"
|
||||
echo " Broker: wss://broker.${BASE_DOMAIN}"
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
BASE_DOMAIN="${NETMAKER_BASE_DOMAIN:-nm.101-96-214-81.nip.io}"
|
||||
ENV_FILE="${NETMAKER_ENV_FILE:-/root/netmaker/netmaker.env}"
|
||||
NETWORK_ID="${NETMAKER_NETWORK_ID:-homevpn}"
|
||||
NETWORK_RANGE="${NETMAKER_NETWORK_RANGE:-100.88.0.0/24}"
|
||||
|
||||
if [[ ! -f "${ENV_FILE}" ]]; then
|
||||
echo "[AbyssInfo] Missing ${ENV_FILE}" >&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}"
|
||||
|
||||
echo "[AbyssInfo] Checking Netmaker network: ${NETWORK_ID}"
|
||||
networks_json="$(curl -ksS -H "${auth_header}" "${api_url}/api/networks")"
|
||||
if echo "${networks_json}" | jq -e --arg id "${NETWORK_ID}" '.[]? | select(.netid == $id)' >/dev/null; then
|
||||
echo "[AbyssInfo] Network already exists: ${NETWORK_ID}"
|
||||
else
|
||||
echo "[AbyssInfo] Creating Netmaker network: ${NETWORK_ID} (${NETWORK_RANGE})"
|
||||
payload="$(jq -n \
|
||||
--arg netid "${NETWORK_ID}" \
|
||||
--arg addressrange "${NETWORK_RANGE}" \
|
||||
'{
|
||||
netid: $netid,
|
||||
addressrange: $addressrange,
|
||||
defaultacl: "yes",
|
||||
defaultmtu: 1420,
|
||||
defaultlistenport: 51821,
|
||||
defaultkeepalive: 20,
|
||||
auto_join: true
|
||||
}')"
|
||||
|
||||
response_file="$(mktemp)"
|
||||
code="$(curl -ksS -o "${response_file}" -w "%{http_code}" \
|
||||
-X POST "${api_url}/api/networks" \
|
||||
-H "${auth_header}" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "${payload}" || true)"
|
||||
|
||||
if [[ "${code}" -lt 200 || "${code}" -ge 300 ]]; then
|
||||
echo "[AbyssInfo] Failed to create network. HTTP ${code}" >&2
|
||||
head -c 500 "${response_file}" >&2 || true
|
||||
echo >&2
|
||||
rm -f "${response_file}"
|
||||
exit 1
|
||||
fi
|
||||
rm -f "${response_file}"
|
||||
fi
|
||||
|
||||
echo "[AbyssInfo] Current networks:"
|
||||
curl -ksS -H "${auth_header}" "${api_url}/api/networks" \
|
||||
| jq -r '.[] | "- \(.netid) \(.addressrange // "")"'
|
||||
|
||||
echo "[AbyssInfo] Backend config:"
|
||||
echo "NETMAKER_BASE_URL=${api_url}"
|
||||
echo "NETMAKER_NETWORK_ID=${NETWORK_ID}"
|
||||
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
echo "[AbyssInfo] Server environment installer"
|
||||
echo "[AbyssInfo] User: $(whoami)"
|
||||
echo "[AbyssInfo] OS:"
|
||||
cat /etc/os-release
|
||||
|
||||
if [[ "$(id -u)" != "0" ]]; then
|
||||
echo "[AbyssInfo] Please run as root." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[AbyssInfo] Installing base packages ..."
|
||||
dnf install -y dnf-plugins-core ca-certificates curl git tar gzip unzip policycoreutils-python-utils
|
||||
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
echo "[AbyssInfo] Adding Docker CE repository ..."
|
||||
rm -f /etc/yum.repos.d/docker-ce.repo
|
||||
dnf clean all
|
||||
if ! dnf config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo; then
|
||||
echo "[AbyssInfo] Aliyun Docker repository failed, using official repository ..."
|
||||
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
|
||||
fi
|
||||
dnf clean all
|
||||
dnf makecache --disablerepo='*' --enablerepo='docker-ce-stable' || true
|
||||
echo "[AbyssInfo] Installing Docker Engine and Compose plugin ..."
|
||||
dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
else
|
||||
echo "[AbyssInfo] Docker already installed."
|
||||
fi
|
||||
|
||||
systemctl enable --now docker
|
||||
|
||||
if ! command -v nginx >/dev/null 2>&1; then
|
||||
echo "[AbyssInfo] Installing Nginx ..."
|
||||
dnf install -y nginx
|
||||
else
|
||||
echo "[AbyssInfo] Nginx already installed."
|
||||
fi
|
||||
|
||||
mkdir -p /opt/abyssinfo-home-vpn
|
||||
|
||||
if systemctl is-active --quiet firewalld; then
|
||||
echo "[AbyssInfo] Configuring running firewalld ..."
|
||||
firewall-cmd --permanent --add-service=ssh
|
||||
firewall-cmd --permanent --add-service=http
|
||||
firewall-cmd --permanent --add-service=https
|
||||
firewall-cmd --reload
|
||||
else
|
||||
echo "[AbyssInfo] firewalld is not running; skipping OS firewall changes."
|
||||
fi
|
||||
|
||||
echo "[AbyssInfo] Versions:"
|
||||
docker --version
|
||||
docker compose version
|
||||
git --version
|
||||
nginx -v
|
||||
|
||||
echo "[AbyssInfo] Environment ready."
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
BASE_DOMAIN="${NETMAKER_BASE_DOMAIN:-nm.101-96-214-81.nip.io}"
|
||||
ENV_FILE="${NETMAKER_ENV_FILE:-/root/netmaker/netmaker.env}"
|
||||
|
||||
if [[ ! -f "${ENV_FILE}" ]]; then
|
||||
echo "[AbyssInfo] Missing ${ENV_FILE}" >&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
|
||||
|
||||
echo "[AbyssInfo] Probing Netmaker API routes"
|
||||
for path in \
|
||||
/api/networks \
|
||||
/api/v1/networks \
|
||||
/api/v1/enrollment-keys \
|
||||
/api/server/getserverinfo \
|
||||
/api/users/adm/hasadmin \
|
||||
/api/v1/server/getserverinfo
|
||||
do
|
||||
response_file="$(mktemp)"
|
||||
code="$(curl -ksS -o "${response_file}" -w "%{http_code}" \
|
||||
-H "Authorization: Bearer ${MASTER_KEY}" \
|
||||
"https://api.${BASE_DOMAIN}${path}" || true)"
|
||||
printf "%s %s " "${code}" "${path}"
|
||||
head -c 180 "${response_file}" | tr '\n' ' '
|
||||
echo
|
||||
rm -f "${response_file}"
|
||||
done
|
||||
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
GITEA_DOMAIN="${GITEA_DOMAIN:-gitea.abyssinfo.com}"
|
||||
GITEA_WEB_UPSTREAM="${GITEA_WEB_UPSTREAM:-100.88.0.1:3001}"
|
||||
GITEA_OIDC_UPSTREAM="${GITEA_OIDC_UPSTREAM:-100.88.0.1:3080}"
|
||||
NETMAKER_DIR="${NETMAKER_DIR:-/root/netmaker}"
|
||||
CADDYFILE="${CADDYFILE:-${NETMAKER_DIR}/Caddyfile}"
|
||||
|
||||
echo "[AbyssInfo] Configuring public Gitea reverse proxy"
|
||||
echo "[AbyssInfo] Domain: ${GITEA_DOMAIN}"
|
||||
echo "[AbyssInfo] Web upstream: http://${GITEA_WEB_UPSTREAM}"
|
||||
echo "[AbyssInfo] OIDC upstream: http://${GITEA_OIDC_UPSTREAM}"
|
||||
|
||||
if [[ "$(id -u)" != "0" ]]; then
|
||||
echo "[AbyssInfo] Please run as root." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "${CADDYFILE}" ]]; then
|
||||
echo "[AbyssInfo] Missing ${CADDYFILE}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[AbyssInfo] Checking upstreams from server ..."
|
||||
curl -fsS --connect-timeout 3 --max-time 8 "http://${GITEA_OIDC_UPSTREAM}/oidc/.well-known/openid-configuration" >/dev/null \
|
||||
&& echo "[AbyssInfo] OIDC upstream OK" \
|
||||
|| echo "[AbyssInfo] WARN: OIDC upstream is not reachable from server"
|
||||
|
||||
curl -fsSI --connect-timeout 3 --max-time 8 "http://${GITEA_WEB_UPSTREAM}/user/login" >/dev/null \
|
||||
&& echo "[AbyssInfo] Gitea web upstream OK" \
|
||||
|| echo "[AbyssInfo] WARN: Gitea web upstream is not reachable from server"
|
||||
|
||||
python3 - <<PY
|
||||
from pathlib import Path
|
||||
|
||||
caddyfile = Path("${CADDYFILE}")
|
||||
domain = "${GITEA_DOMAIN}"
|
||||
web = "${GITEA_WEB_UPSTREAM}"
|
||||
oidc = "${GITEA_OIDC_UPSTREAM}"
|
||||
start = "# AbyssInfo Gitea public access begin"
|
||||
end = "# AbyssInfo Gitea public access end"
|
||||
block = f"""
|
||||
{start}
|
||||
https://{domain} {{
|
||||
\tencode gzip zstd
|
||||
\thandle /oidc/* {{
|
||||
\t\treverse_proxy http://{oidc} {{
|
||||
\t\t\theader_up Host {{host}}
|
||||
\t\t\theader_up X-Forwarded-Host {{host}}
|
||||
\t\t\theader_up X-Forwarded-Proto https
|
||||
\t\t\theader_up X-Forwarded-Ssl on
|
||||
\t\t}}
|
||||
\t}}
|
||||
\thandle /auth/* {{
|
||||
\t\treverse_proxy http://{oidc} {{
|
||||
\t\t\theader_up Host {{host}}
|
||||
\t\t\theader_up X-Forwarded-Host {{host}}
|
||||
\t\t\theader_up X-Forwarded-Proto https
|
||||
\t\t\theader_up X-Forwarded-Ssl on
|
||||
\t\t}}
|
||||
\t}}
|
||||
\thandle /interaction/* {{
|
||||
\t\treverse_proxy http://{oidc} {{
|
||||
\t\t\theader_up Host {{host}}
|
||||
\t\t\theader_up X-Forwarded-Host {{host}}
|
||||
\t\t\theader_up X-Forwarded-Proto https
|
||||
\t\t\theader_up X-Forwarded-Ssl on
|
||||
\t\t}}
|
||||
\t}}
|
||||
\thandle {{
|
||||
\t\treverse_proxy http://{web} {{
|
||||
\t\t\theader_up Host {{host}}
|
||||
\t\t\theader_up X-Forwarded-Host {{host}}
|
||||
\t\t\theader_up X-Forwarded-Proto https
|
||||
\t\t\theader_up X-Forwarded-Ssl on
|
||||
\t\t}}
|
||||
\t}}
|
||||
}}
|
||||
{end}
|
||||
""".strip()
|
||||
|
||||
text = caddyfile.read_text(encoding="utf-8")
|
||||
if start in text and end in text:
|
||||
before, rest = text.split(start, 1)
|
||||
_old, after = rest.split(end, 1)
|
||||
text = before.rstrip() + "\n\n" + block + "\n" + after.lstrip()
|
||||
else:
|
||||
text = text.rstrip() + "\n\n" + block + "\n"
|
||||
caddyfile.write_text(text, encoding="utf-8")
|
||||
PY
|
||||
|
||||
echo "[AbyssInfo] Reloading Caddy ..."
|
||||
(cd "${NETMAKER_DIR}" && docker compose exec -T caddy caddy reload --config /etc/caddy/Caddyfile)
|
||||
|
||||
echo "[AbyssInfo] Checking public endpoints ..."
|
||||
curl -kfsS --connect-timeout 5 --max-time 12 "https://${GITEA_DOMAIN}/oidc/.well-known/openid-configuration" >/dev/null \
|
||||
&& echo "[AbyssInfo] Public OIDC endpoint OK" \
|
||||
|| echo "[AbyssInfo] WARN: Public OIDC endpoint is not ready. Check DNS and upstream reachability."
|
||||
|
||||
curl -kfsSI --connect-timeout 5 --max-time 12 "https://${GITEA_DOMAIN}/user/login" >/dev/null \
|
||||
&& echo "[AbyssInfo] Public Gitea login endpoint OK" \
|
||||
|| echo "[AbyssInfo] WARN: Public Gitea login endpoint is not ready. Check DNS and upstream reachability."
|
||||
|
||||
echo "[AbyssInfo] Gitea public access configured:"
|
||||
echo " https://${GITEA_DOMAIN}/oidc/.well-known/openid-configuration -> http://${GITEA_OIDC_UPSTREAM}"
|
||||
echo " https://${GITEA_DOMAIN}/user/login -> http://${GITEA_WEB_UPSTREAM}"
|
||||
@@ -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 "$@"
|
||||
@@ -0,0 +1,62 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$ServerHost,
|
||||
[Parameter(Mandatory = $true)][string]$ServerUser,
|
||||
[Parameter(Mandatory = $true)][string]$SshKey,
|
||||
[Parameter(Mandatory = $true)][string]$ProjectRoot
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function New-Secret {
|
||||
-join (1..64 | ForEach-Object { "{0:x}" -f (Get-Random -Minimum 0 -Maximum 16) })
|
||||
}
|
||||
|
||||
$apiDir = Join-Path $ProjectRoot "services\api"
|
||||
$envPath = Join-Path $apiDir ".env"
|
||||
$remote = "${ServerUser}@${ServerHost}"
|
||||
|
||||
$existing = @{}
|
||||
if (Test-Path -LiteralPath $envPath) {
|
||||
Get-Content -LiteralPath $envPath | ForEach-Object {
|
||||
if ($_ -and -not $_.StartsWith("#") -and $_.Contains("=")) {
|
||||
$parts = $_.Split("=", 2)
|
||||
$existing[$parts[0].TrimStart([char]0xFEFF)] = $parts[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$masterKey = (& ssh.exe -i $SshKey $remote "grep '^MASTER_KEY=' /root/netmaker/netmaker.env | cut -d= -f2-").Trim()
|
||||
if (-not $masterKey) {
|
||||
throw "MASTER_KEY was not found on the Netmaker server."
|
||||
}
|
||||
|
||||
$djangoSecret = New-Secret
|
||||
$jwtSecret = New-Secret
|
||||
$feishuAppId = $existing["FEISHU_APP_ID"]
|
||||
$feishuAppSecret = $existing["FEISHU_APP_SECRET"]
|
||||
|
||||
$content = @"
|
||||
DJANGO_SECRET_KEY=$djangoSecret
|
||||
DJANGO_DEBUG=true
|
||||
DJANGO_ALLOWED_HOSTS=127.0.0.1,localhost,$ServerHost
|
||||
CORS_ALLOWED_ORIGINS=http://localhost:5173,tauri://localhost
|
||||
|
||||
FEISHU_APP_ID=$feishuAppId
|
||||
FEISHU_APP_SECRET=$feishuAppSecret
|
||||
FEISHU_REDIRECT_URI=http://127.0.0.1:8000/auth/feishu/callback
|
||||
TAURI_DEEP_LINK_SCHEME=myvpn
|
||||
|
||||
JWT_SECRET=$jwtSecret
|
||||
JWT_EXPIRES_SECONDS=600
|
||||
OAUTH_STATE_EXPIRES_SECONDS=300
|
||||
LOGIN_CODE_EXPIRES_SECONDS=120
|
||||
|
||||
NETMAKER_BASE_URL=https://api.vpn.abyssinfo.com
|
||||
NETMAKER_API_TOKEN=$masterKey
|
||||
NETMAKER_NETWORK_ID=homevpn
|
||||
NETMAKER_ENROLLMENT_TAGS=homevpn,desktop,vpn
|
||||
NETMAKER_ENROLLMENT_TTL_SECONDS=600
|
||||
"@
|
||||
|
||||
[System.IO.File]::WriteAllText($envPath, $content, [System.Text.UTF8Encoding]::new($false))
|
||||
Write-Host "[AbyssInfo] Wrote services\api\.env"
|
||||
@@ -0,0 +1,34 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$ProjectRoot,
|
||||
[Parameter(Mandatory = $true)][string]$OutputZip
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$apiDir = Join-Path $ProjectRoot "services\api"
|
||||
$stageRoot = Join-Path $env:TEMP ("abyssinfo-api-stage-" + [guid]::NewGuid().ToString("N"))
|
||||
$stageApi = Join-Path $stageRoot "api"
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $stageApi | Out-Null
|
||||
|
||||
$excludeDirs = @(".venv", "__pycache__", "staticfiles")
|
||||
$excludeFiles = @("db.sqlite3")
|
||||
|
||||
Get-ChildItem -LiteralPath $apiDir -Force | ForEach-Object {
|
||||
if ($_.PSIsContainer) {
|
||||
if ($excludeDirs -notcontains $_.Name) {
|
||||
Copy-Item -LiteralPath $_.FullName -Destination $stageApi -Recurse -Force
|
||||
}
|
||||
} elseif ($excludeFiles -notcontains $_.Name) {
|
||||
Copy-Item -LiteralPath $_.FullName -Destination $stageApi -Force
|
||||
}
|
||||
}
|
||||
|
||||
if (Test-Path -LiteralPath $OutputZip) {
|
||||
Remove-Item -LiteralPath $OutputZip -Force
|
||||
}
|
||||
|
||||
Compress-Archive -Path (Join-Path $stageApi "*") -DestinationPath $OutputZip -Force
|
||||
Remove-Item -LiteralPath $stageRoot -Recurse -Force
|
||||
|
||||
Write-Host "[AbyssInfo] Packaged API to $OutputZip"
|
||||
@@ -0,0 +1,76 @@
|
||||
services:
|
||||
|
||||
netmaker:
|
||||
container_name: netmaker
|
||||
image: gravitl/netmaker:$SERVER_IMAGE_TAG
|
||||
env_file: ./netmaker.env
|
||||
restart: always
|
||||
volumes:
|
||||
- dnsconfig:/root/config/dnsconfig
|
||||
- sqldata:/root/data
|
||||
environment:
|
||||
# config-dependant vars
|
||||
- STUN_SERVERS=stun1.l.google.com:19302,stun2.l.google.com:19302,stun3.l.google.com:19302,stun4.l.google.com:19302
|
||||
# The domain/host IP indicating the mq broker address
|
||||
- BROKER_ENDPOINT=wss://broker.${NM_DOMAIN} # For EMQX broker use `BROKER_ENDPOINT=wss://broker.${NM_DOMAIN}/mqtt`
|
||||
# For EMQX broker (uncomment the two lines below)
|
||||
#- BROKER_TYPE=emqx
|
||||
#- EMQX_REST_ENDPOINT=http://mq:18083
|
||||
# The base domain of netmaker
|
||||
- SERVER_NAME=${NM_DOMAIN}
|
||||
- SERVER_API_CONN_STRING=api.${NM_DOMAIN}:443
|
||||
# Address of the CoreDNS server. Defaults to SERVER_HOST
|
||||
- COREDNS_ADDR=${SERVER_HOST}
|
||||
# Overrides SERVER_HOST if set. Useful for making HTTP available via different interfaces/networks.
|
||||
- SERVER_HTTP_HOST=api.${NM_DOMAIN}
|
||||
|
||||
netmaker-ui:
|
||||
container_name: netmaker-ui
|
||||
image: gravitl/netmaker-ui:$UI_IMAGE_TAG
|
||||
env_file: ./netmaker.env
|
||||
environment:
|
||||
# config-dependant vars
|
||||
# URL where UI will send API requests. Change based on SERVER_HOST, SERVER_HTTP_HOST, and API_PORT
|
||||
BACKEND_URL: "https://api.${NM_DOMAIN}"
|
||||
depends_on:
|
||||
- netmaker
|
||||
links:
|
||||
- "netmaker:api"
|
||||
restart: always
|
||||
|
||||
caddy:
|
||||
image: caddy:2.8.4
|
||||
container_name: caddy
|
||||
env_file: ./netmaker.env
|
||||
restart: unless-stopped
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile
|
||||
- caddy_data:/data
|
||||
- caddy_conf:/config
|
||||
ports:
|
||||
- "80:80/tcp"
|
||||
- "443:443/tcp"
|
||||
- "50051:50051"
|
||||
mq:
|
||||
container_name: mq
|
||||
image: eclipse-mosquitto:2.0.15-openssl
|
||||
env_file: ./netmaker.env
|
||||
depends_on:
|
||||
- netmaker
|
||||
restart: unless-stopped
|
||||
command: [ "/mosquitto/config/wait.sh" ]
|
||||
volumes:
|
||||
- ./mosquitto.conf:/mosquitto/config/mosquitto.conf
|
||||
- ./wait.sh:/mosquitto/config/wait.sh
|
||||
- mosquitto_logs:/mosquitto/log
|
||||
- mosquitto_data:/mosquitto/data
|
||||
volumes:
|
||||
caddy_data: { } # runtime data for caddy
|
||||
caddy_conf: { } # configuration file for Caddy
|
||||
sqldata: { }
|
||||
dnsconfig: { } # storage for coredns
|
||||
mosquitto_logs: { } # storage for mqtt logs
|
||||
mosquitto_data: { } # storage for mqtt data
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
# Dashboard
|
||||
https://dashboard.{$NM_DOMAIN} {
|
||||
# Apply basic security headers
|
||||
header {
|
||||
# Enable cross origin access to *.{$NM_DOMAIN}
|
||||
Access-Control-Allow-Origin *.{$NM_DOMAIN}
|
||||
# Enable HTTP Strict Transport Security (HSTS)
|
||||
Strict-Transport-Security "max-age=31536000;"
|
||||
# Enable cross-site filter (XSS) and tell browser to block detected attacks
|
||||
X-XSS-Protection "1; mode=block"
|
||||
# Disallow the site to be rendered within a frame on a foreign domain (clickjacking protection)
|
||||
X-Frame-Options "SAMEORIGIN"
|
||||
# Prevent search engines from indexing
|
||||
X-Robots-Tag "none"
|
||||
# Remove the server name
|
||||
-Server
|
||||
}
|
||||
|
||||
reverse_proxy http://netmaker-ui
|
||||
}
|
||||
|
||||
# API
|
||||
https://api.{$NM_DOMAIN} {
|
||||
reverse_proxy http://netmaker:8081
|
||||
}
|
||||
|
||||
# MQ
|
||||
broker.{$NM_DOMAIN} {
|
||||
@ws {
|
||||
header Connection *Upgrade*
|
||||
header Upgrade websocket
|
||||
}
|
||||
reverse_proxy @ws mq:8883 # For EMQX websockets use `reverse_proxy @ws mq:8083`
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
per_listener_settings false
|
||||
listener 8883
|
||||
protocol websockets
|
||||
allow_anonymous false
|
||||
|
||||
listener 1883
|
||||
protocol websockets
|
||||
allow_anonymous false
|
||||
|
||||
password_file /mosquitto/password.txt
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
#!/bin/ash
|
||||
|
||||
encrypt_password() {
|
||||
echo "${MQ_USERNAME}:${MQ_PASSWORD}" > /mosquitto/password.txt
|
||||
mosquitto_passwd -U /mosquitto/password.txt
|
||||
}
|
||||
|
||||
main(){
|
||||
|
||||
encrypt_password
|
||||
echo "Starting MQ..."
|
||||
# Run the main container command.
|
||||
/docker-entrypoint.sh
|
||||
/usr/sbin/mosquitto -c /mosquitto/config/mosquitto.conf
|
||||
|
||||
}
|
||||
|
||||
main "${@}"
|
||||
@@ -0,0 +1,111 @@
|
||||
# Email used for SSL certificates
|
||||
NM_EMAIL=
|
||||
# The base domain of netmaker
|
||||
NM_DOMAIN=
|
||||
# Public IPv4 endpoint of machine
|
||||
SERVER_HOST=
|
||||
# Public IPv6 endpoint of machine
|
||||
SERVER_HOST6=
|
||||
# The admin master key for accessing the API. Change this in any production installation.
|
||||
MASTER_KEY=
|
||||
# The username to set for MQ access
|
||||
MQ_USERNAME=
|
||||
# The password to set for MQ access
|
||||
MQ_PASSWORD=
|
||||
INSTALL_TYPE=
|
||||
NETMAKER_TENANT_ID=
|
||||
LICENSE_KEY=
|
||||
SERVER_IMAGE_TAG=
|
||||
UI_IMAGE_TAG=
|
||||
# used for HA - identifies this server vs other servers if unset uses host_name
|
||||
HOST_NAME=
|
||||
METRICS_EXPORTER=off
|
||||
#metrics exporter secret
|
||||
METRICS_SECRET=
|
||||
#metrics exporter user
|
||||
METRICS_USERNAME=netmaker
|
||||
# Enables DNS Mode, meaning all nodes will set hosts file for private dns settings
|
||||
DNS_MODE=on
|
||||
# Enable auto update of netclient ? ENUM:- enabled,disabled | default=enabled
|
||||
NETCLIENT_AUTO_UPDATE=enabled
|
||||
# The HTTP API port for Netmaker. Used for API calls / communication from front end.
|
||||
# If changed, need to change port of BACKEND_URL for netmaker-ui.
|
||||
API_PORT=8081
|
||||
EXPORTER_API_PORT=8085
|
||||
# The "allowed origin" for API requests. Change to restrict where API requests can come from with comma-separated
|
||||
# URLs. ex:- https://dashboard.netmaker.domain1.com,https://dashboard.netmaker.domain2.com
|
||||
CORS_ALLOWED_ORIGIN=*
|
||||
# Show keys permanently in UI (until deleted) as opposed to 1-time display.
|
||||
DISPLAY_KEYS=on
|
||||
# Database to use - sqlite, postgres
|
||||
DATABASE=sqlite
|
||||
# The address of the mq server. If running from docker compose it will be "mq". Otherwise, need to input address.
|
||||
# If using "host networking", it will find and detect the IP of the mq container.
|
||||
# For EMQX websockets use `SERVER_BROKER_ENDPOINT=ws://mq:8083/mqtt`
|
||||
SERVER_BROKER_ENDPOINT=ws://mq:1883
|
||||
# Logging verbosity level - 1, 2, or 3
|
||||
VERBOSITY=1
|
||||
DEBUG_MODE=off
|
||||
# Enables the REST backend (API running on API_PORT at SERVER_HTTP_HOST).
|
||||
REST_BACKEND=on
|
||||
# If turned "on", Server will not set Host based on remote IP check.
|
||||
# This is already overridden if SERVER_HOST is set. Turned "off" by default.
|
||||
DISABLE_REMOTE_IP_CHECK=off
|
||||
# Whether or not to send telemetry data to help improve Netmaker. Switch to "off" to opt out of sending telemetry.
|
||||
TELEMETRY=on
|
||||
###
|
||||
#
|
||||
# OAuth section
|
||||
#
|
||||
###
|
||||
# only mentioned domains will be allowded to signup using oauth, by default all domains are allowed
|
||||
ALLOWED_EMAIL_DOMAINS=*
|
||||
# "<azure-ad|github|google|oidc>"
|
||||
AUTH_PROVIDER=
|
||||
# "<client id of your oauth provider>"
|
||||
CLIENT_ID=
|
||||
# "<client secret of your oauth provider>"
|
||||
CLIENT_SECRET=
|
||||
# "https://dashboard.<netmaker base domain>"
|
||||
FRONTEND_URL=
|
||||
# "<only for azure, you may optionally specify the tenant for the OAuth>"
|
||||
AZURE_TENANT=
|
||||
# https://oidc.yourprovider.com - URL of oidc provider
|
||||
OIDC_ISSUER=
|
||||
# Duration of JWT token validity in seconds
|
||||
JWT_VALIDITY_DURATION=43200
|
||||
# Allow a user to connect to multiple networks simultaneously
|
||||
RAC_RESTRICT_TO_SINGLE_NETWORK=false
|
||||
# if turned on data will be cached on to improve performance significantly (IMPORTANT: If HA set to `false` )
|
||||
CACHING_ENABLED=true
|
||||
# if turned on netclient checks if peers are reachable over private/LAN address, and choose that as peer endpoint
|
||||
ENDPOINT_DETECTION=true
|
||||
# config for sending emails
|
||||
# mail server host
|
||||
SMTP_HOST=smtp.gmail.com
|
||||
# mail server port
|
||||
SMTP_PORT=587
|
||||
# sender email
|
||||
EMAIL_SENDER_ADDR=
|
||||
# sender smtp user, if unset sender email will be used
|
||||
EMAIL_SENDER_USER=
|
||||
# sender smtp password
|
||||
EMAIL_SENDER_PASSWORD=
|
||||
# default domain for internal DNS lookup
|
||||
DEFAULT_DOMAIN=nm.internal
|
||||
# managed dns setting, set to true to resolve dns entries on netmaker network
|
||||
MANAGE_DNS=true
|
||||
# set to true, old acl is supported, otherwise, old acl is disabled
|
||||
OLD_ACL_SUPPORT=true
|
||||
# if STUN is set to true, hole punch is called
|
||||
STUN=true
|
||||
# Metrics Collection Port
|
||||
METRICS_PORT=51821
|
||||
# Metrics Collection interval in minutes
|
||||
PUBLISH_METRIC_INTERVAL=15
|
||||
PROMETHEUS_HOST=http://prometheus:9090 #https://prometheus.${NM_DOMAIN}
|
||||
NETMAKER_METRICS_TARGET=http://netmaker-exporter:8085 #https://netmaker-exporter.${NM_DOMAIN}
|
||||
METRICS_SECRET=
|
||||
|
||||
|
||||
|
||||
Vendored
+1222
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user