├── .github └── workflows │ ├── build.yaml │ └── release.yaml ├── .gitignore ├── .golangci.yml ├── LICENSE ├── README.md ├── format.go ├── go.mod ├── go.sum └── main.go /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | build: 8 | name: Build 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | with: 14 | fetch-depth: 0 15 | - name: Get latest go version 16 | id: version 17 | run: | 18 | echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') 19 | - name: Setup Go 20 | uses: actions/setup-go@v2 21 | with: 22 | go-version: ${{ steps.version.outputs.go_version }} 23 | - name: Build geoip 24 | id: build 25 | env: 26 | NO_SKIP: true 27 | run: go run -v . 28 | - name: Upload artifacts 29 | uses: actions/upload-artifact@v2 30 | with: 31 | name: geoip.db 32 | path: geoip.db 33 | - name: Upload artifacts 34 | uses: actions/upload-artifact@v2 35 | with: 36 | name: geoip-cn.db 37 | path: geoip-cn.db -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | - cron: "30 0 * * *" 6 | jobs: 7 | build: 8 | name: Build 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | with: 14 | fetch-depth: 0 15 | - name: Get latest go version 16 | id: version 17 | run: | 18 | echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') 19 | - name: Setup Go 20 | uses: actions/setup-go@v2 21 | with: 22 | go-version: ${{ steps.version.outputs.go_version }} 23 | - name: Build geoip 24 | id: build 25 | run: go run -v . 26 | - name: Generate sha256 hash 27 | if: steps.build.outputs.skip != 'true' 28 | run: | 29 | mkdir dist 30 | sha256sum geoip.db > dist/geoip.db.sha256sum 31 | sha256sum geoip-cn.db > dist/geoip-cn.db.sha256sum 32 | cp geoip.db* dist/ 33 | cp geoip-cn.db* dist/ 34 | 35 | - name: Push artifacts to release branch 36 | if: steps.build.outputs.skip != 'true' 37 | run: | 38 | git config --local user.email "891708+soffchen@users.noreply.github.com" 39 | git config --local user.name "github-action[bot]" 40 | git fetch 41 | git checkout release 42 | git checkout --orphan release-orphan 43 | git rm -rf . 44 | 45 | cp dist/* . 46 | git add geoip.db 47 | git add geoip-cn.db 48 | git add geoip.db.sha256sum 49 | git add geoip-cn.db.sha256sum 50 | 51 | git commit -am "Updated at $(date)" 52 | git branch -D release 53 | git branch -m release 54 | 55 | - name: GitHub Push 56 | uses: ad-m/github-push-action@v0.6.0 57 | with: 58 | github_token: ${{ secrets.GITHUB_TOKEN }} 59 | branch: release 60 | force: true 61 | 62 | - name: Purge CDN Cache 63 | run: | 64 | curl -L https://purge.jsdelivr.net/gh/soffchen/sing-geoip@release/geoip.db > /dev/null 2>&1 65 | curl -L https://purge.jsdelivr.net/gh/soffchen/sing-geoip@release/geoip-cn.db > /dev/null 2>&1 66 | curl -L https://purge.jsdelivr.net/gh/soffchen/sing-geoip@release/geoip.db.sha256sum > /dev/null 2>&1 67 | curl -L https://purge.jsdelivr.net/gh/soffchen/sing-geoip@release/geoip-cn.db.sha256sum > /dev/null 2>&1 68 | 69 | - name: Create a release 70 | if: steps.build.outputs.skip != 'true' 71 | id: create_release 72 | uses: actions/create-release@v1 73 | env: 74 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 75 | with: 76 | tag_name: ${{ steps.build.outputs.tag }} 77 | release_name: ${{ steps.build.outputs.tag }} 78 | draft: false 79 | prerelease: false 80 | - name: Release geoip.db 81 | if: steps.build.outputs.skip != 'true' 82 | uses: actions/upload-release-asset@v1 83 | env: 84 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 85 | with: 86 | upload_url: ${{ steps.create_release.outputs.upload_url }} 87 | asset_path: ./geoip.db 88 | asset_name: geoip.db 89 | asset_content_type: application/octet-stream 90 | - name: Release geoip.db sha256sum 91 | if: steps.build.outputs.skip != 'true' 92 | uses: actions/upload-release-asset@v1 93 | env: 94 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 95 | with: 96 | upload_url: ${{ steps.create_release.outputs.upload_url }} 97 | asset_path: ./geoip.db.sha256sum 98 | asset_name: geoip.db.sha256sum 99 | asset_content_type: text/plain 100 | - name: Release geoip-cn.db 101 | if: steps.build.outputs.skip != 'true' 102 | uses: actions/upload-release-asset@v1 103 | env: 104 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 105 | with: 106 | upload_url: ${{ steps.create_release.outputs.upload_url }} 107 | asset_path: ./geoip-cn.db 108 | asset_name: geoip-cn.db 109 | asset_content_type: application/octet-stream 110 | - name: Release geoip.db sha256sum 111 | if: steps.build.outputs.skip != 'true' 112 | uses: actions/upload-release-asset@v1 113 | env: 114 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 115 | with: 116 | upload_url: ${{ steps.create_release.outputs.upload_url }} 117 | asset_path: ./geoip-cn.db.sha256sum 118 | asset_name: geoip-cn.db.sha256sum 119 | asset_content_type: text/plain 120 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /vendor/ 3 | /*.db 4 | *.mmdb -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | timeout: 5m 3 | 4 | linters: 5 | enable-all: true 6 | disable: 7 | - errcheck 8 | - wrapcheck 9 | - varnamelen 10 | - stylecheck 11 | - nonamedreturns 12 | - nlreturn 13 | - ireturn 14 | - gomnd 15 | - exhaustivestruct 16 | - ifshort 17 | - goerr113 18 | - gochecknoglobals 19 | - forcetypeassert 20 | - exhaustruct 21 | - exhaustive 22 | - cyclop 23 | - containedctx 24 | - wsl 25 | - nestif 26 | - lll 27 | - funlen 28 | - goconst 29 | - godot 30 | - gocognit 31 | - golint 32 | - goimports 33 | - gochecknoinits 34 | - maligned 35 | - tagliatelle 36 | - gocyclo 37 | - maintidx 38 | - gocritic 39 | - nakedret 40 | 41 | linters-settings: 42 | revive: 43 | rules: 44 | - name: var-naming 45 | disabled: true 46 | govet: 47 | enable-all: true 48 | disable: 49 | - composites 50 | - fieldalignment 51 | - shadow 52 | gosec: 53 | excludes: 54 | - G404 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2022 by nekohasekai 2 | 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 3 of the License, or 6 | (at your option) any later version. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | 13 | You should have received a copy of the GNU General Public License 14 | along with this program. If not, see . -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # geoip.db for sing-box 2 | 3 | This project manages a list of ips, to be used as geoips for routing purpose in [SagerNet/sing-box](https://github.com/SagerNet/sing-box). 4 | 5 | Based on [soffchen/geoip](https://github.com/soffchen/geoip) modified from [Loyalsoldier/geoip](https://github.com/Loyalsoldier/geoip) 6 | 7 | You could download the lastest `geoip.db` from [https://github.com/soffchen/sing-geoip/releases/latest/download/geoip.db](https://github.com/soffchen/sing-geoip/releases/latest/download/geoip.db) or [https://cdn.jsdelivr.net/gh/soffchen/sing-geoip@release/geoip.db](https://cdn.jsdelivr.net/gh/soffchen/sing-geoip@release/geoip.db) with CDN powered by jsdelivr. 8 | -------------------------------------------------------------------------------- /format.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //go:generate go install -v mvdan.cc/gofumpt@latest 4 | //go:generate go install -v github.com/daixiang0/gci@latest 5 | //go:generate gofumpt -l -w . 6 | //go:generate gofmt -s -w . 7 | //go:generate gci write . 8 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module sing-geoip 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/google/go-github/v45 v45.2.0 7 | github.com/maxmind/mmdbwriter v0.0.0-20221024142553-ff6538147996 8 | github.com/oschwald/geoip2-golang v1.8.0 9 | github.com/oschwald/maxminddb-golang v1.10.0 10 | github.com/sagernet/sing v0.1.6 11 | github.com/sirupsen/logrus v1.9.0 12 | ) 13 | 14 | require ( 15 | github.com/google/go-querystring v1.1.0 // indirect 16 | go4.org/netipx v0.0.0-20230125063823-8449b0a6169f // indirect 17 | golang.org/x/crypto v0.6.0 // indirect 18 | golang.org/x/sys v0.5.0 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 5 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= 6 | github.com/google/go-github/v45 v45.2.0 h1:5oRLszbrkvxDDqBCNj2hjDZMKmvexaZ1xw/FCD+K3FI= 7 | github.com/google/go-github/v45 v45.2.0/go.mod h1:FObaZJEDSTa/WGCzZ2Z3eoCDXWJKMenWWTrd8jrta28= 8 | github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 9 | github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 10 | github.com/maxmind/mmdbwriter v0.0.0-20221024142553-ff6538147996 h1:ZllqW+vRQELpmMtWHHDjeIeD6cajKnMvwbEZGT7Ynv0= 11 | github.com/maxmind/mmdbwriter v0.0.0-20221024142553-ff6538147996/go.mod h1:3OQuci6HAhYUu3qasU5SxZjA1vFaH1MB/Ekn0yWs1q4= 12 | github.com/oschwald/geoip2-golang v1.8.0 h1:KfjYB8ojCEn/QLqsDU0AzrJ3R5Qa9vFlx3z6SLNcKTs= 13 | github.com/oschwald/geoip2-golang v1.8.0/go.mod h1:R7bRvYjOeaoenAp9sKRS8GX5bJWcZ0laWO5+DauEktw= 14 | github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg= 15 | github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0= 16 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 17 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 18 | github.com/sagernet/sing v0.1.6 h1:Qy63OUfKpcqKjfd5rPmUlj0RGjHZSK/PJn0duyCCsRg= 19 | github.com/sagernet/sing v0.1.6/go.mod h1:JLSXsPTGRJFo/3X7EcAOCUgJH2/gAoxSJgBsnCZRp/w= 20 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 21 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 22 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 23 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 24 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 25 | go4.org/netipx v0.0.0-20230125063823-8449b0a6169f h1:ketMxHg+vWm3yccyYiq+uK8D3fRmna2Fcj+awpQp84s= 26 | go4.org/netipx v0.0.0-20230125063823-8449b0a6169f/go.mod h1:tgPU4N2u9RByaTN3NC2p9xOzyFpte4jYwsIIRF7XlSc= 27 | golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= 28 | golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= 29 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 30 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 31 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 32 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 33 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 34 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 35 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 36 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net" 7 | "net/http" 8 | "os" 9 | "sort" 10 | "strings" 11 | 12 | "github.com/google/go-github/v45/github" 13 | "github.com/maxmind/mmdbwriter" 14 | "github.com/maxmind/mmdbwriter/inserter" 15 | "github.com/maxmind/mmdbwriter/mmdbtype" 16 | "github.com/oschwald/geoip2-golang" 17 | "github.com/oschwald/maxminddb-golang" 18 | "github.com/sagernet/sing/common" 19 | E "github.com/sagernet/sing/common/exceptions" 20 | "github.com/sagernet/sing/common/rw" 21 | "github.com/sirupsen/logrus" 22 | ) 23 | 24 | var githubClient *github.Client 25 | 26 | func init() { 27 | accessToken, loaded := os.LookupEnv("ACCESS_TOKEN") 28 | if !loaded { 29 | githubClient = github.NewClient(nil) 30 | return 31 | } 32 | transport := &github.BasicAuthTransport{ 33 | Username: accessToken, 34 | } 35 | githubClient = github.NewClient(transport.Client()) 36 | } 37 | 38 | func fetch(from string) (*github.RepositoryRelease, error) { 39 | names := strings.SplitN(from, "/", 2) 40 | latestRelease, _, err := githubClient.Repositories.GetLatestRelease(context.Background(), names[0], names[1]) 41 | if err != nil { 42 | return nil, err 43 | } 44 | return latestRelease, err 45 | } 46 | 47 | func get(downloadURL *string) ([]byte, error) { 48 | logrus.Info("download ", *downloadURL) 49 | response, err := http.Get(*downloadURL) 50 | if err != nil { 51 | return nil, err 52 | } 53 | defer response.Body.Close() 54 | return io.ReadAll(response.Body) 55 | } 56 | 57 | func download(release *github.RepositoryRelease) ([]byte, error) { 58 | geoipAsset := common.Find(release.Assets, func(it *github.ReleaseAsset) bool { 59 | return *it.Name == "Country.mmdb" 60 | }) 61 | if geoipAsset == nil { 62 | return nil, E.New("Country.mmdb not found in upstream release ", release.Name) 63 | } 64 | return get(geoipAsset.BrowserDownloadURL) 65 | } 66 | 67 | func parse(binary []byte) (metadata maxminddb.Metadata, countryMap map[string][]*net.IPNet, err error) { 68 | database, err := maxminddb.FromBytes(binary) 69 | if err != nil { 70 | return 71 | } 72 | metadata = database.Metadata 73 | networks := database.Networks(maxminddb.SkipAliasedNetworks) 74 | countryMap = make(map[string][]*net.IPNet) 75 | var country geoip2.Enterprise 76 | var ipNet *net.IPNet 77 | for networks.Next() { 78 | ipNet, err = networks.Network(&country) 79 | if err != nil { 80 | return 81 | } 82 | var code string 83 | if country.Country.IsoCode != "" { 84 | code = strings.ToLower(country.Country.IsoCode) 85 | } else if country.RegisteredCountry.IsoCode != "" { 86 | code = strings.ToLower(country.RegisteredCountry.IsoCode) 87 | } else if country.RepresentedCountry.IsoCode != "" { 88 | code = strings.ToLower(country.RepresentedCountry.IsoCode) 89 | } else if country.Continent.Code != "" { 90 | code = strings.ToLower(country.Continent.Code) 91 | } else { 92 | continue 93 | } 94 | countryMap[code] = append(countryMap[code], ipNet) 95 | } 96 | err = networks.Err() 97 | return 98 | } 99 | 100 | func newWriter(metadata maxminddb.Metadata, codes []string) (*mmdbwriter.Tree, error) { 101 | return mmdbwriter.New(mmdbwriter.Options{ 102 | DatabaseType: "sing-geoip", 103 | Languages: codes, 104 | IPVersion: int(metadata.IPVersion), 105 | RecordSize: int(metadata.RecordSize), 106 | Inserter: inserter.ReplaceWith, 107 | DisableIPv4Aliasing: true, 108 | IncludeReservedNetworks: true, 109 | }) 110 | } 111 | 112 | func open(path string, codes []string) (*mmdbwriter.Tree, error) { 113 | reader, err := maxminddb.Open(path) 114 | if err != nil { 115 | return nil, err 116 | } 117 | if reader.Metadata.DatabaseType != "sing-geoip" { 118 | return nil, E.New("invalid sing-geoip database") 119 | } 120 | reader.Close() 121 | 122 | return mmdbwriter.Load(path, mmdbwriter.Options{ 123 | Languages: append(reader.Metadata.Languages, common.Filter(codes, func(it string) bool { 124 | return !common.Contains(reader.Metadata.Languages, it) 125 | })...), 126 | Inserter: inserter.ReplaceWith, 127 | }) 128 | } 129 | 130 | func write(writer *mmdbwriter.Tree, dataMap map[string][]*net.IPNet, output string, codes []string) error { 131 | if len(codes) == 0 { 132 | codes = make([]string, 0, len(dataMap)) 133 | for code := range dataMap { 134 | codes = append(codes, code) 135 | } 136 | } 137 | sort.Strings(codes) 138 | codeMap := make(map[string]bool) 139 | for _, code := range codes { 140 | codeMap[code] = true 141 | } 142 | for code, data := range dataMap { 143 | if !codeMap[code] { 144 | continue 145 | } 146 | for _, item := range data { 147 | err := writer.Insert(item, mmdbtype.String(code)) 148 | if err != nil { 149 | return err 150 | } 151 | } 152 | } 153 | outputFile, err := os.Create(output) 154 | if err != nil { 155 | return err 156 | } 157 | defer outputFile.Close() 158 | _, err = writer.WriteTo(outputFile) 159 | return err 160 | } 161 | 162 | func local(input string, output string, codes []string) error { 163 | binary, err := os.ReadFile(input) 164 | if err != nil { 165 | return err 166 | } 167 | metadata, countryMap, err := parse(binary) 168 | if err != nil { 169 | return err 170 | } 171 | var writer *mmdbwriter.Tree 172 | if rw.FileExists(output) { 173 | writer, err = open(output, codes) 174 | } else { 175 | writer, err = newWriter(metadata, codes) 176 | } 177 | if err != nil { 178 | return err 179 | } 180 | return write(writer, countryMap, output, codes) 181 | } 182 | 183 | func release(source string, destination string) error { 184 | sourceRelease, err := fetch(source) 185 | if err != nil { 186 | return err 187 | } 188 | destinationRelease, err := fetch(destination) 189 | if err != nil { 190 | logrus.Warn("missing destination latest release") 191 | } else { 192 | if os.Getenv("NO_SKIP") != "true" && strings.Contains(*destinationRelease.TagName, *sourceRelease.TagName) { 193 | logrus.Info("already latest") 194 | setActionOutput("skip", "true") 195 | return nil 196 | } 197 | } 198 | binary, err := download(sourceRelease) 199 | if err != nil { 200 | return err 201 | } 202 | metadata, countryMap, err := parse(binary) 203 | if err != nil { 204 | return err 205 | } 206 | allCodes := make([]string, 0, len(countryMap)) 207 | for code := range countryMap { 208 | allCodes = append(allCodes, code) 209 | } 210 | writer, err := newWriter(metadata, allCodes) 211 | if err != nil { 212 | return err 213 | } 214 | err = write(writer, countryMap, "geoip.db", nil) 215 | if err != nil { 216 | return err 217 | } 218 | writer, err = newWriter(metadata, []string{"cn"}) 219 | if err != nil { 220 | return err 221 | } 222 | err = write(writer, countryMap, "geoip-cn.db", []string{"cn"}) 223 | if err != nil { 224 | return err 225 | } 226 | if err != nil { 227 | return err 228 | } 229 | 230 | tagName := *sourceRelease.TagName 231 | setActionOutput("tag", tagName) 232 | 233 | return nil 234 | } 235 | 236 | func setActionOutput(name string, content string) { 237 | os.Stdout.WriteString("::set-output name=" + name + "::" + content + "\n") 238 | } 239 | 240 | func main() { 241 | var err error 242 | if len(os.Args) >= 3 { 243 | err = local(os.Args[1], os.Args[2], os.Args[2:]) 244 | } else { 245 | err = release("soffchen/geoip", "soffchen/sing-geoip") 246 | } 247 | if err != nil { 248 | logrus.Fatal(err) 249 | } 250 | } 251 | --------------------------------------------------------------------------------