Files
TianXuan Developer e0a7400bd7 feat: 高精度离线GeoIP — MMDB支持 + 中文路径修复 + 分块自解压
- geoip.py: 修复中文路径 str()→Path 编码问题 (maxminddb Latin-1 bug)
- geoip.py: 启动自动重组geoip_chunks分块 + gunzip解压
- scripts/setup_geoip.py: 手动重组/解压脚本
- .gitignore: 忽略解压后的 .mmdb 文件

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-23 23:42:38 +08:00

67 lines
2.2 KiB
Python

#!/usr/bin/env python3
"""Reassemble split GeoIP database chunks into .mmdb.gz files, then decompress.
Chunks are stored in data/geoip_chunks/ to work around Git push size limits.
Run this once after cloning the repo, or let geoip.py auto-decompress at startup.
"""
import hashlib
import gzip
import shutil
from pathlib import Path
DATA_DIR = Path(__file__).parent.parent / 'data'
CHUNKS_DIR = DATA_DIR / 'geoip_chunks'
CHECKSUMS = {
'GeoLite2-City.mmdb.gz': 'a0b0fd27d587ab5caf1436bdbe8ab0a4',
'GeoLite2-ASN.mmdb.gz': '0b0fd27d587ab5caf1436bdbe8ab0a4', # placeholder
}
def reassemble(name: str) -> Path | None:
"""Reassemble chunks for `name` into a single .gz file. Returns path or None."""
prefix = name + '.gz.part'
chunks = sorted(
p for p in CHUNKS_DIR.iterdir()
if p.name.startswith(prefix)
)
if not chunks:
print(f'No chunks found for {name}.gz')
return None
out_path = DATA_DIR / (name + '.gz')
print(f'Reassembling {len(chunks)} chunks → {out_path.name}')
with open(out_path, 'wb') as out:
for chunk in chunks:
with open(chunk, 'rb') as f:
shutil.copyfileobj(f, out)
print(f'Done: {out_path.stat().st_size:,} bytes')
return out_path
def decompress(name: str) -> Path | None:
"""Decompress .mmdb.gz → .mmdb if not already present."""
gz_path = DATA_DIR / (name + '.gz')
mmdb_path = DATA_DIR / name
if mmdb_path.exists():
print(f'{name} already decompressed')
return mmdb_path
if not gz_path.exists():
print(f'{name}.gz not found')
return None
print(f'Decompressing {gz_path.name}{mmdb_path.name} ({gz_path.stat().st_size:,} bytes) …')
with gzip.open(gz_path, 'rb') as f_in:
with open(mmdb_path, 'wb') as f_out:
shutil.copyfileobj(f_in, f_out, length=16 * 1024 * 1024)
print(f'Done: {mmdb_path.stat().st_size:,} bytes')
return mmdb_path
def main():
for name in ['GeoLite2-City.mmdb', 'GeoLite2-ASN.mmdb']:
gz_path = DATA_DIR / (name + '.gz')
if not gz_path.exists():
reassemble(name)
decompress(name)
print('GeoIP databases ready.')
if __name__ == '__main__':
main()