🌳
pt0/serverF/clusterInfoF.mts
1import * as _ from 'lodash-es'
13let relayProbeCache = {ip: '', port: 0, result: false, ts: 0, warned: false}
14const tcpProbeRelay = async (host: string, port: number, timeoutMs = 2000): Promise<boolean> => {
15 const now = Date.now()
16 if (relayProbeCache.ip === host && relayProbeCache.port === port && now - relayProbeCache.ts < 30_000) {
17 return relayProbeCache.result
18 }
19 const result = await tcpProbePort(host, port, timeoutMs)
20 relayProbeCache = {ip: host, port, result, ts: now, warned: false}
21 return result
24type SvcDef = {
25 cluster_name: string
26 ips: string[]
27 nodePort?: number
28 ptNodePortNo?: number
29 isCnPg?: boolean
30 needsKubePortFwd?: boolean
31 enableSsl?: boolean
32 openPortNoA?: import('../sharedF/portInOpenAAI.mts').OpenPortNoA
35export const getClusterCfg = ({cluster_name}: {cluster_name: string}) => {
36 const nodePorts = getKlustersNodePorts()
37 assertDefined(nodePorts, {cluster_name})
39 const cfg = nodePorts[cluster_name]
40 assertDefined(cfg, {nodePorts, cluster_name})
42 const {nodeIpsExtHost, lanHostIpsH, tailscaleIp, tsRelayIp, openPortNoA, ...nodePortH} = cfg
43 assertDefined(nodeIpsExtHost, {cluster_name})
45 return {nodeIpsExtHost, lanHostIpsH, tailscaleIp, tsRelayIp, openPortNoA, nodePortH}
48export const getSvcInfo = async (svcName: string) => {
49 const nodePorts = getKlustersNodePorts()
50 assertDefined(nodePorts)
51 const svcInfos: Record<string, SvcDef> = {}
53 for (const cluster_name of Object.keys(nodePorts)) {
54 const {nodeIpsExtHost, lanHostIpsH, tailscaleIp, tsRelayIp, openPortNoA, nodePortH} = getClusterCfg({cluster_name})
56 for (const [_svcName, svcDef] of Object.entries(nodePortH)) {
57 if (_svcName != svcName) continue
59 const isLocalDev = isDevPc || isOcWeb
60 let resolvedIps = [nodeIpsExtHost]
61 if (isLocalDev && lanHostIpsH?.clusterVip) {
62 const onLan = await isOnSameLan({cluster_name, nodeIpsExtHost, clusterVip: lanHostIpsH.clusterVip})
63 if (onLan) {
64 resolvedIps = [lanHostIpsH.clusterVip]
65 }
66 }
67 if (!isLocalDev && tailscaleIp) {
68 resolvedIps = [tailscaleIp]
69 }
71 let newDef: SvcDef = {
72 cluster_name, ips: resolvedIps, openPortNoA
73 }
75 if (_.isInteger(svcDef)) newDef.nodePort = svcDef as number
76 else newDef = {...newDef, ...svcDef as object}
78 // Tailscale relay pod: dev PC on tailnet connects directly (bypass port-forward)
79 // Port stays as ptNodePortNo — the relay pod listens on each DB's nodeport.
80 // TCP probe prevents 75s hang when tailscale daemon is broken (CGNAT IP on iface but tunnel down)
81 const relayEligible = isLocalDev && !!tsRelayIp && !!newDef.isCnPg && isOnTailnet()
82 if (relayEligible && await tcpProbeRelay(tsRelayIp as string, newDef.ptNodePortNo!)) {
83 newDef.ips = [tsRelayIp as string]
84 newDef.needsKubePortFwd = false
85 } else if (relayEligible && !relayProbeCache.warned) {
86 betLog({tsRelay: 'unreachable — start tailscale: brew services restart tailscale', ip: tsRelayIp})
87 relayProbeCache.warned = true
88 }
90 if (isDevPc && needsProxyQs(newDef)) {
91 const localPort = portForwardMgr.getEndpointPort(_svcName)
92 if (localPort) {
93 newDef.nodePort = localPort
94 newDef.ips = ['127.0.0.1']
95 }
96 }
98 const existingDef = svcInfos[_svcName]
99 throwIf(() => existingDef, {existingDef, newDef, _svcName})
101 svcInfos[_svcName] = newDef
102 }
103 }
105 return svcInfos[svcName]
108export const getReqSvcInfo = async (svcName: string) => {
109 const svcInfo = await getSvcInfo(svcName)
110 if (!svcInfo) {
111 throPtErr(`please define "export const ${svcName} = anOpenPortNo" within nodeports/[cluster_name].mjs`)
112 }
113 return svcInfo