Gitfed
bastien-mrq/gitfed / cmd / gitfed-install / netutil.go
package main

import (
	"context"
	"io"
	"net"
	"net/http"
	"strings"
	"time"
)

// publicIPServices are tried in order — a single provider being down (or
// blocked outbound from this VPS) shouldn't stall the wizard on something
// that's just a convenience prefill, never a hard requirement (screen 2's
// field stays editable either way).
var publicIPServices = []string{
	"https://api.ipify.org",
	"https://ifconfig.me/ip",
	"https://icanhazip.com",
}

// detectPublicIP best-effort discovers this machine's public IP by asking
// an external echo service — there's no reliable way to learn it purely
// from local interfaces on a machine that's behind NAT or has multiple
// addresses. Returns "" (never an error the UI needs to render specially)
// if every service fails; the domain field is always editable regardless.
func detectPublicIP(ctx context.Context) string {
	client := &http.Client{Timeout: 5 * time.Second}
	for _, url := range publicIPServices {
		req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
		if err != nil {
			continue
		}
		resp, err := client.Do(req)
		if err != nil {
			continue
		}
		body, err := io.ReadAll(io.LimitReader(resp.Body, 256))
		resp.Body.Close()
		if err != nil || resp.StatusCode != http.StatusOK {
			continue
		}
		ip := strings.TrimSpace(string(body))
		if net.ParseIP(ip) != nil {
			return ip
		}
	}
	return ""
}

// dnsOutcome is screen 3's result: whether domain currently resolves, and
// to what — compared against the publicIP gathered on screen 2.
type dnsOutcome struct {
	Resolved  bool
	Addresses []string
	Matches   bool // true if publicIP is among Addresses
	Err       error
}

func checkDNS(ctx context.Context, domain, publicIP string) dnsOutcome {
	var resolver net.Resolver
	addrs, err := resolver.LookupHost(ctx, domain)
	if err != nil {
		return dnsOutcome{Err: err}
	}
	out := dnsOutcome{Resolved: true, Addresses: addrs}
	for _, a := range addrs {
		if a == publicIP {
			out.Matches = true
			break
		}
	}
	return out
}