From 8f40bbf1105cf8003e846c7d822524dd4b8f8648 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 20 Apr 2026 17:14:11 -0600 Subject: [PATCH] feat(geoip): Open falls back to lex-latest _*.tar.gz MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prefer _LATEST.tar.gz (what httpcache writes), but fall back to the lexicographically greatest _*.tar.gz — MaxMind's dated Content-Disposition names sort chronologically, so this picks the most recent archive when the cache was populated by hand or by another tool. Exposes FindTarGz for callers that need the resolved path. --- net/geoip/databases.go | 41 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/net/geoip/databases.go b/net/geoip/databases.go index 7a918a3..caa22f0 100644 --- a/net/geoip/databases.go +++ b/net/geoip/databases.go @@ -9,6 +9,7 @@ import ( "net/netip" "os" "path/filepath" + "slices" "strings" "github.com/oschwald/geoip2-golang" @@ -20,15 +21,26 @@ type Databases struct { ASN *geoip2.Reader } -// Open reads /_LATEST.tar.gz for City and ASN editions, -// extracts the .mmdb entry from each archive in memory, and returns open -// readers. No .mmdb files are written to disk. +// Open loads the City and ASN editions from dir. For each edition it +// prefers _LATEST.tar.gz and falls back to the +// lexicographically greatest _*.tar.gz match (MaxMind's +// Content-Disposition names sort chronologically by release date). +// Archives are extracted in memory — no .mmdb files are written to disk. func Open(dir string) (*Databases, error) { - city, err := openMMDBTarGz(filepath.Join(dir, TarGzName(CityEdition))) + cityPath, err := FindTarGz(dir, CityEdition) if err != nil { return nil, fmt.Errorf("city: %w", err) } - asn, err := openMMDBTarGz(filepath.Join(dir, TarGzName(ASNEdition))) + city, err := openMMDBTarGz(cityPath) + if err != nil { + return nil, fmt.Errorf("city: %w", err) + } + asnPath, err := FindTarGz(dir, ASNEdition) + if err != nil { + _ = city.Close() + return nil, fmt.Errorf("asn: %w", err) + } + asn, err := openMMDBTarGz(asnPath) if err != nil { _ = city.Close() return nil, fmt.Errorf("asn: %w", err) @@ -36,6 +48,25 @@ func Open(dir string) (*Databases, error) { return &Databases{City: city, ASN: asn}, nil } +// FindTarGz resolves the cached tarball path for edition inside dir, +// preferring _LATEST.tar.gz and falling back to the +// lexicographically greatest _*.tar.gz match. +func FindTarGz(dir, edition string) (string, error) { + preferred := filepath.Join(dir, TarGzName(edition)) + if _, err := os.Stat(preferred); err == nil { + return preferred, nil + } + matches, err := filepath.Glob(filepath.Join(dir, edition+"_*.tar.gz")) + if err != nil { + return "", err + } + if len(matches) == 0 { + return "", fmt.Errorf("no %s_*.tar.gz in %s", edition, dir) + } + slices.Sort(matches) + return matches[len(matches)-1], nil +} + func openMMDBTarGz(path string) (*geoip2.Reader, error) { f, err := os.Open(path) if err != nil {