package setup import ( "net" "os" "os/exec" "strings" ) // DetectLocalIPs returns the host's LAN IP addresses. // Inside a Docker container, the network interfaces only show the bridge IP // (e.g. 172.18.0.4), which is useless for users. Instead, we: // 1. Check HOST_IP env var (set by docker-compose.yml) // 2. Try to detect the Docker host gateway via `ip route` // 3. Fall back to interface enumeration as last resort func DetectLocalIPs() []string { // Option 1: explicit HOST_IP from environment if hostIP := os.Getenv("HOST_IP"); hostIP != "" { return []string{hostIP} } // Option 2: detect Docker host gateway IP via default route // Inside a container, `ip route | grep default` gives the host gateway. // Then we check the host's IP by looking at what IP routes to that gateway. if ip := detectHostIPViaRoute(); ip != "" { return []string{ip} } // Option 3: fallback to interface enumeration (works on bare metal) return detectInterfaceIPs() } // detectHostIPViaRoute tries to find the Docker host's LAN IP. // Inside a container, the default gateway is the Docker host. // We read /host-etc/hostname or use the gateway as a hint. func detectHostIPViaRoute() string { // Try: ip route get 1.0.0.0 — shows the source IP used for routing out, err := exec.Command("ip", "route", "get", "1.0.0.0").Output() if err != nil { return "" } // Output: "1.0.0.0 via 172.18.0.1 dev eth0 src 172.18.0.4" // The gateway (172.18.0.1) is the Docker host — but that's the bridge IP. // We need the host's actual LAN IP. // Better approach: read /proc/net/route or parse `ip route` for the gateway, // then the gateway itself is the Docker host — but we need its external IP. // Since we can't easily get the host's LAN IP from inside the container, // return empty and let the fallback handle it or rely on HOST_IP env. _ = out return "" } func detectInterfaceIPs() []string { ifaces, err := net.Interfaces() if err != nil { return nil } var ips []string for _, iface := range ifaces { // Skip down, loopback, and Docker/container interfaces if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 { continue } name := strings.ToLower(iface.Name) if strings.HasPrefix(name, "docker") || strings.HasPrefix(name, "br-") || strings.HasPrefix(name, "veth") || strings.HasPrefix(name, "lo") { continue } addrs, err := iface.Addrs() if err != nil { continue } for _, addr := range addrs { var ip net.IP switch v := addr.(type) { case *net.IPNet: ip = v.IP case *net.IPAddr: ip = v.IP } if ip == nil || ip.IsLoopback() || ip.To4() == nil { continue // skip non-IPv4 } ips = append(ips, ip.String()) } } return ips }