#!/usr/bin/env python3 """Backup production CCR and dump hotspot/PPPoE data. Read-mostly: only creates backup files.""" from __future__ import annotations import ftplib import io import json import os import sys from datetime import datetime, timezone from pathlib import Path ROOT = Path(r"c:\laragon\www\light-otantik-nux") sys.path.insert(0, str(ROOT / "scripts")) from mk_client import connect, ok, replies_to_rows, talk, trap_msg # noqa: E402 OUT = ROOT / "deploy" / "prod-import" STAMP = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S") BACKUP_NAME = f"woyla-before-nuxbill-{STAMP}" def dump(sock, path: str) -> list[dict]: return replies_to_rows(talk(sock, [path])) def main() -> int: OUT.mkdir(parents=True, exist_ok=True) sock = connect() print("logged in") r = talk(sock, ["/system/backup/save", f"=name={BACKUP_NAME}", "=dont-encrypt=yes"]) print("backup", "OK" if ok(r) else trap_msg(r)) if not ok(r): sock.close() return 1 r = talk(sock, ["/export", f"=file={BACKUP_NAME}"]) print("export", "OK" if ok(r) else trap_msg(r)) if not ok(r): sock.close() return 1 files = dump(sock, "/file/print") names = [f.get("name", "") for f in files] print("backup files on router:", [n for n in names if BACKUP_NAME in n]) data = { "stamp": STAMP, "backup_name": BACKUP_NAME, "identity": dump(sock, "/system/identity/print"), "resource": dump(sock, "/system/resource/print"), "hotspot": dump(sock, "/ip/hotspot/print"), "hotspot_profile": dump(sock, "/ip/hotspot/profile/print"), "hotspot_user_profile": dump(sock, "/ip/hotspot/user/profile/print"), "hotspot_users": dump(sock, "/ip/hotspot/user/print"), "hotspot_active": dump(sock, "/ip/hotspot/active/print"), "hotspot_hosts": dump(sock, "/ip/hotspot/host/print"), "ip_binding": dump(sock, "/ip/hotspot/ip-binding/print"), "walled_garden": dump(sock, "/ip/hotspot/walled-garden/print"), "walled_garden_ip": dump(sock, "/ip/hotspot/walled-garden/ip/print"), "ppp_profile": dump(sock, "/ppp/profile/print"), "ppp_secret": dump(sock, "/ppp/secret/print"), "ppp_active": dump(sock, "/ppp/active/print"), "pppoe_server": dump(sock, "/interface/pppoe-server/server/print"), "ip_pool": dump(sock, "/ip/pool/print"), "ip_address": dump(sock, "/ip/address/print"), "radius": dump(sock, "/radius/print"), } sock.close() json_path = OUT / f"mikrotik-dump-{STAMP}.json" json_path.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8") print("wrote", json_path) print( "counts", "hs_users", len(data["hotspot_users"]), "ppp", len(data["ppp_secret"]), "hs_profiles", len(data["hotspot_user_profile"]), "ppp_profiles", len(data["ppp_profile"]), "hs_active", len(data["hotspot_active"]), "ppp_active", len(data["ppp_active"]), ) host = os.environ.get("MK_HOST", "10.15.15.1") user = os.environ.get("MK_USER", "admin") password = os.environ.get("MK_PASS", "") ftp = ftplib.FTP() ftp.encoding = "latin-1" ftp.connect(host, 21, timeout=20) ftp.login(user, password) ftp.set_pasv(True) def ftp_get(remote: str, local: Path) -> None: buf = io.BytesIO() ftp.retrbinary(f"RETR {remote}", buf.write) local.write_bytes(buf.getvalue()) print("ftp", remote, "->", local, local.stat().st_size) ftp_get(f"{BACKUP_NAME}.backup", OUT / f"{BACKUP_NAME}.backup") try: ftp_get(f"{BACKUP_NAME}.rsc", OUT / f"{BACKUP_NAME}.rsc") except Exception as e: print("rsc download skip", e) hs_dir = OUT / "woylaHotspot" hs_dir.mkdir(exist_ok=True) try: ftp.cwd("woylaHotspot") names = [] ftp.retrlines("NLST", names.append) print("hotspot files", names) for name in names: base = name.split("/")[-1] if not base or base in (".", ".."): continue try: ftp_get(base, hs_dir / base) except Exception as e: print("skip", base, e) except Exception as e: print("hotspot dir", e) ftp.quit() print("DONE") return 0 if __name__ == "__main__": raise SystemExit(main())