import fs from 'fs'; import path from 'path'; import {extract} from "tar"; import {Reader} from '@maxmind/geoip2-node'; const ENABLE_MAXMIND=false 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} Resolves when the data check and optional update process is completed. */ async function checkAndUpdateMaxmindData(outputDir, tempDir, url, username, password) { if (username === '' || password === '') { console.log('No MaxMind credentials provided. Skipping MaxMind database update.'); return } 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; if( ENABLE_MAXMIND ) { 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 } }