#!/usr/bin/env python3 """Clone RB2011 local hotspot web files onto RB4011. RB2011 is not modified.""" from __future__ import annotations from io import BytesIO from pathlib import Path import re import paramiko from mk_hub_exec import HOST as HUB_HOST from mk_hub_exec import PASSWORD as HUB_PASS from mk_hub_exec import USER as HUB_USER from mk_nko_wg_exec import connect_mk, import_rsc, run as nko_run ROOT_FILES = [ "login.html", "alogin.html", "status.html", "error.html", "logout.html", "ticket.html", "chat.html", "apropos.html", "services.html", "code.html", "pay.html", "md5.js", "logo.png", "favicon.png", "bundle.jpg", "slide-1.jpg", "slide-2.jpg", "slide-3.jpg", ] DIRS = ["styles", "fonts", "scripts"] RSC = r""" :put "NKO-CLONE-HUB-HS START" /ip hotspot set [find name=hotspot1] addresses-per-mac=2 :do { /ip hotspot profile set [find name=hsprof1] dns-name="" html-directory=hotspot http-cookie-lifetime=1h } on-error={} :put "NKO-CLONE-HUB-HS RSC DONE" /ip hotspot print detail """ def rewrite_html(data: bytes) -> bytes: t = data.decode("utf-8", "replace") # Keep otantik-hub-nkoabang intact; rewrite leftover hub slug in JS/forms/URLs. t = re.sub(r"otantik-hub(?!-nkoabang)", "otantik-hub-nkoabang", t) t = t.replace( "http://83.147.38.107/hotspot/otantik-hub/", "http://83.147.38.107/hotspot/otantik-hub-nkoabang/", ) t = t.replace("Otantik Hub", "Otantik Hub Nkoabang") t = t.replace("Otantik Hub Nkoabang Nkoabang", "Otantik Hub Nkoabang") return t.encode("utf-8") def sftp_get_bytes(sftp, remote: str) -> bytes: bio = BytesIO() sftp.getfo(remote, bio) return bio.getvalue() def sftp_listdir(sftp, path: str) -> list[str]: try: return sftp.listdir(path) except OSError: return [] def sftp_put_bytes(sftp, remote: str, data: bytes) -> None: remote = remote.replace("\\", "/") parts = remote.split("/") if len(parts) > 1: cur = "" for p in parts[:-1]: cur = f"{cur}/{p}" if cur else p try: sftp.stat(cur) except OSError: try: sftp.mkdir(cur) except OSError: pass with sftp.file(remote, "wb") as f: f.write(data) def hub_connect() -> paramiko.SSHClient: mk = paramiko.SSHClient() mk.set_missing_host_key_policy(paramiko.AutoAddPolicy()) mk.connect( HUB_HOST, username=HUB_USER, password=HUB_PASS, timeout=25, banner_timeout=30, allow_agent=False, look_for_keys=False, ) return mk def pull_hub() -> dict[str, bytes]: mk = hub_connect() sftp = mk.open_sftp() out: dict[str, bytes] = {} try: for name in ROOT_FILES: remote = "hotspot/" + name try: data = sftp_get_bytes(sftp, remote) out[name] = data print("hub get", name, len(data)) except OSError as e: print("hub miss", name, e) for folder in DIRS: for fn in sftp_listdir(sftp, "hotspot/" + folder): rel = folder + "/" + fn remote = "hotspot/" + rel try: if sftp.stat(remote).st_mode & 0o40000: continue except OSError: pass try: data = sftp_get_bytes(sftp, remote) out[rel] = data print("hub get", rel, len(data)) except OSError as e: print("hub miss", rel, e) return out finally: sftp.close() mk.close() def main() -> None: idn = nko_run("/system identity print") print("target", idn) if "Nkoabang" not in idn: raise SystemExit("refusing: not RB4011 Nkoabang") hub_id = Path("_tmp_hub_hs_dump.txt").read_text(encoding="utf-8", errors="replace")[:80] print("source expected Otantik Hub / RB2011") files = pull_hub() if "login.html" not in files or "styles/bootstrap.css" not in files: raise SystemExit("hub pull incomplete: " + ", ".join(sorted(files)[:12])) patched: dict[str, bytes] = {} for rel, data in files.items(): if rel.endswith((".html", ".js", ".css")): if rel.endswith((".html", ".js")): data = rewrite_html(data) data = data.replace(b"\r\n", b"\n").replace(b"\r", b"\n") patched[rel] = data print(import_rsc("wo-clone-hub-hs.rsc", RSC, timeout=60)) jump, mk = connect_mk() try: sftp = mk.open_sftp() for rel, data in patched.items(): sftp_put_bytes(sftp, "hotspot/" + rel, data) print("nko put", rel, len(data)) with sftp.file("hotspot/login.html", "r") as f: body = f.read() if isinstance(body, bytes): body = body.decode("utf-8", "replace") print("verify styles/bootstrap.css", "styles/bootstrap.css" in body) print("verify wo.css", 'href="wo.css"' in body) print("verify woHotspotLite", "woHotspotLite" in body) print("verify ui/ui_custom", "ui/ui_custom" in body) print("verify nkoabang slug", "otantik-hub-nkoabang" in body) print("verify title Nkoabang", "Otantik Hub Nkoabang" in body) for rel in ["styles/bootstrap.css", "styles/style.css", "fonts/bootstrap-icons.css", "scripts/custom.js", "scripts/bootstrap.min.js"]: try: print("size", rel, sftp.stat("hotspot/" + rel).st_size) except OSError: print("MISS", rel) sftp.close() finally: mk.close() jump.close() print("DONE cloned RB2011 local hotspot onto RB4011") if __name__ == "__main__": main()