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>
11 KiB
eSIM Provisioning System - Project Setup Instructions
Project Overview
Build a closed-loop eSIM provisioning system for emergency/NTN 5G deployments. System runs on a UE device (Android or Raspberry Pi) that connects to a 5G gNB and provides WiFi AP with automatic eSIM provisioning.
Repository Structure
Create the following directory structure:
esim-provisioning/
├── README.md
├── requirements.txt
├── config.yaml.example
├── docker-compose.yml (optional)
├── server/
│ ├── __init__.py
│ ├── app.py # Main Flask application
│ ├── profile_generator.py # eSIM profile generation using pySim
│ ├── database.py # SQLite database interface
│ ├── core_adapters/
│ │ ├── __init__.py
│ │ ├── base.py # Base adapter class
│ │ ├── open5gs.py # Open5GS adapter
│ │ └── free5gc.py # free5gc adapter
│ ├── models.py # Database models
│ └── utils.py # Helper functions
├── portal/
│ ├── index.html # Captive portal landing page
│ ├── css/
│ │ └── style.css
│ ├── js/
│ │ └── script.js
│ └── assets/
│ └── (images if needed)
├── scripts/
│ ├── init_db.py # Initialize database
│ ├── sync_subscribers.py # Sync to core network
│ ├── export_subscribers.py # Export subscriber data
│ └── setup_wifi_ap.sh # WiFi AP setup (for Pi)
├── systemd/
│ ├── esim-provisioning.service
│ └── captive-portal.service
├── configs/
│ ├── nginx.conf # nginx captive portal config
│ ├── dnsmasq.conf # DNS/DHCP config
│ └── hostapd.conf # WiFi AP config (for Pi)
├── android/
│ ├── README.md # Android setup instructions
│ └── termux_setup.sh # Termux installation script
└── tests/
├── test_profile_generator.py
├── test_database.py
└── test_core_adapters.py
Step 1: Initialize Project
Create the repository structure and initialize Python virtual environment:
# Create project directory
mkdir esim-provisioning
cd esim-provisioning
# Initialize git
git init
# Create virtual environment
python3 -m venv venv
source venv/bin/activate
# Create all directories
mkdir -p server/core_adapters portal/css portal/js portal/assets scripts systemd configs android tests
Step 2: Create Requirements File
Create requirements.txt with:
Flask==3.0.0
pySim==1.0.0
cryptography==41.0.7
pymongo==4.6.0
requests==2.31.0
pyyaml==6.0.1
Step 3: Create Configuration Template
Create config.yaml.example:
network:
mcc: "302"
mnc: "720"
plmn: "302720"
op_key: "63bfa50ee6523365ff14c1f45f88737d" # Hex string
wifi:
ssid: "Emergency-5G"
channel: 6
ip: "192.168.4.1"
subnet: "192.168.4.0/24"
dhcp_range: "192.168.4.10,192.168.4.250"
server:
host: "0.0.0.0"
port: 5000
debug: false
database:
path: "/opt/esim-provisioning/data/subscribers.db"
rate_limiting:
max_profiles_per_mac: 1
window_hours: 1
core:
type: "open5gs" # or "free5gc"
mongodb_uri: "mongodb://localhost:27017"
database_name: "open5gs"
profile:
carrier_name: "Ad-Hoc Network"
profile_name: "Emergency 5G"
apn: "internet"
Step 4: Core Server Components
database.py
Create database interface with:
- SQLite connection management
- Schema creation (subscribers table)
- CRUD operations for subscribers
- Rate limiting checks by MAC address
- Export functionality
Schema:
CREATE TABLE subscribers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
imsi TEXT UNIQUE NOT NULL,
iccid TEXT UNIQUE NOT NULL,
ki TEXT NOT NULL,
opc TEXT NOT NULL,
mac_address TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
synced_to_core BOOLEAN DEFAULT 0
);
CREATE INDEX idx_imsi ON subscribers(imsi);
CREATE INDEX idx_mac_address ON subscribers(mac_address);
profile_generator.py
Implement eSIM profile generation:
- Generate random IMSI (15 digits: MCC + MNC + MSIN)
- Generate random Ki (16 bytes)
- Derive OPc from Ki and OP using Milenage algorithm
- Generate ICCID (19-20 digits)
- Create SGP.22 eSIM profile using pySim
- Return profile bytes and credentials
Key functions:
def generate_imsi(mcc, mnc) -> str
def generate_iccid() -> str
def generate_ki() -> bytes
def derive_opc(ki: bytes, op: bytes) -> bytes
def create_esim_profile(imsi, ki, opc, iccid, profile_name, carrier_name) -> bytes
core_adapters/base.py
Define abstract base class:
class CoreAdapter(ABC):
@abstractmethod
def add_subscriber(self, imsi, ki, opc):
pass
@abstractmethod
def remove_subscriber(self, imsi):
pass
@abstractmethod
def list_subscribers(self):
pass
core_adapters/open5gs.py
Implement Open5GS adapter using MongoDB:
- Connect to MongoDB
- Insert subscriber with proper schema
- Configure slice, APN, AMBR settings
- Handle errors gracefully
app.py
Main Flask application with endpoints:
GET / # Captive portal redirect
GET /activate # Serve captive portal HTML
POST /api/provision # Generate and provision eSIM
GET /api/profile/:id # Download eSIM profile (LPA)
GET /api/export # Export subscribers JSON
GET /api/status # System status
Implement:
- Rate limiting middleware (check MAC address)
- Profile generation workflow
- LPA protocol handling
- Error handling and logging
Step 5: Captive Portal
portal/index.html
Simple single-page interface:
- "Activate eSIM" button
- Instructions for installation
- Progress indicator
- Success/error messages
- Mobile-friendly responsive design
portal/js/script.js
Client-side logic:
- POST to
/api/provisionwith MAC address - Poll for profile generation status
- Trigger eSIM download via LPA URL scheme
- Handle errors
Step 6: System Scripts
scripts/init_db.py
Initialize SQLite database with schema
scripts/sync_subscribers.py
Sync subscribers to core:
# Read from SQLite
# For each unsynced subscriber:
# - Call core adapter
# - Mark as synced
# - Log result
scripts/export_subscribers.py
Export to JSON format:
{
"exported_at": "2025-02-11T10:30:00Z",
"count": 123,
"subscribers": [
{
"imsi": "302720123456789",
"ki": "00112233445566778899aabbccddeeff",
"opc": "63bfa50ee6523365ff14c1f45f88737d",
"iccid": "8910302720123456789",
"created_at": "2025-02-11T09:15:00Z"
}
]
}
scripts/setup_wifi_ap.sh
Bash script for Raspberry Pi WiFi AP setup:
- Install hostapd, dnsmasq, nginx
- Configure network interfaces
- Set up iptables for captive portal
- Enable IP forwarding
- Configure services
Step 7: Configuration Files
configs/nginx.conf
Configure captive portal:
- Listen on port 80
- Catch-all server block
- Redirect to /activate
- Proxy /api/* to Flask backend
configs/dnsmasq.conf
DNS/DHCP configuration:
- Interface binding
- DHCP range
- DNS hijacking (return WiFi AP IP for all domains)
- Lease time
configs/hostapd.conf
WiFi AP configuration:
- SSID
- Channel
- Interface
- Open network (no password)
Step 8: Systemd Services
systemd/esim-provisioning.service
Flask server service:
- Run as dedicated user
- Auto-restart on failure
- Log to journal
- Load config from /etc/esim-provisioning/config.yaml
systemd/captive-portal.service
Nginx + dnsmasq orchestration
Step 9: Testing
Create unit tests for:
- Profile generation (valid IMSI, Ki, OPc formats)
- Database operations (CRUD, rate limiting)
- Core adapters (mock MongoDB)
- API endpoints (Flask test client)
Step 10: Documentation
README.md
Include:
- Project overview
- System requirements
- Installation instructions (Pi and Android)
- Configuration guide
- Usage instructions
- Troubleshooting
- Architecture diagram
- License
Implementation Priority
- First: Database layer + models
- Second: Profile generator (without pySim, use mock for testing)
- Third: Flask API with all endpoints
- Fourth: Core adapters (Open5GS first)
- Fifth: Captive portal frontend
- Sixth: Integration with pySim for real profile generation
- Seventh: System scripts and configs
- Eighth: Testing and documentation
Key Implementation Notes
- Use environment variables or config.yaml for all configuration (never hardcode)
- Log everything (use Python logging module)
- Handle all errors gracefully (no stack traces to user)
- Validate all inputs (IMSI format, MAC address format)
- Use secrets module for cryptographic randomness
- Make core adapters truly modular (easy to add new cores)
- Keep captive portal extremely simple (mobile-first)
- Rate limiting must be robust (prevent abuse)
- Database operations should be atomic
- Profile generation should be fast (<1 second)
Testing Checklist
- Can generate valid IMSI
- Can generate valid Ki/OPc
- Can create eSIM profile
- Can store subscriber in database
- Rate limiting works (same MAC can't request multiple times)
- Can export subscribers to JSON
- Open5GS adapter can add subscriber
- API endpoints return correct responses
- Captive portal loads on mobile
- Profile downloads correctly
- End-to-end: WiFi → provision → 5G connection
Deployment Targets
Raspberry Pi:
- Raspberry Pi 4 (4GB+ RAM)
- Quectel RM500Q 5G modem
- Raspberry Pi OS Lite 64-bit
- Python 3.11+
Android:
- Android 10+ with root/Termux
- 5G modem built-in
- Termux + proot environment
- Python 3.11+
Security Considerations (Future)
Currently implementing with no security for simplicity, but design should allow easy addition of:
- WiFi password authentication
- HTTPS for API endpoints
- Activation code requirements
- Subscriber audit logging
- Profile encryption at rest
Success Criteria
System is complete when:
- User connects to WiFi
- Captive portal loads automatically
- Click "Activate eSIM" button
- eSIM profile downloads to device
- Device installs profile
- Device connects to 5G using new eSIM
- Open5GS authenticates successfully
Total user time: <60 seconds
Starting Point
Begin by creating the project structure, then implement in this order:
database.py- Get data persistence workingprofile_generator.py- Mock implementation firstapp.py- Flask API with all endpointsportal/index.html- Simple UIcore_adapters/open5gs.py- Core integration- Integration testing
- Real pySim integration
- Deployment scripts
Focus on getting a working end-to-end flow before optimizing or adding features.