From 83b680b2ea1edb733965deda941a9176df3f18de Mon Sep 17 00:00:00 2001 From: 1445043649 <> Date: Mon, 11 May 2026 09:17:19 +0800 Subject: [PATCH] feat(check): postgres preflight in scripts/check.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 check_postgres_preflight() —— make dev 链 (check.py → serve.sh) 的最后一道 preflight: - config.yaml 不存在 → silent skip(让 setup_wizard 引导) - backend != postgres → silent skip - DATABASE_URL 未设 → FAIL with hint - postgres 设了但 host:port 3s socket 不通 → FAIL with 启 docker 提示 - 通则 OK + 显示 host:port 不在 serve.sh 里加:Makefile 已经把 check.py 串在 serve.sh 之前,FAIL 会 自然阻断启动;避免 bash + python 两处实现 PG 探测。 doctor.py 的 check_database 是事后诊断(make doctor);本 check 是事前 preflight(make dev/start)—— 互补。 Stage 0 PR2 T2.6. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/check.py | 89 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/scripts/check.py b/scripts/check.py index b633b101..44b7c934 100644 --- a/scripts/check.py +++ b/scripts/check.py @@ -3,10 +3,13 @@ from __future__ import annotations +import os import shutil +import socket import subprocess import sys from pathlib import Path +from urllib.parse import urlparse def configure_stdio() -> None: @@ -57,6 +60,80 @@ def parse_node_major(version_text: str) -> int | None: return int(major_str) +def check_postgres_preflight() -> tuple[bool, str | None]: + """If config.yaml selects postgres, verify the URL is set + host:port reachable. + + Returns ``(ok, message)``. Skips silently when: + - config.yaml does not exist (user hasn't run 'make setup' yet) + - database.backend is not 'postgres' (sqlite/memory don't need preflight) + - DATABASE_URL env var is not set (user hasn't filled .env yet) + + Fails when database.backend is 'postgres' AND DATABASE_URL parses but the + host:port socket cannot be opened in 3 seconds. + """ + repo_root = Path(__file__).resolve().parent.parent + config_path = repo_root / "config.yaml" + if not config_path.exists(): + return True, None # No config yet — let setup_wizard handle it + + try: + import yaml # type: ignore[import-not-found] + except ImportError: + return True, "PyYAML not installed; skipping Postgres preflight" + + try: + data = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {} + except Exception as exc: + return False, f"Failed to parse config.yaml: {exc}" + + db = data.get("database") or {} + backend = (db.get("backend") or "sqlite").lower() + if backend != "postgres": + return True, None # not on PG, no preflight needed + + raw_url = db.get("postgres_url") or db.get("url") or "" + if isinstance(raw_url, str) and raw_url.startswith("$"): + env_name = raw_url[1:] + # Load .env if available so this preflight matches what serve.sh sees + env_path = repo_root / ".env" + if env_path.exists(): + try: + from dotenv import load_dotenv # type: ignore[import-not-found] + + load_dotenv(env_path, override=False) + except ImportError: + pass + url = os.environ.get(env_name, "") + if not url: + return False, f"database.backend=postgres but {env_name} is not set in .env" + else: + url = raw_url + + if not url: + return False, "database.backend=postgres but no postgres_url is configured" + + parsed = urlparse(url.replace("postgresql+asyncpg://", "postgresql://")) + host = parsed.hostname + port = parsed.port or 5432 + if not host: + return False, f"Cannot parse host from DATABASE_URL: {url[:60]}..." + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(3) + try: + sock.connect((host, port)) + return True, f"Postgres reachable at {host}:{port}" + except OSError as exc: + return ( + False, + f"Postgres unreachable at {host}:{port} ({exc}). " + f"Run 'docker compose -f docker/docker-compose-dev.yaml up -d postgres' " + f"or check DATABASE_URL.", + ) + finally: + sock.close() + + def main() -> int: configure_stdio() print("==========================================") @@ -143,6 +220,18 @@ def main() -> int: print(" Or visit: https://nginx.org/en/download.html") failed = True + print() + print("Checking Postgres preflight...") + pg_ok, pg_msg = check_postgres_preflight() + if pg_ok: + if pg_msg: + print(f" OK {pg_msg}") + else: + print(" -- skipped (config.yaml missing or backend != postgres)") + else: + print(f" FAIL {pg_msg}") + failed = True + print() if not failed: print("==========================================")