+@app.get("/api/services")
+async def api_list_services(request: Request):
+ """返回本机可作为链接「关联服务」的 systemd 单元名清单(供前端下拉选择)。"""
+ if not _auth_ok(request):
+ return JSONResponse({"ok": False, "error": "未授权"}, status_code=401)
+ keywords = ("web", "lexicon", "study", "station", "word", "codebuddy",
+ "nginx", "chatbot", "gitweb", "frontend", "backend",
+ "mountion", "builder", "keepalive", "healthcheck", "sync")
+ result = []
+ try:
+ out = subprocess.run(
+ ["systemctl", "list-unit-files", "--type=service", "--no-legend", "--no-pager"],
+ capture_output=True, text=True, timeout=10,
+ )
+ for line in out.stdout.splitlines():
+ parts = line.split()
+ name = parts[0] if parts else ""
+ if not name.endswith(".service"):
+ continue
+ low = name.lower()
+ if any(k in low for k in keywords):
+ result.append(name)
+ except Exception as e:
+ logger.warning(f"列举 systemd 服务失败: {e}")
+ result = []
+ # 始终包含本应用与反代,确保可选
+ for must in ("codebuddy-web.service", "nginx.service"):
+ if must not in result:
+ result.append(must)
+ result = sorted(set(result))
+ return JSONResponse({"ok": True, "services": result})
+
+