feat: plug-and-play refactor — docker-npm action, CF support, whitelist live-update

- Replace iptables-allports with docker-npm action (DOCKER-USER + xt_string
  X-Forwarded-For matching + INPUT chain) matching user's working setup
- Add telegram_notif.sh (deployed to /data/action.d/ at first run, user-editable)
- Add cloudflare.conf action; jail.cloudflare.local enabled via CF compose file
- Two compose files: docker-compose.yml (standard) and docker-compose.cloudflare.yml
- entrypoint: modprobe xt_string, DOCKER-USER chain check, CF jail auto-selection,
  telegram_notif.sh deployment to persistent volume on first run
- Fix whitelist live-update: addignoreip/delignoreip called alongside jail.local write
- Hardcode AUTOBAN_THR=75 and DEFAULT_DAYS=3 (remove env vars)
- Include Nginx Proxy Manager in both compose files with shared log bind mount
- Rewrite filters for actual NPM log format ([Client <HOST>] real IP extraction)
- Add DATA_DIR, Telegram, CF API key fields to .env.example

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-20 15:08:06 +00:00
parent dd7f8dd1a2
commit 920b69cfca
14 changed files with 446 additions and 224 deletions

View File

@@ -19,9 +19,9 @@ const CF_SYNC = process.env.CF_SYNC || '/usr/local/bin/cloudflare-wh
const MANUAL_JAIL = process.env.MANUAL_JAIL || 'manual-bans';
const BAN_HIST_FILE = process.env.BAN_HIST_FILE || '/data/ban-history.json';
const SUBNETS = (process.env.SUBNETS_TO_IGNORE || '10.0.0.0/8,172.16.0.0/12').split(',').map(s => s.trim());
const DEFAULT_DAYS = parseInt(process.env.DEFAULT_LOOKBACK_DAYS || '3');
const DEFAULT_DAYS = 3;
const ABUSE_KEY = process.env.ABUSEIPDB_API_KEY;
const AUTOBAN_THR = parseInt(process.env.AUTOBAN_THRESHOLD || '75');
const AUTOBAN_THR = 75;
// Optional: POST to this URL on every manual ban (Discord, Slack, n8n, etc.)
const WEBHOOK_URL = process.env.WEBHOOK_URL || '';
@@ -73,11 +73,12 @@ async function addWhitelist(ip, note) {
}
}
fs.writeFileSync(JAIL_LOCAL, lines.join('\n'));
// unban from all jails
// Live-update running fail2ban (no reload needed)
const jails = await getJails();
await Promise.all(jails.map(j =>
run(`fail2ban-client set ${j} unbanip ${ip}`).catch(() => {})
));
await Promise.all(jails.map(j => Promise.all([
run(`fail2ban-client set ${j} addignoreip ${ip}`).catch(() => {}),
run(`fail2ban-client set ${j} unbanip ${ip}`).catch(() => {}),
])));
if (fs.existsSync(CF_SYNC)) exec(`${CF_SYNC} &`);
}
@@ -87,6 +88,11 @@ async function removeWhitelist(ip) {
new RegExp(`\\s*${ip.replace(/\./g, '\\.')}(?:\\s*#[^\\n]*)?`, 'g'), ''
);
fs.writeFileSync(JAIL_LOCAL, updated);
// Live-update running fail2ban (no reload needed)
const jails = await getJails();
await Promise.all(jails.map(j =>
run(`fail2ban-client set ${j} delignoreip ${ip}`).catch(() => {})
));
if (fs.existsSync(CF_SYNC)) exec(`${CF_SYNC} &`);
}