# Unit 07 — Operational Scripts ## Goal CLI scripts for database initialization, subscriber sync, JSON export, and Raspberry Pi WiFi AP setup. ## Files - `scripts/init_db.py` - `scripts/sync_subscribers.py` - `scripts/export_subscribers.py` - `scripts/setup_wifi_ap.sh` --- ## `scripts/init_db.py` ```python #!/usr/bin/env python3 """Initialize the SQLite subscriber database.""" import sys, os sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) from server.utils import load_config from server.database import init_db def main(): config_path = os.environ.get("CONFIG_PATH", "config.yaml") cfg = load_config(config_path) db_path = cfg["database"]["path"] os.makedirs(os.path.dirname(db_path), exist_ok=True) init_db(db_path) print(f"Database initialized at {db_path}") if __name__ == "__main__": main() ``` --- ## `scripts/sync_subscribers.py` ```python #!/usr/bin/env python3 """Sync unsynced subscribers to the configured core network.""" import sys, os, logging sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) from server.utils import load_config from server.database import get_connection, list_unsynced, mark_synced from server.core_adapters import get_adapter logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) def main(): config_path = os.environ.get("CONFIG_PATH", "config.yaml") cfg = load_config(config_path) adapter = get_adapter(cfg) if adapter is None: logger.error("No core adapter configured. Set core.type in config.") sys.exit(1) conn = get_connection(cfg["database"]["path"]) unsynced = list_unsynced(conn) logger.info(f"Found {len(unsynced)} unsynced subscribers") success = 0 for sub in unsynced: try: adapter.add_subscriber(sub.imsi, sub.ki, sub.opc) mark_synced(conn, sub.imsi) success += 1 logger.info(f"Synced {sub.imsi}") except Exception as e: logger.error(f"Failed to sync {sub.imsi}: {e}") conn.close() logger.info(f"Sync complete: {success}/{len(unsynced)} succeeded") if __name__ == "__main__": main() ``` --- ## `scripts/export_subscribers.py` ```python #!/usr/bin/env python3 """Export all subscribers to JSON on stdout.""" import sys, os, json from datetime import datetime sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) from server.utils import load_config from server.database import get_connection, export_all def main(): config_path = os.environ.get("CONFIG_PATH", "config.yaml") cfg = load_config(config_path) conn = get_connection(cfg["database"]["path"]) subscribers = export_all(conn) conn.close() output = { "exported_at": datetime.utcnow().isoformat() + "Z", "count": len(subscribers), "subscribers": subscribers } print(json.dumps(output, indent=2, default=str)) if __name__ == "__main__": main() ``` --- ## `scripts/setup_wifi_ap.sh` Raspberry Pi WiFi AP setup. Must be run as root. Steps: 1. Install packages: `hostapd dnsmasq nginx python3 python3-pip` 2. Configure static IP on `wlan0`: `/etc/dhcpcd.conf` entry `interface wlan0 static ip_address=192.168.4.1/24` 3. Write `/etc/hostapd/hostapd.conf` (SSID=Emergency-5G, open auth, channel 6) 4. Write `/etc/dnsmasq.conf` (DHCP range 192.168.4.10-250, `address=/#/192.168.4.1`) 5. Write nginx site config (proxy `/api/` → Flask, serve portal static) 6. Enable `net.ipv4.ip_forward=1` in `/etc/sysctl.conf` 7. Add iptables rules: all port 80 from `wlan0` → `192.168.4.1:80` (captive portal) 8. Enable and start: `hostapd`, `dnsmasq`, `nginx`, `esim-provisioning` 9. Install Python dependencies: `pip3 install -r /opt/esim-provisioning/requirements.txt` 10. Print success message and next steps Script structure: ```bash #!/bin/bash set -euo pipefail [ "$(id -u)" = "0" ] || { echo "Must run as root"; exit 1; } SSID="Emergency-5G" CHANNEL=6 AP_IP="192.168.4.1" DHCP_RANGE="192.168.4.10,192.168.4.250,24h" INSTALL_DIR="/opt/esim-provisioning" ... ``` --- ## Verification ```bash # init_db CONFIG_PATH=config.yaml python scripts/init_db.py # export (after some provisioning) CONFIG_PATH=config.yaml python scripts/export_subscribers.py | python -m json.tool # sync (requires core adapter running) CONFIG_PATH=config.yaml python scripts/sync_subscribers.py ```