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,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/"
|
||||
Reference in New Issue
Block a user