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¶
Prompts for all configuration step by step:
- Serial port (auto-detects available ports)
- Device ID (default: random 8 hex characters)
- Network mode (WiFi / GPRS)
- WiFi SSID & password / GPRS APN
- Device hostname
- C2 server IP & port
- Module selection (checkboxes)
- Recon sub-features (camera, BLE)
- OTA enabled
- 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)¶
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 _commentfields are ignored
Generated sdkconfig¶
deploy.py generates sdkconfig.defaults with the following Kconfig options:
Device¶
Network¶
C2 Server¶
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¶
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)¶
BLE (if enabled)¶
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¶
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)¶
Manual¶
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):
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:
$IDF_PATHenvironment variable~/esp-idf/opt/esp-idf
See also
- API Reference — Build & OTA API endpoints
- Security — Key management details
- Architecture — Transport & crypto internals