From b153df4fca0d84a4572e430aa23fbc6e9981d588 Mon Sep 17 00:00:00 2001 From: Sefinek Date: Thu, 6 Mar 2025 14:52:05 +0100 Subject: [PATCH] Submodule --- .gitmodules | 3 ++ index.js | 16 ++++----- scripts | 1 + services/axios.js | 13 ------- services/cache.js | 30 ---------------- services/discord.js | 45 ------------------------ services/fetchServerIP.js | 41 ---------------------- services/reloadApp.js | 24 ------------- services/summaries.js | 73 --------------------------------------- services/updates.js | 36 ------------------- utils/isLocalIP.js | 10 ------ utils/log.js | 14 -------- 12 files changed, 12 insertions(+), 294 deletions(-) create mode 100644 .gitmodules create mode 160000 scripts delete mode 100644 services/axios.js delete mode 100644 services/cache.js delete mode 100644 services/discord.js delete mode 100644 services/fetchServerIP.js delete mode 100644 services/reloadApp.js delete mode 100644 services/summaries.js delete mode 100644 services/updates.js delete mode 100644 utils/isLocalIP.js delete mode 100644 utils/log.js diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..4a597ce --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "scripts"] + path = scripts + url = https://github.com/sefinek/UFW-Integration-Scripts diff --git a/index.js b/index.js index da3cb18..8994fd5 100644 --- a/index.js +++ b/index.js @@ -5,12 +5,12 @@ const fs = require('node:fs'); const chokidar = require('chokidar'); -const isLocalIP = require('./utils/isLocalIP.js'); -const { reportedIPs, loadReportedIPs, saveReportedIPs, isIPReportedRecently, markIPAsReported } = require('./services/cache.js'); -const log = require('./utils/log.js'); -const axios = require('./services/axios.js'); -const serverAddress = require('./services/fetchServerIP.js'); -const discordWebhooks = require('./services/discord.js'); +const isLocalIP = require('./scripts/utils/isLocalIP.js'); +const { reportedIPs, loadReportedIPs, saveReportedIPs, isIPReportedRecently, markIPAsReported } = require('./scripts/services/cache.js'); +const log = require('./scripts/utils/log.js'); +const axios = require('./scripts/services/axios.js'); +const serverAddress = require('./scripts/services/fetchServerIP.js'); +const discordWebhooks = require('./scripts/services/discord.js'); const config = require('./config.js'); const { version } = require('./package.json'); const { UFW_LOG_FILE, ABUSEIPDB_API_KEY, SERVER_ID, AUTO_UPDATE_ENABLED, AUTO_UPDATE_SCHEDULE, DISCORD_WEBHOOKS_ENABLED, DISCORD_WEBHOOKS_URL } = config.MAIN; @@ -140,8 +140,8 @@ const processLogLine = async line => { }); // Auto updates - if (AUTO_UPDATE_ENABLED && AUTO_UPDATE_SCHEDULE) await require('./services/updates.js')(); - if (DISCORD_WEBHOOKS_ENABLED && DISCORD_WEBHOOKS_URL) await require('./services/summaries.js')(); + if (AUTO_UPDATE_ENABLED && AUTO_UPDATE_SCHEDULE) await require('./scripts/services/updates.js')(); + if (DISCORD_WEBHOOKS_ENABLED && DISCORD_WEBHOOKS_URL) await require('./scripts/services/summaries.js')(); await discordWebhooks(0, `[UFW-AbuseIPDB-Reporter](https://github.com/sefinek/UFW-AbuseIPDB-Reporter) has been successfully launched on the device \`${SERVER_ID}\`.`); diff --git a/scripts b/scripts new file mode 160000 index 0000000..b18b33a --- /dev/null +++ b/scripts @@ -0,0 +1 @@ +Subproject commit b18b33a087f03b901bcddae3dd8094c66ab436a5 diff --git a/services/axios.js b/services/axios.js deleted file mode 100644 index c329bae..0000000 --- a/services/axios.js +++ /dev/null @@ -1,13 +0,0 @@ -const axios = require('axios'); -const { version } = require('../package.json'); - -axios.defaults.headers.common = { - 'User-Agent': `Mozilla/5.0 (compatible; UFW-AbuseIPDB-Reporter/${version}; +https://github.com/sefinek/UFW-AbuseIPDB-Reporter)`, - 'Accept': 'application/json', - 'Cache-Control': 'no-cache', - 'Connection': 'keep-alive', -}; - -axios.defaults.timeout = 30000; - -module.exports = axios; \ No newline at end of file diff --git a/services/cache.js b/services/cache.js deleted file mode 100644 index d2150ee..0000000 --- a/services/cache.js +++ /dev/null @@ -1,30 +0,0 @@ -const { existsSync, readFileSync, writeFileSync } = require('node:fs'); -const { CACHE_FILE, IP_REPORT_COOLDOWN } = require('../config.js').MAIN; -const log = require('../utils/log.js'); - -const reportedIPs = new Map(); - -const loadReportedIPs = () => { - if (existsSync(CACHE_FILE)) { - readFileSync(CACHE_FILE, 'utf8') - .split('\n') - .forEach(line => { - const [ip, time] = line.split(' '); - if (ip && time) reportedIPs.set(ip, Number(time)); - }); - log(0, `Loaded ${reportedIPs.size} IPs from ${CACHE_FILE}`); - } else { - log(0, `${CACHE_FILE} does not exist. No data to load.`); - } -}; - -const saveReportedIPs = () => writeFileSync(CACHE_FILE, Array.from(reportedIPs).map(([ip, time]) => `${ip} ${time}`).join('\n'), 'utf8'); - -const isIPReportedRecently = ip => { - const reportedTime = reportedIPs.get(ip); - return reportedTime && (Date.now() / 1000 - reportedTime < IP_REPORT_COOLDOWN / 1000); -}; - -const markIPAsReported = ip => reportedIPs.set(ip, Math.floor(Date.now() / 1000)); - -module.exports = { reportedIPs, loadReportedIPs, saveReportedIPs, isIPReportedRecently, markIPAsReported }; \ No newline at end of file diff --git a/services/discord.js b/services/discord.js deleted file mode 100644 index 5f4f28c..0000000 --- a/services/discord.js +++ /dev/null @@ -1,45 +0,0 @@ -const axios = require('axios'); -const log = require('../utils/log.js'); -const { SERVER_ID, DISCORD_WEBHOOKS_ENABLED, DISCORD_WEBHOOKS_URL } = require('../config.js').MAIN; - -const TYPES = { - 0: { type: 'SUCCESS', emoji: '\\✅', color: 0x60D06D }, - 1: { type: 'WARN', emoji: '\\⚠️', color: 0xFFB02E }, - 2: { type: 'ERROR', emoji: '\\❌', color: 0xF92F60 }, - 3: { type: 'FAIL', emoji: '\\🔴', color: 0xF8312F }, - 4: { type: 'INFO', emoji: '\\📄', color: 0xF2EEF8 }, - 5: { type: 'DEBUG', emoji: '\\🛠️', color: 0xB4ACBC }, - 6: { type: 'CRITICAL', emoji: '\\🔴', color: 0xF8312F }, - 7: { type: 'NOTICE', emoji: '\\📝', color: 0xF3EEF8 }, -}; - -module.exports = async (id, description) => { - if (!DISCORD_WEBHOOKS_ENABLED || !DISCORD_WEBHOOKS_URL) return; - - const logType = TYPES[id]; - if (!logType) return log(1, 'Invalid log type ID provided!'); - - const config = { - method: 'POST', - url: DISCORD_WEBHOOKS_URL, - headers: { 'Content-Type': 'application/json' }, - data: { - embeds: [{ - title: `${logType.emoji} ${SERVER_ID}: ${logType.type} [ID ${id}]`, - description, - color: logType.color, - footer: { - text: `Date: ${new Date().toLocaleString()} | sefinek/UFW-AbuseIPDB-Reporter`, - }, - timestamp: new Date().toISOString(), - }], - }, - }; - - try { - const res = await axios(config); - if (res.status !== 204) log(1, 'Failed to deliver Discord Webhook'); - } catch (err) { - log(2, `Failed to send Discord Webhook! ${err.stack}`); - } -}; \ No newline at end of file diff --git a/services/fetchServerIP.js b/services/fetchServerIP.js deleted file mode 100644 index 536815a..0000000 --- a/services/fetchServerIP.js +++ /dev/null @@ -1,41 +0,0 @@ -const { networkInterfaces } = require('node:os'); -const axios = require('./axios.js'); -const isLocalIP = require('../utils/isLocalIP.js'); -const log = require('../utils/log.js'); -const { IP_REFRESH_INTERVAL } = require('../config.js').MAIN; - -const ipAddrList = new Set(); - -const fetchIPv4Address = async () => { - try { - const { data } = await axios.get('https://api.sefinek.net/api/v2/ip'); - if (data?.success && data?.message) ipAddrList.add(data.message); - } catch (err) { - log(2, `Error fetching IPv4 address: ${err.message}`); - } -}; - -const fetchIPv6Address = () => { - try { - Object.values(networkInterfaces()).flat().forEach(({ address, internal }) => { - if (!internal && address && !isLocalIP(address)) ipAddrList.add(address); - }); - } catch (err) { - log(2, `Error fetching IPv6 address: ${err.message}`); - } -}; - -const fetchServerIPs = async () => { - ipAddrList.clear(); - await fetchIPv4Address(); - fetchIPv6Address(); -}; - -(async () => { - await fetchServerIPs(); - setInterval(fetchServerIPs, IP_REFRESH_INTERVAL); - - // console.debug(ipAddrList); -})(); - -module.exports = () => Array.from(ipAddrList); \ No newline at end of file diff --git a/services/reloadApp.js b/services/reloadApp.js deleted file mode 100644 index 9d03217..0000000 --- a/services/reloadApp.js +++ /dev/null @@ -1,24 +0,0 @@ -const { exec } = require('node:child_process'); -const ecosystem = require('../ecosystem.config.js'); -const discordWebhooks = require('./discord.js'); -const log = require('../utils/log.js'); - -const executeCmd = cmd => - new Promise((resolve, reject) => { - exec(cmd, (err, stdout, stderr) => { - if (err || stderr) reject(err || stderr); - else resolve(stdout); - }); - }); - -module.exports = async () => { - const process = ecosystem.apps[0].name; - await discordWebhooks(4, `Restarting the ${process} process...`); - - try { - console.log(await executeCmd('npm install --omit=dev')); - console.log(await executeCmd(`pm2 restart ${process}`)); - } catch (err) { - log(2, err); - } -}; \ No newline at end of file diff --git a/services/summaries.js b/services/summaries.js deleted file mode 100644 index ea89035..0000000 --- a/services/summaries.js +++ /dev/null @@ -1,73 +0,0 @@ -const { CronJob } = require('cron'); -const fs = require('node:fs/promises'); -const discordWebhooks = require('./discord.js'); -const log = require('../utils/log.js'); -const { CACHE_FILE } = require('../config.js').MAIN; - -const formatHourRange = hour => `${hour.toString().padStart(2, '0')}:00-${hour.toString().padStart(2, '0')}:59`; -const pluralizeReport = count => (count === 1 ? 'report' : 'reports'); - -const sendWebhook = async () => { - try { - await fs.access(CACHE_FILE); - } catch { - return log(2, `Cache file not found: ${CACHE_FILE}`); - } - - let data; - try { - data = (await fs.readFile(CACHE_FILE, 'utf8')).trim(); - } catch (err) { - return log(2, `Error reading file: ${err.message}`); - } - - if (!data) { - log(0, `Cache file is empty: ${CACHE_FILE}`); - return discordWebhooks(4, `Cache file is empty: \`${CACHE_FILE}\``); - } - - try { - const yesterday = new Date(); - yesterday.setUTCDate(yesterday.getUTCDate() - 1); - const yesterdayString = yesterday.toISOString().split('T')[0]; - - const hourlySummary = {}; - const uniqueEntries = new Set(); - - data.split('\n').forEach((line) => { - const [ip, timestamp] = line.split(' '); - if (!ip || isNaN(timestamp)) return; - - const entryKey = `${ip}_${timestamp}`; - if (uniqueEntries.has(entryKey)) return; - uniqueEntries.add(entryKey); - - const dateObj = new Date(parseInt(timestamp, 10) * 1000); - if (dateObj.toISOString().split('T')[0] !== yesterdayString) return; - - const hour = dateObj.getUTCHours(); - hourlySummary[hour] = (hourlySummary[hour] || 0) + 1; - }); - - const totalReports = Object.values(hourlySummary).reduce((sum, count) => sum + count, 0); - const sortedEntries = Object.entries(hourlySummary).sort((a, b) => b[1] - a[1]); - const maxReports = sortedEntries.length > 0 ? sortedEntries[0][1] : 0; - const topHours = sortedEntries - .filter(([, count]) => count === maxReports && count > 1) - .map(([hour]) => parseInt(hour)); - - const summaryString = Object.entries(hourlySummary) - .map(([hour, count]) => `${formatHourRange(parseInt(hour))}: ${count} ${pluralizeReport(count)}${topHours.includes(parseInt(hour)) ? ' 🔥' : ''}`) - .join('\n'); - - await discordWebhooks(7, `Midnight. Summary of IP address reports (${totalReports}) from yesterday (${yesterdayString}).\nGood night to you, sleep well! 😴\n\`\`\`${summaryString}\`\`\``); - log(0, `Reported IPs yesterday by hour:\n${summaryString}\nTotal reported IPs: ${totalReports} ${pluralizeReport(totalReports)}`); - } catch (err) { - log(2, err); - } -}; - -module.exports = async () => { - // await sendWebhook(); - new CronJob('0 0 * * *', sendWebhook, null, true, 'UTC'); -}; \ No newline at end of file diff --git a/services/updates.js b/services/updates.js deleted file mode 100644 index 9100109..0000000 --- a/services/updates.js +++ /dev/null @@ -1,36 +0,0 @@ -const { AUTO_UPDATE_SCHEDULE } = require('../config.js').MAIN; - -const simpleGit = require('simple-git'); -const { CronJob } = require('cron'); -const restartApp = require('./reloadApp.js'); -const log = require('../utils/log.js'); -const discordWebhooks = require('./discord.js'); - -const git = simpleGit(); - -const pull = async () => { - await discordWebhooks(4, 'Updating the local repository in progress `(git pull)`...'); - log(0, 'Running git pull...'); - - try { - const { summary } = await git.pull(); - log(0, `Changes: ${summary.changes}; Deletions: ${summary.insertions}; Insertions: ${summary.insertions}`); - await discordWebhooks(4, `**Changes:** ${summary.changes}; **Deletions:** ${summary.insertions}; **Insertions:** ${summary.insertions}`); - } catch (err) { - log(2, err); - } -}; - -const pullAndRestart = async () => { - try { - await pull(); - await restartApp(); - } catch (err) { - log(2, err); - } -}; - -// https://crontab.guru -new CronJob(AUTO_UPDATE_SCHEDULE, pullAndRestart, null, true, 'UTC'); - -module.exports = pull; \ No newline at end of file diff --git a/utils/isLocalIP.js b/utils/isLocalIP.js deleted file mode 100644 index 7d7ab80..0000000 --- a/utils/isLocalIP.js +++ /dev/null @@ -1,10 +0,0 @@ -const ipaddr = require('ipaddr.js'); - -module.exports = ip => { - const range = ipaddr.parse(ip).range(); - return [ - 'unspecified', 'multicast', 'linkLocal', 'loopback', 'reserved', 'benchmarking', - 'amt', 'broadcast', 'carrierGradeNat', 'private', 'as112', 'uniqueLocal', - 'ipv4Mapped', 'rfc6145', '6to4', 'teredo', 'as112v6', 'orchid2', 'droneRemoteIdProtocolEntityTags', - ].includes(range); -}; \ No newline at end of file diff --git a/utils/log.js b/utils/log.js deleted file mode 100644 index c743e7c..0000000 --- a/utils/log.js +++ /dev/null @@ -1,14 +0,0 @@ -const discordWebhooks = require('../services/discord.js'); - -const levels = { - 0: { method: 'log', label: '[INFO]' }, - 1: { method: 'warn', label: '[WARN]' }, - 2: { method: 'error', label: '[FAIL]' }, -}; - -module.exports = (level, msg) => { - const { method, label } = levels[level] || { method: 'log', label: '[N/A]' }; - console[method](`${label} ${msg}`); - - if (level >= 1) discordWebhooks(level, msg).catch(console.error); -}; \ No newline at end of file