From d78797db821c4385be217ada45e6a84452e8a794 Mon Sep 17 00:00:00 2001 From: test2 Date: Sat, 25 Jul 2026 20:44:47 +0300 Subject: [PATCH] Fix Dokploy Bad Gateway: panel-first compose and stable boot Co-authored-by: Cursor --- README.md | 13 +++++++++++++ app/security.py | 14 +++++++++----- docker-compose.yml | 43 +++++++++++++++++++++++-------------------- requirements.txt | 1 - scripts/entrypoint.sh | 20 ++++++++++++++++++-- 5 files changed, 63 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 3666511..3785e61 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,19 @@ POSTGRES_PASSWORD=надежный-пароль-бд 4. Deploy. Панель: порт `8000` (или домен через Traefik в Dokploy). 5. Вход: `/admin/login` +### Если Bad Gateway (502) + +1. В Dokploy Domain укажите сервис **`panel`**, порт **`8000`** (не `postgres`). +2. Откройте логи сервиса `panel` — должны быть строки `Database is ready` и `Starting uvicorn`. +3. Задайте `DATABASE_URL` явно, если пароль БД содержит спецсимволы: + +```env +DATABASE_URL=postgresql+asyncpg://vpn:ПАРОЛЬ@postgres:5432/vpn_panel +POSTGRES_PASSWORD=ПАРОЛЬ +``` + +4. Redeploy после правок. + Админ при каждом старте **синхронизируется из ENV**. Файл `.env` на сервере **не обязателен** — все переменные читаются из Compose / Dokploy UI. diff --git a/app/security.py b/app/security.py index 2a06f8d..53fb1dc 100644 --- a/app/security.py +++ b/app/security.py @@ -1,11 +1,15 @@ -from passlib.context import CryptContext - -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") +import bcrypt def hash_password(password: str) -> str: - return pwd_context.hash(password) + raw = password.encode("utf-8") + # bcrypt limit is 72 bytes + digest = bcrypt.hashpw(raw[:72], bcrypt.gensalt()) + return digest.decode("utf-8") def verify_password(password: str, password_hash: str) -> bool: - return pwd_context.verify(password, password_hash) + try: + return bcrypt.checkpw(password.encode("utf-8")[:72], password_hash.encode("utf-8")) + except ValueError: + return False diff --git a/docker-compose.yml b/docker-compose.yml index d678949..58ece4c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,21 +1,5 @@ services: - postgres: - image: postgres:16-alpine - restart: unless-stopped - environment: - POSTGRES_USER: ${POSTGRES_USER:-vpn} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-vpn} - POSTGRES_DB: ${POSTGRES_DB:-vpn_panel} - volumes: - - postgres_data:/var/lib/postgresql/data - networks: - - vpn_net - healthcheck: - test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] - interval: 5s - timeout: 5s - retries: 10 - + # IMPORTANT for Dokploy: attach domain to this service, port 8000 panel: build: context: . @@ -24,6 +8,8 @@ services: depends_on: postgres: condition: service_healthy + expose: + - "8000" ports: - "${PANEL_PORT:-8000}:8000" environment: @@ -39,7 +25,7 @@ services: POSTGRES_USER: ${POSTGRES_USER:-vpn} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-vpn} POSTGRES_DB: ${POSTGRES_DB:-vpn_panel} - DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-vpn}:${POSTGRES_PASSWORD:-vpn}@postgres:5432/${POSTGRES_DB:-vpn_panel} + DATABASE_URL: ${DATABASE_URL:-postgresql+asyncpg://vpn:vpn@postgres:5432/vpn_panel} PUBLIC_HOST: ${PUBLIC_HOST:-127.0.0.1} PUBLIC_PORT: ${PUBLIC_PORT:-51820} @@ -62,8 +48,25 @@ services: test: ["CMD", "curl", "-f", "http://127.0.0.1:8000/health"] interval: 15s timeout: 5s - retries: 5 - start_period: 20s + retries: 10 + start_period: 90s + + postgres: + image: postgres:16-alpine + restart: unless-stopped + environment: + POSTGRES_USER: ${POSTGRES_USER:-vpn} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-vpn} + POSTGRES_DB: ${POSTGRES_DB:-vpn_panel} + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + - vpn_net + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] + interval: 5s + timeout: 5s + retries: 10 networks: vpn_net: diff --git a/requirements.txt b/requirements.txt index 30c0faa..9ec57ae 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,6 @@ asyncpg==0.30.0 alembic==1.14.0 pydantic==2.10.4 pydantic-settings==2.7.0 -passlib[bcrypt]==1.7.4 bcrypt==4.2.1 python-multipart==0.0.20 jinja2==3.1.5 diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh index 7c87289..f08dd60 100644 --- a/scripts/entrypoint.sh +++ b/scripts/entrypoint.sh @@ -1,6 +1,21 @@ #!/bin/sh set -e +echo "==> VpnPanel starting" + +# Always build DATABASE_URL from POSTGRES_* (Dokploy-friendly) +export DATABASE_URL="$( + python - <<'PY' +import os +from urllib.parse import quote_plus + +user = os.environ.get("POSTGRES_USER", "vpn") +password = os.environ.get("POSTGRES_PASSWORD", "vpn") +db = os.environ.get("POSTGRES_DB", "vpn_panel") +print(f"postgresql+asyncpg://{quote_plus(user)}:{quote_plus(password)}@postgres:5432/{db}") +PY +)" + echo "Waiting for database..." python - <<'PY' import asyncio @@ -11,7 +26,7 @@ from sqlalchemy import text from sqlalchemy.ext.asyncio import create_async_engine -async def wait_db(retries: int = 40) -> None: +async def wait_db(retries: int = 60) -> None: url = os.environ["DATABASE_URL"] engine = create_async_engine(url) for i in range(retries): @@ -25,6 +40,7 @@ async def wait_db(retries: int = 40) -> None: print(f"DB not ready ({i + 1}/{retries}): {exc}") await asyncio.sleep(1) await engine.dispose() + print("ERROR: database did not become ready", file=sys.stderr) sys.exit(1) @@ -37,5 +53,5 @@ alembic upgrade head echo "Bootstrapping admin..." python -m app.bootstrap -echo "Starting application..." +echo "Starting uvicorn..." exec "$@"