VPS Monitor 动态化改造:从硬编码到自动发现服务

实现

1. 扫描逻辑

新建 src/lib/services-discovery.ts:

// Docker 容器
async function scanDocker(): Promise<DetectedService[]> {
  const raw = await sh(`docker ps --format '{{.Names}}|{{.Ports}}' 2>/dev/null`);
  const services: DetectedService[] = [];
  for (const line of raw.split("\n").filter(Boolean)) {
    const [name, ports] = line.split("|");
    if (!ports) continue;
    const matches = ports.matchAll(/(?:0\.0\.0\.0|127\.0\.0\.1|\[::\]):(\d+)->/g);
    const seen = new Set<number>();
    for (const m of matches) {
      const port = parseInt(m[1], 10);
      if (seen.has(port)) continue;
      seen.add(port);
      services.push({ port, source: "docker", rawName: name, host: "docker" });
    }
  }
  return services;
}

// 监听端口
async function scanPorts(): Promise<DetectedService[]> {
  const raw = await sh(`ss -tlnp 2>/dev/null`);
  const services: DetectedService[] = [];
  for (const line of raw.split("\n").slice(1)) {
    const parts = line.split(/\s+/);
    if (parts.length < 4) continue;
    const localAddr = parts[3];
    const m = localAddr.match(/:(\d+)$/);
    if (!m) continue;
    const port = parseInt(m[1], 10);
    let host = localAddr.replace(/:\d+$/, "").replace(/^\[|\]$/g, "");
    if (host === "*") host = "0.0.0.0";
    const procMatch = line.match(/users:\(\("([^"]+)",pid=(\d+)/);
    const rawName = procMatch ? procMatch[1] : `port-${port}`;
    services.push({ port, source: "port", rawName, host });
  }
  return services;
}

// K8s NodePort(ss 抓不到)
async function scanK8sNodePorts(): Promise<Set<number>> {
  const raw = await sh(`kubectl get svc -A --no-headers 2>/dev/null | awk '{print $6}'`);
  const ports = new Set<number>();
  for (const line of raw.split("\n")) {
    for (const m of line.matchAll(/:(\d+)\//g)) {
      ports.add(parseInt(m[1], 10));
    }
  }
  return ports;
}

2. 系统服务过滤

const BLACKLIST_PORTS = new Set([
  22, 53, 80, 111, 443, 631,           // 系统
  3080, 6443, 10250, 10257, 10259,     // k3s
  36961, 40353,                         // Tailscale
  21115, 21116, 21117, 21118, 21119,   // RustDesk 内部
]);

const BLACKLIST_PROCS = [
  "sshd", "systemd-resolve", "rpcbind",
  "k3s-server", "k3s-agent", "tailscaled",
  "hbbs", "hbbr", "containerd", "dockerd",
  "systemd", "cron", "dbus",
];

3. 显示配置

新建 config/services.json:

{
  "services": [
    { "port": 3003, "name": "VPS Monitor", "desc": "このダッシュボード", "category": "monitoring", "public": true, "path": "/" },
    { "port": 3000, "name": "one-api", "desc": "LLM ゲートウェイ", "category": "llm", "public": true, "path": "/" },
    { "port": 5678, "name": "n8n", "desc": "ワークフロー自動化", "category": "automation", "public": false, "path": "/" },
    { "port": 5001, "name": "RAG Video Service", "desc": "動画処理", "category": "ai", "public": false, "path": "/" },
    { "port": 5002, "name": "RAG Stock Service", "desc": "株式分析", "category": "ai", "public": false, "path": "/" }
  ]
}

关键:这份配置是「显示美化」,不是「白名单」。扫描到的服务如果没在这里,**仍会显示**(用进程名或 port-XXXX)。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注