Skip to content

Deploy System

The deploy.py script handles the complete firmware build, provisioning, and flash pipeline. It generates ESP-IDF configurations, compiles firmware, creates cryptographic keys, writes NVS partitions, and flashes devices.


Pipeline Overview

graph LR
    A[Configuration] --> B[sdkconfig.defaults]
    B --> C[idf.py build]
    C --> D[Master Key]
    D --> E[NVS Partition]
    E --> F[Flash Device]
    F --> G[Update keys.json]

    style A fill:#a855f7,color:#fff
    style G fill:#4ade80,color:#000
Step Description
1. Configuration DeviceConfig from CLI args, wizard, or deploy.json
2. sdkconfig Generate sdkconfig.defaults with all Kconfig options
3. Build Full idf.py build (clean build, 3-8 minutes)
4. Master Key Generate random 32-byte ChaCha20 key (or use provided)
5. NVS Partition Create factory NVS binary with master key
6. Flash Write bootloader + partition table + OTA data + NVS + app
7. Keystore Store device_id → key mapping in keys.json

DeviceConfig

All 18 configurable fields:

Field Type Default Description
device_id string random 8 hex Unique device identifier
port string (required) Serial port (/dev/ttyUSB0)
srv_ip string 192.168.1.100 C2 server IP address
srv_port int 2626 C2 server port
network_mode string wifi "wifi" or "gprs"
wifi_ssid string "" WiFi network name
wifi_pass string "" WiFi password
gprs_apn string sl2sfr GPRS Access Point Name
hostname string "" Device hostname (mDNS)
mod_network bool true Enable Network module
mod_recon bool false Enable Recon module
mod_fakeap bool false Enable FakeAP module
mod_honeypot bool false Enable Honeypot module
mod_canbus bool false Enable CAN Bus module
recon_camera bool false Enable camera (requires recon)
recon_ble_trilat bool false Enable BLE trilateration (requires recon)
ota_enabled bool true Enable OTA updates
ota_allow_http bool false Allow insecure HTTP OTA
master_key_hex string "" Pre-defined key (64 hex chars) or empty for random

CLI Usage

Interactive Wizard

python deploy.py

Prompts for all configuration step by step:

  1. Serial port (auto-detects available ports)
  2. Device ID (default: random 8 hex characters)
  3. Network mode (WiFi / GPRS)
  4. WiFi SSID & password / GPRS APN
  5. Device hostname
  6. C2 server IP & port
  7. Module selection (checkboxes)
  8. Recon sub-features (camera, BLE)
  9. OTA enabled
  10. Master key (generate or provide)

Quick CLI Deploy

python deploy.py \
    -p /dev/ttyUSB0 \
    -d abc12345 \
    --wifi MySSID MyPassword \
    --srv 192.168.1.100 \
    --mod-fakeap \
    --mod-recon \
    --recon-camera

Batch Deploy (deploy.json)

python deploy.py --config deploy.json

Processes all devices defined in the JSON file sequentially.

Special Modes

Flag Description
--provision-only Generate key + write NVS only (no rebuild, no full flash)
--flash-only Flash existing build (skip rebuild)
--build-only Build firmware only (no flash)
--erase Erase entire flash before writing
--monitor Launch serial monitor after flash

Examples:

# Provision-only: just write the crypto key
python deploy.py --provision-only -p /dev/ttyUSB0 -d abc12345

# Flash existing build (no recompile)
python deploy.py --flash-only -p /dev/ttyUSB0 -d abc12345 \
    --wifi MySSID MyPassword --srv 192.168.1.100

# Build only (no device needed)
python deploy.py --build-only -d abc12345 \
    --wifi MySSID MyPassword --srv 192.168.1.100

# Clean install
python deploy.py --erase -p /dev/ttyUSB0 -d abc12345 \
    --wifi MySSID MyPassword --srv 192.168.1.100

# Deploy + monitor output
python deploy.py --monitor -p /dev/ttyUSB0 -d abc12345 \
    --wifi MySSID MyPassword --srv 192.168.1.100

CLI Arguments

