Add database backups and automatic restore

Daemon job "backup" (online copy, quick_check, rotation), restore of the
newest valid copy when the database cannot be opened, change journal reset
after restore, last_restore in /health, docs and tests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
ayurishchevandClaude Sonnet 5 committed 2026-09-21 08:55:42 +03:00
1 parent c99665542a
commit 02f7b49e12
10 files changed
+294 -21

No files matched your search

+17 -4
View File
@@ -14,13 +14,27 @@ from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.triggers.cron import CronTrigger
import cidr_collector as cc
import db
from cidr_collector import CIDRCollector, FQDNCollector, load_full_config
from storage import StorageError, save_json_atomic, try_lock
logger = logging.getLogger("collector_daemon")
DEFAULT_CRONS = {"asn": "0 2 * * *", "fqdn": "0 3 * * *"}
COLLECTORS = {"asn": CIDRCollector, "fqdn": FQDNCollector}
DEFAULT_CRONS = {"asn": "0 2 * * *", "fqdn": "0 3 * * *", "backup": "30 4 * * *"}
def run_backup():
"""Задание backup: онлайн-копия базы, проверка, ротация (число копий - backup_keep в config.json)."""
keep = max(1, int(load_full_config().get("backup_keep", cc.DEFAULT_BACKUP_KEEP)))
with db.session() as conn:
path = db.backup_database(conn, datetime.datetime.now(), keep)
logger.info("Database backup written to %s (keeping the last %d)", path, keep)
# Задания: имя -> вызываемый объект (новый экземпляр на каждый запуск - свежий config.json)
COLLECTORS = {"asn": lambda: CIDRCollector().run_collection(),
"fqdn": lambda: FQDNCollector().run_collection(),
"backup": run_backup}
LOCK_FILE = os.path.join(cc.DATA_DIR, "collector.daemon.lock")
# Состояние заданий: действующий cron, результат последнего запуска, отклонённый cron (чтобы не спамить логом)
@@ -58,8 +72,7 @@ def run_job(name, scheduler):
state["running"] = True
write_status(scheduler)
try:
# Новый экземпляр на каждый запуск - свежий config.json
COLLECTORS[name]().run_collection()
COLLECTORS[name]()
state["last_error"] = None
except Exception as e:
logger.exception("%s collection failed", name)