Self-hosted mission management, flight log analysis, GPS flight replay with video export, AI report generation, invoicing, and real-time airspace monitoring for commercial drone operators.
Version 2.96.0 | Quick Start | Features | Configuration | Contributing | License
Live Demo: command-demo.barnardhq.com (login: demo / demo123)
DroneOpsCommand is a self-hosted, full-stack platform for managing commercial drone operations end-to-end. It covers the complete lifecycle from flight data ingestion and GPS telemetry visualization through AI-powered report generation, invoicing, and client delivery — all running on your own hardware.
Designed for FAA Part 107 certified operators running missions such as search & rescue, inspections, mapping, videography, and more.
- 100% self-hosted — runs on your own hardware via Docker Compose. No cloud dependencies, no per-seat licensing, no subscription fees.
- AI report generation — local via Ollama (Llama 3.1 8B Instruct
q4_K_Mdefault) or cloud via Claude API. Your data stays on your hardware with Ollama; Claude API available for faster, higher-quality output. - White-label ready — company name, tagline, and branding are fully configurable from the Settings UI. No code changes needed to make it yours.
- Full lifecycle — flight log upload, GPS path visualization, animated flight replay with video export, telemetry analysis, mission management, AI reports, PDF export, invoicing, and email delivery in one platform.
- Real-time airspace — live aircraft tracking via OpenSky Network with anonymous or authenticated access.
- Mobile-friendly — responsive dark-themed UI works on phones, tablets, and desktops.
- Quick Start
- Features
- Architecture
- Configuration
- Updating
- Pages & Workflows
- Backend Services
- API Reference
- Roadmap
- Development
- Contributing
- License
- Docker and Docker Compose (v2+)
- x86_64 or ARM64 host
Minimum resources (self-hosted):
| Resource | Minimum | Recommended | Notes |
|---|---|---|---|
| RAM | 12 GB | 16 GB | docker-compose.yml gives Ollama mem_reservation: 8g / mem_limit: 10g; the default Llama 3.1 8B q4_K_M model is ~6 GB resident with its KV cache. Backend + Postgres + flight-parser + Redis push the total past 8 GB, so 8 GB of host RAM is not enough unless you run LLM_PROVIDER=claude and drop the ollama service. |
| CPU | 4 cores | 6–8 cores | docker-compose.yml pins Ollama to 6 cores. Fewer cores means slow AI report generation. |
| Disk | 30 GB | 100 GB+ | Flight logs, Postgres, the ~4.9 GB Ollama model, video exports. Grows with usage. |
Docker Desktop users (Windows/Mac) — READ THIS. Docker Desktop runs containers inside a Linux VM with its own RAM/CPU limits. The defaults are usually too low for DroneOpsCommand. Open Docker Desktop → Settings → Resources and raise Memory to at least 8 GB (16 GB recommended) and CPUs to at least 4 before
docker compose up. If the VM runs out of memory the stack will crash at startup or under load with no clear error.setup-server.shdoes not run on Windows/Mac, so you won't see a preflight warning — allocate the VM resources manually.
Windows? See the Windows Self-Hosting Guide for step-by-step Docker Desktop + WSL 2 setup.
# 1. Clone and configure
git clone https://github.com/BigBill1418/DroneOpsCommand.git
cd DroneOpsCommand
cp .env.example .env
# 2. Set your secrets. These four have NO default (ADR-0012) — compose
# interpolates them with `:?` and REFUSES TO START if any is empty:
# - POSTGRES_PASSWORD openssl rand -base64 32
# - DATABASE_URL postgresql+asyncpg://doc:<that password>@db:5432/doc
# - REPLICATION_PASSWORD openssl rand -base64 32 (any value on a standalone install)
# - JWT_SECRET_KEY openssl rand -hex 32
# 3. Launch
docker compose up -d
# 4. Wait for the AI model to download (first run only, ~4.9 GB)
docker compose logs -f ollama-setup
# 5. Open the app
# Web UI: http://localhost:3080
# API docs: http://localhost:3080/docs
# First visit shows the setup wizard — create your admin account there.- PostgreSQL schema is created automatically
- Setup wizard prompts you to create the admin account (no env vars needed)
- Aircraft fleet (7 DJI models) and rate templates (14 billing presets) are pre-loaded
- Ollama downloads the Llama 3.1 8B Instruct
q4_K_Mmodel (~4.9 GB) - All storage directories are created
Run the setup script to install the boot-time systemd unit:
sudo ./setup-server.sh # install droneops.service
sudo ./setup-server.sh --uninstall # remove it (also cleans up legacy autopull units)This installs one systemd unit:
droneops.service— starts the Docker Compose stack on boot
# Useful commands
systemctl status droneops # stack status
journalctl -u droneops -f # stack start/stop logs
docker compose logs -f backend # application logsgit pull --ff-only
docker compose up -d --build # rebuild only what changed
docker compose ps # confirm everything is healthyDatabase migrations run automatically on backend startup. There is deliberately no in-repo deploy script or auto-update poller — see Updating below for the full rationale (ADR-0018).
All containers have healthchecks and restart: unless-stopped, so individual services auto-recover from crashes. The backend retries DB and Redis connections on startup to handle restart race conditions.
After logging in, go to Settings > Branding to set your company name, tagline, website, and contact email. These appear on PDF reports, emails, the login page, and customer-facing pages.
- Create and track drone missions across 9 mission types: Search & Rescue, Videography, Lost Pet Recovery, Inspection, Mapping, Photography, Survey, Security & Investigations, Other
- Multi-step mission wizard: Details, Flights, Images, Report, Invoice
- Mission status tracking: Draft, Completed, Sent
- Billable/non-billable designation per mission
- Edit existing missions at any step
- Drag-and-drop upload of DJI flight logs (.txt, .csv, .dat, .log)
- Folder upload support — drop an entire SD card directory and all valid logs are extracted
- Automatic batched uploads (40MB per batch) for large log sets — no more 413 errors
- Dedicated flight-parser microservice for DJI TXT log decryption via DJI API
- SHA-256 deduplication — re-uploading the same log is silently skipped
- Original log files stored on disk for future re-processing
- Manual flight entry for non-DJI aircraft
- Aggregate flight statistics: total flights, total time, total distance, max altitude, max speed
- Per-drone breakdown of flight time (visual bar chart)
- Top flights by duration and distance
- Searchable, sortable flight log table with unit conversions (meters to feet/miles, m/s to mph)
- Average distance and duration per flight
- Flight detail drawer with interactive GPS flight path map over the shared dark basemap
- Green takeoff marker, orange landing marker, cyan flight path trace
- Export individual flights as GPX, KML, or CSV
- Dedicated telemetry page with time-series charts
- Altitude, speed, battery percentage, voltage, temperature, satellite count, signal strength, distance from home
- Auto-downsampled to 2,000 points for smooth rendering of large datasets
- Per-flight telemetry accessible from the flight detail view
- Interactive Leaflet map with the shared dark basemap matching the app's dark theme
- Color-coded flight path overlays with start/end point markers
- Convex hull polygon showing total coverage area boundary
- Coverage area calculation in acres (with 30m buffer for camera swath simulation)
- Static map PNG generation for PDF embedding
- UTM coordinate conversion for accurate area measurement via Shapely/PyProj
- Live aircraft positions via OpenSky Network API
- Works anonymously (no account needed) with optional authenticated mode for higher rate limits
- Configurable search radius in nautical miles around your location
- Aircraft callsign, altitude, speed, heading, vertical rate, squawk code, and ground status
- Auto-refresh with configurable interval
- Pull flight logs from your self-hosted OpenDroneLog instance
- Select and attach specific flights to each mission
- GPS track extraction with telemetry data (altitude, speed, distance, duration)
- Automatic data normalization across OpenDroneLog API versions
- Connection testing from Settings page
- Dual LLM provider support: local via Ollama or cloud via Claude API (Anthropic)
- Ollama default model: Llama 3.1 8B Instruct
q4_K_M— runs on your hardware, data stays local - Claude API option: Claude Sonnet for faster, higher-quality reports (requires API key)
- Switchable from Settings page — choose provider per deployment
- Operator enters field notes/narrative, LLM generates professional after-action report
- Structured report sections: Mission Overview, Area Coverage, Flight Operations Summary, Key Findings, Recommendations
- Async generation via Celery worker with status polling
- Editable output — review and modify the generated report before finalizing
- LLM status monitoring on Settings page (online/offline, loaded model, active provider)
- Configurable model, temperature, and token limits
- TipTap-powered WYSIWYG editor for report narratives
- Full formatting: bold, italic, underline, strikethrough, headings, lists, blockquotes, code, alignment, links
- Edit both operator narrative and final report content
- Pre-seeded DJI aircraft profiles (7): Matrice 30T, Matrice 4TD, Mavic 4 Pro, Mavic 3 Pro, Avata 2, FPV, Mini 5 Pro
- Detailed specifications per aircraft: flight time, max speed, camera, thermal imaging, sensors, weight, transmission range
- Add/edit/delete aircraft with custom specs (stored as JSON)
- Assign aircraft to individual flights within a mission
- Aircraft cards displayed in mission detail and PDF reports
- Branded PDF reports with custom company branding via WeasyPrint
- Includes: mission metadata, report narrative, flight map, aircraft specs, mission images with captions
- Invoice section with line items, totals, tax calculation
- Payment links (PayPal/Venmo) for unpaid invoices
- Optional client download link for mission footage — withheld until the invoice is paid in full (ADR-0039; per-report operator override available)
- Generated timestamp and mission ID
- Per-mission invoicing with automatic duplicate prevention
- Line items with 6 categories: Billed Time, Travel, Rapid Deployment, Equipment, Special Circumstances, Other
- Quantity, unit price, and calculated line totals
- Automatic subtotal, configurable tax rate, tax amount, and grand total
- Paid-in-full tracking
- Rate templates for quick line item creation (configurable in Settings)
- Sort ordering for line item display
- 14 reusable billing templates seeded on first boot: Standard Hourly Rate, Travel - Mileage, Travel - Flat Rate, Rapid Deployment, Night Operations Surcharge, Thermal Imaging, Video Editing, Report Preparation, PV Thermal Inspection — Field Day, Mobilization — Regional Overnight, Lodging + Per Diem, Weather Standby, Data Processing & QA, Third-Party Analytics (pass-through)
- Configurable default quantity, unit (hours, miles, flat, each), and rate
- Active/inactive toggle
- Add/edit/delete from Settings page
- Pilot profiles with name, FAA certificate number, and certifications
- Per-pilot flight hour tracking and summary
- Assign pilots to flights for regulatory compliance
- Managed from the Settings page
- Track individual batteries by serial number and custom name
- Cycle count, health status, and usage history
- Link batteries to flights for lifecycle tracking
- Schedule and log maintenance records for each aircraft
- Customizable maintenance types (no fixed-length limits)
- Attach photos to maintenance records (up to 10MB per image)
- Track completion dates and upcoming maintenance due dates
- Full database backup export from the UI
- Upload and restore backups to recover or migrate data
- Validate backup files before restoring
- Customer profiles: name, company, email, phone, address (including city, state, zip), notes
- Address auto-complete via OpenStreetMap/Nominatim geocoding
- Search across all customer fields
- Customer linked to missions for reporting and email delivery
- Job history tracking
- Digital customer intake with tokenized links and expiration
- Terms of Service signature capture with PDF storage
- Configurable default TOS document upload
- Generate API keys for field devices (DroneOpsSync companion app)
- Device-authenticated upload endpoint for automated flight log sync from remote controllers
- Key management (create, revoke) from the Settings page
- Send PDF reports directly to customer email
- Async SMTP with TLS support via aiosmtplib
- HTML email body with mission title and optional download link (payment-gated per ADR-0039)
- Automated follow-up email delivers the download link the moment the invoice is paid in full — Stripe or manual mark-paid (ADR-0040)
- PDF attached automatically
- SMTP configuration via Settings page with test email button
- Mission status updated to "Sent" after successful delivery
- Store mission footage folder paths (UNAS/Synology NAS)
- Paste share links from UNAS web interface with expiration dates
- Active/expired link status badge with date tracking
- Optional inclusion of download link in client reports and emails — payment-gated: withheld until the invoice is paid in full, then delivered automatically by email and unlocked in the client portal (ADR-0039/0040)
- Supports file paths with special characters, unicode, and spaces
- Real-time weather conditions via Open-Meteo API: temperature, humidity, wind speed/direction/gusts, cloud cover, visibility, pressure
- METAR aviation weather from AviationWeather.gov: flight category (VFR/MVFR/IFR/LIFR), raw METAR string, cloud layers
- FAA Temporary Flight Restrictions (TFRs) from AviationWeather.gov/FAA GeoJSON
- NOTAMs (Notices to Airmen) with classification and effective dates
- National Weather Service alerts with severity levels
- Wind severity indicator: favorable, caution, hazardous
- Configurable home location from Settings page (used for weather, airspace, and METAR data)
- Total billed revenue, average per mission, billable mission count
- Revenue breakdown by: drone/aircraft, line item category, mission type, month, customer
- Top customers by revenue with mission count
- Paid vs. outstanding tracking and collection rate percentage
- Searchable invoice table across all missions
- Prepaid status badges
- Animated GPS flight path playback with real-time telemetry HUD
- Altitude-colored trail segments (ground, <100ft, 100-200ft, 200-400ft, 400ft+)
- Animated drone marker with heading rotation and glow effect
- Ghost trail showing full flight path with colored trail progressing over it
- Playback controls: play/pause (spacebar), skip forward/back, scrub bar
- Variable speed: 0.5x, 1x, 2x, 5x, 10x
- Live telemetry sidebar: altitude, speed, heading, position, elapsed time
- Flight stats panel with duration, distance, max altitude, max speed
- Home point and start/end markers on map
- Follow-drone mode auto-pans the map to track the aircraft
- Dark basemap matching the app's theme
- One-click export: click button → render → auto-download
- Renders full flight replay as a downloadable WebM video (1920x1080, 30fps)
- Canvas-based rendering with the shared dark basemap (fetched one zoom deeper and drawn at half scale for pixel density), altitude-colored trail, drone marker
- Telemetry sidebar overlay with live altitude, speed, heading, position, elapsed time
- Flight stats panel, altitude color legend, and progress bar in the video
- Progress notifications during rendering with percentage updates
- No modal or multi-step flow — instant click-to-download behavior
- Browser-native MediaRecorder with VP9/VP8 codec support
- Ideal for after-action reports and customer deliverables
- Client-facing view of their missions and invoices — no operator internals exposed
- Signed JWT links emailed to clients with configurable expiry — no account creation needed
- Optional password-protected persistent login for repeat clients
- Mission status visibility: Scheduled, In Progress, Processing, Review, Delivered
- Client views and pays invoices via Stripe (card/ACH) directly in the portal
- Stripe webhook automatically marks invoices paid in the operator's financial dashboard
- Operator generates client access links from the mission detail page
- JWT access tokens (configurable expiration, default 30 min)
- Refresh token rotation (configurable, default 30 days)
- Secure password hashing via bcrypt 4.x (direct, no passlib wrapper)
- All API endpoints require authentication
- Admin account seeded on first startup only — password never overwritten on restart
- PostgreSQL advisory lock prevents race conditions during seed
- Single-worker uvicorn for consistent async behavior
- Explicit commit + read-back verification on password changes
Optional: Cloudflare Access SSO for the operator login (off by default). If you put the
operator surface behind Cloudflare Access, the app can verify the Cf-Access-Jwt-Assertion
itself rather than trusting the perimeter — RS256 against your team's JWKS, with iss, aud,
exp (30 s skew), key selection bound to the token's kid, and an e-mail allow-list. It is
inert unless you set both CF_ACCESS_TEAM_DOMAIN and CF_ACCESS_AUD; until you do, the
verifier never runs, no network call is made, and POST /api/auth/sso-exchange answers 404.
POST /api/auth/sso-exchangetrades a verified assertion for the same token pair/api/auth/loginreturns, so an SSO-only operator can still obtain a bearer for any route your Access policy bypasses.LOCAL_LOGIN_DISABLED=true(defaultfalse) retires the password entirely:login,setup,accountandrefreshreturn403. Nothing is deleted — set it back tofalseand redeploy to restore password login.docker compose restartdoes not re-read the environment; useup -d.SERVICE_ACCOUNT_USERNAMESis a comma-separated exemption from that403for non-interactive machine accounts, which cannot hold an Access cookie. The exemption is from the403and nothing else — the password, the lockout and theis_activecheck all still run.- Self-hosted, OSS and demo installs are unaffected by all of the above by construction:
both Access variables are empty and
LOCAL_LOGIN_DISABLEDisfalsein the shippeddocker-compose.ymland.env.example.
Design notes and the two production outages that shaped it:
docs/adr/0047-operator-cloudflare-access-sso.md (five amendments) and
docs/adr/0048-service-account-allowlist-and-sso-bearer-exchange.md.
- At-a-glance stats: total flight hours, total flights, total missions, drafts, customers
- Recent missions table with status badges and quick actions
- Live weather conditions and flight conditions assessment
- METAR aviation weather with color-coded flight categories (VFR/MVFR/IFR/LIFR)
- FAA TFR and NOTAM alerts
- NWS weather alerts with severity levels
- Dark-themed UI with cyan accents, Bebas Neue headings, and Share Tech Mono data fonts
- Upload mission images with drag-and-drop or file picker
- Automatic image resizing (max 1920px) for report optimization
- EXIF orientation correction
- JPEG conversion for consistency and file size reduction
- Captions per image
- Sort ordering for report display
- Images embedded in PDF reports
| Service | Technology | Port | Purpose |
|---|---|---|---|
| Frontend | React 18 + Vite + Mantine UI | 3080 (nginx) | SPA web interface |
| Backend | Python 3.12, FastAPI, SQLAlchemy 2.0 | 8000 | REST API |
| Database | PostgreSQL 16 Alpine | 5434:5432 (bound to 127.0.0.1 + 10.99.0.1, never 0.0.0.0) |
Persistent storage with replication support |
| Flight Parser | Rust (axum) microservice | 8100 | DJI/Litchi/Airdata flight log decryption and parsing |
| LLM | Ollama (Llama 3.1 8B Instruct q4_K_M default) or Claude API |
11434 | AI report generation |
| Queue | Redis 7 Alpine | 6379 | Celery task broker |
| Worker | Celery (same backend image) | — | Async report generation |
| Cloudflared | cloudflare/cloudflared | — | Secure tunnel (optional) |
On BarnardHQ's own deployment the primary runs on BOS-HQ (10.99.0.4) as the
container droneops-standby-db (a promoted former standby — the name is
historical), and it streams WAL to a standby on CHAD-HQ (10.99.0.2),
application_name=chad_hq_standby. Verified live 2026-09-21. This provides:
- Hot standby — read-only replica available for failover
- Continuous WAL shipping — changes stream in real-time to the standby
- Replication user — dedicated
replicatorrole withREPLICATIONprivileges - Managed by NOC — replication health monitored by NOC Master's continuous replication monitor (30s checks, auto-recovery)
The primary entrypoint script (scripts/primary-entrypoint.sh) configures pg_hba.conf for replication access and WAL sender settings.
Note on
docker-compose.standby.yml/scripts/init-standby.sh: both still describe the pre-2026-04-20 topology (primary on CHAD-HQ, standby on HSH-HQ) and their10.99.0.2primary address is the old one. The direction inverted during the HSH→BOS migration. Set the primary host to the real primary before using either on a fresh standby; the files carry the same warning in their headers.
All map tiles come from one registry — frontend/src/lib/basemaps.ts — rendered
by <BasemapLayers/>. No page holds a tile URL, so changing provider is a
one-line edit. Decision and the risks accepted:
ADR-0046;
provider comparison: docs/reports/2026-09-21-basemap-provider-eval.md.
| Layer set | Provider | Notes |
|---|---|---|
| Dark (default) | Esri Canvas/World_Dark_Gray_Base + Reference/World_Transportation |
Keyless. Base has real data to z16, the roads/labels overlay to z19 — which is what keeps street detail crisp at drone zoom while the backdrop upsamples. |
| Satellite | Esri World_Imagery |
Keyless, real data to z19. |
| Hybrid | Imagery + World_Boundaries_and_Places + World_Transportation |
Place names and roads over satellite. |
| Street | OpenStreetMap standard | User-selectable only, never the default — the OSMF Tile Usage Policy allows human-driven viewing, not bulk or pre-emptive fetching. |
No API key, no account, no cost. Esri serves {z}/{y}/{x}; OSM serves
{z}/{x}/{y} — a transposed template returns a real tile of somewhere else.
Neither provider offers an @2x retina variant, and maxNativeZoom values are
measured rather than taken from the services' advertised LOD.
Both providers require attribution, which every map renders. The backend report
renderer (app/services/map_renderer.py) fetches OSM server-side and sends
DroneOpsCommand/<version> (+https://droneops.barnardhq.com; bill@barnardhq.com)
as required by the policy.
Tile-health probe. A weekly Celery task fetches one fixed tile per provider
and compares two perceptual hashes and the byte size against a checked-in
baseline. This exists because the provider this replaced (CARTO) started serving
tiles with an "API KEY REQUIRED" watermark burned into the pixels while still
returning HTTP 200 with correct headers — a failure no status check, retry
handler or cache can see. Inspect it at
GET /api/admin/basemap/tile-health; run it on demand with
POST /api/admin/basemap/tile-health/run. ntfy alerting is wired but ships
off (basemap_probe_ntfy_enabled) until the thresholds are tuned against
real data — ROADMAP MP-2.
- React 18 with TypeScript
- Mantine UI v7 component library with dark theme
- Vite build tool
- React Router for SPA navigation
- TipTap rich text editor
- Leaflet interactive maps over a keyless dark basemap — see Map basemap providers
- Axios HTTP client with JWT interceptors and token refresh
- Mantine Dates for date inputs
- Tabler Icons icon set
- Custom fonts: Bebas Neue (display), Share Tech Mono (monospace), Rajdhani (UI)
- FastAPI async REST framework
- SQLAlchemy 2.0 async ORM with asyncpg driver
- Pydantic v2 request/response validation
- WeasyPrint HTML-to-PDF rendering (Cairo/Pango)
- Jinja2 HTML templates for PDF and email
- Shapely + PyProj geospatial calculations
- staticmap static map image generation
- Pillow image processing
- aiosmtplib async email delivery
- httpx async HTTP client (Ollama, OpenDroneLog, weather APIs)
- Celery distributed task queue
- bcrypt direct password hashing (4.x)
- Proxies
/api/to backend on port 8000 - Proxies
/static/and/uploads/to backend - SPA fallback (
try_filestoindex.html) - Gzip compression enabled
- 200MB max upload size, 300s read timeout
All settings are configured via environment variables in the .env file.
| Variable | Default | Description |
|---|---|---|
POSTGRES_USER |
doc |
PostgreSQL username |
POSTGRES_PASSWORD |
(no default — required) | PostgreSQL password. Per ADR-0012 compose interpolates this with :?, so the containers refuse to start if it is unset. |
POSTGRES_DB |
doc |
Database name |
DATABASE_URL |
(no default — required) | Full async connection string, e.g. postgresql+asyncpg://doc:<pw>@db:5432/doc. Also :?-guarded in compose. |
| Variable | Default | Description |
|---|---|---|
JWT_SECRET_KEY |
(no default — required) | Signs every access/refresh token. :?-guarded in compose per ADR-0012 — the stack will not start without it. Generate with openssl rand -hex 32. |
JWT_ALGORITHM |
HS256 |
Token signing algorithm |
JWT_ACCESS_TOKEN_EXPIRE_MINUTES |
30 |
Access token lifetime |
JWT_REFRESH_TOKEN_EXPIRE_DAYS |
30 |
Refresh token lifetime |
Note: Admin credentials are created via the first-run setup wizard in the browser. There are no
ADMIN_USERNAME/ADMIN_PASSWORDenvironment variables.
| Variable | Default | Description |
|---|---|---|
OPENDRONELOG_URL |
(empty) | Your OpenDroneLog server URL (e.g., http://192.168.1.50:8080) |
DJI_API_KEY |
(empty) | DJI Cloud API key for encrypted flight log parsing (register here) |
OLLAMA_BASE_URL |
http://ollama:11434 |
Ollama API endpoint |
OLLAMA_MODEL |
llama3.1:8b-instruct-q4_K_M |
Ollama model for report generation. Must match what ollama-setup pulls (same value in docker-compose.yml) — point it at a model that was never pulled and report generation fails with a model-not-found error. |
LLM_PROVIDER |
ollama |
Active LLM provider: ollama or claude |
ANTHROPIC_API_KEY |
(empty) | Anthropic API key (required when LLM_PROVIDER=claude) |
CLAUDE_MODEL |
claude-sonnet-4-6 |
Anthropic model id used when LLM_PROVIDER=claude. Override to pin or bump; do not hardcode model ids in service modules. |
| Variable | Default | Description |
|---|---|---|
SMTP_HOST |
(empty) | SMTP server hostname |
SMTP_PORT |
587 |
SMTP port |
SMTP_USER |
(empty) | SMTP username |
SMTP_PASSWORD |
(empty) | SMTP password |
SMTP_FROM_EMAIL |
(empty) | Sender email address |
SMTP_FROM_NAME |
(empty) | Sender display name |
SMTP_USE_TLS |
true |
Enable TLS encryption |
SMTP settings can also be configured from the Settings page in the web UI (stored in database, overrides env vars).
| Variable | Default | Description |
|---|---|---|
STRIPE_SECRET_KEY |
(empty) | Stripe secret key for payment processing |
STRIPE_PUBLISHABLE_KEY |
(empty) | Stripe publishable key (exposed to frontend) |
STRIPE_WEBHOOK_SECRET |
(empty) | Stripe webhook signing secret |
| Variable | Default | Description |
|---|---|---|
FRONTEND_URL |
http://localhost:3080 |
Public URL used in intake emails and client portal links |
FRONTEND_PORT |
3080 |
Host port for the frontend. Use 127.0.0.1:3080 to restrict to localhost when behind a tunnel |
CLOUDFLARE_TUNNEL_TOKEN |
(empty) | Cloudflare Tunnel token for secure remote access without opening ports |
Every rate limiter and the login lockout resolve the real caller through
backend/app/utils/client_ip.py. X-Forwarded-For is honoured only when the
direct peer is a trusted proxy — never blindly.
| Variable | Default | Description |
|---|---|---|
TRUSTED_PROXY_HOSTNAME |
frontend,cloudflared |
Comma-separated Docker Compose service names, each resolved via Docker's embedded DNS on every check. The default matches this compose file's real two-hop chain (cloudflared → nginx → uvicorn) and needs no operator action. Managed tenants must set caddy instead — see docs/managed-hosting.md. |
FORWARDED_ALLOW_IPS |
(empty) | Optional additional comma-separated IPs/CIDRs to trust, for a proxy hop that cannot be resolved by hostname from this container's network. |
| Variable | Default | Description |
|---|---|---|
TOS_SIGNED_DOWNLOAD_EXPIRE_DAYS |
730 |
How long a customer's signed-TOS download link stays valid after acceptance. Bounded but durable — it used to be unbounded. |
INTAKE_TOKEN_EXPIRE_DAYS |
7 |
Lifetime of a customer intake link. |
CLIENT_TOKEN_EXPIRE_DAYS |
30 |
Lifetime of a client-portal access link. |
GOOGLE_REVIEW_URL |
BarnardHQ's g.page link |
Review CTA on the final-invoice PDF, report-delivery email, post-payment portal state and payment-received email. Unset = the CTAs hide themselves. Change this for your own deployment. |
| Variable | Default | Description |
|---|---|---|
OPERATOR_TIMEZONE |
America/Los_Angeles |
Defines the calendar date of a flight (ADR-0017). A flight's instant is stored in UTC; its date is the date in this zone. Evening Pacific flights otherwise roll to the next UTC day. |
| Variable | Default | Description |
|---|---|---|
MANAGED_INSTANCE |
false |
true enables the managed gates (setup wizard skipped, LLM locked to Claude). |
CLIENT_ID |
(empty) | Tenant identifier surfaced on the health endpoint. |
ADMIN_USERNAME / ADMIN_PASSWORD |
(empty) | Auto-provisioned admin for managed instances only. A self-hosted install creates its admin through the first-run setup wizard and ignores these. |
| Variable | Default | Description |
|---|---|---|
DEMO_MODE |
false |
Enables the demo guard middleware and the demo banner. |
DEMO_ADMIN_USERNAME / DEMO_ADMIN_PASSWORD |
(empty) | Demo admin seeded on boot. |
DEMO_RESET_INTERVAL_HOURS |
24 |
Read but unimplemented — nothing in the app acts on it. scripts/demo-nightly-reset.sh on a crontab is the real reset. |
| Variable | Default | Description |
|---|---|---|
NTFY_DRONEOPS_PUBLISHER_TOKEN |
(empty) | Publisher token for self-hosted ntfy (fleet ADR-0036). Unset = watchdogs still log, alerts just do not go out. |
DEVICE_SILENCE_HOURS |
48 |
A device key active recently but silent this long raises an alert. |
DEVICE_SILENCE_ACTIVITY_WINDOW_DAYS |
7 |
How recently a key must have been used to be considered active. |
DEVICE_SILENCE_DEDUP_HOURS |
12 |
Per-key alert cooldown so a long outage does not spam. |
All DSN/endpoint-gated — unset means no-op. A single-tenant self-hosted install can ignore this block.
| Variable | Default | Description |
|---|---|---|
SENTRY_DSN |
(empty) | Sentry/GlitchTip DSN for the backend + Celery worker. |
SENTRY_ENVIRONMENT |
production |
Environment tag. |
SENTRY_TRACES_SAMPLE_RATE |
0.05 |
Trace sampling. |
OTEL_EXPORTER_OTLP_ENDPOINT |
(empty) | OTLP/gRPC collector endpoint. |
OTEL_SERVICE_NAME |
droneops-api |
Service name on emitted spans. |
ENV / TENANT |
prod / shared |
Stamped on logs, traces and the Sentry environment. |
VITE_SENTRY_DSN / VITE_SENTRY_ENVIRONMENT |
(empty) / production |
Frontend Sentry — build-time only, read by Vite at npm run build. |
Since v2.92.1 the Sentry release tag on both halves comes from the bumped source files (
backend/app/version.pyandfrontend/package.json), not from theAPP_VERSIONenv var. TheAPP_VERSION/VITE_APP_VERSIONcompose defaults are cosmetic.
| Variable | Default | Description |
|---|---|---|
REPLICATION_PASSWORD |
(no default — required) | Password for the PostgreSQL replication user. Set in your .env; per ADR-0012 there is no fallback default and containers will refuse to start if this is unset. |
| Variable | Default | Description |
|---|---|---|
UPLOAD_DIR |
/data/uploads |
Mission image storage path |
REPORTS_DIR |
/data/reports |
Generated PDFs and map images |
Two LLM providers are supported, configurable from the Settings page or via environment variables:
| Provider | Model | Where it runs | When to use |
|---|---|---|---|
| Ollama (default) | Llama 3.1 8B Instruct q4_K_M |
Local, on your hardware | Data stays on-premises, no API costs |
| Claude API | Claude Sonnet | Anthropic cloud | Faster, higher-quality reports, requires API key |
Set LLM_PROVIDER=claude and ANTHROPIC_API_KEY in .env to use Claude, or switch providers in Settings at runtime.
The docker-compose.yml pins Ollama to 6 CPU cores (cpuset: "0-5", leaving 2 for the OS and database), sets an 8 GB RAM reservation with a 10 GB hard cap, enables flash attention, serves one request at a time (OLLAMA_NUM_PARALLEL=1, OLLAMA_MAX_LOADED_MODELS=1) and keeps the model loaded permanently (OLLAMA_KEEP_ALIVE=-1). The image is pinned to ollama/ollama:0.23.2 — not :latest — so a patch is an explicit edit.
git pull --ff-only
docker compose up -d --build
docker compose ps # confirm healthyCompose rebuilds only the services whose build context changed, and database migrations run automatically on backend startup — there is no separate migrate step.
There is deliberately no in-repo auto-deploy poller. An earlier
autopull.sh + droneops-autopull.timer + update.sh arrangement was removed
(ADR-0018): autopull.sh
still invoked the already-deleted update.sh, so the repo advertised two deploy
paths while one of them was a corpse. That cost real debugging time. If you want
polling-based CD on a self-hosted install, drive it from outside the repo (a
systemd timer of your own, a CI runner, or a webhook) rather than reintroducing
a second in-repo path.
There is no Watchtower sidecar. It was removed on 2026-06-05 (fleet ADR-0088):
an auto-updater on a deployer-managed host is a second, uncoordinated deploy
path. Base images (postgres:16-alpine, redis:7-alpine, ollama/ollama:0.23.2,
cloudflare/cloudflared) are pinned in docker-compose.yml on purpose —
bump the tag in the file, then docker compose up -d --build. Self-hosters who
want automatic base-image updates should run that tooling outside this repo.
Landing page with mission stats, recent missions table, real-time weather conditions, METAR aviation data, FAA TFR/NOTAM alerts, NWS weather alerts, and flight condition assessment.
List all missions with status badges (Draft/Completed/Sent), billable indicators, search, and quick actions (edit, delete). Click a row to view mission details.
A mission is created from the inline modal behind the "New Mission" button on
/missions — there is no standalone create page. Editing is per-section from
the mission hub at /missions/:id, not a linear wizard:
| Route | Step |
|---|---|
/missions/:id/details/edit |
Customer, title, type, date, location, description, billable toggle, lead source, UNAS folder path, download link URL with expiration |
/missions/:id/flights/edit |
Browse/attach flights, assign aircraft per flight, view flight map and coverage area |
/missions/:id/images/edit |
Upload mission photos (drag-and-drop or file picker), auto-resized and EXIF-corrected |
/missions/:id/report/edit |
Operator narrative, LLM report generation, rich-text editing, PDF, email to customer |
/missions/:id/invoice/edit |
Line items from rate templates or manually, quantities, rates, tax, paid/unpaid |
/missions/:id/edit-legacy |
The old 5-stage wizard, kept reachable |
/missions/new and /missions/:id/edit are soft redirects since v2.67.0 —
they show a toast and bounce to /missions and /missions/:id respectively, so
old operator bookmarks still land somewhere sensible.
Full mission view with metadata, assigned aircraft cards, interactive flight map, coverage stats, download link status, report content, and action buttons (edit, delete, generate PDF, email report).
Flight library with aggregate statistics, per-drone breakdowns, top flights, sortable/searchable table, and a detail drawer with interactive GPS flight path map, telemetry data, and export options (GPX/KML/CSV). Flight Replay button for flights with GPS tracks.
Animated GPS flight path playback with altitude-colored trail, drone marker with heading, live telemetry sidebar (altitude, speed, heading, position), flight stats, and playback controls (play/pause, speed, scrub). One-click video export renders the full replay as a downloadable WebM video with map, flight path, and telemetry overlay.
Drag-and-drop flight log upload with folder support. Batched uploads for large file sets. Progress tracking per file with duplicate detection and error reporting.
Time-series telemetry visualization with altitude, speed, battery, satellites, signal, and distance-from-home charts. Auto-downsampled for smooth rendering.
Live aircraft tracking via OpenSky Network. Works anonymously or with credentials for better rate limits. Configurable location and search radius.
Battery fleet management with serial numbers, cycle counts, health tracking, and per-battery flight history.
Maintenance scheduling and logging per aircraft. Custom maintenance types, photo attachments, and due-date tracking.
Customer CRM with add/edit/delete, address auto-complete via OpenStreetMap geocoding, digital intake forms with TOS signature capture, and search.
Revenue dashboard with total/average/outstanding metrics, breakdowns by drone, category, mission type, month, and customer. Full invoice table with search.
Admin view of every captured Terms-of-Service acceptance: who signed, when, and a link to the stored signed PDF with its SHA-256 anchor.
Not a route — the app renders the setup wizard in place of every authenticated
page while no users exist, then the login screen. It creates the initial admin
account. To reset: docker compose exec backend python reset_to_setup.py.
| Route | Purpose |
|---|---|
/intake/:token |
Tokenized customer intake form |
/tos/accept |
Terms-of-Service acceptance (typed name + checkbox → AcroForm-filled PDF) |
/client/:token |
Client portal — mission list + invoices, opened from a signed link |
/client/login |
Optional password login for repeat clients |
/client/mission/:missionId |
Client-facing mission detail |
Client portal authentication is a separate scope from the operator UI.
System configuration across multiple tabs:
- LLM — Provider selection (Ollama or Claude API), connection status, loaded model, API key configuration
- Flight Data — OpenSky Network credentials, DJI API key, OpenDroneLog server URL with connection test
- SMTP — Email server configuration with test email
- Payment Links — PayPal and Venmo URLs for invoices
- Stripe — Stripe API keys for client portal payments
- Home Location — Coordinates for weather, airspace, and METAR data
- Aircraft Fleet — Add/edit/delete aircraft with specifications
- Pilots — Add/edit/delete pilot profiles with certifications and flight hour tracking
- Rate Templates — Add/edit/delete billing rate presets
- Device Keys — API key management for DroneOpsSync companion app
- Backup — Database export and restore
- Branding — Company name, tagline, website, social media, contact email — used in PDF reports, emails, login page, and customer-facing pages
Renders branded PDF reports from Jinja2 HTML templates via WeasyPrint. Includes mission metadata, report narrative, flight path map, aircraft specs, mission images, invoice with line items and totals, payment links, and optional download link.
Async SMTP client that sends HTML emails with the PDF report attached. Loads configuration from database settings first, falls back to environment variables. Supports TLS.
Dual-provider LLM client supporting Ollama and Claude API. The active provider is selectable from Settings or via LLM_PROVIDER env var.
- Ollama — HTTP client to the
/api/generateendpoint. Sends a structured prompt with mission data, flight telemetry, and operator notes. Temperature 0.3 for consistency, 300s timeout, 6 CPU threads. - Claude API — Anthropic SDK client, model from
CLAUDE_MODEL(defaultclaude-sonnet-4-6). Same structured prompt, cloud-processed. RequiresANTHROPIC_API_KEY.
REST client that fetches flight data from a self-hosted OpenDroneLog instance. Handles multiple API endpoint patterns for version compatibility. Normalizes field names between camelCase and snake_case. Extracts GPS tracks for map rendering.
Generates GeoJSON FeatureCollections with flight path LineStrings, start/end markers, and convex hull polygons. Calculates coverage area in acres using UTM projection and Shapely geometry with configurable buffer distance. Renders static PNG maps using OpenStreetMap tiles, sending an identifying User-Agent per the OSMF Tile Usage Policy.
Standalone Rust (axum) microservice on port 8100 that decrypts and parses DJI flight logs using the DJI Cloud API, plus Litchi and Airdata CSV exports. GET /health reports its own crate version — the only reliable confirmation that a parser deploy landed. Extracts GPS tracks, telemetry time-series, drone metadata, and battery information from encrypted TXT log files.
Proxies requests to the OpenSky Network API for real-time aircraft position data. Supports anonymous and OAuth2-authenticated modes. Converts search radius to bounding box coordinates and normalizes the response into a clean aircraft list.
Aggregates data from 4 external APIs: Open-Meteo (current conditions), AviationWeather.gov (METAR, TFRs), aviationapi.com (NOTAMs fallback), and NWS (weather alerts). Location configurable from Settings.
Full interactive API documentation is available at http://localhost:3080/docs (Swagger UI) when the app is running — nginx proxies /docs, /redoc and /openapi.json straight to the backend. The table below is a curated subset; /openapi.json is the complete, authoritative list.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth/login |
Authenticate and receive JWT tokens |
| POST | /api/auth/refresh |
Refresh access token |
| GET/POST | /api/missions |
List or create missions |
| GET/PUT/DELETE | /api/missions/{id} |
Read, update, or delete a mission |
| POST | /api/missions/{id}/flights |
Attach a flight to a mission |
| POST | /api/missions/{id}/images |
Upload a mission image |
| POST | /api/missions/{id}/report/generate |
Generate LLM report (async) |
| GET | /api/missions/{id}/report/status/{task_id} |
Poll report generation status |
| GET/PUT | /api/missions/{id}/report |
Read or save report content |
| POST | /api/missions/{id}/report/pdf |
Generate and download PDF |
| POST | /api/missions/{id}/report/send |
Email PDF report to customer |
| GET/POST | /api/missions/{id}/invoice |
Get or create invoice |
| POST | /api/missions/{id}/invoice/items |
Add line item to invoice |
| GET | /api/missions/{id}/map |
Get flight path GeoJSON |
| GET | /api/missions/{id}/map/coverage |
Get coverage area in acres |
| POST | /api/missions/{id}/map/render |
Generate static map PNG |
| GET/POST/PUT/DELETE | /api/customers |
Customer CRUD |
| GET/POST/PUT/DELETE | /api/aircraft |
Aircraft CRUD |
| GET | /api/flight-library |
List all flights in the library |
| GET | /api/flight-library/{id} |
Flight detail with GPS track and telemetry |
| GET | /api/flight-library/{id}/track |
Raw GPS track points |
| GET | /api/flight-library/{id}/telemetry |
Downsampled telemetry time-series |
| GET | /api/flight-library/{id}/export/{fmt} |
Export flight as GPX, KML, or CSV |
| GET | /api/flight-library/{id}/details |
Extended DJI log detail (ADR-0043 sidecar) |
| GET | /api/flight-library/{id}/details/series |
Full-resolution frame series |
| GET | /api/flight-library/details/status |
Extended-detail coverage summary |
| GET | /api/flight-library/device-health |
Device-key preflight check (companion app) |
| POST | /api/flight-library/device-upload/async |
Async device upload → {batch_id} |
| GET | /api/flight-library/device-upload/status/{batch_id} |
Poll an async device upload |
| POST | /api/flight-library/import/opendronelog |
Import from OpenDroneLog |
| POST | /api/flight-library/upload |
Upload flight log files (batched) |
| POST | /api/flight-library/device-upload |
Device-authenticated log upload |
| POST | /api/flight-library/manual |
Create a manual flight entry |
| POST | /api/flight-library/reprocess/all |
Re-parse stored flight logs |
| GET | /api/flight-library/airspace/aircraft |
Live aircraft positions (OpenSky proxy) |
| GET/POST/PUT/DELETE | /api/batteries |
Battery CRUD |
| GET/POST/PUT/DELETE | /api/maintenance |
Maintenance record CRUD |
| GET/POST/DELETE | /api/settings/device-keys |
Device API key management |
| POST | /api/admin/devices/{id}/rotate-key |
Zero-touch device key rotation (ADR-0003) |
| POST | /api/backup/validate-upload |
Validate a backup file |
| POST | /api/backup/restore-from-upload |
Restore database from backup |
| POST | /api/backup/create-and-download |
Export a database backup |
| GET/PUT | /api/backup/schedule |
In-app backup schedule |
| POST/GET | /api/backup/jobs, /api/backup/jobs/{id} |
Async backup job + progress poll |
| GET/DELETE | /api/backup/history, /api/backup/history/{filename} |
Backup history |
| GET | /api/flights |
List flights from OpenDroneLog |
| GET | /api/financials/summary |
Financial dashboard data |
| GET | /api/weather/current |
Weather, METAR, TFRs, NOTAMs, NWS alerts |
| GET/PUT | /api/settings/smtp |
SMTP configuration |
| POST | /api/settings/smtp/test |
Send test email |
| GET/PUT | /api/settings/opendronelog |
OpenDroneLog URL |
| GET/PUT | /api/settings/payment |
PayPal/Venmo payment links |
| GET/POST/PUT/DELETE | /api/rate-templates |
Rate template CRUD |
| GET/PUT | /api/settings/llm |
LLM provider selection and API key |
| GET/PUT | /api/settings/dji, /api/settings/opensky, /api/settings/weather, /api/settings/branding |
Per-area settings (each with a POST .../test or /lookup where applicable) |
| GET | /api/llm/status |
Ollama/LLM connection status |
| GET | /api/branding |
Public branding payload (login + customer-facing pages) |
| GET/POST | /api/pilots |
Pilot CRUD |
| GET | /api/pilots/{id}/hours-summary |
Pilot flight hour breakdown |
| POST | /api/client/auth/validate |
Validate client portal token |
| POST | /api/client/auth/login |
Client portal login |
| GET | /api/client/missions |
Client's mission list |
| GET | /api/client/missions/{id} |
Client mission detail |
| GET | /api/client/missions/{id}/invoice |
Client invoice view |
| POST | /api/client/missions/{id}/invoice/pay |
Initiate Stripe payment |
| POST | /api/missions/{id}/client-link |
Generate client access link |
| POST | /api/missions/{id}/client-link/send |
Email client access link |
| POST | /api/webhooks/stripe |
Stripe payment webhook |
| POST | /api/intake/initiate |
Start a customer intake, emails a tokenized link |
| GET/POST | /api/intake/form/{token} |
Public intake form read/submit |
| GET | /api/intake/tos-pdf/{token} |
Serve the TOS template to the intake form |
| POST | /api/tos/accept |
Record a TOS acceptance, produce the signed PDF |
| GET | /api/tos/acceptances |
Admin list of acceptances |
| GET | /api/tos/signed/{audit_id} |
Operator download of a signed TOS |
| GET | /api/tos/signed/by-token/{intake_token} |
Customer download of their own signed copy (bounded by TOS_SIGNED_DOWNLOAD_EXPIRE_DAYS) |
| GET | /api/admin/basemap/tile-health |
Latest basemap tile-health probe result (ADR-0046) |
| POST | /api/admin/basemap/tile-health/run |
Run the probe on demand (60 s cooldown) |
| PUT | /api/admin/basemap/tile-health/ntfy |
Arm/disarm probe alerting |
| GET | /api/missions/{id}/preflight, /api/missions/airspace-preflight |
Pre-flight weather + airspace assessment |
| GET | /api/maintenance/status, /api/maintenance/due, /api/maintenance/next-due |
Maintenance alert sources |
| GET/POST | /api/maintenance/schedules |
Recurring maintenance schedules |
| GET | /api/v1/business-signals |
Business-signal rollup |
| GET | /api/demo/status |
Demo-mode banner payload |
| GET | /api/auth/setup-status |
Check if initial setup is needed |
| POST | /api/auth/setup |
Create initial admin account |
| GET | /api/health |
Health check |
| GET | /health |
Unauthenticated alias for old companion clients (FU-2) |
Transform DroneOpsCommand from a self-hosted tool into a revenue-generating SaaS product. The self-hosted open-source path remains for operators who want it — managed hosting is the commercial tier.
Tenant Architecture
- Schema-per-tenant isolation in PostgreSQL. Each tenant gets their own schema with identical table structures.
- Tenant provisioning API: signup → subdomain claim → payment → automatic schema creation and admin user seeding.
- JWT tenant claims with schema routing middleware. Every authenticated request resolves to the correct tenant schema.
- CI test coverage verifying tenant A cannot read tenant B's data under any code path.
Billing & Licensing
- Stripe subscription integration: plan tiers with mission limits, user seat limits, and storage quotas.
- Stripe Checkout hosted page for signup — no custom payment frontend required.
- Stripe Customer Portal for self-service plan changes, payment method updates, and invoice history.
- Webhook-driven plan enforcement: upgrade/downgrade/cancel reflected in tenant configuration automatically.
- Stripe Tax add-on for US sales tax compliance.
AI Processing (Cloud Offload)
- Managed tenants use cloud LLM (Anthropic or OpenAI API) instead of local Ollama.
- Per-tenant API key configuration with model selection.
- Celery queue hardening: retry logic, dead letter queue, per-tenant job isolation, queue depth monitoring.
- SLA-aware job priority to prevent report generation backlog across tenants.
Infrastructure
- Application servers behind a load balancer with subdomain-based tenant routing.
- Managed PostgreSQL with automated backups.
- S3-compatible object storage for mission images, reports, and deliverables (per-tenant path prefixes).
- Signed URLs for secure file delivery.
- Redis for Celery broker and session caching.
Pricing Tiers (Planned)
- Starter — limited missions/month, 1 user, cloud AI reports, email support.
- Professional — higher limits, multi-user with RBAC, priority report generation, UNAS integration.
- Enterprise — unlimited, custom branding, dedicated support, SLA.
- Thermal Inspection Report Engine — Ingest DJI radiometric RJPEG thermal imagery, auto-detect hotspot anomalies via configurable temperature delta thresholds and OpenCV contour detection, annotate visual images with bounding boxes, GPS coordinates, and measured temperatures, and generate branded PDF inspection reports. Plugs into the existing Celery/WeasyPrint report pipeline as a thermal-specific report template. Industry-configurable severity presets (solar/NETA electrical/roofing).
- Multi-User / Role-Based Access — Admin and Operator roles at minimum. Foundation for teams and the multi-tenant SaaS tier. Implement before schema changes get harder.
- Notification System — Email and in-app alerts for overdue invoices, upcoming maintenance, battery cycle limits, certificate expirations, and completed report generation.
- Report Template Library — Multiple PDF templates for different mission types. Inspection reports, SAR after-action reports, videography delivery summaries. Operator-buildable custom templates. LLM prompt adapts per template type.
- Airspace Pre-Check Workflow — Drop a pin before mission creation and get a pre-flight airspace assessment: LAANC status, nearby TFRs, Class B/C/D proximity. Uses existing weather/FAA API infrastructure.
Claude API Integration— Done. Switchable from Settings or viaLLM_PROVIDER=claudeenv var.- React Native Android App — Mission creation, photo capture on-site, report review, and customer lookup from the field. Communicates with the stack via JWT-authenticated HTTPS API.
- Voice-to-Text — On-device speech recognition in the Android app for dictating operator field notes hands-free during or after missions.
- DroneOpsSync Deep Integration — Field-captured photos auto-upload to the correct mission. Field notes from the controller pre-populate report narrative. JWT API is already in place.
- Public API & Webhooks — Let third-party tools (dispatch software, QuickBooks, project management) integrate with Command. Webhooks on mission status changes, invoice payment, and report delivery.
- Public Demo Instance — Live at command-demo.barnardhq.com with pre-loaded sample data, sandboxed operations (demo guard middleware), demo-mode banner with "Deploy Your Own" CTA, and a nightly reset at 02:23 PT (
scripts/demo-nightly-reset.shfrom the BOS-HQ operator crontab, line23 2 * * *; hosts runAmerica/Los_Angelessince 2026-08-25, so that is local time — older docs saying "09:23 UTC" and this line's former "24-hour auto-reset" are both stale).DEMO_RESET_INTERVAL_HOURSis configured but unimplemented — the script is the reset, because the demo worker and beat must stay stopped (dunning-email hazard, ADR-0042). The demo is not deployer-managed:~/droneops-demoon BOS-HQ is updated by hand. Seedocker-compose.demo.ymlfor deployment config. Always start the demo via./bootstrap.sh— neverdocker compose up -ddirectly. The bootstrap script validates.env.demobefore compose runs, so a missing or incomplete env file produces a clear error instead of silently falling back to default credentials (the failure mode that caused a 6h+ outage on 2026-04-16).
# Backend
cd backend && pip install -r requirements.txt
uvicorn app.main:app --reload
# Frontend
cd frontend && npm install && npm run dev| Volume | Purpose |
|---|---|
postgres_data |
PostgreSQL database files |
ollama_data |
Downloaded LLM model weights |
app_data |
Uploaded images and generated PDFs/maps |
To fully reset the database (destroys all data):
docker compose down -v
docker compose up -dContributions are welcome! Please see CONTRIBUTING.md for guidelines.
MIT License — see LICENSE for details.