New upstream version 4.0.6
Faidon Liambotis
4 years ago
0 | [run] | |
1 | deadline = "10m" | |
2 | tests = true | |
3 | ||
4 | [linters] | |
5 | disable-all = true | |
6 | enable = [ | |
7 | "deadcode", | |
8 | "depguard", | |
9 | "errcheck", | |
10 | "goconst", | |
11 | "gocyclo", | |
12 | "gocritic", | |
13 | "gofmt", | |
14 | "golint", | |
15 | "gosec", | |
16 | "gosimple", | |
17 | "ineffassign", | |
18 | "maligned", | |
19 | "misspell", | |
20 | "nakedret", | |
21 | "staticcheck", | |
22 | "structcheck", | |
23 | "typecheck", | |
24 | "unconvert", | |
25 | "unparam", | |
26 | "varcheck", | |
27 | "vet", | |
28 | "vetshadow", | |
29 | ] | |
30 | ||
31 | # This goes off for MD5 usage, which we use heavily | |
32 | [[issues.exclude-rules]] | |
33 | text = "weak cryptographic primitive" | |
34 | linters = ["gosec"] |
0 | project_name: 'geoipupdate' | |
1 | archives: | |
2 | - id: main | |
3 | wrap_in_directory: true | |
4 | files: | |
5 | - 'CHANGELOG.md' | |
6 | - 'LICENSE-APACHE' | |
7 | - 'LICENSE-MIT' | |
8 | - 'README.md' | |
9 | - 'GeoIP.conf' | |
10 | - 'GeoIP.conf.md' | |
11 | - 'geoipupdate.md' | |
12 | builds: | |
13 | - main: './cmd/geoipupdate' | |
14 | binary: 'geoipupdate' | |
15 | goarch: | |
16 | - '386' | |
17 | - 'amd64' | |
18 | goos: | |
19 | - 'linux' | |
20 | hooks: | |
21 | post: 'make data BUILDDIR=. CONFFILE=/etc/GeoIP.conf DATADIR=/usr/share/GeoIP' | |
22 | ldflags: | |
23 | - '-s -w -X main.version={{.Version}} -X main.defaultConfigFile=/etc/GeoIP.conf -X main.defaultDatabaseDirectory=/usr/share/GeoIP' | |
24 | checksum: | |
25 | name_template: 'checksums-dpkg-rpm.txt' | |
26 | nfpm: | |
27 | vendor: 'MaxMind, Inc.' | |
28 | homepage: https://www.maxmind.com/ | |
29 | maintainer: 'MaxMind, Inc. <support@maxmind.com>' | |
30 | description: Program to perform automatic updates of GeoIP2 and GeoIP Legacy binary databases. | |
31 | license: Apache 2.0 or MIT | |
32 | formats: | |
33 | - deb | |
34 | - rpm | |
35 | bindir: /usr/bin | |
36 | empty_folders: | |
37 | - /usr/share/GeoIP | |
38 | files: | |
39 | 'CHANGELOG.md': '/usr/share/doc/geoipupdate/CHANGELOG.md' | |
40 | 'LICENSE-APACHE': '/usr/share/doc/geoipupdate/LICENSE-APACHE' | |
41 | 'LICENSE-MIT': '/usr/share/doc/geoipupdate/LICENSE-MIT' | |
42 | 'README.md': '/usr/share/doc/geoipupdate/README.md' | |
43 | 'GeoIP.conf': '/usr/share/doc/geoipupdate/GeoIP.conf' | |
44 | 'GeoIP.conf.md': '/usr/share/doc/geoipupdate/GeoIP.conf.md' | |
45 | 'geoipupdate.md': '/usr/share/doc/geoipupdate/geoipupdate.md' | |
46 | config_files: | |
47 | 'GeoIP.conf': '/etc/GeoIP.conf' | |
48 | release: | |
49 | # We disable the release as there is no way to disable the creation of | |
50 | # the archive version and we don't want to upload those. We also can | |
51 | # only do one release. | |
52 | disable: true |
0 | project_name: 'geoipupdate' | |
1 | archives: | |
2 | - id: main | |
3 | wrap_in_directory: true | |
4 | files: | |
5 | - 'CHANGELOG.md' | |
6 | - 'LICENSE-APACHE' | |
7 | - 'LICENSE-MIT' | |
8 | - 'README.md' | |
9 | - 'GeoIP.conf' | |
10 | - 'GeoIP.conf.md' | |
11 | - 'geoipupdate.md' | |
12 | format: zip | |
13 | builds: | |
14 | - main: './cmd/geoipupdate' | |
15 | binary: 'geoipupdate' | |
16 | goarch: | |
17 | - '386' | |
18 | - 'amd64' | |
19 | goos: | |
20 | - 'windows' | |
21 | hooks: | |
22 | post: 'make data OS=Windows_NT BUILDDIR=.' | |
23 | checksum: | |
24 | name_template: 'checksums-windows.txt' | |
25 | release: | |
26 | # We can only do one release. | |
27 | disable: true |
0 | project_name: 'geoipupdate' | |
1 | archives: | |
2 | - id: main | |
3 | wrap_in_directory: true | |
4 | files: | |
5 | - 'CHANGELOG.md' | |
6 | - 'LICENSE-APACHE' | |
7 | - 'LICENSE-MIT' | |
8 | - 'README.md' | |
9 | - 'GeoIP.conf' | |
10 | - 'GeoIP.conf.md' | |
11 | - 'geoipupdate.md' | |
12 | builds: | |
13 | - main: './cmd/geoipupdate' | |
14 | binary: 'geoipupdate' | |
15 | goarch: | |
16 | - '386' | |
17 | - 'amd64' | |
18 | goos: | |
19 | - 'darwin' | |
20 | - 'linux' | |
21 | hooks: | |
22 | post: 'make data BUILDDIR=.' | |
23 | checksum: | |
24 | name_template: 'checksums-darwin-linux.txt' |
0 | language: go | |
1 | ||
2 | os: | |
3 | - linux | |
4 | - osx | |
5 | - windows | |
6 | ||
7 | go: | |
8 | - "1.10.x" | |
9 | - "1.11.x" | |
10 | - "1.12.x" | |
11 | - "1.13.x" | |
12 | ||
13 | install: | |
14 | - go get -t ./... | |
15 | ||
16 | before_script: | |
17 | - | | |
18 | if [[ $TRAVIS_GO_VERSION == '1.13.x' && $TRAVIS_OS_NAME == 'linux' ]]; then | |
19 | curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin | |
20 | fi | |
21 | ||
22 | script: | |
23 | - go test -v -race ./... | |
24 | - | | |
25 | if [[ $TRAVIS_GO_VERSION == '1.13.x' && $TRAVIS_OS_NAME == 'linux' ]]; then | |
26 | golangci-lint run | |
27 | fi |
0 | # CHANGELOG | |
1 | ||
2 | ## 4.0.5 (2019-09-13) | |
3 | ||
4 | * Ignore errors when syncing file system. These errors were primarily due | |
5 | to the file system not supporting the sync call. Reported by devkappa. | |
6 | GitHub #37. | |
7 | * Use CRLF line endings on Windows for text files. | |
8 | * Fix tests on Windows. | |
9 | * Improve man page formatting. Reported by Faidon Liambotis. GitHub #38. | |
10 | * Dependencies are no longer vendored. Reported by Faidon Liambotis. GitHub | |
11 | #39. | |
12 | ||
13 | ## 4.0.4 (2019-08-30) | |
14 | ||
15 | * Do not try to sync the database directory when running on Windows. | |
16 | Syncing this way is not supported there and would lead to an error. Pull | |
17 | request by Nicholi. GitHub #32. | |
18 | ||
19 | ## 4.0.3 (2019-06-07) | |
20 | ||
21 | * Update flock dependency from `theckman/go-flock` to `gofrs/flock`. Pull | |
22 | request by Paul Howarth. GitHub #22. | |
23 | * Switch to Go modules and update dependencies. | |
24 | * Fix version output on Ubuntu PPA and Homebrew releases. | |
25 | ||
26 | ## 4.0.2 (2019-01-18) | |
27 | ||
28 | * Fix dependency in `Makefile`. | |
29 | ||
30 | ## 4.0.1 (2019-01-17) | |
31 | ||
32 | * Improve documentation. | |
33 | * Add script to generate man pages to `Makefile`. | |
34 | ||
35 | ## 4.0.0 (2019-01-14) | |
36 | ||
37 | * Expand installation instructions. | |
38 | * First full release. | |
39 | ||
40 | ## 0.0.2 (2018-11-28) | |
41 | ||
42 | * Fix the output when the version output, `-V`, is passed to `geoipupdate`. | |
43 | ||
44 | ## 0.0.1 (2018-11-27) | |
45 | ||
46 | * Initial version |
0 | ||
1 | Apache License | |
2 | Version 2.0, January 2004 | |
3 | http://www.apache.org/licenses/ | |
4 | ||
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |
6 | ||
7 | 1. Definitions. | |
8 | ||
9 | "License" shall mean the terms and conditions for use, reproduction, | |
10 | and distribution as defined by Sections 1 through 9 of this document. | |
11 | ||
12 | "Licensor" shall mean the copyright owner or entity authorized by | |
13 | the copyright owner that is granting the License. | |
14 | ||
15 | "Legal Entity" shall mean the union of the acting entity and all | |
16 | other entities that control, are controlled by, or are under common | |
17 | control with that entity. For the purposes of this definition, | |
18 | "control" means (i) the power, direct or indirect, to cause the | |
19 | direction or management of such entity, whether by contract or | |
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the | |
21 | outstanding shares, or (iii) beneficial ownership of such entity. | |
22 | ||
23 | "You" (or "Your") shall mean an individual or Legal Entity | |
24 | exercising permissions granted by this License. | |
25 | ||
26 | "Source" form shall mean the preferred form for making modifications, | |
27 | including but not limited to software source code, documentation | |
28 | source, and configuration files. | |
29 | ||
30 | "Object" form shall mean any form resulting from mechanical | |
31 | transformation or translation of a Source form, including but | |
32 | not limited to compiled object code, generated documentation, | |
33 | and conversions to other media types. | |
34 | ||
35 | "Work" shall mean the work of authorship, whether in Source or | |
36 | Object form, made available under the License, as indicated by a | |
37 | copyright notice that is included in or attached to the work | |
38 | (an example is provided in the Appendix below). | |
39 | ||
40 | "Derivative Works" shall mean any work, whether in Source or Object | |
41 | form, that is based on (or derived from) the Work and for which the | |
42 | editorial revisions, annotations, elaborations, or other modifications | |
43 | represent, as a whole, an original work of authorship. For the purposes | |
44 | of this License, Derivative Works shall not include works that remain | |
45 | separable from, or merely link (or bind by name) to the interfaces of, | |
46 | the Work and Derivative Works thereof. | |
47 | ||
48 | "Contribution" shall mean any work of authorship, including | |
49 | the original version of the Work and any modifications or additions | |
50 | to that Work or Derivative Works thereof, that is intentionally | |
51 | submitted to Licensor for inclusion in the Work by the copyright owner | |
52 | or by an individual or Legal Entity authorized to submit on behalf of | |
53 | the copyright owner. For the purposes of this definition, "submitted" | |
54 | means any form of electronic, verbal, or written communication sent | |
55 | to the Licensor or its representatives, including but not limited to | |
56 | communication on electronic mailing lists, source code control systems, | |
57 | and issue tracking systems that are managed by, or on behalf of, the | |
58 | Licensor for the purpose of discussing and improving the Work, but | |
59 | excluding communication that is conspicuously marked or otherwise | |
60 | designated in writing by the copyright owner as "Not a Contribution." | |
61 | ||
62 | "Contributor" shall mean Licensor and any individual or Legal Entity | |
63 | on behalf of whom a Contribution has been received by Licensor and | |
64 | subsequently incorporated within the Work. | |
65 | ||
66 | 2. Grant of Copyright License. Subject to the terms and conditions of | |
67 | this License, each Contributor hereby grants to You a perpetual, | |
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |
69 | copyright license to reproduce, prepare Derivative Works of, | |
70 | publicly display, publicly perform, sublicense, and distribute the | |
71 | Work and such Derivative Works in Source or Object form. | |
72 | ||
73 | 3. Grant of Patent License. Subject to the terms and conditions of | |
74 | this License, each Contributor hereby grants to You a perpetual, | |
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |
76 | (except as stated in this section) patent license to make, have made, | |
77 | use, offer to sell, sell, import, and otherwise transfer the Work, | |
78 | where such license applies only to those patent claims licensable | |
79 | by such Contributor that are necessarily infringed by their | |
80 | Contribution(s) alone or by combination of their Contribution(s) | |
81 | with the Work to which such Contribution(s) was submitted. If You | |
82 | institute patent litigation against any entity (including a | |
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work | |
84 | or a Contribution incorporated within the Work constitutes direct | |
85 | or contributory patent infringement, then any patent licenses | |
86 | granted to You under this License for that Work shall terminate | |
87 | as of the date such litigation is filed. | |
88 | ||
89 | 4. Redistribution. You may reproduce and distribute copies of the | |
90 | Work or Derivative Works thereof in any medium, with or without | |
91 | modifications, and in Source or Object form, provided that You | |
92 | meet the following conditions: | |
93 | ||
94 | (a) You must give any other recipients of the Work or | |
95 | Derivative Works a copy of this License; and | |
96 | ||
97 | (b) You must cause any modified files to carry prominent notices | |
98 | stating that You changed the files; and | |
99 | ||
100 | (c) You must retain, in the Source form of any Derivative Works | |
101 | that You distribute, all copyright, patent, trademark, and | |
102 | attribution notices from the Source form of the Work, | |
103 | excluding those notices that do not pertain to any part of | |
104 | the Derivative Works; and | |
105 | ||
106 | (d) If the Work includes a "NOTICE" text file as part of its | |
107 | distribution, then any Derivative Works that You distribute must | |
108 | include a readable copy of the attribution notices contained | |
109 | within such NOTICE file, excluding those notices that do not | |
110 | pertain to any part of the Derivative Works, in at least one | |
111 | of the following places: within a NOTICE text file distributed | |
112 | as part of the Derivative Works; within the Source form or | |
113 | documentation, if provided along with the Derivative Works; or, | |
114 | within a display generated by the Derivative Works, if and | |
115 | wherever such third-party notices normally appear. The contents | |
116 | of the NOTICE file are for informational purposes only and | |
117 | do not modify the License. You may add Your own attribution | |
118 | notices within Derivative Works that You distribute, alongside | |
119 | or as an addendum to the NOTICE text from the Work, provided | |
120 | that such additional attribution notices cannot be construed | |
121 | as modifying the License. | |
122 | ||
123 | You may add Your own copyright statement to Your modifications and | |
124 | may provide additional or different license terms and conditions | |
125 | for use, reproduction, or distribution of Your modifications, or | |
126 | for any such Derivative Works as a whole, provided Your use, | |
127 | reproduction, and distribution of the Work otherwise complies with | |
128 | the conditions stated in this License. | |
129 | ||
130 | 5. Submission of Contributions. Unless You explicitly state otherwise, | |
131 | any Contribution intentionally submitted for inclusion in the Work | |
132 | by You to the Licensor shall be under the terms and conditions of | |
133 | this License, without any additional terms or conditions. | |
134 | Notwithstanding the above, nothing herein shall supersede or modify | |
135 | the terms of any separate license agreement you may have executed | |
136 | with Licensor regarding such Contributions. | |
137 | ||
138 | 6. Trademarks. This License does not grant permission to use the trade | |
139 | names, trademarks, service marks, or product names of the Licensor, | |
140 | except as required for reasonable and customary use in describing the | |
141 | origin of the Work and reproducing the content of the NOTICE file. | |
142 | ||
143 | 7. Disclaimer of Warranty. Unless required by applicable law or | |
144 | agreed to in writing, Licensor provides the Work (and each | |
145 | Contributor provides its Contributions) on an "AS IS" BASIS, | |
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |
147 | implied, including, without limitation, any warranties or conditions | |
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |
149 | PARTICULAR PURPOSE. You are solely responsible for determining the | |
150 | appropriateness of using or redistributing the Work and assume any | |
151 | risks associated with Your exercise of permissions under this License. | |
152 | ||
153 | 8. Limitation of Liability. In no event and under no legal theory, | |
154 | whether in tort (including negligence), contract, or otherwise, | |
155 | unless required by applicable law (such as deliberate and grossly | |
156 | negligent acts) or agreed to in writing, shall any Contributor be | |
157 | liable to You for damages, including any direct, indirect, special, | |
158 | incidental, or consequential damages of any character arising as a | |
159 | result of this License or out of the use or inability to use the | |
160 | Work (including but not limited to damages for loss of goodwill, | |
161 | work stoppage, computer failure or malfunction, or any and all | |
162 | other commercial damages or losses), even if such Contributor | |
163 | has been advised of the possibility of such damages. | |
164 | ||
165 | 9. Accepting Warranty or Additional Liability. While redistributing | |
166 | the Work or Derivative Works thereof, You may choose to offer, | |
167 | and charge a fee for, acceptance of support, warranty, indemnity, | |
168 | or other liability obligations and/or rights consistent with this | |
169 | License. However, in accepting such obligations, You may act only | |
170 | on Your own behalf and on Your sole responsibility, not on behalf | |
171 | of any other Contributor, and only if You agree to indemnify, | |
172 | defend, and hold each Contributor harmless for any liability | |
173 | incurred by, or claims asserted against, such Contributor by reason | |
174 | of your accepting any such warranty or additional liability. | |
175 | ||
176 | END OF TERMS AND CONDITIONS | |
177 | ||
178 | APPENDIX: How to apply the Apache License to your work. | |
179 | ||
180 | To apply the Apache License to your work, attach the following | |
181 | boilerplate notice, with the fields enclosed by brackets "[]" | |
182 | replaced with your own identifying information. (Don't include | |
183 | the brackets!) The text should be enclosed in the appropriate | |
184 | comment syntax for the file format. We also recommend that a | |
185 | file or class name and description of purpose be included on the | |
186 | same "printed page" as the copyright notice for easier | |
187 | identification within third-party archives. | |
188 | ||
189 | Copyright [yyyy] [name of copyright owner] | |
190 | ||
191 | Licensed under the Apache License, Version 2.0 (the "License"); | |
192 | you may not use this file except in compliance with the License. | |
193 | You may obtain a copy of the License at | |
194 | ||
195 | http://www.apache.org/licenses/LICENSE-2.0 | |
196 | ||
197 | Unless required by applicable law or agreed to in writing, software | |
198 | distributed under the License is distributed on an "AS IS" BASIS, | |
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
200 | See the License for the specific language governing permissions and | |
201 | limitations under the License. |
0 | Permission is hereby granted, free of charge, to any person obtaining a copy of | |
1 | this software and associated documentation files (the "Software"), to deal in | |
2 | the Software without restriction, including without limitation the rights to | |
3 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | |
4 | of the Software, and to permit persons to whom the Software is furnished to do | |
5 | so, subject to the following conditions: | |
6 | ||
7 | The above copyright notice and this permission notice shall be included in all | |
8 | copies or substantial portions of the Software. | |
9 | ||
10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
11 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
12 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
13 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
14 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
15 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
16 | SOFTWARE. |
0 | ifndef BUILDDIR | |
1 | BUILDDIR=build | |
2 | endif | |
3 | ||
4 | ifndef CONFFILE | |
5 | ifeq ($(OS),Windows_NT) | |
6 | CONFFILE=%SystemDrive%\ProgramData\MaxMind\GeoIPUpdate\GeoIP.conf | |
7 | else | |
8 | CONFFILE=/usr/local/etc/GeoIP.conf | |
9 | endif | |
10 | endif | |
11 | ||
12 | ifndef DATADIR | |
13 | ifeq ($(OS),Windows_NT) | |
14 | DATADIR=%SystemDrive%\ProgramData\MaxMind\GeoIPUpdate\GeoIP | |
15 | else | |
16 | DATADIR=/usr/local/share/GeoIP | |
17 | endif | |
18 | endif | |
19 | ||
20 | ifeq ($(OS),Windows_NT) | |
21 | MAYBE_CR=\r | |
22 | endif | |
23 | ||
24 | ifndef VERSION | |
25 | VERSION=unknown | |
26 | endif | |
27 | ||
28 | all: \ | |
29 | $(BUILDDIR)/geoipupdate \ | |
30 | data | |
31 | ||
32 | data: \ | |
33 | $(BUILDDIR)/GeoIP.conf \ | |
34 | $(BUILDDIR)/GeoIP.conf.md \ | |
35 | $(BUILDDIR)/geoipupdate.md \ | |
36 | $(BUILDDIR)/GeoIP.conf.5 \ | |
37 | $(BUILDDIR)/geoipupdate.1 | |
38 | ||
39 | $(BUILDDIR): | |
40 | mkdir -p $(BUILDDIR) | |
41 | ||
42 | $(BUILDDIR)/geoipupdate: $(BUILDDIR) | |
43 | (cd cmd/geoipupdate && go build -ldflags '-X main.defaultConfigFile=$(CONFFILE) -X main.defaultDatabaseDirectory=$(DATADIR) -X "main.version=$(VERSION)"') | |
44 | cp cmd/geoipupdate/geoipupdate $(BUILDDIR) | |
45 | ||
46 | $(BUILDDIR)/GeoIP.conf: $(BUILDDIR) conf/GeoIP.conf.default | |
47 | sed -e 's|CONFFILE|$(CONFFILE)|g' -e 's|DATADIR|$(DATADIR)|g' -e 's|$$|$(MAYBE_CR)|g' conf/GeoIP.conf.default > $(BUILDDIR)/GeoIP.conf | |
48 | ||
49 | $(BUILDDIR)/GeoIP.conf.md: $(BUILDDIR) doc/GeoIP.conf.md | |
50 | sed -e 's|CONFFILE|$(CONFFILE)|g' -e 's|DATADIR|$(DATADIR)|g' -e 's|$$|$(MAYBE_CR)|g' doc/GeoIP.conf.md > $(BUILDDIR)/GeoIP.conf.md | |
51 | ||
52 | $(BUILDDIR)/geoipupdate.md: $(BUILDDIR) doc/geoipupdate.md | |
53 | sed -e 's|CONFFILE|$(CONFFILE)|g' -e 's|DATADIR|$(DATADIR)|g' -e 's|$$|$(MAYBE_CR)|g' doc/geoipupdate.md > $(BUILDDIR)/geoipupdate.md | |
54 | ||
55 | $(BUILDDIR)/GeoIP.conf.5: $(BUILDDIR)/GeoIP.conf.md $(BUILDDIR)/geoipupdate.md | |
56 | dev-bin/make-man-pages.pl "$(BUILDDIR)" | |
57 | ||
58 | $(BUILDDIR)/geoipupdate.1: $(BUILDDIR)/GeoIP.conf.5 | |
59 | ||
60 | clean: | |
61 | rm -rf $(BUILDDIR)/GeoIP.conf \ | |
62 | $(BUILDDIR)/GeoIP.conf.md \ | |
63 | $(BUILDDIR)/geoipupdate \ | |
64 | $(BUILDDIR)/geoipupdate.md \ | |
65 | $(BUILDDIR)/GeoIP.conf.5 \ | |
66 | $(BUILDDIR)/geoipupdate.1 |
0 | # Releasing | |
1 | ||
2 | * Make sure you have [`hub`](https://github.com/github/hub), | |
3 | [`goreleaser`](https://goreleaser.com/), and rpmbuild installed. | |
4 | (rpmbuild is in the Ubuntu package `rpm`). | |
5 | * Update `CHANGELOG.md`. Set the appropriate release date. | |
6 | * Run `GITHUB_TOKEN=<your token> ./dev-bin/release.sh`. For `goreleaser` you | |
7 | will need a token with the `repo` scope. You may create a token | |
8 | [here](https://github.com/settings/tokens/new). | |
9 | * If we're not using Go modules yet, the release might fail depending on | |
10 | your `GO111MODULE` setting. Consider setting it to `off` if necessary. | |
11 | ||
12 | Then release to our PPA: | |
13 | ||
14 | * Switch to the ubuntu-ppa branch. Merge the released tag into it. | |
15 | * Set up to release to launchpad. You can see some information about | |
16 | prerequisites for this | |
17 | [here](https://github.com/maxmind/libmaxminddb/blob/master/README.dev.md). | |
18 | * Delete `dist` directory. | |
19 | * Run `dev-bin/ppa-release.sh` | |
20 | ||
21 | Finally release to Homebrew: | |
22 | ||
23 | * Go to https://github.com/Homebrew/homebrew-core/blob/master/Formula/geoipupdate.rb | |
24 | * Edit the file to update the url and sha256. You can get the sha256 for the | |
25 | tarball with the `sha256sum` command line utility. | |
26 | * Make a commit with the summary `geoipupdate <VERSION>` | |
27 | * Submit a PR with the changes you just made. |
0 | # GeoIP Update | |
1 | ||
2 | [![Build Status](https://travis-ci.com/maxmind/geoipupdate.svg?branch=master)](https://travis-ci.com/maxmind/geoipupdate) | |
3 | ||
4 | The GeoIP Update program performs automatic updates of GeoIP2 and GeoIP Legacy | |
5 | binary databases. CSV databases are _not_ supported. | |
6 | ||
7 | This is the new version of GeoIP Update. If for some reason you need the | |
8 | legacy C version, you can find it | |
9 | [here](https://github.com/maxmind/geoipupdate-legacy). | |
10 | ||
11 | ## Installation | |
12 | ||
13 | We provide releases for Linux, macOS (darwin), and Windows. Please see the | |
14 | [Releases](https://github.com/maxmind/geoipupdate/releases) tab for the | |
15 | latest release. | |
16 | ||
17 | After you install geoipupdate, please refer to our | |
18 | [documentation](https://dev.maxmind.com/geoip/geoipupdate/) for information | |
19 | about configuration. | |
20 | ||
21 | If you're upgrading from geoipupdate 3.x, please see our [upgrade | |
22 | guide](https://dev.maxmind.com/geoip/geoipupdate/upgrading-to-geoip-update-4-x/). | |
23 | ||
24 | ### Installing on Linux via the tarball | |
25 | ||
26 | Download and extract the appropriate tarball for your system. You will end | |
27 | up with a directory named something like `geoipupdate_4.0.0_linux_amd64` | |
28 | depending on the version and architecture. | |
29 | ||
30 | Copy `geoipupdate` to where you want it to live. To install it to | |
31 | `/usr/local/bin/geoipupdate`, run the equivalent of `sudo cp | |
32 | geoipupdate_4.0.0_linux_amd64/geoipupdate /usr/local/bin`. | |
33 | ||
34 | `geoipupdate` looks for the config file `/usr/local/etc/GeoIP.conf` by | |
35 | default. | |
36 | ||
37 | ### Installing on Ubuntu via PPA | |
38 | ||
39 | MaxMind provides a PPA for recent versions of Ubuntu. To add the PPA to | |
40 | your sources, run: | |
41 | ||
42 | ``` | |
43 | $ sudo add-apt-repository ppa:maxmind/ppa | |
44 | ``` | |
45 | ||
46 | Then install `geoipupdate` by running: | |
47 | ||
48 | ``` | |
49 | $ sudo apt update | |
50 | $ sudo apt install geoipupdate | |
51 | ``` | |
52 | ||
53 | ### Installing on Ubuntu or Debian via the deb | |
54 | ||
55 | You can also use the tarball. | |
56 | ||
57 | Download the appropriate .deb for your system. | |
58 | ||
59 | Run `dpkg -i path/to/geoipupdate_4.0.0_linux_amd64.deb` (replacing the | |
60 | version number and architecture as necessary). You will need to be root. | |
61 | For Ubuntu you can prefix the command with `sudo`. This will install | |
62 | `geoipupdate` to `/usr/bin/geoipupdate`. | |
63 | ||
64 | `geoipupdate` looks for the config file `/etc/GeoIP.conf` by default. | |
65 | ||
66 | ### Installing on RedHat or CentOS via the rpm | |
67 | ||
68 | You can also use the tarball. | |
69 | ||
70 | Download the appropriate .rpm for your system. | |
71 | ||
72 | Run `rpm -i path/to/geoipupdate_4.0.0_linux_amd64.rpm` (replacing the | |
73 | version number and architecture as necessary). You will need to be root. | |
74 | This will install `geoipupdate` to `/usr/bin/geoipupdate`. | |
75 | ||
76 | `geoipupdate` looks for the config file `/etc/GeoIP.conf` by default. | |
77 | ||
78 | ### Installing on macOS (darwin) via the tarball | |
79 | ||
80 | This is the same as installing on Linux via the tarball, except choose a | |
81 | tarball with "darwin" in the name. | |
82 | ||
83 | ### Installing on macOS via Homebrew | |
84 | ||
85 | If you are on macOS and you have [Homebrew](http://brew.sh/) you can install | |
86 | `geoipupdate` via `brew` | |
87 | ||
88 | ``` | |
89 | $ brew install geoipupdate | |
90 | ``` | |
91 | ||
92 | ### Installing on Windows | |
93 | ||
94 | Download and extract the appropriate zip for your system. You will end up | |
95 | with a directory named something like `geoipupdate_4.0.0_windows_amd64` | |
96 | depending on the version and architecture. | |
97 | ||
98 | Copy `geoipupdate.exe` to where you want it to live. | |
99 | ||
100 | `geoipupdate` looks for the config file | |
101 | `\ProgramData\MaxMind/GeoIPUpdate\GeoIP.conf` on your system drive by | |
102 | default. | |
103 | ||
104 | ### Installation from source or Git | |
105 | ||
106 | You need the Go compiler (1.8+). You can get it at the [Go | |
107 | website](https://golang.org). | |
108 | ||
109 | The easiest way is via `go get`: | |
110 | ||
111 | $ go get -u github.com/maxmind/geoipupdate/cmd/geoipupdate | |
112 | ||
113 | This installs `geoipupdate` to `$GOPATH/bin/geoipupdate`. | |
114 | ||
115 | # Configuring | |
116 | ||
117 | Please see our [online guide](https://dev.maxmind.com/geoip/geoipupdate/) for | |
118 | directions on how to configure GeoIP Update. | |
119 | ||
120 | # Documentation | |
121 | ||
122 | See our documentation for the [`geoipupdate` program](doc/geoipupdate.md) | |
123 | and the [`GeoIP.conf` configuration file](doc/GeoIP.conf.md). | |
124 | ||
125 | # Default config file and database directory paths | |
126 | ||
127 | We define default paths for the config file and database directory. If | |
128 | these defaults are not appropriate for you, you can change them at build | |
129 | time using flags: | |
130 | ||
131 | go build -ldflags "-X main.defaultConfigFile=/etc/GeoIP.conf \ | |
132 | -X main.defaultDatabaseDirectory=/usr/share/GeoIP" | |
133 | ||
134 | # Bug Reports | |
135 | ||
136 | Please report bugs by filing an issue with our GitHub issue tracker at | |
137 | https://github.com/maxmind/geoipupdate/issues | |
138 | ||
139 | # Copyright and License | |
140 | ||
141 | This software is Copyright (c) 2018 - 2019 by MaxMind, Inc. | |
142 | ||
143 | This is free software, licensed under the [Apache License, Version | |
144 | 2.0](LICENSE-APACHE) or the [MIT License](LICENSE-MIT), at your option. |
0 | <!DOCTYPE html> | |
1 | ||
2 | <html lang="en"> | |
3 | <head> | |
4 | <meta charset="UTF-8"> | |
5 | <meta name="description" content=""> | |
6 | <meta name="author" content="MaxMind, Inc."> | |
7 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
8 | <link href="//mm-staticassets.storage.googleapis.com/gh-pages-theme/maxmind.css" rel="stylesheet"> | |
9 | <link href="//dev.maxmind.com/css/dev.maxmind.com.css" rel="stylesheet"> | |
10 | <link href="//dev.maxmind.com/css/highlight-github.css" rel="stylesheet" > | |
11 | <link rel="shortcut icon" href="//dev.maxmind.com/static/favicon.ico"> | |
12 | <script src="//dev.maxmind.com/js/highlight.pack.js"></script> | |
13 | <script>hljs.initHighlightingOnLoad();</script> | |
14 | <script type="text/javascript"> | |
15 | ||
16 | var _gaq = _gaq || []; | |
17 | _gaq.push(['_setAccount', 'UA-171943-3']); | |
18 | _gaq.push(['_trackPageview']); | |
19 | ||
20 | (function() { | |
21 | var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; | |
22 | ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; | |
23 | var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); | |
24 | })(); | |
25 | ||
26 | </script> | |
27 | <title>{{ page.title }}</title> | |
28 | </head> | |
29 | <body> | |
30 | ||
31 | <div class="container"> | |
32 | <div class="row-fluid"> | |
33 | <div class="span7"> | |
34 | ||
35 | <ul class="nav nav-tabs"> | |
36 | {% for node in site.pages %} | |
37 | {% if page.url == node.url %} | |
38 | <li class="active"><a href="/geoipupdate2{{node.url}}" class="active">{{node.title}}</a></li> | |
39 | {% else %} | |
40 | <li><a href="/geoipupdate2{{node.url}}">{{node.title}}</a></li> | |
41 | {% endif %} | |
42 | {% endfor %} | |
43 | </ul> | |
44 | ||
45 | </div> | |
46 | </div> | |
47 | ||
48 | ||
49 | {{ content }} | |
50 | </div> | |
51 | ||
52 | </body> | |
53 | </html> |
0 | package main | |
1 | ||
2 | import ( | |
3 | "log" | |
4 | "os" | |
5 | ||
6 | flag "github.com/spf13/pflag" | |
7 | ) | |
8 | ||
9 | // Args are command line arguments. | |
10 | type Args struct { | |
11 | ConfigFile string | |
12 | DatabaseDirectory string | |
13 | StackTrace bool | |
14 | Verbose bool | |
15 | } | |
16 | ||
17 | func getArgs() *Args { | |
18 | configFile := flag.StringP( | |
19 | "config-file", | |
20 | "f", | |
21 | defaultConfigFile, | |
22 | "Configuration file", | |
23 | ) | |
24 | databaseDirectory := flag.StringP( | |
25 | "database-directory", | |
26 | "d", | |
27 | "", | |
28 | "Store databases in this directory (uses config if not specified)", | |
29 | ) | |
30 | help := flag.BoolP("help", "h", false, "Display help and exit") | |
31 | stackTrace := flag.Bool("stack-trace", false, "Show a stack trace along with any error message.") | |
32 | verbose := flag.BoolP("verbose", "v", false, "Use verbose output") | |
33 | displayVersion := flag.BoolP("version", "V", false, "Display the version and exit") | |
34 | ||
35 | flag.Parse() | |
36 | ||
37 | if *help { | |
38 | printUsage() | |
39 | } | |
40 | if *displayVersion { | |
41 | log.Printf("geoipupdate %s", version) | |
42 | os.Exit(0) | |
43 | } | |
44 | ||
45 | if *configFile == "" { | |
46 | log.Printf("You must provide a configuration file.") | |
47 | printUsage() | |
48 | } | |
49 | ||
50 | return &Args{ | |
51 | ConfigFile: *configFile, | |
52 | DatabaseDirectory: *databaseDirectory, | |
53 | StackTrace: *stackTrace, | |
54 | Verbose: *verbose, | |
55 | } | |
56 | } | |
57 | ||
58 | func printUsage() { | |
59 | log.Printf("Usage: %s <arguments>\n", os.Args[0]) | |
60 | flag.PrintDefaults() | |
61 | os.Exit(1) | |
62 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "bufio" | |
4 | "log" | |
5 | "net/url" | |
6 | "os" | |
7 | "path/filepath" | |
8 | "regexp" | |
9 | "strconv" | |
10 | "strings" | |
11 | ||
12 | "github.com/pkg/errors" | |
13 | ) | |
14 | ||
15 | // Config is a parsed configuration file. | |
16 | type Config struct { | |
17 | AccountID int | |
18 | DatabaseDirectory string | |
19 | EditionIDs []string | |
20 | LicenseKey string | |
21 | LockFile string | |
22 | PreserveFileTimes bool | |
23 | Proxy *url.URL | |
24 | URL string | |
25 | } | |
26 | ||
27 | // NewConfig parses the configuration file. | |
28 | func NewConfig( // nolint: gocyclo | |
29 | file, | |
30 | databaseDirectory string, | |
31 | ) (*Config, error) { | |
32 | fh, err := os.Open(file) | |
33 | if err != nil { | |
34 | return nil, errors.Wrap(err, "error opening file") | |
35 | } | |
36 | defer func() { | |
37 | if err := fh.Close(); err != nil { | |
38 | log.Fatalf("Error closing config file: %+v", errors.Wrap(err, "closing file")) | |
39 | } | |
40 | }() | |
41 | ||
42 | config := &Config{} | |
43 | scanner := bufio.NewScanner(fh) | |
44 | lineNumber := 0 | |
45 | keysSeen := map[string]struct{}{} | |
46 | var host, proxy, proxyUserPassword string | |
47 | for scanner.Scan() { | |
48 | lineNumber++ | |
49 | line := strings.TrimSpace(scanner.Text()) | |
50 | if line == "" || line[0] == '#' { | |
51 | continue | |
52 | } | |
53 | ||
54 | fields := strings.Fields(line) | |
55 | if len(fields) < 2 { | |
56 | return nil, errors.Errorf("invalid format on line %d", lineNumber) | |
57 | } | |
58 | key := fields[0] | |
59 | value := strings.Join(fields[1:], " ") | |
60 | ||
61 | if _, ok := keysSeen[key]; ok { | |
62 | return nil, errors.Errorf("`%s' is in the config multiple times", key) | |
63 | } | |
64 | keysSeen[key] = struct{}{} | |
65 | ||
66 | switch key { | |
67 | case "AccountID", "UserId": | |
68 | accountID, err := strconv.Atoi(value) | |
69 | if err != nil { | |
70 | return nil, errors.Wrap(err, "invalid account ID format") | |
71 | } | |
72 | config.AccountID = accountID | |
73 | keysSeen["AccountID"] = struct{}{} | |
74 | keysSeen["UserId"] = struct{}{} | |
75 | case "DatabaseDirectory": | |
76 | config.DatabaseDirectory = filepath.Clean(value) | |
77 | case "EditionIDs", "ProductIds": | |
78 | config.EditionIDs = strings.Fields(value) | |
79 | keysSeen["EditionIDs"] = struct{}{} | |
80 | keysSeen["ProductIds"] = struct{}{} | |
81 | case "Host": | |
82 | host = value | |
83 | case "LicenseKey": | |
84 | config.LicenseKey = value | |
85 | case "LockFile": | |
86 | config.LockFile = filepath.Clean(value) | |
87 | case "PreserveFileTimes": | |
88 | if value != "0" && value != "1" { | |
89 | return nil, errors.New("`PreserveFileTimes' must be 0 or 1") | |
90 | } | |
91 | if value == "1" { | |
92 | config.PreserveFileTimes = true | |
93 | } | |
94 | case "Proxy": | |
95 | proxy = value | |
96 | case "ProxyUserPassword": | |
97 | proxyUserPassword = value | |
98 | case "Protocol", "SkipHostnameVerification", "SkipPeerVerification": | |
99 | // Deprecated. | |
100 | default: | |
101 | return nil, errors.Errorf("unknown option on line %d", lineNumber) | |
102 | } | |
103 | } | |
104 | ||
105 | if err := scanner.Err(); err != nil { | |
106 | return nil, errors.Wrap(err, "error reading file") | |
107 | } | |
108 | ||
109 | if _, ok := keysSeen["EditionIDs"]; !ok { | |
110 | return nil, errors.Errorf("the `EditionIDs` option is required") | |
111 | } | |
112 | ||
113 | { | |
114 | _, LicenseKeySeen := keysSeen["LicenseKey"] | |
115 | _, AccountIDSeen := keysSeen["AccountID"] | |
116 | ||
117 | if LicenseKeySeen && !AccountIDSeen { | |
118 | return nil, errors.Errorf("the `AccountID` option is required if the `LicenseKey` option is set") | |
119 | } | |
120 | ||
121 | if AccountIDSeen && !LicenseKeySeen { | |
122 | return nil, errors.Errorf("the `LicenseKey` option is required if the `AccountID` option is set") | |
123 | } | |
124 | ||
125 | if AccountIDSeen && config.AccountID == 0 && LicenseKeySeen && config.LicenseKey != "000000000000" { | |
126 | return nil, errors.New("setting an `AccountID` option of 0 with a `LicenseKey` option other than 000000000000 is disallowed") | |
127 | } | |
128 | } | |
129 | ||
130 | // Set defaults & post-process. | |
131 | ||
132 | // Argument takes precedence. | |
133 | if databaseDirectory != "" { | |
134 | config.DatabaseDirectory = filepath.Clean(databaseDirectory) | |
135 | } | |
136 | ||
137 | if config.DatabaseDirectory == "" { | |
138 | config.DatabaseDirectory = filepath.Clean(defaultDatabaseDirectory) | |
139 | } | |
140 | ||
141 | if host == "" { | |
142 | host = "updates.maxmind.com" | |
143 | } | |
144 | ||
145 | if config.LockFile == "" { | |
146 | config.LockFile = filepath.Join(config.DatabaseDirectory, ".geoipupdate.lock") | |
147 | } | |
148 | ||
149 | config.URL = "https://" + host | |
150 | ||
151 | proxyURL, err := parseProxy(proxy, proxyUserPassword) | |
152 | if err != nil { | |
153 | return nil, err | |
154 | } | |
155 | config.Proxy = proxyURL | |
156 | ||
157 | // We used to recommend using 999999 / 000000000000 for free downloads and | |
158 | // many people still use this combination. We need to check for the | |
159 | // 000000000000 license key to ensure that a real AccountID of 999999 will | |
160 | // work in the future. | |
161 | if (config.AccountID == 0 || config.AccountID == 999999) && config.LicenseKey == "000000000000" { | |
162 | config.AccountID = 0 | |
163 | config.LicenseKey = "" | |
164 | } | |
165 | ||
166 | return config, nil | |
167 | } | |
168 | ||
169 | var schemeRE = regexp.MustCompile(`(?i)\A([a-z][a-z0-9+\-.]*)://`) | |
170 | ||
171 | func parseProxy( | |
172 | proxy, | |
173 | proxyUserPassword string, | |
174 | ) (*url.URL, error) { | |
175 | if proxy == "" { | |
176 | return nil, nil | |
177 | } | |
178 | ||
179 | // If no scheme is provided, use http. | |
180 | matches := schemeRE.FindStringSubmatch(proxy) | |
181 | if matches == nil { | |
182 | proxy = "http://" + proxy | |
183 | } else { | |
184 | scheme := strings.ToLower(matches[1]) | |
185 | // The http package only supports http and socks5. | |
186 | if scheme != "http" && scheme != "socks5" { | |
187 | return nil, errors.Errorf("unsupported proxy type: %s", scheme) | |
188 | } | |
189 | } | |
190 | ||
191 | // Now that we have a scheme, we should be able to parse. | |
192 | u, err := url.Parse(proxy) | |
193 | if err != nil { | |
194 | return nil, errors.Wrap(err, "error parsing proxy URL") | |
195 | } | |
196 | ||
197 | if !strings.Contains(u.Host, ":") { | |
198 | u.Host += ":1080" // The 1080 default historically came from cURL. | |
199 | } | |
200 | ||
201 | // Historically if the Proxy option had a username and password they would | |
202 | // override any specified in the ProxyUserPassword option. Continue that. | |
203 | if u.User != nil { | |
204 | return u, nil | |
205 | } | |
206 | ||
207 | if proxyUserPassword == "" { | |
208 | return u, nil | |
209 | } | |
210 | ||
211 | userPassword := strings.SplitN(proxyUserPassword, ":", 2) | |
212 | if len(userPassword) != 2 { | |
213 | return nil, errors.New("proxy user/password is malformed") | |
214 | } | |
215 | u.User = url.UserPassword(userPassword[0], userPassword[1]) | |
216 | ||
217 | return u, nil | |
218 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "io/ioutil" | |
4 | "net/url" | |
5 | "os" | |
6 | "path/filepath" | |
7 | "testing" | |
8 | ||
9 | "github.com/stretchr/testify/assert" | |
10 | "github.com/stretchr/testify/require" | |
11 | ) | |
12 | ||
13 | func TestNewConfig(t *testing.T) { | |
14 | tests := []struct { | |
15 | Description string | |
16 | Input string | |
17 | Output *Config | |
18 | Err string | |
19 | }{ | |
20 | { | |
21 | Description: "Default config", | |
22 | Input: `# Please see https://dev.maxmind.com/geoip/geoipupdate/ for instructions | |
23 | # on setting up geoipupdate, including information on how to download a | |
24 | # pre-filled GeoIP.conf file. | |
25 | ||
26 | # Enter your account ID and license key below. These are available from | |
27 | # https://www.maxmind.com/en/my_license_key. If you are only using free | |
28 | # GeoLite databases, you may leave the 0 values. | |
29 | AccountID 0 | |
30 | LicenseKey 000000000000 | |
31 | ||
32 | # Enter the edition IDs of the databases you would like to update. | |
33 | # Multiple edition IDs are separated by spaces. | |
34 | EditionIDs GeoLite2-Country GeoLite2-City | |
35 | ||
36 | # The remaining settings are OPTIONAL. | |
37 | ||
38 | # The directory to store the database files. Defaults to DATADIR | |
39 | # DatabaseDirectory DATADIR | |
40 | ||
41 | # The server to use. Defaults to "updates.maxmind.com". | |
42 | # Host updates.maxmind.com | |
43 | ||
44 | # The proxy host name or IP address. You may optionally specify a | |
45 | # port number, e.g., 127.0.0.1:8888. If no port number is specified, 1080 | |
46 | # will be used. | |
47 | # Proxy 127.0.0.1:8888 | |
48 | ||
49 | # The user name and password to use with your proxy server. | |
50 | # ProxyUserPassword username:password | |
51 | ||
52 | # Whether to preserve modification times of files downloaded from the server. | |
53 | # Defaults to "0". | |
54 | # PreserveFileTimes 0 | |
55 | ||
56 | # The lock file to use. This ensures only one geoipupdate process can run at a | |
57 | # time. | |
58 | # Note: Once created, this lockfile is not removed from the filesystem. | |
59 | # Defaults to ".geoipupdate.lock" under the DatabaseDirectory. | |
60 | # LockFile DATADIR/.geoipupdate.lock | |
61 | `, | |
62 | Output: &Config{ | |
63 | DatabaseDirectory: filepath.Clean("/tmp"), | |
64 | EditionIDs: []string{"GeoLite2-Country", "GeoLite2-City"}, | |
65 | LockFile: filepath.Clean("/tmp/.geoipupdate.lock"), | |
66 | URL: "https://updates.maxmind.com", | |
67 | }, | |
68 | }, | |
69 | { | |
70 | Description: "Default config, old names", | |
71 | Input: `# Please see https://dev.maxmind.com/geoip/geoipupdate/ for instructions | |
72 | # on setting up geoipupdate, including information on how to download a | |
73 | # pre-filled GeoIP.conf file. | |
74 | ||
75 | # Enter your account ID and license key below. These are available from | |
76 | # https://www.maxmind.com/en/my_license_key. If you are only using free | |
77 | # GeoLite databases, you may leave the 0 values. | |
78 | UserId 0 | |
79 | LicenseKey 000000000000 | |
80 | ||
81 | # Enter the edition IDs of the databases you would like to update. | |
82 | # Multiple edition IDs are separated by spaces. | |
83 | ProductIds GeoLite2-Country GeoLite2-City | |
84 | ||
85 | # The remaining settings are OPTIONAL. | |
86 | ||
87 | # The directory to store the database files. Defaults to DATADIR | |
88 | # DatabaseDirectory DATADIR | |
89 | ||
90 | # The server to use. Defaults to "updates.maxmind.com". | |
91 | # Host updates.maxmind.com | |
92 | ||
93 | # The proxy host name or IP address. You may optionally specify a | |
94 | # port number, e.g., 127.0.0.1:8888. If no port number is specified, 1080 | |
95 | # will be used. | |
96 | # Proxy 127.0.0.1:8888 | |
97 | ||
98 | # The user name and password to use with your proxy server. | |
99 | # ProxyUserPassword username:password | |
100 | ||
101 | # Whether to preserve modification times of files downloaded from the server. | |
102 | # Defaults to "0". | |
103 | # PreserveFileTimes 0 | |
104 | ||
105 | # The lock file to use. This ensures only one geoipupdate process can run at a | |
106 | # time. | |
107 | # Note: Once created, this lockfile is not removed from the filesystem. | |
108 | # Defaults to ".geoipupdate.lock" under the DatabaseDirectory. | |
109 | # LockFile DATADIR/.geoipupdate.lock | |
110 | `, | |
111 | Output: &Config{ | |
112 | DatabaseDirectory: filepath.Clean("/tmp"), | |
113 | EditionIDs: []string{"GeoLite2-Country", "GeoLite2-City"}, | |
114 | LockFile: filepath.Clean("/tmp/.geoipupdate.lock"), | |
115 | URL: "https://updates.maxmind.com", | |
116 | }, | |
117 | }, | |
118 | { | |
119 | Description: "Everything populated", | |
120 | Input: `# Please see https://dev.maxmind.com/geoip/geoipupdate/ for instructions | |
121 | # on setting up geoipupdate, including information on how to download a | |
122 | # pre-filled GeoIP.conf file. | |
123 | ||
124 | # Enter your account ID and license key below. These are available from | |
125 | # https://www.maxmind.com/en/my_license_key. If you are only using free | |
126 | # GeoLite databases, you may leave the 0 values. | |
127 | AccountID 1234 | |
128 | LicenseKey abcdefghi | |
129 | ||
130 | # Enter the edition IDs of the databases you would like to update. | |
131 | # Multiple edition IDs are separated by spaces. | |
132 | EditionIDs GeoLite2-Country GeoLite2-City GeoIP2-City | |
133 | ||
134 | # The remaining settings are OPTIONAL. | |
135 | ||
136 | # The directory to store the database files. Defaults to DATADIR | |
137 | DatabaseDirectory /home | |
138 | ||
139 | # The server to use. Defaults to "updates.maxmind.com". | |
140 | Host updates.example.com | |
141 | ||
142 | # The proxy host name or IP address. You may optionally specify a | |
143 | # port number, e.g., 127.0.0.1:8888. If no port number is specified, 1080 | |
144 | # will be used. | |
145 | Proxy 127.0.0.1:8888 | |
146 | ||
147 | # The user name and password to use with your proxy server. | |
148 | ProxyUserPassword username:password | |
149 | ||
150 | # Whether to preserve modification times of files downloaded from the server. | |
151 | # Defaults to "0". | |
152 | PreserveFileTimes 1 | |
153 | ||
154 | # The lock file to use. This ensures only one geoipupdate process can run at a | |
155 | # time. | |
156 | # Note: Once created, this lockfile is not removed from the filesystem. | |
157 | # Defaults to ".geoipupdate.lock" under the DatabaseDirectory. | |
158 | LockFile /usr/lock | |
159 | `, | |
160 | Output: &Config{ | |
161 | AccountID: 1234, | |
162 | DatabaseDirectory: filepath.Clean("/tmp"), // Argument takes precedence | |
163 | EditionIDs: []string{"GeoLite2-Country", "GeoLite2-City", "GeoIP2-City"}, | |
164 | LicenseKey: "abcdefghi", | |
165 | LockFile: filepath.Clean("/usr/lock"), | |
166 | Proxy: &url.URL{ | |
167 | Scheme: "http", | |
168 | User: url.UserPassword("username", "password"), | |
169 | Host: "127.0.0.1:8888", | |
170 | }, | |
171 | PreserveFileTimes: true, | |
172 | URL: "https://updates.example.com", | |
173 | }, | |
174 | }, | |
175 | { | |
176 | Description: "Invalid line", | |
177 | Input: `AccountID 123 | |
178 | LicenseKey | |
179 | # Host updates.maxmind.com | |
180 | `, | |
181 | Err: "invalid format on line 2", | |
182 | }, | |
183 | { | |
184 | Description: "Option is there multiple times", | |
185 | Input: `AccountID 123 | |
186 | AccountID 456 | |
187 | `, | |
188 | Err: "`AccountID' is in the config multiple times", | |
189 | }, | |
190 | { | |
191 | Description: "Option is there multiple times with different names", | |
192 | Input: `AccountID 123 | |
193 | UserId 456 | |
194 | `, | |
195 | Err: "`UserId' is in the config multiple times", | |
196 | }, | |
197 | { | |
198 | Description: "Invalid account ID", | |
199 | Input: `AccountID 1a | |
200 | `, | |
201 | Err: `invalid account ID format: strconv.Atoi: parsing "1a": invalid syntax`, | |
202 | }, | |
203 | { | |
204 | Description: "Invalid PreserveFileTimes", | |
205 | Input: `PreserveFileTimes true | |
206 | `, | |
207 | Err: "`PreserveFileTimes' must be 0 or 1", | |
208 | }, | |
209 | { | |
210 | Description: "Unknown option", | |
211 | Input: `AccountID 123 | |
212 | EditionID GeoIP2-City | |
213 | `, | |
214 | Err: "unknown option on line 2", | |
215 | }, | |
216 | { | |
217 | Description: "Missing required key in options", | |
218 | Input: ``, | |
219 | Err: "the `EditionIDs` option is required", | |
220 | }, | |
221 | { | |
222 | Description: "LicenseKey is found but AccountID is not", | |
223 | Input: `LicenseKey abcd | |
224 | EditionIDs GeoIP2-City | |
225 | `, | |
226 | Err: "the `AccountID` option is required if the `LicenseKey` option is set", | |
227 | }, | |
228 | { | |
229 | Description: "AccountID is found but LicenseKey is not", | |
230 | Input: `AccountID 123 | |
231 | EditionIDs GeoIP2-City`, | |
232 | Err: "the `LicenseKey` option is required if the `AccountID` option is set", | |
233 | }, | |
234 | { | |
235 | Description: "AccountID 0 with the LicenseKey 000000000000 is treated as no AccountID/LicenseKey", | |
236 | Input: `AccountID 0 | |
237 | LicenseKey 000000000000 | |
238 | EditionIDs GeoIP2-City`, | |
239 | Output: &Config{ | |
240 | DatabaseDirectory: filepath.Clean("/tmp"), | |
241 | EditionIDs: []string{"GeoIP2-City"}, | |
242 | LockFile: filepath.Clean("/tmp/.geoipupdate.lock"), | |
243 | URL: "https://updates.maxmind.com", | |
244 | }, | |
245 | }, | |
246 | { | |
247 | Description: "AccountID 999999 with the LicenseKey 000000000000 is treated as no AccountID/LicenseKey", | |
248 | Input: `AccountID 999999 | |
249 | LicenseKey 000000000000 | |
250 | EditionIDs GeoIP2-City`, | |
251 | Output: &Config{ | |
252 | DatabaseDirectory: filepath.Clean("/tmp"), | |
253 | EditionIDs: []string{"GeoIP2-City"}, | |
254 | LockFile: filepath.Clean("/tmp/.geoipupdate.lock"), | |
255 | URL: "https://updates.maxmind.com", | |
256 | }, | |
257 | }, | |
258 | { | |
259 | Description: "AccountID 999999 with a non-000000000000 LicenseKey is treated normally", | |
260 | Input: `AccountID 999999 | |
261 | LicenseKey abcd | |
262 | EditionIDs GeoIP2-City`, | |
263 | Output: &Config{ | |
264 | AccountID: 999999, | |
265 | DatabaseDirectory: filepath.Clean("/tmp"), | |
266 | EditionIDs: []string{"GeoIP2-City"}, | |
267 | LicenseKey: "abcd", | |
268 | LockFile: filepath.Clean("/tmp/.geoipupdate.lock"), | |
269 | URL: "https://updates.maxmind.com", | |
270 | }, | |
271 | }, | |
272 | { | |
273 | Description: "Deprecated options", | |
274 | Input: `AccountID 123 | |
275 | LicenseKey abcd | |
276 | EditionIDs GeoIP2-City | |
277 | Protocol http | |
278 | SkipHostnameVerification 1 | |
279 | SkipPeerVerification 1 | |
280 | `, | |
281 | Output: &Config{ | |
282 | AccountID: 123, | |
283 | DatabaseDirectory: filepath.Clean("/tmp"), | |
284 | EditionIDs: []string{"GeoIP2-City"}, | |
285 | LicenseKey: "abcd", | |
286 | LockFile: filepath.Clean("/tmp/.geoipupdate.lock"), | |
287 | URL: "https://updates.maxmind.com", | |
288 | }, | |
289 | }, | |
290 | { | |
291 | Description: "CRLF line ending works", | |
292 | Input: "AccountID 123\r\nLicenseKey 123\r\nEditionIDs GeoIP2-City\r\n", | |
293 | Output: &Config{ | |
294 | AccountID: 123, | |
295 | DatabaseDirectory: filepath.Clean("/tmp"), | |
296 | EditionIDs: []string{"GeoIP2-City"}, | |
297 | LicenseKey: "123", | |
298 | LockFile: filepath.Clean("/tmp/.geoipupdate.lock"), | |
299 | URL: "https://updates.maxmind.com", | |
300 | }, | |
301 | }, | |
302 | { | |
303 | Description: "CR line ending does not work", | |
304 | Input: "AccountID 0\rLicenseKey 123\rEditionIDs GeoIP2-City\r", | |
305 | Err: `invalid account ID format: strconv.Atoi: parsing "0 LicenseKey 123 EditionIDs GeoIP2-City": invalid syntax`, | |
306 | }, | |
307 | { | |
308 | Description: "Multiple spaces between option and value works", | |
309 | Input: `AccountID 123 | |
310 | LicenseKey 456 | |
311 | EditionIDs GeoLite2-City GeoLite2-Country | |
312 | `, | |
313 | Output: &Config{ | |
314 | AccountID: 123, | |
315 | DatabaseDirectory: filepath.Clean("/tmp"), | |
316 | EditionIDs: []string{"GeoLite2-City", "GeoLite2-Country"}, | |
317 | LicenseKey: "456", | |
318 | LockFile: filepath.Clean("/tmp/.geoipupdate.lock"), | |
319 | URL: "https://updates.maxmind.com", | |
320 | }, | |
321 | }, | |
322 | { | |
323 | Description: "Tabs between options and values works", | |
324 | Input: "AccountID\t123\nLicenseKey\t\t456\nEditionIDs\t\t\tGeoLite2-City\t\t\t\tGeoLite2-Country\t\t\t\t\n", | |
325 | Output: &Config{ | |
326 | AccountID: 123, | |
327 | DatabaseDirectory: filepath.Clean("/tmp"), | |
328 | EditionIDs: []string{"GeoLite2-City", "GeoLite2-Country"}, | |
329 | LicenseKey: "456", | |
330 | LockFile: filepath.Clean("/tmp/.geoipupdate.lock"), | |
331 | URL: "https://updates.maxmind.com", | |
332 | }, | |
333 | }, | |
334 | } | |
335 | ||
336 | tempFh, err := ioutil.TempFile("", "conf-test") | |
337 | require.NoError(t, err) | |
338 | tempName := tempFh.Name() | |
339 | require.NoError(t, tempFh.Close()) | |
340 | defer func() { | |
341 | _ = os.Remove(tempName) | |
342 | }() | |
343 | ||
344 | for _, test := range tests { | |
345 | require.NoError(t, ioutil.WriteFile(tempName, []byte(test.Input), 0600)) | |
346 | config, err := NewConfig(tempName, "/tmp") | |
347 | if test.Err == "" { | |
348 | assert.NoError(t, err, test.Description) | |
349 | } else { | |
350 | assert.EqualError(t, err, test.Err, test.Description) | |
351 | } | |
352 | assert.Equal(t, test.Output, config, test.Description) | |
353 | } | |
354 | } | |
355 | ||
356 | func TestParseProxy(t *testing.T) { | |
357 | tests := []struct { | |
358 | Proxy string | |
359 | UserPassword string | |
360 | Output string | |
361 | Err string | |
362 | }{ | |
363 | { | |
364 | Proxy: "127.0.0.1", | |
365 | Output: "http://127.0.0.1:1080", | |
366 | }, | |
367 | { | |
368 | Proxy: "127.0.0.1:8888", | |
369 | Output: "http://127.0.0.1:8888", | |
370 | }, | |
371 | { | |
372 | Proxy: "http://127.0.0.1:8888", | |
373 | Output: "http://127.0.0.1:8888", | |
374 | }, | |
375 | { | |
376 | Proxy: "socks5://127.0.0.1", | |
377 | Output: "socks5://127.0.0.1:1080", | |
378 | }, | |
379 | { | |
380 | Proxy: "socks5://127.0.0.1:8888", | |
381 | Output: "socks5://127.0.0.1:8888", | |
382 | }, | |
383 | { | |
384 | Proxy: "Garbage", | |
385 | Output: "http://Garbage:1080", | |
386 | }, | |
387 | { | |
388 | Proxy: "ftp://127.0.0.1", | |
389 | Err: "unsupported proxy type: ftp", | |
390 | }, | |
391 | { | |
392 | Proxy: "ftp://127.0.0.1:8888", | |
393 | Err: "unsupported proxy type: ftp", | |
394 | }, | |
395 | { | |
396 | Proxy: "login:password@127.0.0.1", | |
397 | Output: "http://login:password@127.0.0.1:1080", | |
398 | }, | |
399 | { | |
400 | Proxy: "login:password@127.0.0.1", | |
401 | UserPassword: "something:else", | |
402 | Output: "http://login:password@127.0.0.1:1080", | |
403 | }, | |
404 | { | |
405 | Proxy: "127.0.0.1", | |
406 | UserPassword: "something:else", | |
407 | Output: "http://something:else@127.0.0.1:1080", | |
408 | }, | |
409 | { | |
410 | Proxy: "127.0.0.1:8888", | |
411 | UserPassword: "something:else", | |
412 | Output: "http://something:else@127.0.0.1:8888", | |
413 | }, | |
414 | { | |
415 | Proxy: "user:password@127.0.0.1:8888", | |
416 | UserPassword: "user2:password2", | |
417 | Output: "http://user:password@127.0.0.1:8888", | |
418 | }, | |
419 | { | |
420 | Proxy: "http://user:password@127.0.0.1:8888", | |
421 | UserPassword: "user2:password2", | |
422 | Output: "http://user:password@127.0.0.1:8888", | |
423 | }, | |
424 | } | |
425 | ||
426 | for _, test := range tests { | |
427 | output, err := parseProxy(test.Proxy, test.UserPassword) | |
428 | if test.Err != "" { | |
429 | assert.EqualError(t, err, test.Err) | |
430 | assert.Nil(t, output) | |
431 | } else { | |
432 | assert.NoError(t, err) | |
433 | assert.Equal(t, test.Output, output.String()) | |
434 | } | |
435 | } | |
436 | } |
0 | // +build !windows | |
1 | ||
2 | package main | |
3 | ||
4 | var ( | |
5 | // These match what you'd get building the C geoipupdate from source. | |
6 | defaultConfigFile = "/usr/local/etc/GeoIP.conf" | |
7 | defaultDatabaseDirectory = "/usr/local/share/GeoIP" | |
8 | ) |
0 | package main | |
1 | ||
2 | import ( | |
3 | "os" | |
4 | ) | |
5 | ||
6 | var ( | |
7 | // I'm not sure these make sense. However they can be overridden at runtime | |
8 | // and in the configuration, so we have some flexibility. | |
9 | defaultConfigFile = os.Getenv("SYSTEMDRIVE") + `\ProgramData\MaxMind\GeoIPUpdate\GeoIP.conf` | |
10 | defaultDatabaseDirectory = os.Getenv("SYSTEMDRIVE") + `\ProgramData\MaxMind\GeoIPUpdate\GeoIP` | |
11 | ) |
0 | package main | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "compress/gzip" | |
5 | "crypto/md5" | |
6 | "fmt" | |
7 | "io" | |
8 | "io/ioutil" | |
9 | "log" | |
10 | "net/http" | |
11 | "net/url" | |
12 | "os" | |
13 | "path/filepath" | |
14 | "strings" | |
15 | "time" | |
16 | ||
17 | "github.com/gofrs/flock" | |
18 | "github.com/pkg/errors" | |
19 | ) | |
20 | ||
21 | // version is the program's version number. | |
22 | var version = "unknown" | |
23 | ||
24 | func main() { | |
25 | log.SetFlags(0) | |
26 | ||
27 | args := getArgs() | |
28 | ||
29 | config, err := NewConfig(args.ConfigFile, args.DatabaseDirectory) | |
30 | if err != nil { | |
31 | fatal(args, "Error loading configuration file", err) | |
32 | } | |
33 | if args.Verbose { | |
34 | log.Printf("Using config file %s", args.ConfigFile) | |
35 | log.Printf("Using database directory %s", config.DatabaseDirectory) | |
36 | } | |
37 | ||
38 | lock, err := setup(config, args.Verbose) | |
39 | if err != nil { | |
40 | fatal(args, "Error preparing to update", err) | |
41 | } | |
42 | defer func() { | |
43 | if err := lock.Unlock(); err != nil { | |
44 | fatal(args, "Error unlocking lock file", errors.Wrap(err, "unlocking")) | |
45 | } | |
46 | }() | |
47 | ||
48 | if err := run(config, args.Verbose); err != nil { | |
49 | fatal(args, "Error retrieving updates", err) | |
50 | } | |
51 | } | |
52 | ||
53 | func fatal( | |
54 | args *Args, | |
55 | msg string, | |
56 | err error, | |
57 | ) { | |
58 | if args.StackTrace { | |
59 | log.Print(msg + fmt.Sprintf(": %+v", err)) | |
60 | } else { | |
61 | log.Print(msg + fmt.Sprintf(": %s", err)) | |
62 | } | |
63 | os.Exit(1) | |
64 | } | |
65 | ||
66 | func setup( | |
67 | config *Config, | |
68 | verbose bool, | |
69 | ) (*flock.Flock, error) { | |
70 | maybeSetProxy(config, verbose) | |
71 | ||
72 | if err := checkEnvironment(config); err != nil { | |
73 | return nil, err | |
74 | } | |
75 | ||
76 | lock := flock.New(config.LockFile) | |
77 | ok, err := lock.TryLock() | |
78 | if err != nil { | |
79 | return nil, errors.Wrap(err, "error acquiring a lock") | |
80 | } | |
81 | if !ok { | |
82 | return nil, errors.Errorf("could not acquire lock on %s", config.LockFile) | |
83 | } | |
84 | if verbose { | |
85 | log.Printf("Acquired lock file lock (%s)", config.LockFile) | |
86 | } | |
87 | ||
88 | return lock, nil | |
89 | } | |
90 | ||
91 | // Do not set a timeout to allow for very slow connections. Note the client | |
92 | // will have TCP KeepAlive's enabled by default due to using | |
93 | // http.DefaultTransport (which uses a net.Dialer with KeepAlive set). | |
94 | var client = &http.Client{} | |
95 | ||
96 | func maybeSetProxy( | |
97 | config *Config, | |
98 | verbose bool, | |
99 | ) { | |
100 | if config.Proxy == nil { | |
101 | return | |
102 | } | |
103 | ||
104 | if verbose { | |
105 | log.Printf("Using proxy: %s", config.Proxy) | |
106 | } | |
107 | http.DefaultTransport.(*http.Transport).Proxy = http.ProxyURL(config.Proxy) | |
108 | } | |
109 | ||
110 | func checkEnvironment( | |
111 | config *Config, | |
112 | ) error { | |
113 | fi, err := os.Stat(config.DatabaseDirectory) | |
114 | if err != nil { | |
115 | return errors.Wrap(err, "database directory is not available") | |
116 | } | |
117 | ||
118 | if !fi.IsDir() { | |
119 | return errors.New("database directory is not a directory") | |
120 | } | |
121 | ||
122 | // I don't think there is a reliable cross platform way to check the | |
123 | // directory is writable. We'll discover that when we try to write to it | |
124 | // anyway. | |
125 | ||
126 | return nil | |
127 | } | |
128 | ||
129 | func run( | |
130 | config *Config, | |
131 | verbose bool, | |
132 | ) error { | |
133 | for _, editionID := range config.EditionIDs { | |
134 | if err := updateEdition(config, verbose, editionID); err != nil { | |
135 | return errors.WithMessage(err, "error updating "+editionID) | |
136 | } | |
137 | } | |
138 | return nil | |
139 | } | |
140 | ||
141 | func updateEdition( | |
142 | config *Config, | |
143 | verbose bool, | |
144 | editionID string, | |
145 | ) error { | |
146 | filename, err := getFilename(config, verbose, editionID) | |
147 | if err != nil { | |
148 | return errors.WithMessage(err, "error retrieving filename") | |
149 | } | |
150 | ||
151 | md5, err := getCurrentMD5(config, verbose, filename) | |
152 | if err != nil { | |
153 | return errors.WithMessage(err, "error retrieving current MD5 of "+filename) | |
154 | } | |
155 | ||
156 | if err := maybeUpdate( | |
157 | config, | |
158 | verbose, | |
159 | editionID, | |
160 | filename, | |
161 | md5, | |
162 | ); err != nil { | |
163 | return errors.WithMessage(err, "error updating") | |
164 | } | |
165 | ||
166 | return nil | |
167 | } | |
168 | ||
169 | func getFilename( | |
170 | config *Config, | |
171 | verbose bool, | |
172 | editionID string, | |
173 | ) (string, error) { | |
174 | url := fmt.Sprintf( | |
175 | "%s/app/update_getfilename?product_id=%s", | |
176 | config.URL, | |
177 | url.QueryEscape(editionID), | |
178 | ) | |
179 | ||
180 | if verbose { | |
181 | log.Printf("Performing get filename request to %s", url) | |
182 | } | |
183 | res, err := client.Get(url) | |
184 | if err != nil { | |
185 | return "", errors.Wrap(err, "error performing HTTP request") | |
186 | } | |
187 | defer func() { | |
188 | if err := res.Body.Close(); err != nil { | |
189 | log.Fatalf("Error closing response body: %+v", errors.Wrap(err, "closing body")) | |
190 | } | |
191 | }() | |
192 | ||
193 | buf, err := ioutil.ReadAll(io.LimitReader(res.Body, 256)) | |
194 | if err != nil { | |
195 | return "", errors.Wrap(err, "error reading response body") | |
196 | } | |
197 | ||
198 | if res.StatusCode != http.StatusOK { | |
199 | return "", errors.Errorf("unexpected HTTP status code: %s: %s", res.Status, buf) | |
200 | } | |
201 | ||
202 | if len(buf) == 0 { | |
203 | return "", errors.New("response body is empty") | |
204 | } | |
205 | ||
206 | if bytes.Count(buf, []byte("\n")) > 0 || | |
207 | bytes.Count(buf, []byte("\x00")) > 0 { | |
208 | return "", errors.New("invalid characters in filename") | |
209 | } | |
210 | ||
211 | return string(buf), nil | |
212 | } | |
213 | ||
214 | const zeroMD5 = "00000000000000000000000000000000" | |
215 | ||
216 | func getCurrentMD5( | |
217 | config *Config, | |
218 | verbose bool, | |
219 | filename string, | |
220 | ) (string, error) { | |
221 | path := filepath.Join(config.DatabaseDirectory, filename) | |
222 | ||
223 | fh, err := os.Open(path) | |
224 | if err != nil { | |
225 | if os.IsNotExist(err) { | |
226 | if verbose { | |
227 | log.Printf("Not calculating MD5 sum as file does not exist: %s", path) | |
228 | } | |
229 | return zeroMD5, nil | |
230 | } | |
231 | return "", errors.Wrap(err, "error opening file") | |
232 | } | |
233 | defer func() { | |
234 | if err := fh.Close(); err != nil { | |
235 | log.Fatalf("Error closing file: %+v", errors.Wrap(err, "closing file")) | |
236 | } | |
237 | }() | |
238 | ||
239 | fi, err := fh.Stat() | |
240 | if err != nil { | |
241 | return "", errors.Wrap(err, "error stat'ing file") | |
242 | } | |
243 | if !fi.Mode().IsRegular() { | |
244 | return "", errors.New("not a regular file") | |
245 | } | |
246 | ||
247 | h := md5.New() | |
248 | if _, err := io.Copy(h, fh); err != nil { | |
249 | return "", errors.Wrap(err, "error reading file") | |
250 | } | |
251 | sum := fmt.Sprintf("%x", h.Sum(nil)) | |
252 | if verbose { | |
253 | log.Printf("Calculated MD5 sum for %s: %s", path, sum) | |
254 | } | |
255 | return sum, nil | |
256 | } | |
257 | ||
258 | func maybeUpdate( | |
259 | config *Config, | |
260 | verbose bool, | |
261 | editionID, | |
262 | filename, | |
263 | md5 string, | |
264 | ) error { | |
265 | url := fmt.Sprintf( | |
266 | "%s/geoip/databases/%s/update?db_md5=%s", | |
267 | config.URL, | |
268 | url.PathEscape(editionID), | |
269 | url.QueryEscape(md5), | |
270 | ) | |
271 | ||
272 | req, err := http.NewRequest(http.MethodGet, url, nil) | |
273 | if err != nil { | |
274 | return errors.Wrap(err, "error creating request") | |
275 | } | |
276 | if config.AccountID != 0 { | |
277 | req.SetBasicAuth(fmt.Sprintf("%d", config.AccountID), config.LicenseKey) | |
278 | } | |
279 | ||
280 | if verbose { | |
281 | log.Printf("Performing update request to %s", url) | |
282 | } | |
283 | res, err := client.Do(req) | |
284 | if err != nil { | |
285 | return errors.Wrap(err, "error performing HTTP request") | |
286 | } | |
287 | defer func() { | |
288 | if err := res.Body.Close(); err != nil { | |
289 | log.Fatalf("Error closing response body: %+v", errors.Wrap(err, "closing body")) | |
290 | } | |
291 | }() | |
292 | ||
293 | if res.StatusCode == http.StatusNotModified { | |
294 | if verbose { | |
295 | log.Printf("No new updates available for %s", editionID) | |
296 | } | |
297 | return nil | |
298 | } | |
299 | ||
300 | if res.StatusCode != http.StatusOK { | |
301 | buf, err := ioutil.ReadAll(io.LimitReader(res.Body, 256)) | |
302 | if err == nil { | |
303 | return errors.Errorf("unexpected HTTP status code: %s: %s", res.Status, buf) | |
304 | } | |
305 | return errors.Errorf("unexpected HTTP status code: %s", res.Status) | |
306 | } | |
307 | ||
308 | newMD5 := res.Header.Get("X-Database-MD5") | |
309 | if newMD5 == "" { | |
310 | return errors.New("no X-Database-MD5 header found") | |
311 | } | |
312 | lastModified, err := getLastModified(res.Header) | |
313 | if err != nil { | |
314 | return err | |
315 | } | |
316 | ||
317 | return writeAndCheck(config, verbose, filename, res.Body, newMD5, lastModified) | |
318 | } | |
319 | ||
320 | func getLastModified( | |
321 | headers http.Header, | |
322 | ) (time.Time, error) { | |
323 | lastModifiedStr := headers.Get("Last-Modified") | |
324 | if lastModifiedStr == "" { | |
325 | return time.Time{}, errors.New("no Last-Modified header found") | |
326 | } | |
327 | ||
328 | t, err := time.ParseInLocation(time.RFC1123, lastModifiedStr, time.UTC) | |
329 | if err != nil { | |
330 | return time.Time{}, errors.Wrap(err, "error parsing time") | |
331 | } | |
332 | ||
333 | return t, nil | |
334 | } | |
335 | ||
336 | func writeAndCheck( | |
337 | config *Config, | |
338 | verbose bool, | |
339 | filename string, | |
340 | body io.Reader, | |
341 | newMD5 string, | |
342 | lastModified time.Time, | |
343 | ) error { | |
344 | targetTest := filepath.Join( | |
345 | config.DatabaseDirectory, | |
346 | fmt.Sprintf("%s.test", filename), | |
347 | ) | |
348 | ||
349 | fh, err := os.OpenFile(targetTest, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) | |
350 | if err != nil { | |
351 | return errors.Wrap(err, "error creating file") | |
352 | } | |
353 | ||
354 | gzReader, err := gzip.NewReader(body) | |
355 | if err != nil { | |
356 | _ = fh.Close() | |
357 | _ = os.Remove(targetTest) | |
358 | return errors.Wrap(err, "error creating gzip reader") | |
359 | } | |
360 | ||
361 | md5Writer := md5.New() | |
362 | multiWriter := io.MultiWriter(fh, md5Writer) | |
363 | ||
364 | if _, err := io.Copy(multiWriter, gzReader); err != nil { | |
365 | _ = fh.Close() | |
366 | _ = os.Remove(targetTest) | |
367 | _ = gzReader.Close() | |
368 | return errors.Wrap(err, "error reading/writing") | |
369 | } | |
370 | ||
371 | if err := gzReader.Close(); err != nil { | |
372 | _ = fh.Close() | |
373 | _ = os.Remove(targetTest) | |
374 | return errors.Wrap(err, "error closing gzip reader") | |
375 | } | |
376 | ||
377 | if err := fh.Sync(); err != nil { | |
378 | _ = fh.Close() | |
379 | _ = os.Remove(targetTest) | |
380 | return errors.Wrap(err, "error syncing file") | |
381 | } | |
382 | ||
383 | if err := fh.Close(); err != nil { | |
384 | _ = os.Remove(targetTest) | |
385 | return errors.Wrap(err, "error closing file") | |
386 | } | |
387 | ||
388 | gotMD5 := fmt.Sprintf("%x", md5Writer.Sum(nil)) | |
389 | if !strings.EqualFold(gotMD5, newMD5) { | |
390 | _ = os.Remove(targetTest) | |
391 | return errors.Errorf("MD5 of new database (%s) does not match expected MD5 (%s)", | |
392 | gotMD5, newMD5) | |
393 | } | |
394 | ||
395 | target := filepath.Join(config.DatabaseDirectory, filename) | |
396 | ||
397 | if err := os.Rename(targetTest, target); err != nil { | |
398 | _ = os.Remove(targetTest) | |
399 | return errors.New("error moving database into place") | |
400 | } | |
401 | ||
402 | if config.PreserveFileTimes { | |
403 | if err := os.Chtimes(target, lastModified, lastModified); err != nil { | |
404 | return errors.Wrap(err, "error setting times on file") | |
405 | } | |
406 | } | |
407 | ||
408 | // fsync the directory. http://austingroupbugs.net/view.php?id=672 | |
409 | ||
410 | dh, err := os.Open(config.DatabaseDirectory) | |
411 | if err != nil { | |
412 | return errors.Wrap(err, "error opening database directory") | |
413 | } | |
414 | defer func() { | |
415 | if err := dh.Close(); err != nil { | |
416 | log.Fatalf("Error closing directory: %+v", errors.Wrap(err, "closing directory")) | |
417 | } | |
418 | }() | |
419 | ||
420 | // We ignore Sync errors as they primarily happen on file systems that do | |
421 | // not support sync. | |
422 | _ = dh.Sync() | |
423 | ||
424 | if verbose { | |
425 | log.Printf("Updated %s", target) | |
426 | } | |
427 | return nil | |
428 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "compress/gzip" | |
5 | "crypto/md5" | |
6 | "fmt" | |
7 | "io" | |
8 | "io/ioutil" | |
9 | "net/http" | |
10 | "net/http/httptest" | |
11 | "os" | |
12 | "path/filepath" | |
13 | "regexp" | |
14 | "testing" | |
15 | "time" | |
16 | ||
17 | "github.com/stretchr/testify/assert" | |
18 | "github.com/stretchr/testify/require" | |
19 | ) | |
20 | ||
21 | func TestUpdateEdition(t *testing.T) { | |
22 | tests := []struct { | |
23 | Description string | |
24 | CreateDirectory bool | |
25 | DatabaseBefore string | |
26 | DatabaseAfter string | |
27 | FilenameStatus int | |
28 | FilenameBody string | |
29 | DownloadStatus int | |
30 | DownloadBody string | |
31 | DownloadHeaders map[string]string | |
32 | ExpectedTime time.Time | |
33 | Err string | |
34 | }{ | |
35 | { | |
36 | Description: "Initial download, success", | |
37 | CreateDirectory: true, | |
38 | DatabaseAfter: "database goes here", | |
39 | FilenameStatus: http.StatusOK, | |
40 | FilenameBody: "GeoIP2-City.mmdb", | |
41 | DownloadStatus: http.StatusOK, | |
42 | DownloadBody: "database goes here", | |
43 | }, | |
44 | { | |
45 | Description: "No update, success", | |
46 | CreateDirectory: true, | |
47 | DatabaseBefore: "database goes here", | |
48 | DatabaseAfter: "database goes here", | |
49 | FilenameStatus: http.StatusOK, | |
50 | FilenameBody: "GeoIP2-City.mmdb", | |
51 | DownloadStatus: http.StatusNotModified, | |
52 | DownloadBody: "database goes here", | |
53 | }, | |
54 | { | |
55 | Description: "Update, success", | |
56 | CreateDirectory: true, | |
57 | DatabaseBefore: "database goes here", | |
58 | DatabaseAfter: "new database goes here", | |
59 | FilenameStatus: http.StatusOK, | |
60 | FilenameBody: "GeoIP2-City.mmdb", | |
61 | DownloadStatus: http.StatusOK, | |
62 | DownloadBody: "new database goes here", | |
63 | }, | |
64 | { | |
65 | Description: "Update, success, and modification time is set", | |
66 | CreateDirectory: true, | |
67 | DatabaseBefore: "new database goes here", | |
68 | DatabaseAfter: "newer database goes here", | |
69 | FilenameStatus: http.StatusOK, | |
70 | FilenameBody: "GeoIP2-City.mmdb", | |
71 | DownloadStatus: http.StatusOK, | |
72 | DownloadBody: "newer database goes here", | |
73 | DownloadHeaders: map[string]string{ | |
74 | "Last-Modified": time.Date(2018, 7, 24, 0, 0, 0, 0, time.UTC).Format(time.RFC1123), | |
75 | }, | |
76 | ExpectedTime: time.Date(2018, 7, 24, 0, 0, 0, 0, time.UTC), | |
77 | }, | |
78 | { | |
79 | Description: "Get filename fails", | |
80 | CreateDirectory: true, | |
81 | DatabaseBefore: "database goes here", | |
82 | DatabaseAfter: "database goes here", | |
83 | FilenameStatus: http.StatusBadRequest, | |
84 | Err: "error retrieving filename: unexpected HTTP status code: 400 Bad Request", | |
85 | }, | |
86 | { | |
87 | Description: "Get filename is missing body", | |
88 | CreateDirectory: true, | |
89 | DatabaseBefore: "database goes here", | |
90 | DatabaseAfter: "database goes here", | |
91 | FilenameStatus: http.StatusOK, | |
92 | Err: "error retrieving filename: response body is empty", | |
93 | }, | |
94 | { | |
95 | Description: "Get filename has newlines", | |
96 | CreateDirectory: true, | |
97 | DatabaseBefore: "database goes here", | |
98 | DatabaseAfter: "database goes here", | |
99 | FilenameStatus: http.StatusOK, | |
100 | FilenameBody: "bad\nfilename", | |
101 | Err: "error retrieving filename: invalid characters in filename", | |
102 | }, | |
103 | { | |
104 | Description: "Download request fails", | |
105 | CreateDirectory: true, | |
106 | DatabaseBefore: "database goes here", | |
107 | DatabaseAfter: "database goes here", | |
108 | FilenameStatus: http.StatusOK, | |
109 | FilenameBody: "GeoIP2-City.mmdb", | |
110 | DownloadStatus: http.StatusBadRequest, | |
111 | Err: "error updating: unexpected HTTP status code: 400 Bad Request", | |
112 | }, | |
113 | { | |
114 | Description: "Download request is missing X-Database-MD5", | |
115 | CreateDirectory: true, | |
116 | DatabaseBefore: "database goes here", | |
117 | DatabaseAfter: "database goes here", | |
118 | FilenameStatus: http.StatusOK, | |
119 | FilenameBody: "GeoIP2-City.mmdb", | |
120 | DownloadStatus: http.StatusOK, | |
121 | DownloadBody: "new database goes here", | |
122 | DownloadHeaders: map[string]string{ | |
123 | "X-Database-MD5": "", | |
124 | }, | |
125 | Err: "error updating: no X-Database-MD5 header found", | |
126 | }, | |
127 | { | |
128 | Description: "Download fails because database directory does not exist", | |
129 | FilenameStatus: http.StatusOK, | |
130 | FilenameBody: "GeoIP2-City.mmdb", | |
131 | DownloadStatus: http.StatusOK, | |
132 | DownloadBody: "new database goes here", | |
133 | Err: `error updating: error creating file: open \S+GeoIP2-City\.mmdb\.test: (?:no such file or directory|The system cannot find the path specified)`, | |
134 | }, | |
135 | { | |
136 | Description: "Download fails because provided checksum does not match", | |
137 | CreateDirectory: true, | |
138 | DatabaseBefore: "database goes here", | |
139 | DatabaseAfter: "database goes here", | |
140 | FilenameStatus: http.StatusOK, | |
141 | FilenameBody: "GeoIP2-City.mmdb", | |
142 | DownloadStatus: http.StatusOK, | |
143 | DownloadBody: "new database goes here", | |
144 | DownloadHeaders: map[string]string{ | |
145 | "X-Database-MD5": "5d41402abc4b2a76b9719d911017c592", // "hello" | |
146 | }, | |
147 | Err: `error updating: MD5 of new database \(985ecf3d7959b146208b3dc0189b21a5\) does not match expected MD5 \(5d41402abc4b2a76b9719d911017c592\)`, | |
148 | }, | |
149 | { | |
150 | Description: "Download request redirects are followed", | |
151 | CreateDirectory: true, | |
152 | DatabaseBefore: "database goes here", | |
153 | DatabaseAfter: "database goes here", | |
154 | FilenameStatus: http.StatusOK, | |
155 | FilenameBody: "GeoIP2-City.mmdb", | |
156 | DownloadStatus: http.StatusMovedPermanently, | |
157 | DownloadHeaders: map[string]string{ | |
158 | "Location": "/go-here", | |
159 | }, | |
160 | }, | |
161 | { | |
162 | Description: "MD5 sums are case insensitive", | |
163 | CreateDirectory: true, | |
164 | DatabaseBefore: "database goes here", | |
165 | DatabaseAfter: "new database goes here", | |
166 | FilenameStatus: http.StatusOK, | |
167 | FilenameBody: "GeoIP2-City.mmdb", | |
168 | DownloadStatus: http.StatusOK, | |
169 | DownloadBody: "new database goes here", | |
170 | DownloadHeaders: map[string]string{ | |
171 | "X-Database-MD5": "985ECF3D7959B146208B3DC0189B21A5", | |
172 | }, | |
173 | }, | |
174 | } | |
175 | ||
176 | updateRE := regexp.MustCompile(`\A/geoip/databases/\S+/update\z`) | |
177 | ||
178 | tempDir, err := ioutil.TempDir("", "gutest-") | |
179 | require.NoError(t, err) | |
180 | err = os.RemoveAll(tempDir) | |
181 | require.NoError(t, err) | |
182 | ||
183 | for _, test := range tests { | |
184 | server := httptest.NewServer( | |
185 | http.HandlerFunc( | |
186 | func(rw http.ResponseWriter, r *http.Request) { | |
187 | if r.URL.Path == "/app/update_getfilename" { | |
188 | rw.WriteHeader(test.FilenameStatus) | |
189 | _, err := rw.Write([]byte(test.FilenameBody)) | |
190 | require.NoError(t, err) | |
191 | return | |
192 | } | |
193 | ||
194 | if updateRE.MatchString(r.URL.Path) { | |
195 | buf := &bytes.Buffer{} | |
196 | gzWriter := gzip.NewWriter(buf) | |
197 | md5Writer := md5.New() | |
198 | multiWriter := io.MultiWriter(gzWriter, md5Writer) | |
199 | _, err := multiWriter.Write([]byte(test.DownloadBody)) | |
200 | require.NoError(t, err) | |
201 | err = gzWriter.Close() | |
202 | require.NoError(t, err) | |
203 | ||
204 | rw.Header().Set( | |
205 | "X-Database-MD5", | |
206 | fmt.Sprintf("%x", md5Writer.Sum(nil)), | |
207 | ) | |
208 | if test.DownloadStatus == http.StatusOK { | |
209 | rw.Header().Set( | |
210 | "Last-Modified", | |
211 | time.Now().Format(time.RFC1123), | |
212 | ) | |
213 | } | |
214 | for k, v := range test.DownloadHeaders { | |
215 | rw.Header().Set(k, v) | |
216 | } | |
217 | ||
218 | rw.WriteHeader(test.DownloadStatus) | |
219 | ||
220 | if test.DownloadStatus == http.StatusOK { | |
221 | _, err := rw.Write(buf.Bytes()) | |
222 | require.NoError(t, err) | |
223 | } | |
224 | ||
225 | return | |
226 | } | |
227 | ||
228 | if r.URL.Path == "/go-here" { | |
229 | rw.WriteHeader(http.StatusNotModified) | |
230 | return | |
231 | } | |
232 | ||
233 | rw.WriteHeader(http.StatusBadRequest) | |
234 | }, | |
235 | ), | |
236 | ) | |
237 | ||
238 | config := &Config{ | |
239 | AccountID: 123, | |
240 | DatabaseDirectory: tempDir, | |
241 | EditionIDs: []string{"GeoIP2-City"}, | |
242 | LicenseKey: "testing", | |
243 | LockFile: filepath.Join(tempDir, ".geoipupdate.lock"), | |
244 | URL: server.URL, | |
245 | } | |
246 | verbose := false | |
247 | if !test.ExpectedTime.IsZero() { | |
248 | config.PreserveFileTimes = true | |
249 | } | |
250 | ||
251 | if test.CreateDirectory { | |
252 | err := os.Mkdir(config.DatabaseDirectory, 0755) | |
253 | require.NoError(t, err) | |
254 | } | |
255 | ||
256 | currentDatabasePath := filepath.Join( | |
257 | config.DatabaseDirectory, | |
258 | "GeoIP2-City.mmdb", | |
259 | ) | |
260 | if test.DatabaseBefore != "" { | |
261 | err := ioutil.WriteFile( | |
262 | currentDatabasePath, | |
263 | []byte(test.DatabaseBefore), | |
264 | 0644, | |
265 | ) | |
266 | require.NoError(t, err) | |
267 | } | |
268 | ||
269 | err := updateEdition(config, verbose, config.EditionIDs[0]) | |
270 | if test.Err == "" { | |
271 | assert.NoError(t, err, test.Description) | |
272 | } else { | |
273 | // regex because some errors have filenames. | |
274 | assert.Regexp(t, test.Err, err.Error(), test.Description) | |
275 | } | |
276 | ||
277 | server.Close() | |
278 | ||
279 | if test.DatabaseAfter != "" { | |
280 | buf, err := ioutil.ReadFile(currentDatabasePath) | |
281 | require.NoError(t, err, test.Description) | |
282 | assert.Equal(t, test.DatabaseAfter, string(buf)) | |
283 | } | |
284 | ||
285 | if !test.ExpectedTime.IsZero() { | |
286 | fi, err := os.Stat(currentDatabasePath) | |
287 | require.NoError(t, err) | |
288 | assert.WithinDuration(t, test.ExpectedTime, fi.ModTime(), 0) | |
289 | } | |
290 | ||
291 | if test.CreateDirectory { | |
292 | err := os.RemoveAll(config.DatabaseDirectory) | |
293 | require.NoError(t, err) | |
294 | } | |
295 | } | |
296 | } | |
297 | ||
298 | func TestGetCurrentMD5(t *testing.T) { | |
299 | tempDir, err := ioutil.TempDir("", "gutest-") | |
300 | require.NoError(t, err) | |
301 | defer func() { | |
302 | err := os.RemoveAll(tempDir) | |
303 | require.NoError(t, err) | |
304 | }() | |
305 | ||
306 | config := &Config{ | |
307 | DatabaseDirectory: tempDir, | |
308 | } | |
309 | ||
310 | dirFile := filepath.Join(tempDir, "mydir") | |
311 | err = os.Mkdir(dirFile, 0755) | |
312 | require.NoError(t, err) | |
313 | ||
314 | verbose := false | |
315 | md5, err := getCurrentMD5(config, verbose, "mydir") | |
316 | assert.EqualError(t, err, "not a regular file") | |
317 | assert.Equal(t, "", md5) | |
318 | } |
0 | // +build go1.12 | |
1 | ||
2 | package main | |
3 | ||
4 | import "runtime/debug" | |
5 | ||
6 | func init() { | |
7 | if info, ok := debug.ReadBuildInfo(); ok && info.Main.Version != "(devel)" { | |
8 | version = info.Main.Version | |
9 | } | |
10 | } |
0 | # Please see https://dev.maxmind.com/geoip/geoipupdate/ for instructions | |
1 | # on setting up geoipupdate, including information on how to download a | |
2 | # pre-filled GeoIP.conf file. | |
3 | ||
4 | # Enter your account ID and license key below. These are available from | |
5 | # https://www.maxmind.com/en/my_license_key. If you are only using free | |
6 | # GeoLite databases, you may leave the 0 values. | |
7 | AccountID 0 | |
8 | LicenseKey 000000000000 | |
9 | ||
10 | # Enter the edition IDs of the databases you would like to update. | |
11 | # Multiple edition IDs are separated by spaces. | |
12 | EditionIDs GeoLite2-Country GeoLite2-City | |
13 | ||
14 | # The remaining settings are OPTIONAL. | |
15 | ||
16 | # The directory to store the database files. Defaults to DATADIR | |
17 | # DatabaseDirectory DATADIR | |
18 | ||
19 | # The server to use. Defaults to "updates.maxmind.com". | |
20 | # Host updates.maxmind.com | |
21 | ||
22 | # The proxy host name or IP address. You may optionally specify a | |
23 | # port number, e.g., 127.0.0.1:8888. If no port number is specified, 1080 | |
24 | # will be used. | |
25 | # Proxy 127.0.0.1:8888 | |
26 | ||
27 | # The user name and password to use with your proxy server. | |
28 | # ProxyUserPassword username:password | |
29 | ||
30 | # Whether to preserve modification times of files downloaded from the server. | |
31 | # Defaults to "0". | |
32 | # PreserveFileTimes 0 | |
33 | ||
34 | # The lock file to use. This ensures only one geoipupdate process can run at a | |
35 | # time. | |
36 | # Note: Once created, this lockfile is not removed from the filesystem. | |
37 | # Defaults to ".geoipupdate.lock" under the DatabaseDirectory. | |
38 | # LockFile DATADIR/.geoipupdate.lock |
0 | #!/usr/bin/env perl | |
1 | use strict; | |
2 | use warnings; | |
3 | ||
4 | use File::Temp qw( tempfile ); | |
5 | ||
6 | sub main { | |
7 | my $build_dir = $ARGV[0] // 'build'; | |
8 | ||
9 | _make_man( | |
10 | 'geoipupdate', | |
11 | 1, | |
12 | "$build_dir/geoipupdate.md", | |
13 | "$build_dir/geoipupdate.1", | |
14 | ); | |
15 | _make_man( | |
16 | 'GeoIP.conf', | |
17 | 5, | |
18 | "$build_dir/GeoIP.conf.md", | |
19 | "$build_dir/GeoIP.conf.5", | |
20 | ); | |
21 | return 1; | |
22 | } | |
23 | ||
24 | sub _make_man { | |
25 | my ( $name, $section, $input, $output ) = @_; | |
26 | ||
27 | my ( $fh, $tmp ) = tempfile(); | |
28 | binmode $fh or die $!; | |
29 | print {$fh} "% $name($section)\n\n" or die $!; | |
30 | my $contents = _read($input); | |
31 | print {$fh} $contents or die $!; | |
32 | close $fh or die $!; | |
33 | ||
34 | system( | |
35 | 'pandoc', | |
36 | '-s', | |
37 | '-f', 'markdown', | |
38 | '-t', 'man', | |
39 | $tmp, | |
40 | '-o', $output, | |
41 | ) == 0 or die 'pandoc failed'; | |
42 | ||
43 | return; | |
44 | } | |
45 | ||
46 | sub _read { | |
47 | my ($file) = @_; | |
48 | open my $fh, '<', $file or die $!; | |
49 | binmode $fh or die $!; | |
50 | my $contents = ''; | |
51 | while ( !eof($fh) ) { | |
52 | my $line = <$fh>; | |
53 | die 'error reading' unless defined $line; | |
54 | $contents .= $line; | |
55 | } | |
56 | close $fh or die $!; | |
57 | return $contents; | |
58 | } | |
59 | ||
60 | exit( main() ? 0 : 1 ); |
0 | #!/bin/bash | |
1 | ||
2 | set -eu -o pipefail | |
3 | ||
4 | changelog=$(cat CHANGELOG.md) | |
5 | ||
6 | ||
7 | if [[ -z ${GITHUB_TOKEN:-} ]]; then | |
8 | echo 'GITHUB_TOKEN must be set for goreleaser!' | |
9 | exit 1 | |
10 | fi | |
11 | ||
12 | regex=' | |
13 | ## ([0-9]+\.[0-9]+\.[0-9]+) \(([0-9]{4}-[0-9]{2}-[0-9]{2})\) | |
14 | ||
15 | ((.| | |
16 | )*)' | |
17 | ||
18 | if [[ ! $changelog =~ $regex ]]; then | |
19 | echo "Could not find date line in change log!" | |
20 | exit 1 | |
21 | fi | |
22 | ||
23 | version="${BASH_REMATCH[1]}" | |
24 | date="${BASH_REMATCH[2]}" | |
25 | notes="$(echo "${BASH_REMATCH[3]}" | sed -n -e '/^## [0-9]\+\.[0-9]\+\.[0-9]\+/,$!p')" | |
26 | ||
27 | if [[ "$date" != $(date +"%Y-%m-%d") ]]; then | |
28 | echo "$date is not today!" | |
29 | exit 1 | |
30 | fi | |
31 | ||
32 | if [ -n "$(git status --porcelain)" ]; then | |
33 | echo ". is not clean." >&2 | |
34 | exit 1 | |
35 | fi | |
36 | ||
37 | tag="v$version" | |
38 | ||
39 | echo $'\nRelease notes:' | |
40 | echo "$notes" | |
41 | ||
42 | ||
43 | read -p "Continue? (y/n) " ok | |
44 | ||
45 | if [ "$ok" != "y" ]; then | |
46 | echo "Aborting" | |
47 | exit 1 | |
48 | fi | |
49 | ||
50 | echo "Creating tag $tag" | |
51 | ||
52 | message="$version | |
53 | ||
54 | $notes" | |
55 | ||
56 | git tag -a -m "$message" "$tag" | |
57 | ||
58 | # goreleaser's `--rm-dist' should clear out `dist', but it didn't work for me. | |
59 | rm -rf dist | |
60 | goreleaser release --rm-dist -f .goreleaser.yml --release-notes <(echo "$message") | |
61 | make clean BUILDDIR=. | |
62 | ||
63 | rm -rf dist | |
64 | goreleaser release --rm-dist -f .goreleaser-windows.yml --skip-publish | |
65 | hub release edit -m "$message" \ | |
66 | -a "dist/geoipupdate_${version}_windows_386.zip" \ | |
67 | -a "dist/geoipupdate_${version}_windows_amd64.zip" \ | |
68 | -a dist/checksums-windows.txt \ | |
69 | "$tag" | |
70 | make clean BUILDDIR=. | |
71 | ||
72 | rm -rf dist | |
73 | goreleaser release --rm-dist -f .goreleaser-packages.yml --skip-publish | |
74 | ||
75 | git push | |
76 | ||
77 | hub release edit -m "$message" \ | |
78 | -a dist/checksums-dpkg-rpm.txt \ | |
79 | -a "dist/geoipupdate_${version}_linux_386.deb" \ | |
80 | -a "dist/geoipupdate_${version}_linux_amd64.deb" \ | |
81 | -a "dist/geoipupdate_${version}_linux_386.rpm" \ | |
82 | -a "dist/geoipupdate_${version}_linux_amd64.rpm" \ | |
83 | "$tag" | |
84 | make clean BUILDDIR=. |
0 | # NAME | |
1 | ||
2 | GeoIP.conf - Configuration file for geoipupdate | |
3 | ||
4 | # SYNOPSIS | |
5 | ||
6 | This file allows you to configure your `geoipupdate` program to | |
7 | download GeoIP2, GeoLite2, and GeoIP Legacy databases. | |
8 | ||
9 | # DESCRIPTION | |
10 | ||
11 | The file consists of one setting per line. Lines starting with `#` | |
12 | are comments and will not be processed. All setting keywords are case | |
13 | sensitive. | |
14 | ||
15 | ## Required settings: | |
16 | ||
17 | `EditionIDs` | |
18 | ||
19 | : List of database edition IDs. Edition IDs may consist | |
20 | of letters, digits, and dashes (e.g., "GeoIP2-City", "106"). Note: this | |
21 | was formerly called `ProductIds`. | |
22 | ||
23 | ## Optional settings: | |
24 | ||
25 | `AccountID` | |
26 | ||
27 | : Your MaxMind account ID. This was formerly known as `UserId`. | |
28 | ||
29 | `DatabaseDirectory` | |
30 | ||
31 | : The directory to store the database files. If not set, the default is | |
32 | DATADIR. This can be overridden at run time by the `-d` command line | |
33 | argument. | |
34 | ||
35 | `Host` | |
36 | ||
37 | : The host name of the server to use. The default is `updates.maxmind.com`. | |
38 | ||
39 | `Proxy` | |
40 | ||
41 | : The proxy host name or IP address. You may optionally specify | |
42 | a port number, e.g., `127.0.0.1:8888`. If no port number is specified, | |
43 | 1080 will be used. | |
44 | ||
45 | `ProxyUserPassword` | |
46 | ||
47 | : The proxy user name and password, separated by a colon. For instance, | |
48 | `username:password`. | |
49 | ||
50 | `PreserveFileTimes` | |
51 | ||
52 | : Whether to preserve modification times of files downloaded from the | |
53 | server. This option is either `0` or `1`. The default is `0`. | |
54 | ||
55 | `LicenseKey` | |
56 | ||
57 | : Your case-sensitive MaxMind license key. | |
58 | ||
59 | `LockFile` | |
60 | ||
61 | : The lock file to use. This ensures only one `geoipupdate` process can run | |
62 | at a time. Note: Once created, this lockfile is not removed from the | |
63 | filesystem. The default is `.geoipupdate.lock` under the | |
64 | `DatabaseDirectory`. | |
65 | ||
66 | ## Deprecated settings: | |
67 | ||
68 | The following are deprecated and will be ignored if present: | |
69 | ||
70 | `Protocol` | |
71 | ||
72 | `SkipPeerVerification` | |
73 | ||
74 | `SkipHostnameVerification` | |
75 | ||
76 | # FILES | |
77 | ||
78 | `GeoIP.conf` | |
79 | ||
80 | : Default `geoipupdate` configuration file. | |
81 | ||
82 | # SEE ALSO | |
83 | ||
84 | `geoipupdate`(1) |
0 | # NAME | |
1 | ||
2 | geoipupdate - GeoIP2, GeoLite2, and GeoIP Legacy Update Program | |
3 | ||
4 | # SYNOPSIS | |
5 | ||
6 | geoipupdate [-Vvh] [-f CONFIG_FILE] [-d TARGET_DIRECTORY] | |
7 | ||
8 | # DESCRIPTION | |
9 | ||
10 | `geoipupdate` automatically updates GeoIP2, GeoLite2, and GeoIP Legacy | |
11 | databases. The program connects to the MaxMind GeoIP Update server to | |
12 | check for new databases. If a new database is available, the program will | |
13 | download and install it. | |
14 | ||
15 | If you are using a firewall, you must have the DNS and HTTPS ports | |
16 | open. | |
17 | ||
18 | # OPTIONS | |
19 | ||
20 | ||
21 | `-d`, `--database-directory` | |
22 | ||
23 | : Install databases to a custom directory. This is optional. If provided, it | |
24 | overrides any `DatabaseDirectory` set in the configuration file. | |
25 | ||
26 | `-f`, `--config-file` | |
27 | ||
28 | : The configuration file to use. See `GeoIP.conf` and its documentation for | |
29 | more information. This is optional. It defaults to CONFFILE. | |
30 | ||
31 | `-h`, `--help` | |
32 | ||
33 | : Display help and exit. | |
34 | ||
35 | `--stack-trace` | |
36 | ||
37 | : Show a stack trace on any error message. This is primarily useful for | |
38 | debugging. | |
39 | ||
40 | `-V`, `--version` | |
41 | ||
42 | : Display version information and exit. | |
43 | ||
44 | `-v`, `--verbose` | |
45 | ||
46 | : Enable verbose mode. Prints out the steps that `geoipupdate` takes. | |
47 | ||
48 | # USAGE | |
49 | ||
50 | Typically you should run `geoipupdate` weekly. On most Unix-like systems, | |
51 | this can be achieved by using cron. Below is a sample crontab file that | |
52 | runs `geoipupdate` on each Wednesday at noon: | |
53 | ||
54 | # top of crontab | |
55 | ||
56 | MAILTO=your@email.com | |
57 | ||
58 | 0 12 * * 3 geoipupdate | |
59 | ||
60 | # end of crontab | |
61 | ||
62 | ||
63 | To use with a proxy server, update your `GeoIP.conf` file as specified | |
64 | in the `GeoIP.conf` man page or set the `http_proxy` environment | |
65 | variable. | |
66 | ||
67 | # RETURN CODES | |
68 | ||
69 | `geoipupdate` returns 0 on success and 1 on error. | |
70 | ||
71 | # FILES | |
72 | ||
73 | * `GeoIP.conf` - Configuration file for GeoIP Update. See the | |
74 | `GeoIP.conf` documentation for more information. | |
75 | ||
76 | # AUTHOR | |
77 | ||
78 | Written by William Storey. | |
79 | ||
80 | # REPORTING BUGS | |
81 | ||
82 | Report bugs to [support@maxmind.com](mailto:support@maxmind.com). | |
83 | ||
84 | # COPYRIGHT | |
85 | ||
86 | This software is Copyright (c) 2018-2019 by MaxMind, Inc. | |
87 | ||
88 | This is free software, licensed under the Apache License, Version 2.0 or | |
89 | the MIT License, at your option. | |
90 | ||
91 | # MORE INFORMATION | |
92 | ||
93 | Visit [our website](https://www.maxmind.com/en/geoip2-services-and-databases) | |
94 | to learn more about the GeoIP2 and GeoIP Legacy databases or to sign up | |
95 | for a subscription. | |
96 | ||
97 | # SEE ALSO | |
98 | ||
99 | `GeoIP.conf`(5) |
0 | module github.com/maxmind/geoipupdate | |
1 | ||
2 | go 1.12 | |
3 | ||
4 | require ( | |
5 | github.com/davecgh/go-spew v1.1.1 // indirect | |
6 | github.com/gofrs/flock v0.7.1 | |
7 | github.com/kr/pretty v0.1.0 // indirect | |
8 | github.com/pkg/errors v0.8.1 | |
9 | github.com/spf13/pflag v1.0.3 | |
10 | github.com/stretchr/testify v1.4.0 | |
11 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect | |
12 | ) |
0 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |
1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | |
2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |
3 | github.com/gofrs/flock v0.7.1 h1:DP+LD/t0njgoPBvT5MJLeliUIVQR03hiKR6vezdwHlc= | |
4 | github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= | |
5 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= | |
6 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | |
7 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | |
8 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= | |
9 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | |
10 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= | |
11 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | |
12 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | |
13 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | |
14 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= | |
15 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= | |
16 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | |
17 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= | |
18 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | |
19 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | |
20 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= | |
21 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | |
22 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= | |
23 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |