OTA for External Developers
External distributors use the same Debian build and image audit as official releases. A device can install from any HTTP(S) source when given a signed manifest and the matching trusted Ed25519 public key.
Build a Custom Release
Generate a signing key pair and keep the private key outside the repository:
openssl genpkey -algorithm ed25519 -out ota_private_key.pem
openssl pkey -in ota_private_key.pem -pubout -out ota_public_key.pem
scripts/validate_ota_pubkey.sh ota_public_key.pem
Build the complete Debian firmware and signed OTA asset set:
VERSION="v1.0.0-custom"
CHANNEL="custom"
BASE_URL="https://firmware.example.com/aiden/$VERSION"
OTA_PRIVATE_KEY_PATH="$PWD/ota_private_key.pem" \
OTA_PUBLIC_KEY_PATH="$PWD/ota_public_key.pem" \
AGENT_CONFIG_PATH="$PWD/agent.toml" \
OTA_CHANNEL="$CHANNEL" \
OTA_BUILD_VERSION="$VERSION" \
OTA_BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
OTA_BASE_URL="$BASE_URL" \
./debian_build.sh
OTA_BASE_URL pins asset URLs for direct static hosting. For managed GitHub
dev/staging/prod publication, use the channel release workflow,
which sets both the repository and immutable asset URLs from its release plan.
The publishable directory contains:
output/debian/image/
├── boot_a.img.tar.gz
├── boot_b.img.tar.gz
├── manifest.json
├── rootfs.img.tar.gz
└── update.img.tar.gz
The directory also contains the directly flashable local update.img and its
checksum. The release allowlist intentionally publishes the compressed image,
not that extra local copy.
Host the Assets
GitHub Releases for a Custom Distributor
Official managed channels use the channel workflow. A separate distributor can publish a custom tag manually and update through an explicit manifest URL:
TAG="$VERSION"
REPO="YOUR_USERNAME/aiden-firmware"
gh release create "$TAG" \
--title "Custom Firmware $TAG" \
output/debian/image/boot_a.img.tar.gz \
output/debian/image/boot_b.img.tar.gz \
output/debian/image/rootfs.img.tar.gz \
output/debian/image/update.img.tar.gz \
output/debian/image/manifest.json
Static Web Server or CDN
Build with OTA_BASE_URL set to the final directory URL, then upload without
renaming or recompressing anything:
rsync -avz \
output/debian/image/*.img.tar.gz \
output/debian/image/manifest.json \
For local testing, serve the same directory directly:
cd output/debian/image
python3 -m http.server 8000
Trust and Update
Copy the public key to the device through a trusted channel:
DEVICE_IP="192.168.42.1"
scp ota_public_key.pem "root@$DEVICE_IP:/userdata/ota/custom_pubkey.pem"
Run an update from a direct manifest URL:
ota update \
--manifest-url "https://firmware.example.com/aiden/v1.0.0-custom/manifest.json" \
--public-key /userdata/ota/custom_pubkey.pem
Add --dry-run to download and verify every asset without writing partitions or
switching slots.
Persistent Device Configuration
The Debian factory configuration lives at
/userdata/debian/ota/config.json. To pin a device to a custom source, add
manifest_url and public_key_path while preserving the generated factory
version, build time, safety margin, and partition hashes:
{
"manifest_url": "https://firmware.example.com/aiden/latest/manifest.json",
"public_key_path": "/userdata/ota/custom_pubkey.pem"
}
Do not replace the complete generated file with only those two fields. The factory baseline is required for downgrade and inactive-slot verification.
Manifest Rules
The generated manifest is signed canonical JSON. Each parts entry names
boot or rootfs and contains either a slot-neutral asset or
slot-specific asset_a and asset_b values.
Published assets are .img.tar.gz files. For each asset:
sizeandsha256describe the downloaded archive;image_sha256describes the extracted image written to the partition;urlis present whenOTA_BASE_URLwas supplied;bootremains slot-specific because its DTB carries slot-specific bootargs.
The build rejects a target-slot download set that exceeds the capacity derived from the dedicated OTA partition.
Channel and Version Strategy
The channel must match [A-Za-z0-9._-]. Managed dev/staging/prod devices enforce
that the signed channel matches their configuration, even for direct manifest
URLs. Use a test device configured with a custom channel/source for these custom
examples. Signing keys remain the trust boundary; channel names are not a
substitute for signature verification.
Use monotonically increasing build times and traceable versions, for example a UTC timestamp plus Git commit. OTA rejects older build times and conflicting versions with the same build time.
Security Requirements
- Never commit the private signing key or production
agent.toml. - Use HTTPS for production distribution.
- Verify the public key fingerprint through a separate trusted channel.
- Never modify, rename, or recompress assets after
manifest.jsonis signed. - Treat different distribution authorities as separate signing keys.
Troubleshooting
Inspect the Debian boot-time health unit and log:
systemctl status aiden-ota-health.service
journalctl -u aiden-ota-health.service
tail -f /var/log/ota/ota.log
Useful checks:
ota status
ota update --manifest-url URL --public-key KEY --dry-run
ota verify-manifest /path/to/manifest.json --public-key /path/to/pubkey.pem
Common failures:
manifest signature verification failed: wrong key, modified manifest, or a private/public key mismatch.invalid channel: the channel contains unsupported characters.missing required release asset: the hosted filename differs from the signed manifest or the GitHub Release does not contain all six allowlisted assets.- archive or image checksum mismatch: an asset was changed after signing.