Onboard Software Deployment via Inventory Service
PRD: Onboard Software Deployment via Inventory Service
| Field | Value |
|---|---|
| Status | Draft |
| Owner | TBD |
| Contributors | TBD |
| Date | 2026-04-21 |
1. Executive Summary
Problem Statement: Deploying the MAVLink Shim onboard software to a UAV requires manually handling mTLS certificates, configuring endpoint URLs, copying binaries, and restarting services — all as separate steps with no unified workflow. The same problem exists for virtual UAV simulators: the Simulator Controller today uses a custom cloud-init startup script that installs AOS bundles independently from any cert or registration logic, leaving simulators outside the inventory lifecycle.
Proposed Solution:
Each Inventory Service deployment (one per environment — dev, staging, prod, client-X) exposes a /install endpoint that serves a ready-to-run bash install script (modeled after get-docker.sh). The script is already scoped to its environment because it is served by that environment’s Inventory Service instance — no env parameter needed. For physical UAVs, an operator copies the URL from the UI, SSHs into the UAV, and runs it interactively. For simulators, the Simulator Controller embeds the same script call in the VM’s cloud-init metadata, passing the UAV ID non-interactively.
Success Criteria:
- An operator can fully install and start the MAVLink Shim on a physical UAV by running a single
curl ... | bashcommand obtained from the Inventory Service UI of the target environment. - The install URL contains no UAV ID and no env parameter — the Inventory Service instance itself determines the scope.
- The script handles the full registration lifecycle: submit registration request → wait for approval → fetch issued cert bundle → install and start.
- Deploying a new Inventory Service instance for a new client environment automatically makes its
/installendpoint available with no code change. - The installed
mavlink_shimsuccessfully connects to Avatar via mTLS within 60 s of the script completing after approval. - A new simulator VM spawned by the Simulator Controller is fully registered, approved, and running
mavlink_shimwith correct certs automatically — no manual steps required afterkubectl apply.
Scope Clarity: A — requirements are clear and bounded for MVP.
2. User Experience & Functionality
User Personas
| Persona | Description |
|---|---|
| Field Technician | SSHes into a UAV on the ground, runs the generated script interactively, confirms the shim is running. No knowledge of certs or environment config required. |
| Client Operator | Receives the install URL from DroneUp for their dedicated Inventory Service instance. Runs it on their UAV fleet. May not know what an mTLS cert is. |
| Simulator Controller (automated) | The uncrew-simulator-controller Kubernetes operator that spawns GCP VMs. Passes the UAV ID and the target env’s Inventory Service install URL via cloud-init metadata — no human in the loop. |
User Stories & Acceptance Criteria
US-01: Get install command from Inventory Service UI
As a platform engineer, I want the Inventory Service UI to show a ready-to-run install command so that I can hand it to a field technician before the UAV is even registered.
- The Inventory Service UI shows an “Install Onboard Software” section with a one-liner:
curl -fsSL https://<this-inventory-instance>/install | bash - The URL contains no UAV ID and no env parameter — it is served by the specific Inventory Service instance the operator is viewing.
- The same URL works for any UAV being enrolled into this environment.
- A “Copy” button copies the command to clipboard.
US-02: Script collects UAV ID at runtime
As a field technician, I want the script to ask me for the UAV ID (or read it from hardware) so that I do not need to know it before running the command.
- On startup the script attempts to read the hardware serial number from
/sys/firmware/devicetree/base/serial-number(Skynode). - If auto-detection succeeds, the script prints the detected ID and asks for confirmation:
Detected UAV ID: <id>. Continue? [Y/n] - If auto-detection fails or the operator declines, the script prompts:
Enter UAV ID: - The resolved UAV ID is used for all subsequent Inventory Service calls.
US-03: Script registers UAV and waits for approval
As a field technician, I want the script to handle registration automatically so that I do not need to use a separate device to trigger cert issuance.
- The script calls
RegisterNewUavwith the resolved UAV ID. - If the UAV is already
APPROVED, the script skips registration and proceeds to cert fetch. - If the UAV is
REQUESTED, the script prints:Registration submitted. Waiting for approval in Inventory Service... (Press Ctrl+C to cancel and resume later) - The script polls every 10 s for up to 30 min. On approval it proceeds automatically.
- If the UAV is
REJECTED, the script exits with a clear error and exit code 1.
US-04: Install script downloads binary and factory certificates
As a field technician, I want the install script to download everything the MAVLink Shim needs so that I do not have to manually copy files to the UAV.
- The script downloads the correct onboard software(or AOS bundle) for the UAV’s CPU architecture (aarch64 for Skynode, x86_64 for simulator VMs) from Google Artifact Registry.
- After approval, the script fetches the issued cert bundle from the Inventory Service:
- CA cert for this environment’s certificate authority
- UAV client certificate
- UAV client private key
- Certs are written to
/etc/mavlink-shim/certs/with0600permissions. - The script writes
/etc/mavlink-shim/config.envwith all required environment variables.
US-05: Environment configuration baked into the script
As a field technician, I want the installed software to automatically connect to the correct Avatar endpoint so that I do not configure any URLs manually.
- The install script is generated by the Inventory Service at request time and contains values from that instance’s own deployment config: Avatar gRPC endpoint, cert domain template.
- The
config.envwritten to the UAV contains at minimum:MAVLINK_SHIM_AVATAR_URL=<from inventory deployment config> MAVLINK_SHIM_ROOT_CERT_PATH=/etc/mavlink-shim/certs/ca.crt MAVLINK_SHIM_CLIENT_CERT_PATH=/etc/mavlink-shim/certs/client.crt MAVLINK_SHIM_CLIENT_KEY_PATH=/etc/mavlink-shim/certs/client.key - No hardcoded endpoint URLs exist in shared code — each Inventory Service instance serves its own values.
US-06: Script installs and starts the service
As a field technician, I want the script to install and start
mavlink_shimas a system service so that it runs automatically on UAV reboot.
- The script installs docker images and run them.
- The script installs a systemd unit file and runs
systemctl enable --now mavlink-shim. - If
mavlink_shimis already running (upgrade case), the script stops it, replaces the binary and certs, then restarts. - The script prints a final status line; exits non-zero on any failure.
US-07: Client environment isolation via separate deployments
As a platform engineer, I want each client to have a fully isolated Inventory Service deployment with its own CA so that a UAV in one client’s environment cannot authenticate to another client’s Avatar instance.
- Each Inventory Service deployment has its own CA keypair configured at deploy time (Helm values / Google Secret Manager) — no CA is shared between deployments.
- The install script served by
client-acme’s Inventory Service fetches certs signed by theclient-acmeCA only. - A new client environment is onboarded by deploying a new Inventory Service instance with a new CA — no application code change required.
US-08: Idempotent re-run (upgrade path)
As a platform engineer, I want to re-run the install command to upgrade the MAVLink Shim so that upgrades use the same workflow as first installs.
- Re-running the script replaces the binary and refreshes the cert bundle, then restarts the service.
- The script does not fail if the service is not currently running.
- The previous binary is backed up before replacement.
US-09: Headless (non-interactive) mode for simulators
As the Simulator Controller, I want to invoke the install script non-interactively with a pre-known UAV ID so that simulator VMs are fully provisioned without human input during cloud-init startup.
- The script accepts
UAV_IDas an environment variable:UAV_ID=sim-abc123 curl -fsSL .../install | bash - When
UAV_IDis set, the script skips hardware detection and all interactive prompts. - When no TTY is present and
UAV_IDis unset, the script exits with a clear error rather than hanging. - The Simulator Controller sets
UAV_IDequal to the GCP VM instance name (deterministic, unique per simulator).
US-10: Auto-approval for simulators
As a platform engineer, I want simulator UAVs to be approved automatically on registration in sandbox/dev so that spinning up a new simulator requires no manual approval step.
- The Inventory Service deployment config includes an
AUTO_APPROVEflag (defaultfalse). - When
AUTO_APPROVE=true,RegisterNewUavtransitions the UAV directly toAPPROVEDand issues certs immediately. AUTO_APPROVEis only set for sandbox/dev Inventory Service deployments — never for staging, prod, or client instances.- The UI shows a persistent warning banner when running against an auto-approve instance.
- Auto-approved UAVs are visually distinguished in the UAV list (e.g. “Auto” tag).
Non-Goals (MVP)
| Out of Scope | Rationale |
|---|---|
| Fleet-wide batch install | MVP targets single UAV; fleet tooling deferred |
| Web UI showing real-time installation progress | Script runs on UAV; UI cannot observe it without telemetry infrastructure |
| Automatic cert rotation / renewal | Separate Inventory Service operation; deferred |
| Rollback triggered from the UI | Script backs up previous binary; rollback is manual for MVP |
| Deploying AI Node software | Separate hardware target; deferred to v1.1 |
| Client self-service environment creation | DroneUp engineers deploy new Inventory Service instances; self-service deferred |
| Software signature verification | Deferred to v1.1 |
3. Regulatory & Compliance
| Area | Status | Notes |
|---|---|---|
| FAA 44807 Exemption | Review required | Onboard software changes may require notification to or re-assessment by the FAA exemption holder. Confirm with the safety team before deploying to production UAVs. |
| DO-178C | Not applicable (MVP) | MAVLink Shim is not currently certified under DO-178C. If certification is pursued, the install script would need to produce a Configuration Index record. |
| Software Configuration Management | In scope | The Inventory Service must record: UAV ID, binary version installed, operator identity, and install timestamp. Minimum CM traceability record. |
| ITAR | Confirm with legal | MAVLink Shim interfaces with autopilot and implements geofencing / failsafe logic. Confirm with legal whether the binary is ITAR-controlled before distributing to client environments. |
| PII / Secret Handling | In scope | Private keys fetched during install must never be logged. The script must delete any temp files containing key material after writing to final paths. |
| Client Data Isolation | In scope | Each client’s CA keypair lives in that client’s Inventory Service deployment secrets — no cross-tenant access is possible at the infrastructure level. |
4. Technical Specifications
Architecture Overview
Each Inventory Service deployment is scoped to one environment. The install script served by any given instance is pre-configured with that instance’s Avatar endpoint and CA — the UAV or VM that runs the script automatically gets the right environment:
─── Physical UAV path ──────────────────────────────────────────────
Inventory Service UI (e.g. client-acme's instance)
│ Operator clicks "Get install command"
▼
curl -fsSL https://inventory.client-acme.droneup.cloud/install | bash
│
│ Operator SSHes into UAV, runs interactively
▼
Install Script (Avatar URL + CA baked in from this instance's config)
│
├── 1. Detect hardware serial / prompt "Enter UAV ID:"
├── 2. RegisterNewUav(uavID) → this Inventory Service
├── 3. Poll until APPROVED ← [Admin approves in UI]
├── 4. Fetch cert bundle (token-gated)
├── 5. Download mavlink_shim binary (aarch64)
├── 6. Write /etc/mavlink-shim/certs/ + config.env
└── 7. systemctl enable --now mavlink-shim
─── Simulator path ─────────────────────────────────────────────────
kubectl apply -f simulator.yaml
│
▼
Simulator Controller (K8s operator)
│ Reconciles Simulator CR
│ Knows target Inventory Service URL from its own config
├── Generates VM name → used as UAV_ID
└── Creates GCP VM with cloud-init metadata:
startup-script: |
UAV_ID=sim-<name> \
curl -fsSL https://inventory.dev.droneup.cloud/install | bash
│
│ VM boots, cloud-init runs startup script
▼
Install Script (headless: UAV_ID set, no TTY prompts)
│
├── 1. UAV_ID from env var (skip hardware detect)
├── 2. RegisterNewUav(uavID) → this Inventory Service
├── 3. AUTO_APPROVE=true → instant APPROVED + certs issued
├── 4. Fetch cert bundle (token-gated)
├── 5. Download mavlink_shim binary (x86_64 for GCP VM)
├── 6. Write /etc/mavlink-shim/certs/ + config.env
└── 7. systemctl enable --now mavlink-shim
─── Common ─────────────────────────────────────────────────────────
mavlink_shim
│ Reads config.env
└── mTLS connect → Avatar (endpoint baked into config.env by install script)Affected Repos
| Repo | Language | Role |
|---|---|---|
uncrew-inventory-service | Go / gRPC | Serves /install script (values from own config); cert issuance; AUTO_APPROVE deployment flag |
uncrew-mavlink-shim | C++ / CMake | The onboard binary; must publish versioned aarch64 and x86_64 artifacts to Google Artifact Registry |
uncrew-apollo-frontend | React (assumed) | Inventory Service UI — shows install command; auto-approve warning banner |
uncrew-simulator-controller | Go / K8s operator | Embeds install script call in VM cloud-init metadata with UAV_ID env var; knows each env’s Inventory Service URL |
Integration Points
New Inventory Service endpoints:
| Endpoint | Method | Auth | Purpose |
|---|---|---|---|
GET /install | GET | None | Serves the bash install script; Avatar URL and CA endpoint baked in from this instance’s deployment config |
GET /internal/install/{uav_id}/certs/{file} | GET | Short-lived token (issued post-approval) | Serves individual cert files (ca.crt, client.crt, client.key) |
No env parameter on any endpoint — the Inventory Service instance is the environment.
Inventory Service deployment config (Helm values / env vars):
These values are already configured per deployment. The /install endpoint reads them to generate the script:
| Config key | Example value | Purpose |
|---|---|---|
AVATAR_ENDPOINT | avatar.client-acme.droneup.cloud:9091 | Written into config.env on the UAV |
CERT_DOMAIN_TEMPLATE | {UAVID}.uav.client-acme.droneup.cloud | Used during cert issuance |
MAVLINK_SHIM_VERSION | v2.1.0 | Binary version the script downloads |
AUTO_APPROVE | false | If true, UAVs approved instantly on registration |
CA_CERT / CA_KEY | (from Secret Manager) | Used to sign UAV client certs |
MAVLink Shim CI/CD — new publishing step:
- GitHub Actions in
uncrew-mavlink-shimmust publish versionedmavlink_shimbinaries for bothaarch64andx86_64to Google Artifact Registry on each release tag. - The Inventory Service reads
MAVLINK_SHIM_VERSIONfrom its deployment config to populate the download URL in the script.
Data Model Changes
New install_tokens table in Inventory Service:
| Column | Type | Notes |
|---|---|---|
id | UUID | PK, used as bearer token |
uav_id | TEXT | FK → registrations.uav_id |
expires_at | TIMESTAMPTZ | 1 h from issuance |
used_at | TIMESTAMPTZ | Nullable; token is single-use |
Token is issued only after the UAV transitions to APPROVED. No env column — this table lives inside a single env’s Inventory Service database.
No other schema changes are required. The existing registrations and certificates tables are already scoped to the deployment — no env_slug FK needed.
Install Script Structure
The script is generated at request time by the Inventory Service from its own deployment config. It is the same script regardless of which UAV runs it — UAV identity is resolved at runtime. Two modes:
- Interactive (physical UAV): operator runs it in a shell; script detects hardware serial or prompts.
- Headless (simulator):
UAV_IDenv var is set by the Simulator Controller; all prompts skipped.
#!/usr/bin/env bash
set -euo pipefail
# Values injected by the Inventory Service at request time (from its own deployment config):
INVENTORY_URL="https://inventory.client-acme.droneup.cloud"
AVATAR_URL="avatar.client-acme.droneup.cloud:9091"
VERSION="v2.1.0"
ARCH="$(uname -m)" # aarch64 (Skynode) or x86_64 (simulator VM)
# 1. Resolve UAV ID — headless or interactive
if [[ -n "${UAV_ID:-}" ]]; then
echo "Headless mode: UAV_ID=${UAV_ID}"
else
if [ ! -t 0 ]; then
echo "ERROR: UAV_ID env var must be set when running non-interactively." >&2
exit 1
fi
SERIAL="$(cat /sys/firmware/devicetree/base/serial-number 2>/dev/null | tr -d '\0' || true)"
if [[ -n "${SERIAL}" ]]; then
read -rp "Detected UAV ID: ${SERIAL}. Continue? [Y/n] " confirm
[[ "${confirm,,}" != "n" ]] && UAV_ID="${SERIAL}"
fi
if [[ -z "${UAV_ID:-}" ]]; then
read -rp "Enter UAV ID: " UAV_ID
fi
fi
# 2. Register UAV (idempotent — proceeds immediately if already APPROVED)
STATUS=$(curl -fsSL -X POST "${INVENTORY_URL}/v1/register" \
-d "{\"uav_id\":\"${UAV_ID}\"}" | jq -r '.status')
# 3. Poll for approval (instant if AUTO_APPROVE=true on this instance)
if [[ "${STATUS}" != "APPROVED" ]]; then
echo "Registration submitted. Waiting for approval..."
for i in $(seq 1 180); do # 30 min at 10 s intervals
sleep 10
STATUS=$(curl -fsSL "${INVENTORY_URL}/v1/uav/${UAV_ID}/status" | jq -r '.status')
[[ "${STATUS}" == "APPROVED" ]] && break
[[ "${STATUS}" == "REJECTED" ]] && { echo "Registration rejected."; exit 1; }
echo " still waiting... (${i})"
done
[[ "${STATUS}" != "APPROVED" ]] && { echo "Approval timeout."; exit 1; }
fi
# 4. Fetch cert bundle
TOKEN=$(curl -fsSL "${INVENTORY_URL}/v1/uav/${UAV_ID}/install-token" | jq -r '.token')
mkdir -p /etc/mavlink-shim/certs
set +x # suppress key material from trace logs
for f in ca.crt client.crt client.key; do
curl -fsSL -H "Authorization: Bearer ${TOKEN}" \
"${INVENTORY_URL}/internal/install/${UAV_ID}/certs/${f}" \
-o "/etc/mavlink-shim/certs/${f}"
chmod 0600 "/etc/mavlink-shim/certs/${f}"
done
set -x
# 5. Download binary
curl -fsSL "https://us-east1-docker.pkg.dev/.../mavlink_shim_${VERSION}_${ARCH}" \
-o /tmp/mavlink_shim
[[ -f /usr/bin/mavlink_shim ]] && cp /usr/bin/mavlink_shim /usr/bin/mavlink_shim.bak
install -m 0755 /tmp/mavlink_shim /usr/bin/mavlink_shim
# 6. Write config
cat > /etc/mavlink-shim/config.env <<EOF
MAVLINK_SHIM_AVATAR_URL=${AVATAR_URL}
MAVLINK_SHIM_ROOT_CERT_PATH=/etc/mavlink-shim/certs/ca.crt
MAVLINK_SHIM_CLIENT_CERT_PATH=/etc/mavlink-shim/certs/client.crt
MAVLINK_SHIM_CLIENT_KEY_PATH=/etc/mavlink-shim/certs/client.key
EOF
# 7. Install systemd unit + start
systemctl enable --now mavlink-shim
echo "mavlink-shim ${VERSION} installed, uav=${UAV_ID}"Security & Privacy
| Concern | Mitigation |
|---|---|
| Install script URL is unauthenticated | Script contains no secrets — values baked in are non-sensitive (endpoint URLs, version). Cert bundle is only served after approval via a token-authenticated endpoint. |
| Private key in transit | Cert bundle fetched over HTTPS with bearer token; key never in URL params |
| Private key at rest on UAV | Written to /etc/mavlink-shim/certs/ with 0600 root-only permissions |
| Private key in script output | set +x before cert fetch loop; key content never echoed |
| CA private key in Inventory Service | Mounted from Google Secret Manager at deploy time; never in the database or logs |
| Cross-tenant isolation | Each client’s Inventory Service is a separate deployment with separate Secret Manager secrets — infrastructure-level isolation, not application-level ACLs |
AUTO_APPROVE on wrong instance | Deployment pipeline enforces AUTO_APPROVE=false for staging/prod/client Helm values; sandbox/dev values are separate files with explicit opt-in |
| Binary integrity | MVP: downloaded from trusted Artifact Registry over HTTPS. v1.1: SHA256 checksum in script |
5. Risks & Phased Rollout
Technical Risks
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Simulator VM is x86_64, not aarch64 | Certain | Medium | Script uses uname -m to select binary; Artifact Registry must publish both architectures |
curl | bash blocked by UAV OS security policy | Low | High | Investigate UAV OS policy before committing to this pattern; fallback: download script manually, verify, run |
| CA private key misconfigured in Secret Manager for a new client env | Low | High | Deployment checklist; automated smoke test that generates a cert and verifies chain before marking env ready |
| Simulator VM cloud-init networking not ready when script runs | Medium | Medium | Script retries Inventory Service calls with exponential back-off before failing |
AUTO_APPROVE accidentally set in a non-sandbox Helm values file | Low | High | CI lint step rejects AUTO_APPROVE=true in any values file not named values-sandbox.yml or values-dev.yml |
Phased Rollout
MVP
GET /installendpoint on Inventory Service — generates script from deployment configGET /internal/install/{uav_id}/certs/{file}— serves cert bundle post-approval with token authinstall_tokenstable — single-use, 1 h TTLAUTO_APPROVEdeployment config flag- Install script: interactive + headless modes,
uname -mbinary selection, cert write, config.env, systemd unit - Inventory Service UI: “Get install command” one-liner with copy button; auto-approve warning banner
uncrew-mavlink-shimCI: publish versioned aarch64 and x86_64 binaries to Artifact Registry- Simulator Controller: embed install script call in VM cloud-init metadata with
UAV_IDenv var
v1.1
- SHA256 checksum verification of binary in install script
- AI Node cert bundle support (second cert path set in script)
- Install audit log (UAV ID, version, timestamp, token ID) written to Inventory Service DB
- Cert-only refresh: re-run script to rotate certs without reinstalling binary
v2.0
- Real-time install status visible in UI (requires telemetry from UAV post-install)
- Binary version pinning in UI (select specific
mavlink_shimversion to deploy) - Automatic cert renewal before expiry
Dependencies
| Dependency | Owner | Risk |
|---|---|---|
uncrew-mavlink-shim publishes versioned aarch64 + x86_64 binaries to Artifact Registry | MAVLink Shim team | Blocking for MVP |
GET /install endpoint on Inventory Service | Inventory team | Blocking for MVP |
| Apollo UI “Get install command” section | Frontend team | Blocking for MVP |
Simulator Controller updated to embed install script call + UAV_ID | Simulator team | Blocking for simulator path in MVP |
| Legal/safety review of 44807 implications | Safety team | Required before production client deployments |
6. Estimation Input
prd_sizing_input:
feature: "onboard-software-deployment"
scope_clarity: "A"
key_terms:
- "mavlink-shim"
- "install-script"
- "certificate"
- "inventory"
- "simulator"
- "cloud-init"
- "auto-approve"
risk_flags:
- "security-certs"
- "cross-repo"
- "firmware-adjacent"
- "multi-arch-binary"
affected_repos:
- "uncrew-inventory-service"
- "uncrew-mavlink-shim"
- "uncrew-apollo-frontend"
- "uncrew-simulator-controller"
domains:
- "backend-go"
- "frontend-react"
- "firmware-cpp"
- "infrastructure-gcp"
regulatory: true
discovery_needed: false