Argument Type Default Description
-d, --device-id string random 8 hex Device identifier
-p, --port string (required) Serial port
--wifi SSID PASS 2 strings WiFi mode with credentials
--gprs [APN] string sl2sfr GPRS mode with optional APN
--srv IP string 192.168.1.100 C2 server IP
--srv-port int 2626 C2 server port
--hostname string Device hostname
--mod-fakeap flag Enable FakeAP module
--mod-honeypot flag Enable Honeypot module
--mod-recon flag Enable Recon module
--recon-camera flag Enable camera
--recon-ble flag Enable BLE trilateration
--no-network flag Disable Network module
--no-ota flag Disable OTA
--ota-http flag Allow HTTP for OTA
--key HEX string random Master key (64 hex chars)
--keystore PATH path tools/C3PO/keys.json Keystore location
--config PATH path Batch JSON config file
--provision-only flag Keys + NVS only
--flash-only flag Flash without rebuild
--build-only flag Build without flash
--erase flag Erase flash first
--monitor flag Serial monitor after flash

Mutually exclusive

--wifi and --gprs are mutually exclusive. Choose one network mode.


deploy.json Format

Batch configuration file with defaults inheritance.

Structure

{
    "defaults": {
        "server": { ... },
        "network": { ... },
        "modules": { ... },
        "ota": { ... }
    },
    "devices": [
        { "device_id": "...", "port": "...", ... },
        { "device_id": "...", "port": "...", ... }
    ]
}

Defaults Section

Applied to all devices unless overridden:

{
    "defaults": {
        "server": {
            "ip": "192.168.1.100",
            "port": 2626
        },
        "network": {
            "mode": "wifi",
            "wifi_ssid": "YOUR_SSID",
            "wifi_pass": "YOUR_PASSWORD",
            "gprs_apn": "sl2sfr"
        },
        "modules": {
            "network": true,
            "fakeap": false,
            "honeypot": false,
            "recon": false,
            "canbus": false,
            "recon_camera": false,
            "recon_ble_trilat": false
        },
        "ota": {
            "enabled": true,
            "allow_http": false
        }
    }
}

Device Examples

{
    "devices": [
        {
            "_comment": "Standard bot — network commands only",
            "device_id": "aaaa0001",
            "port": "/dev/ttyUSB0",
            "hostname": "esp32-bot-01"
        },
        {
            "_comment": "Honeypot — TCP services + WiFi monitoring",
            "device_id": "bbbb0001",
            "port": "/dev/ttyUSB1",
            "hostname": "esp32-honeypot-01",
            "modules": {
                "network": true,
                "honeypot": true
            }
        },
        {
            "_comment": "FakeAP — captive portal + WiFi sniffer",
            "device_id": "cccc0001",
            "port": "/dev/ttyUSB2",
            "modules": {
                "network": true,
                "fakeap": true
            }
        },
        {
            "_comment": "Recon camera — visual reconnaissance",
            "device_id": "dddd0001",
            "port": "/dev/ttyUSB3",
            "modules": {
                "network": false,
                "recon": true,
                "recon_camera": true
            }
        },
        {
            "_comment": "GPRS bot — deployed outside WiFi range",
            "device_id": "eeee0001",
            "port": "/dev/ttyUSB4",
            "master_key": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
            "network": {
                "mode": "gprs",
                "gprs_apn": "internet"
            },
            "server": {
                "ip": "203.0.113.10"
            },
            "ota": {
                "enabled": true,
                "allow_http": true
            }
        }
    ]
}

Override Rules

  • Device-level values override defaults
  • Nested sections (server, network, modules, ota) are merged
  • Top-level keys: device_id, port, hostname, master_key
  • _comment fields are ignored

Generated sdkconfig

deploy.py generates sdkconfig.defaults with the following Kconfig options:

Device

CONFIG_DEVICE_ID="abc12345"
CONFIG_LWIP_LOCAL_HOSTNAME="esp32-bot-01"    # If hostname set

Network

CONFIG_NETWORK_WIFI=y
CONFIG_WIFI_SSID="MySSID"
CONFIG_WIFI_PASS="MyPassword"
CONFIG_NETWORK_GPRS=y
CONFIG_GPRS_APN="sl2sfr"

C2 Server

CONFIG_SERVER_IP="192.168.1.100"
CONFIG_SERVER_PORT=2626

Crypto

CONFIG_CRYPTO_FCTRY_NS="crypto"
CONFIG_CRYPTO_FCTRY_KEY="master_key"
CONFIG_MBEDTLS_CHACHA20_C=y
CONFIG_MBEDTLS_POLY1305_C=y
CONFIG_MBEDTLS_CHACHAPOLY_C=y
CONFIG_MBEDTLS_HKDF_C=y

