Push Tracker
ria-ran--sim-drop/scripts/setup_wifi_ap.sh
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

171 lines
4.9 KiB
Bash
Executable File

#!/bin/bash
# setup_wifi_ap.sh — Configure a Raspberry Pi as a WiFi AP + captive portal
# for the eSIM provisioning system.
# Run as root: sudo bash scripts/setup_wifi_ap.sh
set -euo pipefail
[ "$(id -u)" = "0" ] || { echo "ERROR: Must run as root (sudo)."; exit 1; }
SSID="${ESIM_SSID:-Emergency-5G}"
CHANNEL="${ESIM_CHANNEL:-6}"
AP_IP="${ESIM_AP_IP:-192.168.4.1}"
DHCP_RANGE="${ESIM_DHCP_RANGE:-192.168.4.10,192.168.4.250,24h}"
INSTALL_DIR="${ESIM_INSTALL_DIR:-/opt/esim-provisioning}"
IFACE="${ESIM_IFACE:-wlan0}"
FLASK_PORT="${ESIM_FLASK_PORT:-5000}"
REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)"
echo "=== eSIM Provisioning — WiFi AP Setup ==="
echo " SSID: $SSID"
echo " Channel: $CHANNEL"
echo " AP IP: $AP_IP"
echo " Interface: $IFACE"
echo " Install dir: $INSTALL_DIR"
echo ""
# ---- 1. Install packages ----
echo "[1/8] Installing packages..."
apt-get update -qq
apt-get install -y hostapd dnsmasq nginx python3 python3-pip python3-venv
# ---- 2. Static IP on wlan0 ----
echo "[2/8] Configuring static IP on $IFACE..."
DHCPCD_CONF=/etc/dhcpcd.conf
if ! grep -q "interface $IFACE" "$DHCPCD_CONF" 2>/dev/null; then
cat >> "$DHCPCD_CONF" << EOF
interface $IFACE
static ip_address=$AP_IP/24
nohook wpa_supplicant
EOF
fi
# ---- 3. hostapd ----
echo "[3/8] Configuring hostapd..."
cat > /etc/hostapd/hostapd.conf << EOF
interface=$IFACE
driver=nl80211
ssid=$SSID
utf8_ssid=1
hw_mode=g
channel=$CHANNEL
ieee80211n=1
wmm_enabled=1
auth_algs=1
ignore_broadcast_ssid=0
max_num_sta=50
EOF
sed -i 's|#DAEMON_CONF=.*|DAEMON_CONF="/etc/hostapd/hostapd.conf"|' /etc/default/hostapd
# ---- 4. dnsmasq ----
echo "[4/8] Configuring dnsmasq..."
# Back up original if present
[ -f /etc/dnsmasq.conf ] && mv /etc/dnsmasq.conf /etc/dnsmasq.conf.bak
cat > /etc/dnsmasq.conf << EOF
interface=$IFACE
bind-interfaces
dhcp-range=$DHCP_RANGE
address=/#/$AP_IP
no-resolv
dhcp-leasefile=/var/lib/misc/dnsmasq.leases
log-queries
log-dhcp
EOF
# ---- 5. nginx ----
echo "[5/8] Configuring nginx..."
cat > /etc/nginx/sites-available/esim-provisioning << EOF
server {
listen 80 default_server;
server_name _;
location = / {
return 302 http://$AP_IP/activate;
}
location /activate {
alias $INSTALL_DIR/portal/;
index index.html;
try_files \$uri \$uri/ /index.html;
}
location /css/ { alias $INSTALL_DIR/portal/css/; }
location /js/ { alias $INSTALL_DIR/portal/js/; }
location /api/ {
proxy_pass http://127.0.0.1:$FLASK_PORT;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
}
location / {
return 302 http://$AP_IP/activate;
}
}
EOF
ln -sf /etc/nginx/sites-available/esim-provisioning /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
nginx -t
# ---- 6. IP forwarding ----
echo "[6/8] Enabling IP forwarding..."
sed -i 's|#net.ipv4.ip_forward=1|net.ipv4.ip_forward=1|' /etc/sysctl.conf
sysctl -w net.ipv4.ip_forward=1
# ---- 7. iptables — captive portal redirect ----
echo "[7/8] Configuring iptables..."
# Redirect all HTTP traffic from wlan0 clients to nginx (captive portal)
iptables -t nat -A PREROUTING -i "$IFACE" -p tcp --dport 80 -j DNAT \
--to-destination "$AP_IP:80" 2>/dev/null || true
# Persist iptables rules
apt-get install -y iptables-persistent
netfilter-persistent save
# ---- 8. Deploy server and services ----
echo "[8/8] Deploying server..."
mkdir -p "$INSTALL_DIR" /etc/esim-provisioning
cp -r "$REPO_DIR/server" "$INSTALL_DIR/"
cp -r "$REPO_DIR/portal" "$INSTALL_DIR/"
cp -r "$REPO_DIR/scripts" "$INSTALL_DIR/"
cp -r "$REPO_DIR/requirements.txt" "$INSTALL_DIR/"
[ -f /etc/esim-provisioning/config.yaml ] || \
cp "$REPO_DIR/config.yaml.example" /etc/esim-provisioning/config.yaml
# Create venv and install deps
python3 -m venv "$INSTALL_DIR/venv"
"$INSTALL_DIR/venv/bin/pip" install -q -r "$INSTALL_DIR/requirements.txt"
# Create system user
id esim &>/dev/null || useradd --system --no-create-home --shell /usr/sbin/nologin esim
mkdir -p "$INSTALL_DIR/data"
chown -R esim:esim "$INSTALL_DIR/data"
# Install systemd services
cp "$REPO_DIR/systemd/esim-provisioning.service" /etc/systemd/system/
cp "$REPO_DIR/systemd/captive-portal.service" /etc/systemd/system/
systemctl daemon-reload
# Initialise DB
CONFIG_PATH=/etc/esim-provisioning/config.yaml \
"$INSTALL_DIR/venv/bin/python" "$INSTALL_DIR/scripts/init_db.py"
# Enable everything
systemctl unmask hostapd
systemctl enable hostapd dnsmasq nginx esim-provisioning captive-portal
systemctl restart hostapd dnsmasq nginx esim-provisioning
echo ""
echo "=== Setup Complete ==="
echo ""
echo "Edit /etc/esim-provisioning/config.yaml to set your MCC/MNC/OP key."
echo "Then: systemctl restart esim-provisioning"
echo ""
echo "Clients can connect to WiFi '$SSID' and will be redirected to the"
echo "eSIM activation portal at http://$AP_IP/activate"