p1.1: dynamic self-IP deny (DDNS/trace, keep previous, atomic swap)

Discover WAN via ddns:<host> then trace:<url> before listen. Persist to
self_ip.json. Previous addresses stay denied for keep_previous. Failure
keeps the last known set. /healthz reports age and source.
This commit is contained in:
2026-09-22 22:59:08 -07:00
parent dca635fb62
commit 447842f860
8 changed files with 767 additions and 20 deletions
+26 -15
View File
@@ -18,6 +18,7 @@ import (
"git.wntrmute.dev/kyle/sift/internal/clean"
"git.wntrmute.dev/kyle/sift/internal/fetch"
"git.wntrmute.dev/kyle/sift/internal/guard"
"git.wntrmute.dev/kyle/sift/internal/selfip"
)
const maxReqBody = 64 << 10
@@ -30,6 +31,7 @@ type Server struct {
Auth *auth.Store
Mux *http.ServeMux
OutCap int
SelfIP *selfip.Discoverer
global chan struct{}
perMu sync.Mutex
@@ -80,30 +82,39 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (s *Server) healthz(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{"status": "ok", "version": s.Version})
out := map[string]any{"status": "ok", "version": s.Version}
if s.SelfIP != nil {
out["self_ip"] = s.SelfIP.Status()
}
_ = json.NewEncoder(w).Encode(out)
}
func (s *Server) metrics(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
fails := uint64(0)
if s.SelfIP != nil {
fails = s.SelfIP.Failures.Load()
}
_ = json.NewEncoder(w).Encode(map[string]any{
"requests": s.reqN.Load(),
"blocks": s.blockN.Load(),
"scrapes": s.scrapeN.Load(),
"requests": s.reqN.Load(),
"blocks": s.blockN.Load(),
"scrapes": s.scrapeN.Load(),
"self_ip_failures": fails,
})
}
type scrapeReq struct {
URL string `json:"url"`
Formats []string `json:"formats"`
OnlyMainContent *bool `json:"onlyMainContent"`
SkipTlsVerification *bool `json:"skipTlsVerification"`
RemoveBase64Images *bool `json:"removeBase64Images"`
FastMode *bool `json:"fastMode"`
BlockAds *bool `json:"blockAds"`
StoreInCache *bool `json:"storeInCache"`
MaxAge *int `json:"maxAge"`
Mobile *bool `json:"mobile"`
Origin string `json:"origin"`
URL string `json:"url"`
Formats []string `json:"formats"`
OnlyMainContent *bool `json:"onlyMainContent"`
SkipTlsVerification *bool `json:"skipTlsVerification"`
RemoveBase64Images *bool `json:"removeBase64Images"`
FastMode *bool `json:"fastMode"`
BlockAds *bool `json:"blockAds"`
StoreInCache *bool `json:"storeInCache"`
MaxAge *int `json:"maxAge"`
Mobile *bool `json:"mobile"`
Origin string `json:"origin"`
}
type v1Req struct {