From 0c95156d4cf4b07901f97b8732af067c9acfcc0a Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 20 Apr 2026 16:42:54 -0600 Subject: [PATCH] refactor(check-ip): add IPCheck.Sync for geoip, reuse for tick MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit geoip now syncs via an IPCheck.Sync() method that returns (updated, err) — same signature as gitshallow.Repo.Sync / Fetcher.Fetch. The initial load and the background refresh goroutine both call it, so there is no duplicated fetch+open+swap logic. --- cmd/check-ip/main.go | 60 +++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/cmd/check-ip/main.go b/cmd/check-ip/main.go index 6c5dfa3..9482590 100644 --- a/cmd/check-ip/main.go +++ b/cmd/check-ip/main.go @@ -42,9 +42,31 @@ type IPCheck struct { // the .tar.gz archives must already exist in /maxmind/. GeoIPBasicAuth string - inbound *dataset.View[ipcohort.Cohort] - outbound *dataset.View[ipcohort.Cohort] - geo atomic.Pointer[geoip.Databases] + inbound *dataset.View[ipcohort.Cohort] + outbound *dataset.View[ipcohort.Cohort] + geoFetcher dataset.Fetcher + geo atomic.Pointer[geoip.Databases] +} + +// Sync fetches the GeoIP archives (via httpcache when basic auth is set, +// otherwise by polling their mtime) and, when the fetcher reports a change, +// re-opens the databases and atomically swaps the active snapshot. +func (c *IPCheck) Sync() (bool, error) { + updated, err := c.geoFetcher.Fetch() + if err != nil { + return false, err + } + if c.geo.Load() != nil && !updated { + return false, nil + } + db, err := geoip.Open(filepath.Join(c.CacheDir, "maxmind")) + if err != nil { + return false, err + } + if old := c.geo.Swap(db); old != nil { + _ = old.Close() + } + return true, nil } func main() { @@ -135,10 +157,8 @@ func main() { // httpcache conditional GETs. Without them, poll the existing tar.gz // files in maxmindDir. geoip.Open extracts in-memory — no .mmdb files // are written to disk. - maxmindDir := filepath.Join(cfg.CacheDir, "maxmind") - cityTarPath := filepath.Join(maxmindDir, "GeoLite2-City.tar.gz") - asnTarPath := filepath.Join(maxmindDir, "GeoLite2-ASN.tar.gz") - var geoFetcher dataset.Fetcher + cityTarPath := filepath.Join(cfg.CacheDir, "maxmind", "GeoLite2-City.tar.gz") + asnTarPath := filepath.Join(cfg.CacheDir, "maxmind", "GeoLite2-ASN.tar.gz") if cfg.GeoIPBasicAuth != "" { city := &httpcache.Cacher{ URL: geoip.DownloadBase + "/GeoLite2-City/download?suffix=tar.gz", @@ -154,7 +174,7 @@ func main() { AuthHeader: "Authorization", AuthValue: cfg.GeoIPBasicAuth, } - geoFetcher = dataset.FetcherFunc(func() (bool, error) { + cfg.geoFetcher = dataset.FetcherFunc(func() (bool, error) { cityUpdated, err := city.Fetch() if err != nil { return false, fmt.Errorf("fetch GeoLite2-City: %w", err) @@ -166,16 +186,11 @@ func main() { return cityUpdated || asnUpdated, nil }) } else { - geoFetcher = dataset.PollFiles(cityTarPath, asnTarPath) + cfg.geoFetcher = dataset.PollFiles(cityTarPath, asnTarPath) } - if _, err := geoFetcher.Fetch(); err != nil { + if _, err := cfg.Sync(); err != nil { log.Fatalf("geoip: %v", err) } - geoDB, err := geoip.Open(maxmindDir) - if err != nil { - log.Fatalf("geoip: %v", err) - } - cfg.geo.Store(geoDB) defer func() { _ = cfg.geo.Load().Close() }() for _, ip := range ips { @@ -198,21 +213,8 @@ func main() { case <-ctx.Done(): return case <-t.C: - updated, err := geoFetcher.Fetch() - if err != nil { + if _, err := cfg.Sync(); err != nil { log.Printf("geoip refresh: %v", err) - continue - } - if !updated { - continue - } - db, err := geoip.Open(maxmindDir) - if err != nil { - log.Printf("geoip refresh: %v", err) - continue - } - if old := cfg.geo.Swap(db); old != nil { - _ = old.Close() } } }