diff --git a/app/firewall.py b/app/firewall.py index 8ab21c2..9d34af6 100644 --- a/app/firewall.py +++ b/app/firewall.py @@ -143,9 +143,11 @@ class MikrotikFirewall: """Save current address lists and firewall rules for auditing. Writes structured snapshots to ``/mikrotik/data/`` and to - the common ``data/mikrotik/`` directory (overwritten every run so git - can diff changes to the firewall state over time), plus a timestamped - JSON report under ``/mikrotik/``. + the common ``data/mikrotik/`` directory (scratch mirror for debugging; + **not** a git audit trail), plus a timestamped JSON report under + ``/mikrotik/``. The authoritative long-term audit trail is + the SQLite ``firewall_snapshots`` tables (see ``app.db``), which are + appended here regardless of the file writes. """ state = { "router": self.router_ip, @@ -174,11 +176,31 @@ class MikrotikFirewall: os.path.join(data_dir, f"firewall_{version}.json"), state["firewall_rules"][version]) + # Persist a SQLite snapshot (authoritative audit trail, independent of + # git). Best-effort: never breaks a router sync if the DB is down. + self._persist_snapshot_to_db(state) + timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M-%S") report_path = os.path.join(report_dir, "mikrotik", f"{timestamp}.json") self._write_json(report_path, state) return report_path + @staticmethod + def _persist_snapshot_to_db(state: dict) -> None: + """Write the firewall snapshot into the SQLite store (best-effort).""" + try: + from .db import get_connection + from .db import repositories as repo + repo.save_firewall_snapshot( + get_connection(), + address_lists_v4=state["address_lists"]["ipv4"], + address_lists_v6=state["address_lists"]["ipv6"], + rules_v4=state["firewall_rules"]["ipv4"], + rules_v6=state["firewall_rules"]["ipv6"], + ) + except Exception as exc: + print(f"WARN: could not persist firewall snapshot to db ({exc})") + def sync_blocklist(self, blocked_ipv4: Iterable[str], blocked_ipv6: Iterable[str], dry_run: bool = False, @@ -186,10 +208,12 @@ class MikrotikFirewall: """Synchronize the Blocked list cumulatively and audit the result. The blocklist is grow-only: targets are added when present in the - complete set (all git history of the blocklist files + the current - working files + this scan's targets) and never removed automatically. - Only router entries absent from the complete set — i.e. never blocked - by the system — are pruned. See ``app.blocklist_history``. + complete cumulative set (git history of the blocklist files + the + current working files + this scan's targets) and never removed + automatically. Only router entries absent from the complete set — i.e. + never blocked by the system — are pruned. The history now lives in the + SQLite blocklist ledger (``app.db`` ``blocklist_entries``); git history + remains a read-only fallback. See ``app.blocklist_history``. """ desired_ipv4 = set(blocked_ipv4)