511 lines
24 KiB
Python
511 lines
24 KiB
Python
from __future__ import annotations
|
||
|
||
import json
|
||
import os
|
||
from pathlib import Path
|
||
from typing import Any
|
||
|
||
from .env import env_float, env_int, env_int_list, env_str, env_str_list, load_env_file
|
||
|
||
|
||
# 项目根目录。
|
||
ROOT = Path(__file__).resolve().parents[1]
|
||
# 运行期产物目录。
|
||
RUNTIME_DIR = ROOT / "run"
|
||
# 常见工作区根目录,便于本地开发时自动发现业务仓。
|
||
WORKSPACE_ROOT = ROOT.parent
|
||
# 静态资源目录。
|
||
STATIC_DIR = ROOT / "static"
|
||
# 默认 .env 路径。
|
||
DEFAULT_ENV_PATH = ROOT / ".env"
|
||
|
||
# 先加载项目根目录的 .env。
|
||
load_env_file(Path(env_str("APP_ENV_FILE", str(DEFAULT_ENV_PATH))).expanduser())
|
||
|
||
# 监控目标配置文件路径。
|
||
CONFIG_PATH = Path(env_str("TARGETS_FILE", str(ROOT / "config" / "targets.json"))).expanduser()
|
||
# HTTP 服务监听地址。
|
||
APP_BIND = env_str("APP_BIND", "0.0.0.0") or "0.0.0.0"
|
||
# HTTP 服务监听端口。
|
||
APP_PORT = env_int("APP_PORT", 2026)
|
||
# 当前 HTTP 应用挂载前缀原始值。
|
||
RAW_APP_BASE_PATH = env_str("APP_BASE_PATH", "").strip()
|
||
# 当前 HTTP 应用挂载前缀;空串表示直接挂在根路径。
|
||
APP_BASE_PATH = "/" + RAW_APP_BASE_PATH.strip("/") if RAW_APP_BASE_PATH else ""
|
||
# Yumi 内部实例固定走本机回环地址。
|
||
YUMI_INTERNAL_HOST = env_str("YUMI_INTERNAL_HOST", "127.0.0.1") or "127.0.0.1"
|
||
# Yumi 内部实例监听端口。
|
||
YUMI_INTERNAL_PORT = env_int("YUMI_INTERNAL_PORT", 2027)
|
||
# HaiYi 内部实例固定走本机回环地址。
|
||
HAIYI_INTERNAL_HOST = env_str("HAIYI_INTERNAL_HOST", "127.0.0.1") or "127.0.0.1"
|
||
# HaiYi 内部实例监听端口。
|
||
HAIYI_INTERNAL_PORT = env_int("HAIYI_INTERNAL_PORT", 2028)
|
||
# 网关反代到内部实例时统一带这一段共享密钥。
|
||
INTERNAL_PROXY_SHARED_SECRET = env_str("INTERNAL_PROXY_SHARED_SECRET", "hy-app-monitor-internal")
|
||
# 网关反代内部实例时的统一超时秒数。
|
||
GATEWAY_PROXY_TIMEOUT_SECONDS = env_int("GATEWAY_PROXY_TIMEOUT_SECONDS", 600)
|
||
# 本地账号库统一落到 run 目录,避免写入版本库。
|
||
AUTH_DB_PATH = Path(env_str("AUTH_DB_PATH", str(RUNTIME_DIR / "auth.sqlite3"))).expanduser()
|
||
# 会话 Cookie 名称固定成单值,前后端都按这个名字取。
|
||
AUTH_SESSION_COOKIE_NAME = env_str("AUTH_SESSION_COOKIE_NAME", "hy_app_monitor_session") or "hy_app_monitor_session"
|
||
# 简单本地账号默认保活 24 小时,过期后重新登录。
|
||
AUTH_SESSION_TTL_HOURS = env_int("AUTH_SESSION_TTL_HOURS", 24)
|
||
# 部署在 HTTPS 反代后时,可通过 .env 把 Cookie 切到 Secure。
|
||
AUTH_COOKIE_SECURE = env_str("AUTH_COOKIE_SECURE", "0").lower() in {"1", "true", "yes", "on"}
|
||
|
||
# 服务健康检查超时时间。
|
||
PROBE_TIMEOUT_SECONDS = env_float("PROBE_TIMEOUT_SECONDS", 3.0)
|
||
# 服务健康检查缓存秒数。
|
||
PROBE_CACHE_TTL_SECONDS = env_float("PROBE_CACHE_TTL_SECONDS", 5.0)
|
||
# 服务健康检查并发数。
|
||
PROBE_MAX_WORKERS = env_int("PROBE_MAX_WORKERS", 12)
|
||
|
||
# 主机指标缓存秒数。
|
||
HOST_METRIC_CACHE_TTL_SECONDS = env_float("HOST_METRIC_CACHE_TTL_SECONDS", 25.0)
|
||
# 主机指标远端采集超时时间;兼容旧的 TAT_* 变量名。
|
||
HOST_METRIC_TIMEOUT_SECONDS = env_int("HOST_METRIC_TIMEOUT_SECONDS", env_int("TAT_METRIC_TIMEOUT_SECONDS", 30))
|
||
# 主机指标 CPU 采样间隔;兼容旧的 TAT_* 变量名。
|
||
HOST_METRIC_CPU_SAMPLE_SECONDS = env_float("HOST_METRIC_CPU_SAMPLE_SECONDS", env_float("TAT_CPU_SAMPLE_SECONDS", 1.0))
|
||
# 兼容旧代码里仍在引用的变量名。
|
||
TAT_METRIC_TIMEOUT_SECONDS = HOST_METRIC_TIMEOUT_SECONDS
|
||
TAT_CPU_SAMPLE_SECONDS = HOST_METRIC_CPU_SAMPLE_SECONDS
|
||
# 业务运行目录根路径。
|
||
RUNTIME_BASE_DIR = env_str("RUNTIME_BASE_DIR", "/opt/likei")
|
||
# 远端宿主机默认 SSH 用户。
|
||
SSH_USER = env_str("SSH_USER", "root") or "root"
|
||
# 非 root 用户时,允许统一在远端命令前补 sudo -n。
|
||
SSH_USE_SUDO = env_str("SSH_USE_SUDO", "0").lower() in {"1", "true", "yes", "on"}
|
||
# 远端宿主机默认 SSH 端口。
|
||
SSH_PORT = env_int("SSH_PORT", 22)
|
||
# SSH 建连超时秒数。
|
||
SSH_CONNECT_TIMEOUT_SECONDS = env_int("SSH_CONNECT_TIMEOUT_SECONDS", 10)
|
||
# SSH 严格主机检查策略,默认首次连接自动接收。
|
||
SSH_STRICT_HOST_KEY_CHECKING = env_str("SSH_STRICT_HOST_KEY_CHECKING", "accept-new") or "accept-new"
|
||
# 运行期默认使用 deploy 机项目目录下的专用私钥。
|
||
SSH_PRIVATE_KEY_PATH = Path(env_str("SSH_PRIVATE_KEY_PATH", str(ROOT / "run" / "ssh" / "id_ed25519"))).expanduser()
|
||
# 已知主机文件同样放到项目 run 目录,避免污染系统级 known_hosts。
|
||
SSH_KNOWN_HOSTS_PATH = Path(
|
||
env_str("SSH_KNOWN_HOSTS_PATH", str(ROOT / "run" / "ssh" / "known_hosts"))
|
||
).expanduser()
|
||
# 滚动重启单实例最大等待秒数。
|
||
ROLLING_RESTART_TIMEOUT_SECONDS = env_int("ROLLING_RESTART_TIMEOUT_SECONDS", 180)
|
||
# 滚动重启探测轮询间隔。
|
||
ROLLING_RESTART_POLL_SECONDS = env_float("ROLLING_RESTART_POLL_SECONDS", 2.0)
|
||
# 单实例恢复后额外稳定等待秒数。
|
||
ROLLING_RESTART_STABILIZE_SECONDS = env_float("ROLLING_RESTART_STABILIZE_SECONDS", 2.0)
|
||
# 依赖方较多的服务发布前先在 Nacos 摘掉当前实例,避免调用方继续打到正在重启的节点。
|
||
NACOS_RELEASE_DRAIN_SERVICES = tuple(env_str_list("NACOS_RELEASE_DRAIN_SERVICES", ["other"]))
|
||
# Nacos 摘实例后额外等待调用方本地缓存刷新。
|
||
NACOS_RELEASE_DRAIN_SECONDS = env_float("NACOS_RELEASE_DRAIN_SECONDS", 8.0)
|
||
# 等待 Nacos enabled 状态变化的最长秒数。
|
||
NACOS_RELEASE_DRAIN_TIMEOUT_SECONDS = env_int("NACOS_RELEASE_DRAIN_TIMEOUT_SECONDS", 30)
|
||
# 日志查看固定读取最近多少条。
|
||
SERVICE_LOG_PREVIEW_LINES = env_int("SERVICE_LOG_PREVIEW_LINES", 1000)
|
||
# 日志导出最大允许条数,避免单次下载打爆页面进程。
|
||
SERVICE_LOG_EXPORT_MAX_LINES = env_int("SERVICE_LOG_EXPORT_MAX_LINES", 10000)
|
||
# 服务更新时本地构建最长等待秒数。
|
||
SERVICE_BUILD_TIMEOUT_SECONDS = env_int("SERVICE_BUILD_TIMEOUT_SECONDS", 3600)
|
||
# 服务更新时目标机预拉镜像最长等待秒数。
|
||
SERVICE_PULL_TIMEOUT_SECONDS = env_int("SERVICE_PULL_TIMEOUT_SECONDS", 600)
|
||
# 固定构建工作区根目录,留空时按本地 / deploy 机路径自动推导。
|
||
SERVICE_BUILD_CACHE_ROOT = env_str("SERVICE_BUILD_CACHE_ROOT", "")
|
||
# Java Maven 并行线程,默认按 1 个 CPU 组装 reactor。
|
||
JAVA_MAVEN_THREADS = env_str("JAVA_MAVEN_THREADS", "1C")
|
||
# Java 镜像 build/push 最大并行数。
|
||
JAVA_IMAGE_BUILD_MAX_WORKERS = env_int("JAVA_IMAGE_BUILD_MAX_WORKERS", 3)
|
||
|
||
# 腾讯云区域,SSH 引导和带外脚本会复用。
|
||
DEPLOY_REGION = env_str("DEPLOY_REGION", "me-saudi-arabia")
|
||
# 腾讯云 SecretId。
|
||
TENCENT_SECRET_ID = env_str("TENCENT_SECRET_ID", "")
|
||
# 腾讯云 SecretKey。
|
||
TENCENT_SECRET_KEY = env_str("TENCENT_SECRET_KEY", "")
|
||
# gateway 发布做 CLB 摘流时使用的负载均衡实例 ID。
|
||
GATEWAY_CLB_LOAD_BALANCER_ID = env_str("GATEWAY_CLB_LOAD_BALANCER_ID", "")
|
||
# gateway 可能同时挂在 HTTP / HTTPS 等多个监听器下,这里统一允许逗号分隔配置。
|
||
GATEWAY_CLB_LISTENER_IDS = env_str_list("GATEWAY_CLB_LISTENER_IDS", [])
|
||
# gateway 发布时只保护这些域名规则;留空表示 listener 下匹配到的规则全部处理。
|
||
GATEWAY_CLB_ALLOWED_DOMAINS = env_str_list("GATEWAY_CLB_ALLOWED_DOMAINS", [])
|
||
# CDN 刷新工具可选限制到固定业务域名;留空时允许提交任意腾讯云 CDN 域名。
|
||
CDN_ALLOWED_DOMAINS = env_str_list("CDN_ALLOWED_DOMAINS", [])
|
||
# CDN 工具页默认展示最近多少条刷新记录。
|
||
CDN_PURGE_TASK_LIMIT = env_int("CDN_PURGE_TASK_LIMIT", 20)
|
||
# CLB 异步任务轮询间隔。
|
||
GATEWAY_CLB_POLL_SECONDS = env_float("GATEWAY_CLB_POLL_SECONDS", 2.0)
|
||
# CLB 摘流 / 恢复异步任务最长等待秒数。
|
||
GATEWAY_CLB_TASK_TIMEOUT_SECONDS = env_int("GATEWAY_CLB_TASK_TIMEOUT_SECONDS", 120)
|
||
# 摘流后额外等待多少秒再重启;默认 0 表示只停新流量,不额外等待连接自然排空。
|
||
GATEWAY_CLB_DRAIN_SECONDS = env_float("GATEWAY_CLB_DRAIN_SECONDS", 0.0)
|
||
|
||
|
||
def pick_existing_path(candidates: list[Path]) -> Path:
|
||
# 配置未显式指定时,优先挑当前机器上真正存在的路径。
|
||
for candidate in candidates:
|
||
if candidate.exists():
|
||
return candidate
|
||
return candidates[0]
|
||
|
||
|
||
def env_or_candidate_path(env_key: str, candidates: list[Path]) -> Path:
|
||
# 允许 .env 显式覆盖,也兼容本地开发和远端 deploy 两种目录布局。
|
||
override = os.environ.get(env_key, "").strip()
|
||
if override:
|
||
return Path(override).expanduser()
|
||
return pick_existing_path(candidates)
|
||
|
||
|
||
def truthy(value: Any, default: bool = False) -> bool:
|
||
# 配置里布尔值可能来自 JSON 布尔或字符串,统一在这里规范化。
|
||
if value is None:
|
||
return default
|
||
if isinstance(value, bool):
|
||
return value
|
||
if isinstance(value, str):
|
||
return value.strip().lower() in {"1", "true", "yes", "on"}
|
||
return bool(value)
|
||
|
||
# 页面默认刷新间隔。
|
||
DEFAULT_REFRESH_INTERVAL_SECONDS = env_int("DEFAULT_REFRESH_INTERVAL_SECONDS", 10)
|
||
# 页面可选刷新间隔。
|
||
REFRESH_INTERVAL_OPTIONS = env_int_list("REFRESH_INTERVAL_OPTIONS", [5, 10, 15, 30, 60])
|
||
|
||
# Nacos API 基础地址。
|
||
NACOS_BASE_URL = env_str("NACOS_BASE_URL", "http://10.2.1.17:8848").removesuffix("/nacos").rstrip("/")
|
||
# Nacos 目标命名空间 ID。
|
||
NACOS_NAMESPACE_ID = env_str("NACOS_NAMESPACE_ID", "f7a8594e-090c-4830-a3ad-b5c0f9afcc07")
|
||
# Nacos 目标命名空间名。
|
||
NACOS_NAMESPACE_NAME = env_str("NACOS_NAMESPACE_NAME", "RedCircleServiceProd")
|
||
# Nacos 默认注册分组。
|
||
NACOS_DISCOVERY_GROUP = env_str("NACOS_DISCOVERY_GROUP", "DEFAULT_GROUP")
|
||
# Nacos 默认注册集群。
|
||
NACOS_DISCOVERY_CLUSTER_NAME = env_str("NACOS_DISCOVERY_CLUSTER_NAME", "DEFAULT")
|
||
# Nacos 请求超时。
|
||
NACOS_TIMEOUT_SECONDS = env_float("NACOS_TIMEOUT_SECONDS", 6.0)
|
||
# Nacos 结果缓存秒数。
|
||
NACOS_CACHE_TTL_SECONDS = env_float("NACOS_CACHE_TTL_SECONDS", 15.0)
|
||
# Nacos service list 单页大小。
|
||
NACOS_PAGE_SIZE = env_int("NACOS_PAGE_SIZE", 200)
|
||
# Nacos 可选用户名。
|
||
NACOS_USERNAME = env_str("NACOS_USERNAME", "")
|
||
# Nacos 可选密码。
|
||
NACOS_PASSWORD = env_str("NACOS_PASSWORD", "")
|
||
|
||
# Harbor 镜像仓地址,服务更新会推送镜像并让目标机主动拉取。
|
||
HARBOR_REGISTRY = env_str("HARBOR_REGISTRY", "10.2.1.3:18082")
|
||
# Harbor 项目名。
|
||
HARBOR_PROJECT = env_str("HARBOR_PROJECT", "likei")
|
||
# Harbor 登录用户名。
|
||
HARBOR_USERNAME = env_str("HARBOR_USERNAME", "")
|
||
# Harbor 登录密码。
|
||
HARBOR_PASSWORD = env_str("HARBOR_PASSWORD", "")
|
||
# Docker 构建平台,和现有 Jenkins/构建脚本口径保持一致。
|
||
BUILD_DOCKER_PLATFORM = env_str("BUILD_DOCKER_PLATFORM", "linux/amd64")
|
||
# Java 服务默认拉取的 Git ref。
|
||
UPDATE_DEFAULT_JAVA_GIT_REF = env_str("UPDATE_DEFAULT_JAVA_GIT_REF", "main")
|
||
# Go 服务默认拉取的 Git ref。
|
||
UPDATE_DEFAULT_GO_GIT_REF = env_str("UPDATE_DEFAULT_GO_GIT_REF", "main")
|
||
# Admin 前端默认拉取的 Git ref。
|
||
UPDATE_DEFAULT_ADMIN_GIT_REF = env_str("UPDATE_DEFAULT_ADMIN_GIT_REF", "main")
|
||
# Java 仓库路径,默认兼容本地工作区和远端 deploy 机。
|
||
JAVA_REPO_ROOT = env_or_candidate_path(
|
||
"JAVA_REPO_ROOT",
|
||
[
|
||
WORKSPACE_ROOT / "chatapp3" / "chatapp3-java",
|
||
Path("/opt/deploy/sources/chatapp3-java"),
|
||
],
|
||
)
|
||
# Go 仓库路径,默认兼容本地工作区和远端 deploy 机。
|
||
GOLANG_REPO_ROOT = env_or_candidate_path(
|
||
"GOLANG_REPO_ROOT",
|
||
[
|
||
WORKSPACE_ROOT / "chatapp3" / "chatapp3-golang",
|
||
Path("/opt/deploy/sources/chatapp3-golang"),
|
||
],
|
||
)
|
||
# Dashboard CDC Worker 仓库地址;仓库缺失时允许按这里自动 clone。
|
||
DASHBOARD_CDC_REPO_URL = env_str("DASHBOARD_CDC_REPO_URL", "git@gitea.haiyihy.com:admin/dashboard-cdc-worker.git")
|
||
# Dashboard CDC Worker 仓库路径,默认兼容本地工作区和远端 deploy 机。
|
||
DASHBOARD_CDC_REPO_ROOT = env_or_candidate_path(
|
||
"DASHBOARD_CDC_REPO_ROOT",
|
||
[
|
||
WORKSPACE_ROOT / "chatapp3" / "dashboard-cdc-worker",
|
||
WORKSPACE_ROOT / "dashboard-cdc-worker",
|
||
Path("/opt/deploy/sources/dashboard-cdc-worker"),
|
||
],
|
||
)
|
||
# Dashboard CDC Worker 在发布页和 compose 里的服务名。
|
||
DASHBOARD_CDC_SERVICE_NAME = env_str("DASHBOARD_CDC_SERVICE_NAME", "dashboard-cdc-worker").strip() or "dashboard-cdc-worker"
|
||
# Dashboard CDC Worker 是否接入发布;默认启用。
|
||
DASHBOARD_CDC_ENABLED = truthy(os.environ.get("DASHBOARD_CDC_ENABLED"), True)
|
||
# Admin 前端仓库地址;仓库缺失时允许按这里自动 clone。
|
||
ADMIN_FRONTEND_REPO_URL = env_str("ADMIN_FRONTEND_REPO_URL", "")
|
||
# Admin 前端仓库路径,默认兼容本地工作区和远端 deploy 机。
|
||
ADMIN_FRONTEND_REPO_ROOT = env_or_candidate_path(
|
||
"ADMIN_FRONTEND_REPO_ROOT",
|
||
[
|
||
WORKSPACE_ROOT / "chatapp3" / "chatapp3-admin",
|
||
Path("/opt/deploy/sources/chatapp3-admin"),
|
||
],
|
||
)
|
||
# Admin 前端在发布页里的服务名。
|
||
ADMIN_FRONTEND_SERVICE_NAME = env_str("ADMIN_FRONTEND_SERVICE_NAME", "admin").strip() or "admin"
|
||
# Admin 前端是否启用;未显式指定时,只要仓库和目标机信息齐备就自动开启。
|
||
ADMIN_FRONTEND_HOST = env_str("ADMIN_FRONTEND_HOST", "")
|
||
# Admin 前端目标机私网 IP。
|
||
ADMIN_FRONTEND_IP = env_str("ADMIN_FRONTEND_IP", "")
|
||
# Admin 前端目标机实例 ID,主要用于 TAT 引导 SSH。
|
||
ADMIN_FRONTEND_INSTANCE_ID = env_str("ADMIN_FRONTEND_INSTANCE_ID", "")
|
||
# Admin 前端 SSH 主机,默认沿用私网 IP。
|
||
ADMIN_FRONTEND_SSH_HOST = env_str("ADMIN_FRONTEND_SSH_HOST", ADMIN_FRONTEND_IP).strip() or ADMIN_FRONTEND_IP
|
||
# Admin 前端 SSH 用户。
|
||
ADMIN_FRONTEND_SSH_USER = env_str("ADMIN_FRONTEND_SSH_USER", SSH_USER).strip() or SSH_USER
|
||
# Admin 前端 SSH 端口。
|
||
ADMIN_FRONTEND_SSH_PORT = env_int("ADMIN_FRONTEND_SSH_PORT", SSH_PORT)
|
||
# Admin 前端是否需要 sudo。
|
||
ADMIN_FRONTEND_SSH_USE_SUDO = env_str("ADMIN_FRONTEND_SSH_USE_SUDO", "0").lower() in {"1", "true", "yes", "on"}
|
||
# Admin 前端远端运行目录。
|
||
ADMIN_FRONTEND_REMOTE_DIR = env_str("ADMIN_FRONTEND_REMOTE_DIR", "/opt/yumi-admin/frontend-admin").strip() or "/opt/yumi-admin/frontend-admin"
|
||
# Admin 前端静态站点目录。
|
||
ADMIN_FRONTEND_SITE_DIR = (
|
||
env_str("ADMIN_FRONTEND_SITE_DIR", f"{ADMIN_FRONTEND_REMOTE_DIR.rstrip('/')}/site").strip()
|
||
or f"{ADMIN_FRONTEND_REMOTE_DIR.rstrip('/')}/site"
|
||
)
|
||
# Admin 前端 compose 文件路径。
|
||
ADMIN_FRONTEND_COMPOSE_FILE = (
|
||
env_str("ADMIN_FRONTEND_COMPOSE_FILE", f"{ADMIN_FRONTEND_REMOTE_DIR.rstrip('/')}/docker-compose.yml").strip()
|
||
or f"{ADMIN_FRONTEND_REMOTE_DIR.rstrip('/')}/docker-compose.yml"
|
||
)
|
||
# Admin 前端 compose 服务名。
|
||
ADMIN_FRONTEND_COMPOSE_SERVICE = env_str("ADMIN_FRONTEND_COMPOSE_SERVICE", "caddy").strip() or "caddy"
|
||
# Admin 前端当前发布元数据文件路径。
|
||
ADMIN_FRONTEND_RELEASE_META_PATH = (
|
||
env_str(
|
||
"ADMIN_FRONTEND_RELEASE_META_PATH",
|
||
f"{ADMIN_FRONTEND_REMOTE_DIR.rstrip('/')}/.hy-app-monitor-admin-release.json",
|
||
).strip()
|
||
or f"{ADMIN_FRONTEND_REMOTE_DIR.rstrip('/')}/.hy-app-monitor-admin-release.json"
|
||
)
|
||
# Admin 前端构建脚本。
|
||
ADMIN_FRONTEND_BUILD_SCRIPT = env_str("ADMIN_FRONTEND_BUILD_SCRIPT", "build:antdv-next").strip() or "build:antdv-next"
|
||
# Admin 前端构建产物目录。
|
||
ADMIN_FRONTEND_DIST_DIR = env_str("ADMIN_FRONTEND_DIST_DIR", "apps/dist").strip() or "apps/dist"
|
||
# Admin 前端对外访问地址,仅用于结果展示和排查。
|
||
ADMIN_FRONTEND_PUBLIC_URL = env_str("ADMIN_FRONTEND_PUBLIC_URL", "").strip()
|
||
# 未显式指定 ADMIN_FRONTEND_ENABLED 时,只要关键参数齐备就默认启用。
|
||
ADMIN_FRONTEND_ENABLED = truthy(
|
||
os.environ.get("ADMIN_FRONTEND_ENABLED"),
|
||
bool(
|
||
ADMIN_FRONTEND_SERVICE_NAME
|
||
and ADMIN_FRONTEND_HOST
|
||
and (ADMIN_FRONTEND_SSH_HOST or ADMIN_FRONTEND_IP)
|
||
and (ADMIN_FRONTEND_REPO_URL or str(ADMIN_FRONTEND_REPO_ROOT).strip())
|
||
),
|
||
)
|
||
|
||
# chatapp-cron 仓库地址;仓库缺失时允许按这里自动 clone。
|
||
CHATAPP_CRON_REPO_URL = env_str("CHATAPP_CRON_REPO_URL", "git@gitea.haiyihy.com:hy/chatapp-cron.git")
|
||
# chatapp-cron 仓库路径,默认兼容本地工作区和远端 deploy 机。
|
||
CHATAPP_CRON_REPO_ROOT = env_or_candidate_path(
|
||
"CHATAPP_CRON_REPO_ROOT",
|
||
[
|
||
WORKSPACE_ROOT / "chatapp3" / "chatapp-cron",
|
||
WORKSPACE_ROOT / "chatapp-cron",
|
||
Path("/opt/deploy/sources/chatapp-cron"),
|
||
],
|
||
)
|
||
# chatapp-cron 默认拉取的 Git ref。
|
||
CHATAPP_CRON_DEFAULT_GIT_REF = env_str("CHATAPP_CRON_DEFAULT_GIT_REF", "main")
|
||
# chatapp-cron 在通用部署页里的服务名。
|
||
CHATAPP_CRON_SERVICE_NAME = env_str("CHATAPP_CRON_SERVICE_NAME", "chatapp-cron").strip() or "chatapp-cron"
|
||
# chatapp-cron 远端主机名称。
|
||
CHATAPP_CRON_HOST = env_str("CHATAPP_CRON_HOST", "cron").strip() or "cron"
|
||
# chatapp-cron 远端主机私网 IP。
|
||
# 定时任务服务器:ins-nt6bsay0 / cron / 10.3.1.14。
|
||
CHATAPP_CRON_IP = env_str("CHATAPP_CRON_IP", "10.3.1.14")
|
||
# chatapp-cron 云服务器实例 ID,主要供 TAT SSH 引导使用。
|
||
CHATAPP_CRON_INSTANCE_ID = env_str("CHATAPP_CRON_INSTANCE_ID", "ins-nt6bsay0")
|
||
# chatapp-cron SSH 主机,默认沿用私网 IP。
|
||
CHATAPP_CRON_SSH_HOST = env_str("CHATAPP_CRON_SSH_HOST", CHATAPP_CRON_IP).strip() or CHATAPP_CRON_IP
|
||
# chatapp-cron SSH 用户。
|
||
CHATAPP_CRON_SSH_USER = env_str("CHATAPP_CRON_SSH_USER", SSH_USER).strip() or SSH_USER
|
||
# chatapp-cron SSH 端口。
|
||
CHATAPP_CRON_SSH_PORT = env_int("CHATAPP_CRON_SSH_PORT", SSH_PORT)
|
||
# chatapp-cron 是否需要 sudo。
|
||
CHATAPP_CRON_SSH_USE_SUDO = env_str("CHATAPP_CRON_SSH_USE_SUDO", "0").lower() in {"1", "true", "yes", "on"}
|
||
# chatapp-cron 远端运行目录。
|
||
CHATAPP_CRON_REMOTE_DIR = env_str("CHATAPP_CRON_REMOTE_DIR", "/opt/deploy/runtime/current").strip() or "/opt/deploy/runtime/current"
|
||
# chatapp-cron compose 文件路径。
|
||
CHATAPP_CRON_COMPOSE_FILE = (
|
||
env_str("CHATAPP_CRON_COMPOSE_FILE", f"{CHATAPP_CRON_REMOTE_DIR.rstrip('/')}/docker-compose.yml").strip()
|
||
or f"{CHATAPP_CRON_REMOTE_DIR.rstrip('/')}/docker-compose.yml"
|
||
)
|
||
# chatapp-cron compose 服务名。
|
||
CHATAPP_CRON_COMPOSE_SERVICE = env_str("CHATAPP_CRON_COMPOSE_SERVICE", "week-star-job").strip() or "week-star-job"
|
||
# chatapp-cron 远端二进制路径。
|
||
CHATAPP_CRON_BINARY_PATH = (
|
||
env_str("CHATAPP_CRON_BINARY_PATH", f"{CHATAPP_CRON_REMOTE_DIR.rstrip('/')}/week-star-job/service").strip()
|
||
or f"{CHATAPP_CRON_REMOTE_DIR.rstrip('/')}/week-star-job/service"
|
||
)
|
||
# chatapp-cron 当前发布元数据文件路径。
|
||
CHATAPP_CRON_RELEASE_META_PATH = (
|
||
env_str(
|
||
"CHATAPP_CRON_RELEASE_META_PATH",
|
||
f"{CHATAPP_CRON_REMOTE_DIR.rstrip('/')}/.hy-app-monitor-chatapp-cron-release.json",
|
||
).strip()
|
||
or f"{CHATAPP_CRON_REMOTE_DIR.rstrip('/')}/.hy-app-monitor-chatapp-cron-release.json"
|
||
)
|
||
# chatapp-cron 是否启用;未显式指定时,只要目标机和仓库信息齐备就自动开启。
|
||
CHATAPP_CRON_ENABLED = truthy(
|
||
os.environ.get("CHATAPP_CRON_ENABLED"),
|
||
bool(
|
||
CHATAPP_CRON_SERVICE_NAME
|
||
and CHATAPP_CRON_HOST
|
||
and (CHATAPP_CRON_SSH_HOST or CHATAPP_CRON_IP)
|
||
and CHATAPP_CRON_REMOTE_DIR
|
||
and (CHATAPP_CRON_REPO_URL or str(CHATAPP_CRON_REPO_ROOT).strip())
|
||
),
|
||
)
|
||
|
||
# Mongo 连接串。
|
||
MONGO_URI = env_str("MONGO_URI", "")
|
||
# Mongo 默认关注的业务库名。
|
||
MONGO_DB_NAME = env_str("MONGO_DB_NAME", "tarab_all")
|
||
# Mongo 机器实例 ID,主要用于和主机指标联动显示。
|
||
MONGO_INSTANCE_ID = env_str("MONGO_INSTANCE_ID", "ins-o9mmob66")
|
||
# Mongo 结果缓存秒数。
|
||
MONGO_CACHE_TTL_SECONDS = env_float("MONGO_CACHE_TTL_SECONDS", 20.0)
|
||
# Mongo 建连超时。
|
||
MONGO_CONNECT_TIMEOUT_MS = env_int("MONGO_CONNECT_TIMEOUT_MS", 3000)
|
||
# Mongo server selection 超时。
|
||
MONGO_SERVER_SELECTION_TIMEOUT_MS = env_int("MONGO_SERVER_SELECTION_TIMEOUT_MS", 3000)
|
||
# Mongo socket 超时。
|
||
MONGO_SOCKET_TIMEOUT_MS = env_int("MONGO_SOCKET_TIMEOUT_MS", 5000)
|
||
|
||
# MySQL 主机地址。
|
||
MYSQL_HOST = env_str("MYSQL_HOST", "")
|
||
# MySQL 端口。
|
||
MYSQL_PORT = env_int("MYSQL_PORT", 3306)
|
||
# MySQL 登录账号。
|
||
MYSQL_USER = env_str("MYSQL_USER", "")
|
||
# MySQL 登录密码;允许空串,兼容特殊场景。
|
||
MYSQL_PASSWORD = os.environ.get("MYSQL_PASSWORD", "")
|
||
# MySQL 默认库;为空时由页面显式选择。
|
||
MYSQL_DEFAULT_DATABASE = env_str("MYSQL_DEFAULT_DATABASE", "")
|
||
# MySQL 指标与元数据缓存秒数。
|
||
MYSQL_CACHE_TTL_SECONDS = env_float("MYSQL_CACHE_TTL_SECONDS", 8.0)
|
||
# MySQL 建连超时秒数。
|
||
MYSQL_CONNECT_TIMEOUT_SECONDS = env_int("MYSQL_CONNECT_TIMEOUT_SECONDS", 5)
|
||
# MySQL 读超时秒数。
|
||
MYSQL_READ_TIMEOUT_SECONDS = env_int("MYSQL_READ_TIMEOUT_SECONDS", 8)
|
||
# MySQL 写超时秒数。
|
||
MYSQL_WRITE_TIMEOUT_SECONDS = env_int("MYSQL_WRITE_TIMEOUT_SECONDS", 15)
|
||
# MySQL 表浏览默认分页大小。
|
||
MYSQL_DEFAULT_PAGE_SIZE = env_int("MYSQL_DEFAULT_PAGE_SIZE", 100)
|
||
# MySQL 表浏览最大分页大小。
|
||
MYSQL_MAX_PAGE_SIZE = env_int("MYSQL_MAX_PAGE_SIZE", 200)
|
||
# SQL 工作台单次最多返回多少行。
|
||
MYSQL_QUERY_MAX_ROWS = env_int("MYSQL_QUERY_MAX_ROWS", 500)
|
||
|
||
# CPU 告警阈值。
|
||
CPU_WARN_PERCENT = env_float("CPU_WARN_PERCENT", 60.0)
|
||
# CPU 严重阈值。
|
||
CPU_BAD_PERCENT = env_float("CPU_BAD_PERCENT", 85.0)
|
||
# 内存告警阈值。
|
||
MEMORY_WARN_PERCENT = env_float("MEMORY_WARN_PERCENT", 70.0)
|
||
# 内存严重阈值。
|
||
MEMORY_BAD_PERCENT = env_float("MEMORY_BAD_PERCENT", 90.0)
|
||
# 磁盘告警阈值。
|
||
DISK_WARN_PERCENT = env_float("DISK_WARN_PERCENT", 75.0)
|
||
# 磁盘严重阈值。
|
||
DISK_BAD_PERCENT = env_float("DISK_BAD_PERCENT", 90.0)
|
||
# 进程数告警阈值。
|
||
PROCESS_WARN_COUNT = env_int("PROCESS_WARN_COUNT", 180)
|
||
# 进程数严重阈值。
|
||
PROCESS_BAD_COUNT = env_int("PROCESS_BAD_COUNT", 260)
|
||
# 服务耗时告警阈值。
|
||
SERVICE_LATENCY_WARN_MS = env_int("SERVICE_LATENCY_WARN_MS", 1000)
|
||
# 服务耗时严重阈值。
|
||
SERVICE_LATENCY_BAD_MS = env_int("SERVICE_LATENCY_BAD_MS", 3000)
|
||
|
||
# 统一暴露给前端的阈值定义。
|
||
THRESHOLDS = {
|
||
"cpuPercent": {"warn": CPU_WARN_PERCENT, "bad": CPU_BAD_PERCENT},
|
||
"memoryPercent": {"warn": MEMORY_WARN_PERCENT, "bad": MEMORY_BAD_PERCENT},
|
||
"diskPercent": {"warn": DISK_WARN_PERCENT, "bad": DISK_BAD_PERCENT},
|
||
"processCount": {"warn": PROCESS_WARN_COUNT, "bad": PROCESS_BAD_COUNT},
|
||
"latencyMs": {"warn": SERVICE_LATENCY_WARN_MS, "bad": SERVICE_LATENCY_BAD_MS},
|
||
}
|
||
|
||
|
||
def utc_now() -> str:
|
||
# 时间函数延迟导入,避免这里堆太多全局依赖。
|
||
from datetime import datetime, timezone
|
||
|
||
# 统一输出无微秒的 UTC ISO 字符串。
|
||
return datetime.now(timezone.utc).replace(microsecond=0).isoformat()
|
||
|
||
|
||
def settings_payload() -> dict[str, Any]:
|
||
# 把页面需要的动态设置统一打包返回。
|
||
return {
|
||
"refreshIntervalSeconds": DEFAULT_REFRESH_INTERVAL_SECONDS,
|
||
"refreshIntervalOptions": list(REFRESH_INTERVAL_OPTIONS),
|
||
"thresholds": THRESHOLDS,
|
||
}
|
||
|
||
|
||
def load_hosts() -> list[dict[str, Any]]:
|
||
# 读取监控目标配置。
|
||
payload = json.loads(CONFIG_PATH.read_text(encoding="utf-8"))
|
||
# 没有 hosts 时回退为空列表。
|
||
hosts = payload.get("hosts") or []
|
||
# 规范化后的主机列表。
|
||
normalized: list[dict[str, Any]] = []
|
||
|
||
# 逐台主机做结构清洗。
|
||
for host in hosts:
|
||
normalized.append(
|
||
{
|
||
"host": str(host.get("name") or "").strip(),
|
||
"group": str(host.get("group") or "").strip(),
|
||
"ip": str(host.get("ip") or "").strip(),
|
||
"instanceId": str(host.get("instanceId") or "").strip(),
|
||
"sshHost": str(host.get("sshHost") or host.get("ip") or "").strip(),
|
||
"sshUser": str(host.get("sshUser") or SSH_USER).strip() or SSH_USER,
|
||
"sshUseSudo": truthy(host.get("sshUseSudo"), SSH_USE_SUDO),
|
||
"sshPort": int(host.get("sshPort") or SSH_PORT),
|
||
"services": host.get("services") or [],
|
||
}
|
||
)
|
||
|
||
# 返回可供各模块复用的主机列表。
|
||
return normalized
|
||
|
||
|
||
def flatten_services(hosts: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||
# 这里把主机结构拍平成服务列表,方便并发探测。
|
||
services: list[dict[str, Any]] = []
|
||
|
||
# 逐台主机展开服务。
|
||
for host in hosts:
|
||
for service in host.get("services") or []:
|
||
services.append(
|
||
{
|
||
"host": host["host"],
|
||
"group": host["group"],
|
||
"ip": host["ip"],
|
||
"instanceId": host["instanceId"],
|
||
"sshHost": host["sshHost"],
|
||
"sshUser": host["sshUser"],
|
||
"sshUseSudo": truthy(host.get("sshUseSudo"), SSH_USE_SUDO),
|
||
"sshPort": int(host["sshPort"]),
|
||
"service": str(service.get("name") or "").strip(),
|
||
"kind": str(service.get("kind") or "java").strip(),
|
||
"port": int(service.get("port")),
|
||
"path": str(service.get("path") or "/").strip(),
|
||
}
|
||
)
|
||
|
||
# 返回扁平服务列表。
|
||
return services
|