3 Simple python replacement for the MaxMind geoipupdate program.
7 from argparse
import ArgumentDefaultsHelpFormatter
, ArgumentParser
11 from pathlib
import Path
23 The entry point of the geoipyupdate script.
25 # Create an argument parser using our docsctring as its description.
26 parser
= ArgumentParser(description
=sys
.modules
[__name__
].__doc
__,
27 formatter_class
=ArgumentDefaultsHelpFormatter
)
29 # XDG_CONFIG_HOME defaults to ~/.config
30 default_xdgch
= str(Path
.home() / ".config")
31 xdgch
= os
.environ
.get("XDG_CONFIG_HOME", default_xdgch
)
32 default_config_file
= os
.path
.join(xdgch
,
36 parser
.add_argument('-c',
38 default
=default_config_file
,
39 help='path to configuration file')
41 args
= parser
.parse_args()
43 # Load/parse the config
44 with open(args
.config_file
, "rb") as f
:
45 config
= tomllib
.load(f
)
46 editions
= config
["database"]["editions"]
47 datadir
= config
["database"]["datadir"]
48 account_id
= config
["account"]["account_id"]
49 license_key
= config
["account"]["license_key"]
51 # Impersonate the true client
52 headers
= {'User-Agent': 'geoipupdate/6.1.0'}
55 url_server
= "https://updates.maxmind.com"
57 for edition
in editions
:
58 # The final location of this database, which also happens to
59 # be where the previous database might be found.
60 dbfile
= os
.path
.join(datadir
, f
"{edition}.mmdb")
62 # Compute the hash of the old database, if there is
63 # one. Otherwise, leave it blank. This is passed to the server
64 # who might tell us that the database was 304 Not Modified.
66 if os
.path
.isfile(dbfile
):
67 with open(dbfile
, 'rb') as f
:
68 oldhash
= hashlib
.md5(f
.read()).hexdigest()
70 url_path
= f
"/geoip/databases/{edition}/update?db_md5={oldhash}"
71 url
= f
"{url_server}{url_path}"
73 auth
=(account_id
, license_key
),
78 if r
.status_code
== 304:
79 # The database hasn't changed since we last downloaded it.
84 # Insist on md5 verification of the downloads, i.e. don't handle
85 # the case where the md5 response header is missing.
86 xdbmd5
= r
.headers
["X-Database-MD5"]
88 # First download the gzipped file to /tmp or wherever. When
89 # python-3.12 is more widespread, delete_on_close=False might
90 # be a better alternative, allowing us to use a context
92 f
= tempfile
.NamedTemporaryFile(delete
=False)
93 for chunk
in r
.iter_content(chunk_size
=128):
97 # Now gunzip it to a new temporary file (can't simply gunzip in
98 # place, because now the name would be predictable). We need
99 # delete=False here because we intend to move this file to the
101 g
= tempfile
.NamedTemporaryFile(delete
=False)
102 gdata
= gzip
.open(f
.name
, 'rb').read()
104 # We're done with f. Remove it ASAP in case something goes
108 newhash
= hashlib
.md5(gdata
).hexdigest()
109 if newhash
== xdbmd5
:
114 f
"{edition} hash doesn't match X-Database-MD5 header"
117 # Overwrite the old database file with the new (gunzipped) one.
118 shutil
.move(g
.name
, dbfile
)