🌳
pt0/deployF/dockerF/ensureVmLanRouteAI.mts
1import { execSync } from 'child_process'
4const routedIps = new Set<string>()
6type VmNet = {gw: string, subnetsA: {subnetInt: number, mask: number}[]}
7let vmNetCache: VmNet | null | undefined
9const ipToInt = (ip: string) => ip.split('.').reduce((acc, oct) => (acc << 8) + parseInt(oct), 0) >>> 0
11const getVmNet = (): VmNet | null => {
12 if (vmNetCache !== undefined) return vmNetCache
13 try {
14 const routes = execSync('colima ssh -- ip -4 route show', {stdio: ['ignore', 'pipe', 'ignore'], timeout: 15000}).toString()
15 const gw = routes.match(/^default via (\d+\.\d+\.\d+\.\d+)/m)?.[1]
16 const subnetsA = [...routes.matchAll(/^(\d+\.\d+\.\d+\.\d+)\/(\d+) dev \S+ proto kernel/gm)]
17 .map(m => ({subnetInt: ipToInt(m[1]!), mask: (~0 << (32 - parseInt(m[2]!))) >>> 0}))
18 if (!gw || !subnetsA.length) return (vmNetCache = null)
19 return (vmNetCache = {gw, subnetsA})
20 } catch {
21 return (vmNetCache = null)
22 } // catch:userapproved — colima not the docker host / VM down: nothing to fix
25// colima/lima vz VMs default to a NAT subnet that can collide with the real LAN's subnet —
26// when they overlap, LAN hosts (e.g. the LAN registry) are swallowed by the VM's connected
27// route and build pulls/pushes fail with "no route to host". Punch a /32 host route via the
28// NAT gateway.
29export const ensureVmLanRoute = async ({hostNamesA}: {hostNamesA: (string | undefined)[]}) => {
30 if (process.platform !== 'darwin') return
31 for (const hostName of hostNamesA) {
32 if (!hostName) continue
33 let ip: string
34 try {
35 ;({address: ip} = await dnsLookup(hostName, {family: 4}))
36 } catch { continue } // catch:userapproved — unresolvable host: let the build/push error surface it
37 if (routedIps.has(ip)) continue
38 const vmNet = getVmNet()
39 if (!vmNet) return
40 const ipInt = ipToInt(ip)
41 if (!vmNet.subnetsA.some(s => (ipInt & s.mask) === (s.subnetInt & s.mask))) continue
42 try {
43 execSync(`colima ssh -- sudo ip route replace ${ip}/32 via ${vmNet.gw}`, {stdio: ['ignore', 'pipe', 'ignore'], timeout: 15000})
44 console.log(`colima VM: routed ${ip}/32 via ${vmNet.gw} (LAN host inside VM NAT subnet)`)
45 routedIps.add(ip)
46 } catch { /* VM route add failed — push will surface the real error */ } // catch:userapproved
47 }