#!/bin/bash # ============================================================= # CrowdSec Local Attack — 4-Hour Digest Mailer # Host: NAS16 (192.168.150.40) # Schedule: Every 4 hours via OMV Scheduled Jobs # Recommended cron: 0 0,4,8,12,16,20 * * * # # Reads: /var/log/crowdsec-notifier/local_pending.json # Sends: One batched HTML email: # - Dark header + red status banner # - 3 stat cards: Local Bans / Origin / CAPI Today # - Single table: IP Address / Scenario / Type / # Duration / Scope / Detected At / Links # - Amber CAPI footer note # - Dark footer # Clears: local_pending.json after a successful send. # Skips: Send entirely if there are zero pending bans. # ============================================================= SMTP_HOST="smtppro.zoho.com" SMTP_PORT="465" SMTP_USER="rufus.king@kingdezigns.com" SMTP_PASS="Valerie8545" FROM_EMAIL="rufus.king@kingdezigns.com" TO_EMAIL="rufus.king@kingdezigns.com" LOCAL_PENDING="/var/log/crowdsec-notifier/local_pending.json" CAPI_DIGEST="/var/log/crowdsec-notifier/capi_digest.json" LOG_FILE="/var/log/crowdsec-notifier/notifier.log" mkdir -p /var/log/crowdsec-notifier touch "$LOCAL_PENDING" touch "$CAPI_DIGEST" log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"; } log "--- cs_local_digest run start ---" # --- Count pending bans --- PENDING_COUNT=$(grep -c . "$LOCAL_PENDING" 2>/dev/null || echo 0) PENDING_COUNT=$(echo "$PENDING_COUNT" | tr -d ' ') if [[ "$PENDING_COUNT" -eq 0 ]]; then log "No local bans in this window. Skipping email." log "--- cs_local_digest run complete (no bans) ---" exit 0 fi log "Building digest for $PENDING_COUNT local ban(s)." NOW=$(date '+%Y-%m-%d %H:%M:%S UTC') CAPI_TODAY=$(wc -l < "$CAPI_DIGEST" 2>/dev/null | tr -d ' ') CAPI_TODAY=${CAPI_TODAY:-0} # Snapshot the pending file before clearing SNAPSHOT=$(cat "$LOCAL_PENDING") # --- Build table rows --- # NOTE: $SNAPSHOT is expanded inline inside the Python heredoc. # Do NOT pipe into python3 - with a heredoc: the heredoc becomes stdin, # silently replacing the pipe. Pass data as an inline variable instead. TABLE_ROWS=$(python3 << PYEOF import json snapshot = """$SNAPSHOT""" rows = [] for line in snapshot.strip().splitlines(): line = line.strip() if not line: continue try: d = json.loads(line) except Exception: continue ip = d.get("value", "unknown") scenario = d.get("scenario", "unknown") duration = d.get("duration", "unknown") ban_type = d.get("type", "ban").upper() scope = d.get("scope", "Ip").upper() ban_time = d.get("start_at", "") if ban_time: ban_time = ban_time[:19].replace("T", " ") + " UTC" else: ban_time = "unknown" rows.append(f""" 🌐 {ip} {scenario} {ban_type} {duration} {scope} {ban_time} CTI WHOIS Abuse """) print("\n".join(rows)) PYEOF ) log "Generated ${PENDING_COUNT} table row(s)." # --- Write the full email HTML --- HTML_FILE=$(mktemp /tmp/cs_local_digest_XXXXXX.html) cat > "$HTML_FILE" << HTMLEOF
CrowdSec Intrusion Prevention
🛡 HAS — homeassistant
$NOW  |  Machine: homeassistant
🚨 LOCAL BANS — 4H DIGEST
🚨  $PENDING_COUNT locally-detected attack(s) banned in the past 4 hours.
Local Bans
$PENDING_COUNT
this window
Origin
⚡ LOCAL
crowdsec detection
CAPI Today
$CAPI_TODAY
cloud blocklist bans
📌 Banned IPs — Past 4 Hours
$TABLE_ROWS
IP Address Scenario Type Duration Scope Detected At Links
⛅  Cloud blocklist (CAPI):  $CAPI_TODAY pre-emptive ban(s) recorded today. Full CAPI digest delivered at midnight by cs_digest.sh.
CrowdSec Intrusion Prevention  |  homeassistant (HAS)  |  KingDezigns Infrastructure
HTMLEOF # --- Send the email --- python3 << PYEOF >> "$LOG_FILE" 2>&1 import smtplib, ssl from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText with open("$HTML_FILE") as f: html_body = f.read() msg = MIMEMultipart("alternative") msg["Subject"] = "CrowdSec 4H Local Digest — $PENDING_COUNT Ban(s) on HAS" msg["From"] = "CrowdSec KingDezigns <$FROM_EMAIL>" msg["To"] = "$TO_EMAIL" msg.attach(MIMEText(html_body, "html")) ctx = ssl.create_default_context() with smtplib.SMTP_SSL("$SMTP_HOST", $SMTP_PORT, context=ctx, timeout=15) as s: s.login("$SMTP_USER", "$SMTP_PASS") s.sendmail("$FROM_EMAIL", ["$TO_EMAIL"], msg.as_string()) print("sent") PYEOF SEND_STATUS=$? rm -f "$HTML_FILE" if [[ $SEND_STATUS -eq 0 ]]; then log "4-hour digest sent. $PENDING_COUNT ban(s) reported. Clearing local_pending.json." > "$LOCAL_PENDING" else log "ERROR: Digest email failed. local_pending.json NOT cleared — will retry next run." fi log "--- cs_local_digest run complete ---"