Files
mihomo-patchbay/shared/host-info.ts
T
im0o ce181e9ae5 服务端代答主机指标与公网 IP 检测
mihomo 的管理接口只报自己的内存,CPU、主机内存、网关、内网地址只有
跑在同一台机器上的 Patchbay 服务端答得出,所以加 /api/host 由它代答。
/api/host/exit 把本机某个入站端口设成代理去访问 ip.sb——浏览器发不出
「经由某个端口」的请求,这条只能走服务端。
2026-08-11 22:26:17 +08:00

91 lines
3.1 KiB
TypeScript

/**
* 主机信息的纯解析。
*
* mihomo 的管理接口只报自己的内存,CPU、主机内存、网关这些要靠 Patchbay 服务端
* 自己读 /proc 和 os 模块——服务端和内核跑在同一台机器上,这正是它能代答的原因。
* 读文件在 server/host.ts;这里只做字符串到数字的解析,让 bun test 够得着。
*/
/** GET /api/host 的响应。数据由 server/host.ts 采集,类型放这里让前后端共用。 */
export interface HostInfo {
/** 0-100。第一次请求没有参照快照,是 null */
cpuUsage: number | null
cpuCount: number
memTotal: number
/** 还能用的内存。Linux 用 MemAvailable,其余平台退回 freemem */
memAvailable: number
/** 1 分钟负载。Windows 上拿不到,是 null */
load1: number | null
uptimeSeconds: number
platform: string
release: string
arch: string
/** 非回环网卡上的 IPv4,含网卡名 */
lanAddresses: Array<{ iface: string; address: string }>
gateway: string | null
nameservers: string[]
}
/** GET /api/host/exit 的响应:经由某个入站端口探测到的公网 IP。 */
export interface ExitProbe {
ok: boolean
ip?: string
country?: string
countryCode?: string
/** 整个探测来回的毫秒数 */
ms?: number
message?: string
}
/** /proc/meminfo 里的 MemAvailable(内核估算的「还能用多少」),换算成字节。 */
export function parseMemAvailable(meminfo: string): number | null {
const match = /^MemAvailable:\s+(\d+)\s*kB/m.exec(meminfo)
if (!match) return null
return Number(match[1]) * 1024
}
/**
* /proc/net/route 里的默认网关。
*
* 格式是制表分隔的表:目的网络为 00000000 的那行是默认路由,Gateway 列是
* 小端序的十六进制 IPv4——字节序要倒回来才是人读的顺序。
*/
export function parseGateway(procNetRoute: string): string | null {
for (const line of procNetRoute.split('\n').slice(1)) {
const cols = line.trim().split(/\s+/)
if (cols.length < 3 || cols[1] !== '00000000') continue
const hex = cols[2]!
if (!/^[0-9a-fA-F]{8}$/.test(hex)) continue
const bytes = [6, 4, 2, 0].map((i) => parseInt(hex.slice(i, i + 2), 16))
return bytes.join('.')
}
return null
}
/** /etc/resolv.conf 里的 DNS 服务器,按出现顺序。 */
export function parseNameservers(resolvConf: string): string[] {
const result: string[] = []
for (const line of resolvConf.split('\n')) {
const match = /^\s*nameserver\s+(\S+)/.exec(line)
if (match) result.push(match[1]!)
}
return result
}
/** 一次 CPU 时间快照:忙碌和总量的累计毫秒(os.cpus() 各核求和)。 */
export interface CpuTimes {
busy: number
total: number
}
/**
* 两次快照之间的 CPU 使用率,0 到 100。
* 时间没有前进(同一毫秒内连问两次,或计数回绕)时返回 null,不硬造一个数。
*/
export function cpuUsageBetween(prev: CpuTimes, next: CpuTimes): number | null {
const total = next.total - prev.total
const busy = next.busy - prev.busy
if (total <= 0 || busy < 0) return null
return Math.min(100, (busy / total) * 100)
}