138 lines
3.1 KiB
Go
138 lines
3.1 KiB
Go
package geo
|
|
|
|
import (
|
|
"archive/tar"
|
|
"compress/gzip"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.leaktechnologies.dev/Leak_Technologies/Skyfeed/internal/config"
|
|
)
|
|
|
|
const (
|
|
ipdbFileName = "GeoLite2-City.mmdb"
|
|
keyFileName = "maxmind.key"
|
|
)
|
|
|
|
// EnsureIPDBUpToDate checks the local MaxMind database and refreshes monthly.
|
|
func EnsureIPDBUpToDate() error {
|
|
dbPath := filepath.Join(config.DataDir, ipdbFileName)
|
|
info, err := os.Stat(dbPath)
|
|
|
|
if os.IsNotExist(err) {
|
|
fmt.Println("[geo] No IP database found, downloading...")
|
|
return updateIPDB(dbPath)
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("unable to check IP DB: %w", err)
|
|
}
|
|
|
|
modTime := info.ModTime().UTC()
|
|
now := time.Now().UTC()
|
|
firstOfMonth := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC)
|
|
|
|
if modTime.Before(firstOfMonth) {
|
|
fmt.Println("[geo] IP database is older than this month, refreshing...")
|
|
return updateIPDB(dbPath)
|
|
}
|
|
|
|
fmt.Println("[geo] IP database is current.")
|
|
return nil
|
|
}
|
|
|
|
// ForceUpdateIPDB forces an immediate refresh.
|
|
func ForceUpdateIPDB() error {
|
|
dbPath := filepath.Join(config.DataDir, ipdbFileName)
|
|
fmt.Println("[geo] Forcing IP database update...")
|
|
return updateIPDB(dbPath)
|
|
}
|
|
|
|
// updateIPDB downloads and extracts the official GeoLite2 City database using your MaxMind key.
|
|
func updateIPDB(dest string) error {
|
|
keyPath := filepath.Join(config.ConfigDir, keyFileName)
|
|
keyBytes, err := os.ReadFile(keyPath)
|
|
if err != nil {
|
|
return fmt.Errorf("[geo] Missing MaxMind license key.\nPlease run:\n echo \"YOUR_KEY\" > %s", keyPath)
|
|
}
|
|
|
|
key := strings.TrimSpace(string(keyBytes))
|
|
url := fmt.Sprintf("https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=%s&suffix=tar.gz", key)
|
|
|
|
tmpTar := dest + ".tar.gz"
|
|
if err := downloadFile(url, tmpTar); err != nil {
|
|
return fmt.Errorf("failed to download GeoLite2 archive: %w", err)
|
|
}
|
|
defer os.Remove(tmpTar)
|
|
|
|
if err := extractMMDB(tmpTar, dest); err != nil {
|
|
return fmt.Errorf("failed to extract mmdb: %w", err)
|
|
}
|
|
|
|
fmt.Println("[geo] IP database updated successfully →", dest)
|
|
return nil
|
|
}
|
|
|
|
// downloadFile streams a file from URL to disk.
|
|
func downloadFile(url, dest string) error {
|
|
resp, err := http.Get(url)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("unexpected HTTP status: %s", resp.Status)
|
|
}
|
|
|
|
out, err := os.Create(dest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer out.Close()
|
|
|
|
_, err = io.Copy(out, resp.Body)
|
|
return err
|
|
}
|
|
|
|
// extractMMDB extracts the .mmdb file from a tar.gz archive.
|
|
func extractMMDB(src, dest string) error {
|
|
f, err := os.Open(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
gz, err := gzip.NewReader(f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer gz.Close()
|
|
|
|
tr := tar.NewReader(gz)
|
|
for {
|
|
h, err := tr.Next()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if filepath.Ext(h.Name) == ".mmdb" {
|
|
out, err := os.Create(dest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer out.Close()
|
|
_, err = io.Copy(out, tr)
|
|
return err
|
|
}
|
|
}
|
|
return fmt.Errorf("no .mmdb found in archive")
|
|
}
|