Start nav-site in background when pm2 and systemd are unavailable.
Fall back to a pid-file background process after deploy, and detect running state via HTTP port checks in the management menu. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+112
-3
@@ -6,7 +6,10 @@ import json
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
DEFAULT_REPO = "https://git.bz121.com/dekun/LocalNav.git"
|
||||
@@ -74,6 +77,91 @@ def resolve_project_dir(explicit: str | None = None) -> Path | None:
|
||||
return None
|
||||
|
||||
|
||||
def pid_file_path(project_dir: Path) -> Path:
|
||||
return project_dir / "logs" / "nav-site.pid"
|
||||
|
||||
|
||||
def get_nav_port(project_dir: Path | None = None) -> str:
|
||||
if project_dir:
|
||||
env_file = project_dir / ".env"
|
||||
if env_file.is_file():
|
||||
for line in env_file.read_text(encoding="utf-8-sig").splitlines():
|
||||
s = line.strip()
|
||||
if s.startswith("NAV_PORT="):
|
||||
val = s.split("=", 1)[1].strip().strip("\"'")
|
||||
if val:
|
||||
return val
|
||||
return os.environ.get("NAV_PORT", "5070")
|
||||
|
||||
|
||||
def http_ready(port: str, timeout: float = 2.0) -> bool:
|
||||
url = f"http://127.0.0.1:{port}/login"
|
||||
try:
|
||||
with urllib.request.urlopen(url, timeout=timeout) as resp:
|
||||
return resp.status == 200
|
||||
except (urllib.error.URLError, TimeoutError, OSError):
|
||||
return False
|
||||
|
||||
|
||||
def port_open(port: str) -> bool:
|
||||
try:
|
||||
p = int(port)
|
||||
except ValueError:
|
||||
return False
|
||||
try:
|
||||
with socket.create_connection(("127.0.0.1", p), timeout=1.5):
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def read_background_pid(project_dir: Path) -> int | None:
|
||||
path = pid_file_path(project_dir)
|
||||
if not path.is_file():
|
||||
return None
|
||||
try:
|
||||
return int(path.read_text(encoding="utf-8").strip())
|
||||
except (ValueError, OSError):
|
||||
return None
|
||||
|
||||
|
||||
def is_process_running(pid: int) -> bool:
|
||||
if pid <= 0:
|
||||
return False
|
||||
if platform.system() == "Windows":
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
["tasklist", "/FI", f"PID eq {pid}"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
return str(pid) in (proc.stdout or "")
|
||||
except OSError:
|
||||
return False
|
||||
try:
|
||||
os.kill(pid, 0)
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def stop_background_process(project_dir: Path) -> bool:
|
||||
pid = read_background_pid(project_dir)
|
||||
if not pid or not is_process_running(pid):
|
||||
pid_file_path(project_dir).unlink(missing_ok=True)
|
||||
return False
|
||||
try:
|
||||
if platform.system() == "Windows":
|
||||
subprocess.run(["taskkill", "/PID", str(pid), "/F"], check=False)
|
||||
else:
|
||||
os.kill(pid, 15)
|
||||
except OSError:
|
||||
pass
|
||||
pid_file_path(project_dir).unlink(missing_ok=True)
|
||||
return True
|
||||
|
||||
|
||||
def pm2_available() -> str | None:
|
||||
return shutil.which("pm2")
|
||||
|
||||
@@ -149,17 +237,35 @@ def systemd_active(name: str = APP_NAME) -> bool:
|
||||
def service_status_text(project_dir: Path | None) -> str:
|
||||
if not project_dir:
|
||||
return "未安装"
|
||||
|
||||
port = get_nav_port(project_dir)
|
||||
if http_ready(port):
|
||||
pm2_state = pm2_app_status()
|
||||
if pm2_state:
|
||||
return f"运行中 · pm2 {pm2_state} · :{port}"
|
||||
if systemd_unit_ready() and systemd_active():
|
||||
return f"运行中 · systemd · :{port}"
|
||||
pid = read_background_pid(project_dir)
|
||||
if pid and is_process_running(pid):
|
||||
return f"运行中 · 后台 pid {pid} · :{port}"
|
||||
return f"运行中 · 端口 {port}"
|
||||
|
||||
pm2_state = pm2_app_status()
|
||||
if pm2_state:
|
||||
return f"pm2 {pm2_state}"
|
||||
return f"pm2 {pm2_state}(未响应 :{port})"
|
||||
if systemd_unit_ready() and systemd_active():
|
||||
return "systemd active"
|
||||
return f"systemd active(未响应 :{port})"
|
||||
if systemd_unit_ready():
|
||||
return "systemd 已配置但未运行"
|
||||
pid = read_background_pid(project_dir)
|
||||
if pid and is_process_running(pid):
|
||||
return f"后台 pid {pid}(未响应 :{port})"
|
||||
if port_open(port):
|
||||
return f"端口 {port} 已占用(进程未知)"
|
||||
return "未运行"
|
||||
|
||||
|
||||
def stop_service() -> list[str]:
|
||||
def stop_service(project_dir: Path | None = None) -> list[str]:
|
||||
"""停止并移除进程管理中的 nav-site,返回已执行操作说明。"""
|
||||
actions: list[str] = []
|
||||
pm2 = pm2_available()
|
||||
@@ -175,4 +281,7 @@ def stop_service() -> list[str]:
|
||||
subprocess.run([systemctl, "disable", f"{APP_NAME}.service"], check=False)
|
||||
actions.append("systemctl stop/disable nav-site")
|
||||
|
||||
if project_dir and stop_background_process(project_dir):
|
||||
actions.append("已停止后台 nav-site 进程")
|
||||
|
||||
return actions
|
||||
|
||||
Reference in New Issue
Block a user