Flash the image
Write Air OS to a fresh SD card or CM4/CM5 eMMC.
Lightweight Raspberry Pi module that turns Starlink into a reliable HD video and telemetry downlink for drones — stream 1080p and MAVLink data over satellite from anywhere.
Built for the battlefield, tested in the field
SRT protocol with local encoder backoff and forward error correction tuned for satellite uplink. Streams H.264 reliably over Starlink's variable bandwidth.
Forward telemetry from ArduPilot/PX4 over Starlink to any GCS, cloud, or analytics endpoint. Serial and TCP input with CRC validation and multi-destination routing.
Track your UAV in real-time on an interactive map with GPS trail, flight mode, altitude, speed, and satellite count — accessible from anywhere via Starlink.
RPi Camera Module and thermal imagers (V4L2). Crash-safe MKV recording on-board with simultaneous live stream over satellite — never lose footage.
Built-in Tailscale + ZeroTier create a secure mesh between the UAV, Starlink, and your command post. WiFi management UI scans + connects from the browser — no SSH needed. Access the dashboard from any device, anywhere on the planet.
Runs on a 45g Raspberry Pi. Plug a USB stick labelled AIRREC and recordings auto-flow to it — SD card stays clean. Direct laptop-to-Pi cable works without a router. Configure everything through the web dashboard over Starlink or local Ethernet.
Live SD/eMMC wear visibility (JEDEC wear buckets), PSU under-voltage detection, per-stage pipeline FPS chip, and audit-logged guidance state transitions. Fails forward: alert pill warns months before the SD card dies in the field.
Optional Pi 5 + Hailo-8 variant runs YOLOv8 on-device at 30 fps. Operators tap a target in the live video; the Air ground station locks on and autonomously guides an ArduPlane airframe onto it. Compile-time + license gate keep public builds log-only. Read more →
Direct Ethernet first: the reliable setup path before WiFi, ZeroTier, or license activation
Flash air-os-latest.img.xz to an SD card with Raspberry Pi Imager. One image works on Pi 4, Pi 5, CM4, and CM5.
Watch the SD card + CM4/CM5 eMMC flashing tutorial. Watch the physical hookup walkthrough. Watch Windows adapter, ZeroTier, and license setup. Watch the AirOS first setup walkthrough.
For first setup, plug Ethernet directly between laptop and Pi. On Windows, set the Ethernet adapter to manual IPv4 192.168.144.10, subnet mask 255.255.255.0, gateway/DNS blank. Power on, wait 60-120 seconds, then open http://192.168.144.1/. air.local is optional and only works on router/LAN setups with mDNS/Bonjour.
Open the web UI (admin/admin, force-rotate on first login). Configure WiFi, ZeroTier, System, Backup, and other setup pages before activation if needed. Paste your license key only after the Pi has internet; the license gates video transmission and telemetry forwarding, not basic setup.
Live H.264 / H.265 video over WebRTC preview, RTP/UDP, or SRT. MAVLink telemetry forwarded to any GCS. Real-time map. Strike variant adds AI tracking + autonomous terminal guidance.
Write Air OS to a fresh SD card or CM4/CM5 eMMC.
Insert storage, connect Ethernet/LAN, and bring up Raspberry Pi, CM4, or CM5 hardware.
Set the Windows Ethernet adapter, connect to your custom ZeroTier network, and activate the license once the Pi is online.
Reach the web UI through direct Ethernet, log in, get the Pi online, and activate after internet is available.
Software is free to try. A license is only required to run production missions; hardware kits bundle it at no extra cost.
Standard variant only. Perpetual license keyed to one Raspberry Pi hardware ID. Use with your own Pi + camera + Starlink hardware.
Modified Starlink Mini + Raspberry Pi + camera + cables. Fully assembled, flashed, and ready to fly. OS license included at no extra cost.
AI object tracking + autonomous terminal guidance. Hailo-8 HAT on Pi 5 unlocks the full AI tracker; the same image runs the operator-tap targeting path on Pi 4 / Pi 5 without HAT (no AI, manual selection only). End-user verification required before enable.
UAH prices shown at the official NBU rate of 44.77 UAH / USD — USD is canonical. The Air OS software itself is free to download and try — flash it on any Raspberry Pi. A license is only required to run video and telemetry transmission on production hardware.
Get Air for Raspberry Pi, Intel N100 mini PCs, or viewer apps for laptops and Android controllers
Current verified unified Pi 4 / Pi 5 / CM4 / CM5 image. Flash it, connect Ethernet, then open 192.168.144.1.
~720 MB. First SSH login: air:air; change the password after setup.
Standard is the recommended Pi image for camera, telemetry, VPN, joystick, maps, and normal OTA updates. Use Strike only when you specifically need the Hailo/AI tracking build.
⚠ First-boot SSH credential is air:air.
Change the password with passwd after first login.
Do not expose :22 to the public internet before changing it.
Optional Hailo/AI build: Download Strike variant (.img.xz).
Authenticate the checksum with the independently pinned AirOS
release key before trusting the image. Set air_image
to the Strike filename when downloading Strike. The
AIR_OS_INSTALLER_SIGNATURE_V2 statement also binds
the provenance and adjacent verifier bytes. The fail-closed
inline bootstrap authenticates them without executing code from
the download mirror.
(
set -euo pipefail
umask 077
AIR_INSTALLER_PUBKEY=f79ecbda6904e34556616c7a537dea8740609b19f07a556b9eb783d7751838fb
air_image=air-os-latest.img.xz # air-os-latest-strike.img.xz for Strike
air_download_dir="$(pwd)/air-os-verified"; mkdir -- "$air_download_dir"; cd -- "$air_download_dir"
air_verify_tmp="$(mktemp -d ./verify.XXXXXX)"; trap 'rm -rf -- "$air_verify_tmp"' EXIT
air_fetch() {
local f="$1" max="$2" size partial="$air_verify_tmp/download.part"
rm -f -- "$partial"
if ! curl --proto '=https' --tlsv1.2 --fail --location --show-error --max-filesize "$max" "https://air.airdroper.org/downloads/$f" | head -c "$((max + 1))" >"$partial"; then rm -f -- "$partial"; return 1; fi
size="$(stat -c '%s' "$partial")"
if ! [[ "$size" =~ ^[0-9]+$ ]] || test "$size" -le 0 || test "$size" -gt "$max"; then rm -f -- "$partial"; return 1; fi
mv -f -- "$partial" "$f"
}
air_fetch "$air_image.sha256" 65536
air_fetch "$air_image.sha256.sig" 256
air_fetch "$air_image.provenance" 1048576
air_fetch "$air_image.verify.sh" 1048576
air_fetch air-os-installer-ed25519.pub 128
key="${AIR_INSTALLER_PUBKEY,,}"; test "$(tr -d '\r\n' < air-os-installer-ed25519.pub)" = "$key"
test "$(wc -l <"$air_image.sha256" | tr -d '[:space:]')" = 1
checksum_line="$(cat "$air_image.sha256")"; checksum_digest="${checksum_line%% *}"; checksum_name="${checksum_line#* }"
[[ "$checksum_digest" =~ ^[0-9a-f]{64}$ ]]; test "$checksum_line" = "$checksum_digest $air_image"; test "$checksum_name" = "$air_image"
checksum_size="$(stat -c '%s' "$air_image.sha256")"; checksum_sha="$(sha256sum "$air_image.sha256" | awk '{print $1}')"
provenance_size="$(stat -c '%s' "$air_image.provenance")"; provenance_sha="$(sha256sum "$air_image.provenance" | awk '{print $1}')"
verifier_size="$(stat -c '%s' "$air_image.verify.sh")"; verifier_sha="$(sha256sum "$air_image.verify.sh" | awk '{print $1}')"
printf 'AIR_OS_INSTALLER_SIGNATURE_V2\nname=%s.sha256\nsize=%s\nsha256=%s\nprovenance_name=%s.provenance\nprovenance_size=%s\nprovenance_sha256=%s\nverifier_name=%s.verify.sh\nverifier_size=%s\nverifier_sha256=%s\n' "$air_image" "$checksum_size" "$checksum_sha" "$air_image" "$provenance_size" "$provenance_sha" "$air_image" "$verifier_size" "$verifier_sha" >"$air_verify_tmp/statement"
signature="$(tr -d '\r\n' <"$air_image.sha256.sig")"; [[ "$signature" =~ ^[A-Za-z0-9+/]{86}==$ ]]
printf '%s' "$signature" | openssl base64 -d -A -out "$air_verify_tmp/signature.bin"; test "$(wc -c <"$air_verify_tmp/signature.bin")" = 64
printf '\x30\x2a\x30\x05\x06\x03\x2b\x65\x70\x03\x21\x00' >"$air_verify_tmp/public.der"; for ((offset=0; offset<64; offset+=2)); do printf "\\x${key:offset:2}"; done >>"$air_verify_tmp/public.der"
openssl pkeyutl -verify -pubin -keyform DER -inkey "$air_verify_tmp/public.der" -rawin -in "$air_verify_tmp/statement" -sigfile "$air_verify_tmp/signature.bin"
air_fetch "$air_image" 8589934592
test "$(sha256sum "$air_image" | awk '{print $1}')" = "$checksum_digest"
printf 'Verified image retained at %s/%s\n' "$air_download_dir" "$air_image"
)
loading…
Need help flashing? Watch the SD card + CM4/CM5 eMMC tutorial. Hardware hookup: LAN and CM4/CM5 bring-up. Network and activation: Windows adapter, ZeroTier, and license. First setup: AirOS walkthrough.
Need the Hailo/AI Strike build instead: Download Strike variant (.img.xz, ~1.05 GB)
For Ubuntu/Debian mini PCs with SSD/NVMe. This is not a flashable image: unpack the package and run the installer on the existing OS.
Download N100 package (.tar.xz)After install, open http://<mini-pc-ip>/. First web login is admin / admin.
Use this for N100/N95/N200-class boxes that already boot Ubuntu/Debian. It uses the separate x86_64 OTA lane and Intel VAAPI video acceleration when available.
(
set -euo pipefail
umask 077
export AIR_INSTALLER_PUBKEY=f79ecbda6904e34556616c7a537dea8740609b19f07a556b9eb783d7751838fb
air_verify_tmp="$(mktemp -d ./.air-n100-verify.XXXXXX)"; trap 'rm -rf -- "$air_verify_tmp"' EXIT
air_fetch() {
local f="$1" max="$2" size partial="$air_verify_tmp/download.part"
rm -f -- "$partial"
if ! curl --proto '=https' --tlsv1.2 --fail --location --show-error --max-filesize "$max" "https://air.airdroper.org/downloads/$f" | head -c "$((max + 1))" >"$partial"; then rm -f -- "$partial"; return 1; fi
size="$(stat -c '%s' "$partial")"
if ! [[ "$size" =~ ^[0-9]+$ ]] || test "$size" -le 0 || test "$size" -gt "$max"; then rm -f -- "$partial"; return 1; fi
mv -f -- "$partial" "$f"
}
air_fetch air-os-n100-latest.tar.xz.sha256 65536
air_fetch air-os-n100-latest.tar.xz.sig 256
air_fetch air-os-installer-ed25519.pub 128
key="${AIR_INSTALLER_PUBKEY,,}"; test "$(tr -d '\r\n' < air-os-installer-ed25519.pub)" = "$key"
test "$(wc -l < air-os-n100-latest.tar.xz.sha256 | tr -d '[:space:]')" = 1
checksum_line="$(cat air-os-n100-latest.tar.xz.sha256)"; checksum_digest="${checksum_line%% *}"
test "$checksum_line" = "$checksum_digest air-os-n100-latest.tar.xz"; [[ "$checksum_digest" =~ ^[0-9a-f]{64}$ ]]
tr -d '\r\n' < air-os-n100-latest.tar.xz.sig | openssl base64 -d -A -out package.sig.bin
test "$(wc -c < package.sig.bin)" -eq 64
printf '\x30\x2a\x30\x05\x06\x03\x2b\x65\x70\x03\x21\x00' > installer-public.der
for ((i=0; i<64; i+=2)); do printf "\\x${key:i:2}"; done >> installer-public.der
air_fetch air-os-n100-latest.tar.xz 8589934592
name=air-os-n100-latest.tar.xz; size="$(stat -c '%s' "$name")"; sha="$(sha256sum "$name" | awk '{print $1}')"
printf 'AIR_OS_INSTALLER_SIGNATURE_V1\nname=%s\nsize=%s\nsha256=%s\n' "$name" "$size" "$sha" > package.signature-input
openssl pkeyutl -verify -pubin -keyform DER -inkey installer-public.der -rawin -in package.signature-input -sigfile package.sig.bin
test "$sha" = "$checksum_digest"
)
Free standalone viewer with low-latency decode, recording, telemetry overlay, camera switching, and English/Ukrainian UI. Auto-detects H.264 / H.265 from AirOS. The current public build is unsigned and automatic update checks are disabled.
Download unsigned .exeUnsigned preview: Windows will show Unknown Publisher because this file has no Authenticode certificate. Verify its SHA-256 before running it. A checksum detects changed bytes but does not authenticate Airdroper as the publisher.
This operator-approved unsigned lane is separate from the future signed production lane. It publishes immutable versioned bytes, a checksum, build/security evidence, and a plain release record, but it does not publish signed updater metadata and does not replace the signed-production aliases.
View the unsigned release record
Signed production status: No approved Air Viewer Authenticode signer fingerprint is published yet. The signed-production aliases remain unavailable; the unsigned preview above is a separate, explicitly labelled lane.
After release engineering independently approves a concrete signer fingerprint, the signed executable will require a timestamped Authenticode signature, SHA-256 checksum, and signed update metadata. Do not treat the unsigned preview checksum as publisher authentication.
$approvedSignerSha256 = (Read-Host 'Paste the independently approved 64-hex signer SHA-256').Trim().ToLowerInvariant()
if ($approvedSignerSha256 -cnotmatch '^[0-9a-f]{64}$') { throw 'No approved signer fingerprint supplied' }
Invoke-WebRequest https://air.airdroper.org/downloads/air-viewer-latest.exe -OutFile air-viewer-latest.exe
Invoke-WebRequest https://air.airdroper.org/downloads/air-viewer-latest.exe.sha256 -OutFile air-viewer-latest.exe.sha256
$expected = (Get-Content .\air-viewer-latest.exe.sha256).Split()[0]
$actual = (Get-FileHash .\air-viewer-latest.exe -Algorithm SHA256).Hash.ToLowerInvariant()
if ($actual -ne $expected) { throw 'SHA-256 mismatch' }
$signature = Get-AuthenticodeSignature .\air-viewer-latest.exe
if ($signature.Status -ne 'Valid') { throw "Invalid publisher: $($signature.Status)" }
if ($null -eq $signature.SignerCertificate) { throw 'Missing signer certificate' }
if ($null -eq $signature.TimeStamperCertificate) { throw 'Missing trusted timestamp' }
$signerSha256 = $signature.SignerCertificate.GetCertHashString([System.Security.Cryptography.HashAlgorithmName]::SHA256).ToLowerInvariant()
if ($signerSha256 -cne $approvedSignerSha256) { throw 'Publisher fingerprint mismatch' }
$base = 'https://air.airdroper.org/downloads'
$recordText = (Invoke-WebRequest "$base/air-viewer-unsigned-current/release.txt").Content
$expectedKeys = @(
'schema', 'version', 'source_revision', 'release_mode',
'authenticode_status', 'authenticode_required', 'sha256', 'size_bytes',
'approval_checksum_sha256', 'latest_checksum_sha256',
'build_manifest_sha256', 'dependency_manifest_sha256', 'sbom_sha256',
'security_evidence_sha256', 'update_trust_sha256', 'immutable_url',
'build_generated_utc', 'warning'
)
$lines = $recordText.TrimEnd("`r", "`n").Split("`n")
if ($lines.Count -ne $expectedKeys.Count) { throw 'Invalid release record field count' }
$record = @{}
for ($i = 0; $i -lt $lines.Count; $i++) {
if ($lines[$i] -cnotmatch '^([a-z][a-z0-9_]*)=(.+)$') { throw 'Invalid release record line' }
$key = $Matches[1]; $value = $Matches[2]
if ($key -cne $expectedKeys[$i] -or $record.ContainsKey($key)) { throw 'Invalid release record field order' }
$record[$key] = $value
}
if ($record.schema -cne 'org.airdroper.air-viewer.public-unsigned.v3') { throw 'Invalid release schema' }
if ($record.immutable_url -cnotmatch '^https://air\.airdroper\.org/downloads/air-viewer-unsigned-releases/v[0-9]+\.[0-9]+\.[0-9]+/air-viewer\.exe$') { throw 'Unsafe immutable URL' }
$immutableBase = $record.immutable_url.Substring(0, $record.immutable_url.LastIndexOf('/'))
Invoke-WebRequest $record.immutable_url -OutFile air-viewer.exe
Invoke-WebRequest "$immutableBase/air-viewer.exe.sha256" -OutFile air-viewer.exe.sha256
$line = (Get-Content -Raw .\air-viewer.exe.sha256).TrimEnd("`r", "`n")
if ($line -cnotmatch '^([0-9a-f]{64}) air-viewer\.exe$') { throw 'Invalid checksum file' }
$sidecarHash = (Get-FileHash .\air-viewer.exe.sha256 -Algorithm SHA256).Hash.ToLowerInvariant()
if ($sidecarHash -cne $record.approval_checksum_sha256) { throw 'Checksum evidence mismatch' }
$actual = (Get-FileHash .\air-viewer.exe -Algorithm SHA256).Hash.ToLowerInvariant()
if ($actual -cne $Matches[1] -or $actual -cne $record.sha256) { throw 'SHA-256 mismatch' }
if ((Get-Item .\air-viewer.exe).Length -ne [int64]$record.size_bytes) { throw 'Size mismatch' }
$signature = Get-AuthenticodeSignature .\air-viewer.exe
if ($signature.Status -ne 'NotSigned') { throw "Unexpected signature state: $($signature.Status)" }
Write-Warning 'SHA-256 passed; this preview is intentionally unsigned and Windows cannot authenticate its publisher.'
Native APK for Android phones and controller screens such as SIYI MK32. Auto chooses RTSP passthrough when available, falls back to the AirOS Web Preview for USB/WebRTC cameras, and supports low-latency OSD overlays. ~5 MB.
Download APKInstall from Android Files after enabling installs from unknown apps for your browser or file manager.
Use Auto mode for AirOS. Setup links such as airviewer://connect?host=192.168.144.1&mode=auto&start=1 can prefill and start the Android viewer. Release upgrades must retain certificate SHA-256 929906c64e87507d023f51365b7e0ed953753ebd484e9951aeb7827c3d44cee2.
curl -L -o air-viewer-android-latest.apk https://github.com/AirdroperUA/air-releases/releases/latest/download/air-viewer-android-latest.apk
curl -sS -L https://github.com/AirdroperUA/air-releases/releases/latest/download/air-viewer-android-latest.apk.sha256 | sha256sum -c -
apksigner verify --verbose --print-certs air-viewer-android-latest.apk
# Signer #1 certificate SHA-256 digest must be:
# 929906c64e87507d023f51365b7e0ed953753ebd484e9951aeb7827c3d44cee2