Flash & Partitions

CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"       # OTA enabled
# or
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_noota.csv" # OTA disabled

IP Stack

CONFIG_LWIP_IPV4_NAPT=y
CONFIG_LWIP_IPV4_NAPT_PORTMAP=y
CONFIG_LWIP_IP_FORWARD=y

Modules

CONFIG_MODULE_NETWORK=y         # If mod_network
CONFIG_MODULE_RECON=y           # If mod_recon
CONFIG_MODULE_FAKEAP=y          # If mod_fakeap
CONFIG_MODULE_HONEYPOT=y        # If mod_honeypot
CONFIG_MODULE_CANBUS=y          # If mod_canbus
CONFIG_RECON_MODE_CAMERA=y      # If recon_camera
CONFIG_RECON_MODE_MLAT=y        # If mod_recon (always with recon)

Camera (if enabled)

CONFIG_SPIRAM=y
CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y

BLE (if enabled)

CONFIG_BT_ENABLED=y
CONFIG_BT_BLUEDROID_ENABLED=y
CONFIG_BT_BLE_ENABLED=y

OTA

CONFIG_ESPILON_OTA_ENABLED=y
CONFIG_ESPILON_OTA_ALLOW_HTTP=y                    # If allow_http
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=y   # If HTTPS (default)

Logging

CONFIG_ESPILON_LOG_LEVEL_INFO=y
CONFIG_ESPILON_LOG_BOOT_SUMMARY=y

Partition Layout

With OTA (default)

Uses partitions.csv:

Address Partition Size Description
0x1000 Bootloader ~28 KB ESP-IDF bootloader
0x8000 Partition table 4 KB Partition layout
0xD000 OTA data 8 KB OTA state (active partition)
0x10000 Factory NVS 24 KB Crypto keys (master_key)
0x20000 OTA_0 (app) ~1.5 MB Application firmware

Without OTA

Uses partitions_noota.csv:

Address Partition Size Description
0x1000 Bootloader ~28 KB ESP-IDF bootloader
0x8000 Partition table 4 KB Partition layout
0x10000 Factory NVS 24 KB Crypto keys
0x20000 Factory (app) ~1.8 MB Application firmware

Key Generation

Automatic (default)

master_key = secrets.token_bytes(32)  # 256-bit random

Manual

python deploy.py --key 0a1b2c3d4e5f...64_hex_chars

The key must be exactly 64 hex characters (32 bytes).

Storage Flow

deploy.py
  ├─── NVS binary (fctry.bin)
  │    └── namespace="crypto", key="master_key", value=<32 bytes>
  │    └── Flashed to 0x10000 on ESP32
  └─── keys.json
       └── {"device_id": "hex_encoded_key"}
       └── Read by C3PO on device connection

Flash Process

Full Flash (esptool.py)

esptool.py --chip esp32 --baud 460800 \
    --before default_reset --after hard_reset \
    write_flash \
    0x1000  build/bootloader/bootloader.bin \
    0x8000  build/partition_table/partition-table.bin \
    0xD000  build/ota_data_initial.bin \
    0x10000 fctry.bin \
    0x20000 build/espilon_bot.bin

Provision-Only

Only writes the factory NVS partition (key):

esptool.py --chip esp32 --baud 460800 \
    write_flash 0x10000 fctry.bin

Build-Only

No flash at all — just generates the firmware binary and fctry.bin.


Web Build Manager

The Build Manager provides the same functionality via the web dashboard and REST API.

Dashboard /ota → Build Firmware form
    ├── POST /api/ota/build/start
    │   ├── config_from_dict() → DeviceConfig
    │   ├── generate_sdkconfig()
    │   ├── idf.py build (background thread)
    │   └── Copy binary to firmware/
    ├── GET /api/ota/build/status  (poll)
    ├── GET /api/ota/build/log     (incremental)
    └── On success:
        ├── Binary appears in firmware list
        └── URL pre-filled for OTA deploy

ESP-IDF Detection: BuildManager searches for ESP-IDF in:

  1. $IDF_PATH environment variable
  2. ~/esp-idf
  3. /opt/esp-idf

See also