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
}