diff --git a/ecosystem.config.js b/ecosystem.config.js index d29f7f6..b518162 100644 --- a/ecosystem.config.js +++ b/ecosystem.config.js @@ -12,6 +12,8 @@ module.exports = { // Application restart policy settings wait_ready: true, + kill_timeout: 10000, + listen_timeout: 30000, autorestart: true, max_restarts: 10, min_uptime: 20000, diff --git a/index.js b/index.js index 22922b7..de0bfe6 100644 --- a/index.js +++ b/index.js @@ -8,14 +8,13 @@ const banner = require('./scripts/banners/ufw.js'); const { axiosService } = require('./scripts/services/axios.js'); const { saveBufferToFile, loadBufferFromFile, sendBulkReport, BULK_REPORT_BUFFER } = require('./scripts/services/bulk.js'); const { reportedIPs, loadReportedIPs, saveReportedIPs, isIPReportedRecently, markIPAsReported } = require('./scripts/services/cache.js'); +const ABUSE_STATE = require('./scripts/services/state.js'); const { refreshServerIPs, getServerIPs } = require('./scripts/services/ipFetcher.js'); const { repoSlug, repoUrl } = require('./scripts/repo.js'); const isSpecialPurposeIP = require('./scripts/isSpecialPurposeIP.js'); const logger = require('./scripts/logger.js'); const config = require('./config.js'); const { UFW_LOG_FILE, SERVER_ID, EXTENDED_LOGS, AUTO_UPDATE_ENABLED, AUTO_UPDATE_SCHEDULE, DISCORD_WEBHOOK_ENABLED, DISCORD_WEBHOOK_URL } = config.MAIN; - -const ABUSE_STATE = { isLimited: false, isBuffering: false, sentBulk: false }; const RATE_LIMIT_LOG_INTERVAL = 10 * 60 * 1000; const BUFFER_STATS_INTERVAL = 5 * 60 * 1000; @@ -178,19 +177,43 @@ const processLogLine = async (line, test = false) => { } // Watch + let incompleteLine = ''; + let processing = Promise.resolve(); + fileOffset = fs.statSync(UFW_LOG_FILE).size; chokidar.watch(UFW_LOG_FILE, { persistent: true, ignoreInitial: true }) - .on('change', path => { - const stats = fs.statSync(path); + .on('change', filePath => { + const stats = fs.statSync(filePath); if (stats.size < fileOffset) { + incompleteLine = ''; fileOffset = 0; logger.info('The file has been truncated, and the offset has been reset'); } - fs.createReadStream(path, { start: fileOffset, encoding: 'utf8' }).on('data', chunk => { - chunk.split('\n').filter(line => line.trim()).forEach(processLogLine); - }).on('end', () => { - fileOffset = stats.size; + const start = fileOffset; + fileOffset = stats.size; + + processing = processing.then(async () => { + let data = ''; + try { + await new Promise((resolve, reject) => { + fs.createReadStream(filePath, { start, encoding: 'utf8' }) + .on('data', chunk => { data += chunk; }) + .on('end', resolve) + .on('error', reject); + }); + } catch (err) { + logger.error(`Failed to read log chunk: ${err.message}`); + return; + } + + const text = incompleteLine + data; + const lines = text.split('\n'); + incompleteLine = lines.pop(); + + for (const line of lines) { + if (line.trim()) await processLogLine(line); + } }); }); @@ -203,4 +226,18 @@ const processLogLine = async (line, test = false) => { process.send?.('ready'); })(); +const gracefulShutdown = async signal => { + logger.info(`Received ${signal}, flushing pending writes...`); + try { + await saveBufferToFile(); + await saveReportedIPs(); + } catch (err) { + logger.error(`Error during shutdown flush: ${err.message}`); + } + process.exit(0); +}; + +process.on('SIGINT', () => gracefulShutdown('SIGINT')); +process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); + module.exports = processLogLine; diff --git a/package-lock.json b/package-lock.json index b52eafd..38ad413 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "ufw-abuseipdb-reporter", - "version": "0.4.5", + "version": "0.4.6-beta.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ufw-abuseipdb-reporter", - "version": "0.4.5", + "version": "0.4.6-beta.0", "license": "GPL-3.0", "dependencies": { - "axios": "^1.13.2", + "axios": "^1.13.4", "axios-retry": "^4.5.0", "chokidar": "^5.0.0", "cron": "^4.4.0", @@ -23,7 +23,7 @@ }, "devDependencies": { "@eslint/js": "^9.39.2", - "globals": "^17.0.0" + "globals": "^17.3.0" } }, "node_modules/@eslint/js": { @@ -67,9 +67,9 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", + "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -326,9 +326,9 @@ } }, "node_modules/globals": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.0.0.tgz", - "integrity": "sha512-gv5BeD2EssA793rlFWVPMMCqefTlpusw6/2TbAVMy0FzcG8wKJn4O+NqJ4+XWmmwrayJgw5TzrmWjFgmz1XPqw==", + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.3.0.tgz", + "integrity": "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index fcfeae6..bbee5a9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ufw-abuseipdb-reporter", - "version": "0.4.5", + "version": "0.4.6-beta.0", "homepage": "https://github.com/sefinek/UFW-AbuseIPDB-Reporter#readme", "bugs": { "url": "https://github.com/sefinek/UFW-AbuseIPDB-Reporter/issues" @@ -20,7 +20,7 @@ "update": "npm run pull && npm ci --omit=dev && pm2 restart ufw-abuseipdb" }, "dependencies": { - "axios": "^1.13.2", + "axios": "^1.13.4", "axios-retry": "^4.5.0", "chokidar": "^5.0.0", "cron": "^4.4.0", @@ -34,6 +34,6 @@ }, "devDependencies": { "@eslint/js": "^9.39.2", - "globals": "^17.0.0" + "globals": "^17.3.0" } } diff --git a/scripts b/scripts index bd6fd14..7bd68d6 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit bd6fd148cc050c02dd94b5412273c2a3ab29a401 +Subproject commit 7bd68d6d91aa184b9b0fae9122ea4b5f7b2289e7