Files
server/maxmind.js

181 lines
6.6 KiB
JavaScript

import fs from 'fs';
import path from 'path';
import {extract} from "tar";
import {Reader} from '@maxmind/geoip2-node';
let ipdb = null
function setDbFile(file) {
const dbBuffer = fs.readFileSync(file);
ipdb = Reader.openBuffer(dbBuffer);
}
/**
* Downloads and extracts a .tar.gz file from a given URL.
* @param {string} url - The URL to download the file from.
* @param {string} outputDir - The directory to move the final .mmdb file to.
* @param {string} tempDir - The directory to use for temporary files.
* @param {string} username - The username for HTTP basic auth.
* @param {string} password - The password for HTTP basic auth.
*/
async function downloadAndExtractMaxmindData(url, outputDir, tempDir, username, password) {
console.log('Downloading MaxMind database...');
const tempFilePath = path.join(tempDir, `temp_${Date.now()}_${Math.random().toString(36).slice(2, 10)}.tar.gz`);
const tempExtractDir = path.join(tempDir, `temp_extract_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`);
try {
// Ensure the output directory exists
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, {recursive: true});
}
// Create a temporary extract directory
if (!fs.existsSync(tempExtractDir)) {
fs.mkdirSync(tempExtractDir, {recursive: true});
}
// Download the file with HTTP basic authentication and save it as a temporary tar.gz file
const response = await fetch(url, {
headers: {
Authorization: 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64')
}
});
if (!response.ok) {
throw new Error(`Failed to download file: ${response.statusText}`);
}
const fileStream = fs.createWriteStream(tempFilePath);
const reader = response.body.getReader();
await new Promise((resolve, reject) => {
function processChunk({done, value}) {
if (done) {
fileStream.end();
resolve();
return;
}
fileStream.write(value, () => reader.read().then(processChunk).catch(reject));
}
reader.read().then(processChunk).catch(reject);
});
// Extract the tar.gz file into the temporary extract directory
await extract({
file: tempFilePath,
cwd: tempExtractDir
});
// Find the .mmdb file in the temporary extract directory
const mmdbFile = fs.readdirSync(tempExtractDir, {withFileTypes: true})
.map(dirent => {
const fullPath = path.join(tempExtractDir, dirent.name);
if (dirent.isDirectory()) {
const subFiles = fs.readdirSync(fullPath).map(subFile => path.join(fullPath, subFile));
return subFiles.find(file => file.endsWith('.mmdb')) || null;
}
return dirent.name.endsWith('.mmdb') ? fullPath : null;
})
.filter(Boolean)[0];
if (!mmdbFile) {
throw new Error('No .mmdb file found in the extracted contents.');
}
// Move the .mmdb file to the output directory
const dest = path.join(outputDir, path.basename(mmdbFile));
fs.copyFileSync(
mmdbFile,
dest
);
console.log(`MaxMind database downloaded to ${dest}`);
return dest
} finally {
// Clean up the temporary tar.gz file and temporary extract directory
if (fs.existsSync(tempFilePath)) {
fs.unlinkSync(tempFilePath);
}
if (fs.existsSync(tempExtractDir)) {
fs.rmSync(tempExtractDir, {recursive: true, force: true});
}
}
}
/**
* Checks for up-to-date MaxMind database files in the specified output directory. If none of the files
* meet the update criteria, the function downloads and extracts updated MaxMind data from the provided source.
*
* @param {string} outputDir - Directory where MaxMind database files are stored.
* @param {string} tempDir - Temporary directory used during the download and extraction process.
* @param {string} url - URL to download the MaxMind data.
* @param {string} username - Username for authentication to access the MaxMind download service.
* @param {string} password - Password for authentication to access the MaxMind download service.
* @return {Promise<void>} Resolves when the data check and optional update process is completed.
*/
async function checkAndUpdateMaxmindData(outputDir, tempDir, url, username, password) {
console.log('Checking for MaxMind database updates.');
// Ensure the output directory exists
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, {recursive: true});
}
const mmdbFiles = fs.readdirSync(outputDir).filter(file => file.endsWith('.mmdb'));
let shouldDownload = true;
if (mmdbFiles.length) {
for (const mmdbFile of mmdbFiles) {
const filePath = path.join(outputDir, mmdbFile);
const stats = fs.statSync(filePath);
const modifiedDate = new Date(stats.mtime);
const fourDaysAgo = new Date(Date.now() - 4 * 24 * 60 * 60 * 1000);
if (modifiedDate > fourDaysAgo) {
shouldDownload = false;
console.log(`MaxMind database '${mmdbFile}' is recent.`);
setDbFile(filePath)
break;
}
}
}
if (shouldDownload) {
setDbFile(await downloadAndExtractMaxmindData(url, outputDir, tempDir, username, password));
}
}
const url = 'https://download.maxmind.com/geoip/databases/GeoIP2-Country/download?suffix=tar.gz';
const outputDirectory = './maxmind';
const username = '1102431';
const password = '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';
// const username = process.env.MAXMIND_ACCOUNT_NUMBER;
// const password = process.env.MAXMIND_LICENSE_KEY;
await checkAndUpdateMaxmindData(outputDirectory, '/tmp', url, username, password);
setInterval(async () => {
try {
await checkAndUpdateMaxmindData(outputDirectory, '/tmp', url, username, password);
} catch (error) {
console.error('Error during MaxMind database update:', error);
}
}, 24 * 60 * 60 * 1000 + 1000); // 1 day + 1 second
export function countryForIP(ipAddress) {
if (!ipdb) return null
try {
const code = ipdb.country(ipAddress).country.isoCode;
console.log(ipAddress, code)
return code;
}
catch (e) {
console.warn(`IP lookup failed for ${ipAddress}: ${e.message}`)
return null
}
}
const ip = '64.178.217.35'
console.log(`ip: ${ip}`, countryForIP(ip))