import os import json import subprocess import threading NETWORK_CONFIG_PATH = "/etc/aismap/network.json" NETWORK_SCRIPTS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "scripts") NETWORK_DEFAULTS = { "mode": "ap", "wifi_ssid": "", "wifi_psk": "", "wifi_ip": "192.168.22.50/24", "wifi_gw": "192.168.22.1", "wifi_dns": "8.8.8.8", "ap_ip": "192.168.4.1/24", "ap_ssid": "", "ap_psk": "", "iface": "wlan0", } HOSTAPD_CONF = "/etc/hostapd/hostapd.conf" network_config_lock = threading.Lock() def _read_hostapd_conf() -> dict: """Reads SSID, passphrase and interface from the real hostapd.conf.""" result = {} try: with open(HOSTAPD_CONF, "r") as f: for line in f: line = line.strip() if line.startswith("#") or "=" not in line: continue k, v = line.split("=", 1) k, v = k.strip(), v.strip() if k == "ssid": result["ap_ssid"] = v elif k == "wpa_passphrase": result["ap_psk"] = v elif k == "interface": result["iface"] = v except (FileNotFoundError, PermissionError): pass return result def load_network_config() -> dict: merged = dict(NETWORK_DEFAULTS) hostapd_vals = _read_hostapd_conf() merged.update(hostapd_vals) try: with open(NETWORK_CONFIG_PATH, "r") as f: cfg = json.load(f) merged.update(cfg) except (FileNotFoundError, json.JSONDecodeError, PermissionError): pass return merged def save_network_config(cfg: dict): os.makedirs(os.path.dirname(NETWORK_CONFIG_PATH), exist_ok=True) with open(NETWORK_CONFIG_PATH, "w") as f: json.dump(cfg, f, indent=2) def get_current_network_info() -> dict: """Reads live network state from the OS (Linux only).""" info = {"ip": None, "ssid": None, "mode": None, "iface": None} cfg = load_network_config() iface = cfg.get("iface", "wlan0") info["iface"] = iface try: out = subprocess.check_output( ["ip", "-4", "addr", "show", iface], timeout=5, stderr=subprocess.DEVNULL ).decode() for line in out.splitlines(): line = line.strip() if line.startswith("inet "): info["ip"] = line.split()[1] break except Exception: pass try: out = subprocess.check_output( ["iw", "dev", iface, "info"], timeout=5, stderr=subprocess.DEVNULL ).decode() for line in out.splitlines(): line = line.strip() if line.startswith("ssid"): info["ssid"] = line.split(None, 1)[1] if line.startswith("type"): tp = line.split(None, 1)[1].lower() if "ap" in tp: info["mode"] = "ap" elif "managed" in tp or "station" in tp: info["mode"] = "wifi" except Exception: pass if info["mode"] is None: try: subprocess.check_output( ["pgrep", "-x", "hostapd"], timeout=3, stderr=subprocess.DEVNULL ) info["mode"] = "ap" except Exception: try: subprocess.check_output( ["pgrep", "-x", "wpa_supplicant"], timeout=3, stderr=subprocess.DEVNULL ) info["mode"] = "wifi" except Exception: info["mode"] = cfg.get("mode", "ap") return info def switch_network_mode(target_mode: str) -> dict: """Runs the appropriate script to switch AP<->WiFi. Returns result dict.""" if target_mode not in ("ap", "wifi"): return {"ok": False, "error": "Invalid mode, must be 'ap' or 'wifi'"} script_name = "to_ap.sh" if target_mode == "ap" else "to_wifi.sh" script_path = os.path.join(NETWORK_SCRIPTS_DIR, script_name) if not os.path.isfile(script_path): return {"ok": False, "error": f"Script not found: {script_path}"} try: proc = subprocess.run( ["bash", script_path], capture_output=True, text=True, timeout=30, ) if proc.returncode == 0: cfg = load_network_config() cfg["mode"] = target_mode save_network_config(cfg) return {"ok": True, "output": proc.stdout[-500:] if proc.stdout else ""} else: return { "ok": False, "error": f"Script exited with code {proc.returncode}", "output": (proc.stdout or "") + (proc.stderr or ""), } except subprocess.TimeoutExpired: return {"ok": False, "error": "Script timed out (30s)"} except Exception as e: return {"ok": False, "error": str(e)}