Push Tracker
ria-ran--sim-drop/plan/08-system-configs.md
A ashkan@beigi.net 8672b0de6b Add Phase-1 eSIM provisioning implementation
Full working closed-loop eSIM provisioning stack (spec in claude.md / plan/):
Flask API, SQLite subscriber store, credential/profile generator, Open5GS
and free5gc core adapters, captive portal, operational scripts, systemd
units, network configs, and a 44-test suite.

Profile generation currently returns a mock JSON profile; real SGP.22
generation via pySim is the planned Phase-2 upgrade.

Fixes two bugs found while getting the suite green:
- generate_imsi hardcoded a 10-digit MSIN, producing 16-digit IMSIs for a
  3-digit MNC; MSIN length is now 15 - len(mcc) - len(mnc).
- check_rate_limit compared timestamps as strings across mismatched formats
  (SQLite CURRENT_TIMESTAMP vs Python isoformat); both sides now normalized
  via SQLite datetime() so the window check is chronologically correct.

Add .gitignore for venv, caches, runtime config.yaml, and databases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 05:56:31 +00:00

4.3 KiB

Unit 08 — System Configuration Files

Goal

Production-ready nginx, dnsmasq, hostapd configs and systemd service units.

Files

  • configs/nginx.conf
  • configs/dnsmasq.conf
  • configs/hostapd.conf
  • systemd/esim-provisioning.service
  • systemd/captive-portal.service

configs/nginx.conf

# Captive portal + reverse proxy for eSIM provisioning server

server {
    listen 80 default_server;
    server_name _;

    # Redirect any request not matching known paths to the activation portal
    location = / {
        return 302 http://192.168.4.1/activate;
    }

    # Serve the captive portal static files
    location /activate {
        alias /opt/esim-provisioning/portal/;
        index index.html;
        try_files $uri $uri/ /index.html;
    }

    location /css/ {
        alias /opt/esim-provisioning/portal/css/;
    }

    location /js/ {
        alias /opt/esim-provisioning/portal/js/;
    }

    # Proxy API calls to Flask backend
    location /api/ {
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_connect_timeout 10s;
        proxy_read_timeout 30s;
    }

    # Catch-all: redirect everything else to activate
    location / {
        return 302 http://192.168.4.1/activate;
    }
}

configs/dnsmasq.conf

# eSIM Provisioning - DNS/DHCP configuration

# Only listen on WiFi AP interface
interface=wlan0
bind-interfaces

# DHCP range
dhcp-range=192.168.4.10,192.168.4.250,24h

# DNS: return WiFi AP IP for ALL domains (captive portal hijacking)
address=/#/192.168.4.1

# Prevent upstream DNS resolution (keep it local)
no-resolv

# DHCP lease file
dhcp-leasefile=/var/lib/misc/dnsmasq.leases

# Log queries (useful for debugging)
log-queries
log-dhcp

configs/hostapd.conf

# WiFi AP configuration for eSIM provisioning drop node

interface=wlan0
driver=nl80211

# Network identity
ssid=Emergency-5G
utf8_ssid=1

# Radio settings
hw_mode=g
channel=6
ieee80211n=1
wmm_enabled=1

# Open network (no password) — intentional for emergency access
auth_algs=1
ignore_broadcast_ssid=0

# Country code — adjust per deployment region
country_code=CA

# Max clients
max_num_sta=50

systemd/esim-provisioning.service

[Unit]
Description=eSIM Provisioning Server
After=network.target
Wants=network.target

[Service]
Type=simple
User=esim
Group=esim
WorkingDirectory=/opt/esim-provisioning
Environment=CONFIG_PATH=/etc/esim-provisioning/config.yaml
ExecStart=/opt/esim-provisioning/venv/bin/python -m server.app
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=esim-provisioning

# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/opt/esim-provisioning/data

[Install]
WantedBy=multi-user.target

systemd/captive-portal.service

[Unit]
Description=Captive Portal (nginx + dnsmasq)
After=network.target hostapd.service
Wants=nginx.service dnsmasq.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/systemctl start nginx dnsmasq
ExecStop=/bin/systemctl stop nginx dnsmasq

[Install]
WantedBy=multi-user.target

Deployment Notes

Install configs:

# nginx
sudo cp configs/nginx.conf /etc/nginx/sites-available/esim-provisioning
sudo ln -sf /etc/nginx/sites-available/esim-provisioning /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx

# dnsmasq
sudo cp configs/dnsmasq.conf /etc/dnsmasq.d/esim-provisioning.conf
sudo systemctl restart dnsmasq

# hostapd
sudo cp configs/hostapd.conf /etc/hostapd/hostapd.conf
sudo systemctl unmask hostapd
sudo systemctl enable --now hostapd

# systemd services
sudo cp systemd/esim-provisioning.service /etc/systemd/system/
sudo cp systemd/captive-portal.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now esim-provisioning captive-portal

Verification

sudo nginx -t                          # Config syntax check
sudo systemctl status esim-provisioning
sudo systemctl status captive-portal
nslookup google.com 192.168.4.1        # Should return 192.168.4.1
curl http://192.168.4.1/api/status     # Should return JSON status