Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3bd1d93d85 |
+1
-5
@@ -1,6 +1,2 @@
|
|||||||
# API_PORT is the port the service listens on INSIDE the container.
|
API_PORT=28080
|
||||||
# With docker-compose's "28080:8080" mapping, set this to 8080 so the host
|
|
||||||
# can reach it at http://localhost:28080. Override only if you also change
|
|
||||||
# the host-side mapping / non-Docker run.
|
|
||||||
API_PORT=8080
|
|
||||||
GIN_TRUSTED_PROXY_IP=127.0.0.1
|
GIN_TRUSTED_PROXY_IP=127.0.0.1
|
||||||
@@ -31,7 +31,7 @@ The service will be available at `http://localhost:28080`
|
|||||||
|
|
||||||
### Manual Installation
|
### Manual Installation
|
||||||
|
|
||||||
1. Install Go 1.25 or later
|
1. Install Go 1.22 or later
|
||||||
2. Clone and build:
|
2. Clone and build:
|
||||||
```bash
|
```bash
|
||||||
git clone <repository-url>
|
git clone <repository-url>
|
||||||
|
|||||||
@@ -1,49 +1,45 @@
|
|||||||
module thuanle.me/ip-info
|
module thuanle.me/ip-info
|
||||||
|
|
||||||
go 1.25.0
|
go 1.22
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-gonic/gin v1.12.0
|
github.com/gin-gonic/gin v1.10.0
|
||||||
github.com/go-resty/resty/v2 v2.17.2
|
github.com/go-resty/resty/v2 v2.11.0
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/oschwald/maxminddb-golang/v2 v2.5.0
|
github.com/oschwald/maxminddb-golang v1.12.0
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/rs/zerolog v1.35.1
|
github.com/rs/zerolog v1.35.1
|
||||||
github.com/safing/mmdbmeld v0.3.0
|
github.com/safing/mmdbmeld v0.3.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bytedance/gopkg v0.1.4 // indirect
|
github.com/bytedance/sonic v1.11.9 // indirect
|
||||||
github.com/bytedance/sonic v1.15.2 // indirect
|
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.5.1 // indirect
|
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.7 // indirect
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.4 // indirect
|
||||||
github.com/gin-contrib/sse v1.1.1 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.30.3 // indirect
|
github.com/go-playground/validator/v10 v10.22.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.6 // indirect
|
github.com/goccy/go-json v0.10.3 // indirect
|
||||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.15 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.22 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/maxmind/mmdbwriter v1.2.0 // indirect
|
github.com/maxmind/mmdbwriter v1.0.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.4.3 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
github.com/quic-go/qpack v0.6.0 // indirect
|
|
||||||
github.com/quic-go/quic-go v0.60.0 // indirect
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
go.mongodb.org/mongo-driver/v2 v2.7.0 // indirect
|
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||||
golang.org/x/arch v0.29.0 // indirect
|
golang.org/x/arch v0.8.0 // indirect
|
||||||
golang.org/x/crypto v0.54.0 // indirect
|
golang.org/x/crypto v0.25.0 // indirect
|
||||||
golang.org/x/net v0.57.0 // indirect
|
golang.org/x/net v0.27.0 // indirect
|
||||||
golang.org/x/sys v0.47.0 // indirect
|
golang.org/x/sys v0.22.0 // indirect
|
||||||
golang.org/x/text v0.40.0 // indirect
|
golang.org/x/text v0.16.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.11 // indirect
|
google.golang.org/protobuf v1.34.2 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,78 +1,72 @@
|
|||||||
github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
|
github.com/bytedance/sonic v1.11.9 h1:LFHENlIY/SLzDWverzdOvgMztTxcfcF+cqNsz9pK5zg=
|
||||||
github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4=
|
github.com/bytedance/sonic v1.11.9/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||||
github.com/bytedance/sonic v1.15.2 h1:90H+rcF/FwLXwfB1cudOLq/je83n683Utf4Cbp0xHCo=
|
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||||
github.com/bytedance/sonic v1.15.2/go.mod h1:mT2NbXunuaEbnZ+mRIX/vYqKISmgEuHFDI4UzmKx2SA=
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI=
|
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||||
github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
github.com/cloudwego/base64x v0.1.7 h1:NppS+Fgzg5ovhn4NkUXaDT3x9jldgH5ToMCqzBSi2zI=
|
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||||
github.com/cloudwego/base64x v0.1.7/go.mod h1:Cu1PV9zfrSf7ET2tIbWbbEy7jO7HHJ13q4X2SQ8aWYg=
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
|
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
|
||||||
github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
|
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||||
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
|
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.30.3 h1:4MU6YkEwx7GbcPJOZxrtbu+QfF3pJLJuaYTeAH0DYy8=
|
github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
|
||||||
github.com/go-playground/validator/v10 v10.30.3/go.mod h1:4Axh7oCNGcoGkqLoE4YWt6n20mcEIsPRlB7vPk3lpyc=
|
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
github.com/go-resty/resty/v2 v2.17.2 h1:FQW5oHYcIlkCNrMD2lloGScxcHJ0gkjshV3qcQAyHQk=
|
github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8=
|
||||||
github.com/go-resty/resty/v2 v2.17.2/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA=
|
github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
|
||||||
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
|
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||||
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
|
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/maxmind/mmdbwriter v1.2.0 h1:hyvDopImmgvle3aR8AaddxXnT0iQH2KWJX3vNfkwzYM=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/maxmind/mmdbwriter v1.2.0/go.mod h1:EQmKHhk2y9DRVvyNxwCLKC5FrkXZLx4snc5OlLY5XLE=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/maxmind/mmdbwriter v1.0.0 h1:bieL4P6yaYaHvbtLSwnKtEvScUKKD6jcKaLiTM3WSMw=
|
||||||
|
github.com/maxmind/mmdbwriter v1.0.0/go.mod h1:noBMCUtyN5PUQ4H8ikkOvGSHhzhLok51fON2hcrpKj8=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/oschwald/maxminddb-golang/v2 v2.5.0 h1:WvEHCE8HwFS5pKWhW8nvvRxNzczuRUOGBLn2L03VlEQ=
|
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
|
||||||
github.com/oschwald/maxminddb-golang/v2 v2.5.0/go.mod h1:EBnvLGgY+aSckqcgyfB5LPDviqaWdMZPBDwu8c2jJbs=
|
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
|
||||||
github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY=
|
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||||
github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0=
|
|
||||||
github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk=
|
|
||||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
|
||||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
|
||||||
github.com/quic-go/quic-go v0.60.0 h1:xcQioE8OM66UQLeUMHltK1CCcOu3JbVB4JAQdDQSB+0=
|
|
||||||
github.com/quic-go/quic-go v0.60.0/go.mod h1:wpKpjmPpftl30sL6pFh7REVpjbcCVy4zt2vDyK1TuJk=
|
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||||
github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI=
|
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||||
github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
|
|
||||||
github.com/safing/mmdbmeld v0.3.0 h1:eE2SxZF6gD6piH/3nnx1lSlI5kOOgYnoVAQ8NO/RejU=
|
github.com/safing/mmdbmeld v0.3.0 h1:eE2SxZF6gD6piH/3nnx1lSlI5kOOgYnoVAQ8NO/RejU=
|
||||||
github.com/safing/mmdbmeld v0.3.0/go.mod h1:2tYv3HZcXp26XzcOeirOY9ndlInfYRA9ihJ3x0wlQJw=
|
github.com/safing/mmdbmeld v0.3.0/go.mod h1:2tYv3HZcXp26XzcOeirOY9ndlInfYRA9ihJ3x0wlQJw=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
@@ -80,39 +74,82 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
|
|||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
go.mongodb.org/mongo-driver/v2 v2.7.0 h1:RO+zqavD2/GCL3cxOMyZhx6R9Irzr8/6gsoqx5tcY/c=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
go.mongodb.org/mongo-driver/v2 v2.7.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
|
||||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
|
||||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/arch v0.29.0 h1:8sSET5wB0+exBm0FGmOtdHMqjlRdV2DRD3/IV6OZgho=
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
golang.org/x/arch v0.29.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
|
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||||
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
|
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||||
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
|
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
|
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||||
|
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||||
|
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
|
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||||
|
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||||
|
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||||
|
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||||
|
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
|
|||||||
@@ -1,133 +0,0 @@
|
|||||||
package data
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// nested looks up a nested map value by successive string keys, returning "" if absent.
|
|
||||||
func nested(m map[string]any, keys ...string) string {
|
|
||||||
var cur any = m
|
|
||||||
for _, k := range keys {
|
|
||||||
mm, ok := cur.(map[string]any)
|
|
||||||
if !ok {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
cur = mm[k]
|
|
||||||
}
|
|
||||||
s, _ := cur.(string)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseIP is a test helper that panics on invalid input (used only with literals).
|
|
||||||
func parseIP(s string) net.IP {
|
|
||||||
ip := net.ParseIP(s)
|
|
||||||
if ip == nil {
|
|
||||||
panic("invalid ip in test: " + s)
|
|
||||||
}
|
|
||||||
return ip
|
|
||||||
}
|
|
||||||
|
|
||||||
// The package's data helpers use relative paths ("data/..."), so tests must
|
|
||||||
// run with the working directory at the repository root. TestMain moves there
|
|
||||||
// once for the whole package.
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
if root, err := filepath.Abs("../.."); err == nil {
|
|
||||||
_ = os.Chdir(root)
|
|
||||||
}
|
|
||||||
os.Exit(m.Run())
|
|
||||||
}
|
|
||||||
|
|
||||||
// freshDB returns a loaded IpDb, skipping the test when no mmdb is present.
|
|
||||||
func freshDB(t *testing.T) *IpDb {
|
|
||||||
t.Helper()
|
|
||||||
db := Ins()
|
|
||||||
if !db.IsLoaded() {
|
|
||||||
t.Skip("no mmdb present, skipping test that needs real data")
|
|
||||||
}
|
|
||||||
return db
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestQueryValidIP(t *testing.T) {
|
|
||||||
db := freshDB(t)
|
|
||||||
|
|
||||||
data, netRec, err := db.Query("8.8.8.8")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Query(8.8.8.8) unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
if data == nil {
|
|
||||||
t.Fatal("Query(8.8.8.8) returned nil data")
|
|
||||||
}
|
|
||||||
// 8.8.8.8 is Google (AS15169), US.
|
|
||||||
if got := nested(data, "country", "iso_code"); got != "US" {
|
|
||||||
t.Errorf("country.iso_code = %v, want US", got)
|
|
||||||
}
|
|
||||||
if netRec == nil {
|
|
||||||
t.Error("expected a non-nil network record")
|
|
||||||
} else if !netRec.Contains(parseIP("8.8.8.8")) {
|
|
||||||
t.Errorf("network %s should contain 8.8.8.8", netRec)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestQueryInvalidIP(t *testing.T) {
|
|
||||||
db := freshDB(t)
|
|
||||||
|
|
||||||
if _, _, err := db.Query("not-an-ip"); err == nil {
|
|
||||||
t.Fatal("expected error for invalid IP, got nil")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestQueryNotFound(t *testing.T) {
|
|
||||||
db := freshDB(t)
|
|
||||||
|
|
||||||
// 240.0.0.0/4 is reserved/unallocated and should not be in a public GeoIP DB.
|
|
||||||
_, _, err := db.Query("240.0.0.1")
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("expected not-found error, got nil")
|
|
||||||
}
|
|
||||||
if !errors.Is(err, ErrNotFound) {
|
|
||||||
t.Errorf("expected ErrNotFound sentinel, got %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestQueryReloadConcurrent is a regression test for the data race between
|
|
||||||
// Query (reads d.r) and Reload (reassigns d.r / closes the old reader). It
|
|
||||||
// must pass under `go test -race`.
|
|
||||||
func TestQueryReloadConcurrent(t *testing.T) {
|
|
||||||
db := freshDB(t)
|
|
||||||
|
|
||||||
var reloadOK, queries int64
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
|
|
||||||
for w := 0; w < 8; w++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for i := 0; i < 5000; i++ {
|
|
||||||
_, _, _ = db.Query("8.8.8.8")
|
|
||||||
atomic.AddInt64(&queries, 1)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
for r := 0; r < 4; r++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for i := 0; i < 30; i++ {
|
|
||||||
if db.Reload() == nil {
|
|
||||||
atomic.AddInt64(&reloadOK, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
if reloadOK == 0 {
|
|
||||||
t.Fatalf("expected reloads to succeed, got reloads=%d queries=%d", reloadOK, queries)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+3
-11
@@ -9,7 +9,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"thuanle.me/ip-info/configs"
|
"thuanle.me/ip-info/configs"
|
||||||
)
|
)
|
||||||
import reader "github.com/oschwald/maxminddb-golang/v2"
|
import reader "github.com/oschwald/maxminddb-golang"
|
||||||
|
|
||||||
type IpDb struct {
|
type IpDb struct {
|
||||||
r *reader.Reader
|
r *reader.Reader
|
||||||
@@ -61,7 +61,7 @@ func CleanupDataDir() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to delete file %s: %w", filePath, err)
|
return fmt.Errorf("failed to delete file %s: %w", filePath, err)
|
||||||
}
|
}
|
||||||
log.Info().Str("file", filePath).Msg("Deleted temp file")
|
fmt.Printf("Deleted file: %s\n", filePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,11 +83,7 @@ func (d *IpDb) Reload() error {
|
|||||||
d.dbFile = tmpFile
|
d.dbFile = tmpFile
|
||||||
r, err := reader.Open(tmpFile)
|
r, err := reader.Open(tmpFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Str("file", tmpFile).Msg("Failed to open mmdb")
|
log.Err(err).Msg("Failed to open mmdb")
|
||||||
// Open failed: drop the orphaned temp clone and leave d.dbFile/d.r
|
|
||||||
// pointing at the previously loaded DB.
|
|
||||||
_ = os.Remove(tmpFile)
|
|
||||||
d.dbFile = wilDeleteFile
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Info().Str("file", tmpFile).Msg("MMDB reloaded")
|
log.Info().Str("file", tmpFile).Msg("MMDB reloaded")
|
||||||
@@ -108,8 +104,6 @@ func (d *IpDb) Reload() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *IpDb) IsLoaded() bool {
|
func (d *IpDb) IsLoaded() bool {
|
||||||
d.mu.RLock()
|
|
||||||
defer d.mu.RUnlock()
|
|
||||||
return d.r != nil
|
return d.r != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,8 +125,6 @@ func cloneDBFile() (string, error) {
|
|||||||
// Copy the data from srcFile to dstFile
|
// Copy the data from srcFile to dstFile
|
||||||
_, err = io.CopyBuffer(tmpFile, srcFile, buf)
|
_, err = io.CopyBuffer(tmpFile, srcFile, buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Leave nothing behind on a failed copy.
|
|
||||||
_ = os.Remove(tmpFile.Name())
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
-41
@@ -1,57 +1,23 @@
|
|||||||
package data
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrNotFound is returned by Query when the IP parses but is not present in the DB.
|
|
||||||
var ErrNotFound = errors.New("IP not found")
|
|
||||||
|
|
||||||
func (d *IpDb) Query(ipArg string) (map[string]any, *net.IPNet, error) {
|
func (d *IpDb) Query(ipArg string) (map[string]any, *net.IPNet, error) {
|
||||||
// maxminddb-golang v2 requires a netip.Addr.
|
ip := net.ParseIP(ipArg)
|
||||||
addr, err := netip.ParseAddr(ipArg)
|
if ip == nil {
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("invalid IP: %s", ipArg)
|
return nil, nil, fmt.Errorf("invalid IP: %s", ipArg)
|
||||||
}
|
}
|
||||||
|
// Get data of IP.
|
||||||
// Hold the read lock: Reload() reassigns d.r and closes the old reader,
|
|
||||||
// so reading d.r / calling Lookup without synchronization is a data race.
|
|
||||||
d.mu.RLock()
|
|
||||||
defer d.mu.RUnlock()
|
|
||||||
|
|
||||||
if d.r == nil {
|
|
||||||
return nil, nil, fmt.Errorf("db not loaded")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look up the record for the IP.
|
|
||||||
result := d.r.Lookup(addr)
|
|
||||||
if err := result.Err(); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if !result.Found() {
|
|
||||||
return nil, nil, fmt.Errorf("%w: %s", ErrNotFound, ipArg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode the record into a generic map.
|
|
||||||
anyData := make(map[string]any)
|
anyData := make(map[string]any)
|
||||||
if err := result.Decode(&anyData); err != nil {
|
recordNet, ok, err := d.r.LookupNetwork(ip, &anyData)
|
||||||
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
if !ok {
|
||||||
// Convert the matched prefix back to *net.IPNet for callers.
|
return nil, nil, fmt.Errorf("IP not found: %s", ipArg)
|
||||||
var recordNet *net.IPNet
|
|
||||||
if prefix := result.Prefix(); prefix.IsValid() {
|
|
||||||
recordNet = prefixToIPNet(prefix)
|
|
||||||
}
|
}
|
||||||
return anyData, recordNet, nil
|
return anyData, recordNet, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// prefixToIPNet converts a netip.Prefix to a *net.IPNet.
|
|
||||||
func prefixToIPNet(prefix netip.Prefix) *net.IPNet {
|
|
||||||
addr := prefix.Addr().AsSlice()
|
|
||||||
mask := net.CIDRMask(prefix.Bits(), len(addr)*8)
|
|
||||||
return &net.IPNet{IP: addr, Mask: mask}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"net"
|
"net"
|
||||||
@@ -11,63 +10,34 @@ import (
|
|||||||
|
|
||||||
func HandleJson(c *gin.Context) {
|
func HandleJson(c *gin.Context) {
|
||||||
ip := c.ClientIP()
|
ip := c.ClientIP()
|
||||||
// ClientIP can yield an unparseable value from malformed proxy headers;
|
HandleIpInfo(c, ip)
|
||||||
// reject it explicitly instead of letting Query fail into a 500.
|
|
||||||
if net.ParseIP(ip) == nil {
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid ip", "ip": ip})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// selfLookup=true preserves the "default value when lookup internal ip"
|
|
||||||
// contract (commit 9d07639): a valid but DB-absent client IP (loopback /
|
|
||||||
// private addresses) returns 200 {"ip": ...} rather than a 404, since the
|
|
||||||
// caller did not ask for a specific lookup.
|
|
||||||
HandleIpInfo(c, ip, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleOtherIp(c *gin.Context) {
|
func HandleOtherIp(c *gin.Context) {
|
||||||
ip := c.Param("ip")
|
ip := c.Param("ip")
|
||||||
if net.ParseIP(ip) == nil {
|
if net.ParseIP(ip) == nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid ip", "ip": ip})
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid ip"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// The caller explicitly requested this IP, so a not-found is a real 404.
|
HandleIpInfo(c, ip)
|
||||||
HandleIpInfo(c, ip, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleIpInfo(c *gin.Context, ip string, selfLookup bool) {
|
func HandleIpInfo(c *gin.Context, ip string) {
|
||||||
if !data.Ins().IsLoaded() {
|
if !data.Ins().IsLoaded() {
|
||||||
log.Error().Msg("DB is not loaded")
|
log.Error().Msg("DB is not loaded")
|
||||||
c.JSON(http.StatusServiceUnavailable, gin.H{
|
c.String(http.StatusInternalServerError, "Try again later")
|
||||||
"error": "service unavailable, db still loading",
|
|
||||||
"ip": ip,
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ipData, _, err := data.Ins().Query(ip)
|
ipData, _, err := data.Ins().Query(ip)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Distinguish "not found" (valid IP, absent from DB) from other errors.
|
|
||||||
if isNotFound(err) {
|
|
||||||
if selfLookup {
|
|
||||||
// Graceful default for the self-IP (/json) path: the client's
|
|
||||||
// address is valid but simply absent from the public GeoIP DB.
|
|
||||||
c.JSON(http.StatusOK, gin.H{"ip": ip})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": "not found", "ip": ip})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Err(err).Msg("Failed to query IP")
|
log.Err(err).Msg("Failed to query IP")
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error", "ip": ip})
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"ip": ip,
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ipData["ip"] = ip
|
ipData["ip"] = ip
|
||||||
c.JSON(http.StatusOK, ipData)
|
c.JSON(http.StatusOK, ipData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// isNotFound reports whether the query error means the IP was not present in
|
|
||||||
// the DB (as opposed to an invalid IP or an internal lookup failure).
|
|
||||||
func isNotFound(err error) bool {
|
|
||||||
return errors.Is(err, data.ErrNotFound)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ import (
|
|||||||
|
|
||||||
func HandleMetrics(c *gin.Context) {
|
func HandleMetrics(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"updated_at": db_updater.DbUpdatedAtMillis(),
|
"updated_at": db_updater.DbUpdatedAt.UnixMilli(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
@@ -9,7 +8,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"thuanle.me/ip-info/configs"
|
"thuanle.me/ip-info/configs"
|
||||||
"thuanle.me/ip-info/configs/key"
|
"thuanle.me/ip-info/configs/key"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var srv *http.Server
|
var srv *http.Server
|
||||||
@@ -50,12 +48,6 @@ func StartApiService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Shutdown() {
|
func Shutdown() {
|
||||||
// Graceful shutdown: let in-flight requests finish (bounded by a timeout)
|
_ = srv.Close()
|
||||||
// instead of abruptly closing connections.
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
if err := srv.Shutdown(ctx); err != nil {
|
|
||||||
log.Err(err).Msg("Error shutting down API service")
|
|
||||||
}
|
|
||||||
log.Info().Msg("API service stopped")
|
log.Info().Msg("API service stopped")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"thuanle.me/ip-info/configs"
|
"thuanle.me/ip-info/configs"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func download(url string) bool {
|
func download(url string) bool {
|
||||||
@@ -17,10 +16,7 @@ func download(url string) bool {
|
|||||||
|
|
||||||
etag, _ := readFile(filenameEtag)
|
etag, _ := readFile(filenameEtag)
|
||||||
|
|
||||||
client := resty.New().
|
client := resty.New()
|
||||||
SetTimeout(2 * time.Minute).
|
|
||||||
SetRetryCount(2).
|
|
||||||
SetRetryWaitTime(5 * time.Second)
|
|
||||||
resp, err := client.R().
|
resp, err := client.R().
|
||||||
SetHeader("If-None-Match", etag).
|
SetHeader("If-None-Match", etag).
|
||||||
Get(url)
|
Get(url)
|
||||||
|
|||||||
@@ -3,24 +3,12 @@ package db_updater
|
|||||||
import (
|
import (
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"sync/atomic"
|
|
||||||
"thuanle.me/ip-info/configs"
|
"thuanle.me/ip-info/configs"
|
||||||
"thuanle.me/ip-info/internal/data"
|
"thuanle.me/ip-info/internal/data"
|
||||||
"thuanle.me/ip-info/pkg/osx"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DbUpdatedAt stores the DB last-updated time as unix milliseconds.
|
var DbUpdatedAt time.Time
|
||||||
// Accessed concurrently by the cron goroutine (writer) and the /metrics
|
|
||||||
// handler (reader), so it must be synchronized — hence atomic.Int64.
|
|
||||||
var DbUpdatedAt atomic.Int64
|
|
||||||
|
|
||||||
// DbUpdatedAtMillis returns the last DB update time in unix milliseconds,
|
|
||||||
// or 0 if the DB has never been updated.
|
|
||||||
func DbUpdatedAtMillis() int64 {
|
|
||||||
return DbUpdatedAt.Load()
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartUpdateDbService() {
|
func StartUpdateDbService() {
|
||||||
c := cron.New()
|
c := cron.New()
|
||||||
@@ -37,108 +25,14 @@ func fetchDbs() {
|
|||||||
|
|
||||||
if newFlag {
|
if newFlag {
|
||||||
log.Info().Msg("New DB downloaded. Recreating mmdb")
|
log.Info().Msg("New DB downloaded. Recreating mmdb")
|
||||||
// mmdbmeld.WriteMMDB truncates the canonical mmdb before writing. If
|
_ = mergeMmdb()
|
||||||
// the merge fails the file would be left empty/corrupt — and because
|
|
||||||
// download() already persisted the etag, the next daily run would get
|
|
||||||
// 304 Not Modified and never retry, leaving the service broken until a
|
|
||||||
// restart (which then fails to open the empty file). Back the file up
|
|
||||||
// first so we can restore it on failure.
|
|
||||||
backup, backupErr := backupMmdb()
|
|
||||||
if backupErr != nil {
|
|
||||||
// We could not back up the current canonical mmdb. Do NOT proceed:
|
|
||||||
// mergeMmdb() truncates the canonical file before writing, and with
|
|
||||||
// no backup a later merge/reload failure would leave it corrupt
|
|
||||||
// (unrecoverable on restart). Keep serving the current DB and let
|
|
||||||
// the next run retry.
|
|
||||||
log.Err(backupErr).Msg("Failed to back up mmdb, skipping rebuild")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := mergeMmdb(); err != nil {
|
|
||||||
log.Err(err).Msg("Failed to merge mmdb")
|
|
||||||
rollBackMmdb(backup)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err := data.Ins().Reload()
|
err := data.Ins().Reload()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Failed to reload mmdb")
|
log.Err(err).Msg("Failed to reload mmdb")
|
||||||
// Reload failed on the newly merged file: restore the previous
|
|
||||||
// canonical mmdb so we keep serving known-good data.
|
|
||||||
rollBackMmdb(backup)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Both merge and reload succeeded: the new canonical file is live, the
|
DbUpdatedAt = time.Now()
|
||||||
// backup is no longer needed.
|
|
||||||
cleanupMmdbBackup(backup)
|
|
||||||
|
|
||||||
// Stamp the update time from the freshly written canonical file.
|
|
||||||
stampDbUpdatedAt()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// No new download: on startup this is the path that loads an existing DB.
|
|
||||||
// Ensure the timestamp reflects the file we are actually serving.
|
|
||||||
if DbUpdatedAt.Load() == 0 {
|
|
||||||
stampDbUpdatedAt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// stampDbUpdatedAt records the mtime of the canonical mmdb (unix ms) as the
|
|
||||||
// last DB update time. This covers both initial load (pre-existing files) and
|
|
||||||
// post-download reloads, so /metrics never reports 0 while serving real data.
|
|
||||||
func stampDbUpdatedAt() {
|
|
||||||
info, err := os.Stat(configs.MmdbDbFile)
|
|
||||||
if err != nil {
|
|
||||||
log.Err(err).Str("file", configs.MmdbDbFile).Msg("Failed to stat mmdb")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
DbUpdatedAt.Store(info.ModTime().UnixMilli())
|
|
||||||
}
|
|
||||||
|
|
||||||
// backupMmdb copies the canonical mmdb to a sibling backup file. It returns
|
|
||||||
// ("", nil) when there is no canonical file yet (first-ever build), in which
|
|
||||||
// case there is nothing to restore on failure.
|
|
||||||
func backupMmdb() (string, error) {
|
|
||||||
if _, err := os.Stat(configs.MmdbDbFile); os.IsNotExist(err) {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
backup := configs.MmdbDbFile + ".bak"
|
|
||||||
if err := osx.Copy(configs.MmdbDbFile, backup); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return backup, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// rollBackMmdb restores the canonical mmdb from backup (if any) and deletes
|
|
||||||
// the etag files so the next cron run re-downloads and retries the merge.
|
|
||||||
// backup is "" on a first-ever build (no canonical file existed to back up),
|
|
||||||
// in which case there is nothing to restore.
|
|
||||||
func rollBackMmdb(backup string) {
|
|
||||||
if backup != "" {
|
|
||||||
if err := osx.Copy(backup, configs.MmdbDbFile); err != nil {
|
|
||||||
log.Err(err).Msg("Failed to restore mmdb backup")
|
|
||||||
} else {
|
|
||||||
log.Info().Str("file", configs.MmdbDbFile).Msg("Restored mmdb from backup")
|
|
||||||
}
|
|
||||||
_ = os.Remove(backup)
|
|
||||||
}
|
|
||||||
// Drop etags so a failed merge is retried on the next run instead of being
|
|
||||||
// masked by a 304 Not Modified.
|
|
||||||
for _, url := range configs.GeoDbSourcePaths {
|
|
||||||
etag := configs.GeoDbFolder + path.Base(url) + ".etag"
|
|
||||||
if err := os.Remove(etag); err != nil && !os.IsNotExist(err) {
|
|
||||||
log.Err(err).Str("file", etag).Msg("Failed to remove etag")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanupMmdbBackup removes a successful merge's backup file.
|
|
||||||
func cleanupMmdbBackup(backup string) {
|
|
||||||
if backup == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := os.Remove(backup); err != nil && !os.IsNotExist(err) {
|
|
||||||
log.Err(err).Str("file", backup).Msg("Failed to remove mmdb backup")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-11
@@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Copy copies a file from source to destination
|
// Copy copies a file from source to destination
|
||||||
func Copy(from, to string) (err error) {
|
func Copy(from, to string) error {
|
||||||
// Open the source file for reading
|
// Open the source file for reading
|
||||||
srcFile, err := os.Open(from)
|
srcFile, err := os.Open(from)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -19,16 +19,7 @@ func Copy(from, to string) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Closing may flush buffered writes, so its error must be surfaced — a
|
defer dstFile.Close()
|
||||||
// successful copy with a failed close means the destination is incomplete.
|
|
||||||
// Defer the close and only let it overwrite the return value when the copy
|
|
||||||
// itself succeeded; otherwise keep the first error.
|
|
||||||
defer func() {
|
|
||||||
cerr := dstFile.Close()
|
|
||||||
if err == nil {
|
|
||||||
err = cerr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Use a buffer to copy the file in chunks
|
// Use a buffer to copy the file in chunks
|
||||||
buf := make([]byte, 1024*1024) // 1 MB buffer
|
buf := make([]byte, 1024*1024) // 1 MB buffer
|
||||||
|
|||||||
+1
-7
@@ -1,9 +1,3 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
|
||||||
"packageRules": [
|
|
||||||
{
|
|
||||||
"matchManagers": ["gomod"],
|
|
||||||
"postUpdateOptions": ["gomodTidy", "gomodMassage"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user