A
8672b0de6b
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>
4.3 KiB
4.3 KiB
Unit 08 — System Configuration Files
Goal
Production-ready nginx, dnsmasq, hostapd configs and systemd service units.
Files
configs/nginx.confconfigs/dnsmasq.confconfigs/hostapd.confsystemd/esim-provisioning.servicesystemd/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