infra/docs/plans/2026-07-04-drone-logbook-design.md
Viktor Barzin 50778d47d3 drone-logbook: new stack — self-hosted Open DroneLog at dronelog.viktorbarzin.me
Viktor asked to self-host the DJI flight-log analyzer for his DJI Mini 4 Pro
(his fork ViktorBarzin/drone-logbook -> upstream arpanghosh8453/open-dronelog).
Upstream ghcr image with Keel auto-upgrade, DuckDB data on an encrypted
proxmox-lvm PVC (GPS traces = sensitive), NFS /sync-logs drop folder imported
every 8h, daily backup CronJob to /srv/nfs/drone-logbook-backup (vaultwarden
pattern), Authentik-gated ingress, PROFILE_CREATION_PASS from Vault via ESO.
Design + plan in docs/plans/; service-catalog updated.
2026-07-04 08:42:53 +00:00

89 lines
5.8 KiB
Markdown

# Drone Logbook (Open DroneLog) — Design
**Date:** 2026-07-04
**Status:** Approved (Viktor, 2026-07-04)
**Owner request:** "I have a DJI Mini 4 Pro. I'm interested in github.com/ViktorBarzin/drone-logbook" → self-host it in the cluster.
## Goal
Self-host [Open DroneLog](https://github.com/arpanghosh8453/open-dronelog) (upstream of the
`ViktorBarzin/drone-logbook` fork) at **https://dronelog.viktorbarzin.me** so Viktor can import
DJI Fly flight logs from his DJI Mini 4 Pro and analyze them privately: telemetry charts, 3D map
replay, per-flight and lifetime stats. All data stays in the cluster (single DuckDB database).
## Decisions (interview, 2026-07-04)
| Question | Decision |
|---|---|
| Deployment form | Self-hosted Docker web app in k8s (not desktop app, not hosted webapp) |
| Exposure | Public `dronelog.viktorbarzin.me`, **Authentik forward-auth** (`auth = "required"`) |
| Log ingestion | **Both** manual web upload *and* a server-side auto-import drop folder from day one |
| Image source | **Upstream** `ghcr.io/arpanghosh8453/open-dronelog:latest` — NOT the fork |
| Fork disposition | Fork is 0 ahead / 372 behind, adds nothing; delete or park it. Only revive (sync + ADR-0002 GHA build) if Viktor starts modifying the code |
## Architecture
New Tier-1 stack `stacks/drone-logbook/`, modeled line-by-line on `stacks/freshrss/`
(the closest existing shape: single upstream-image app, own data volume, Keel-updated):
- **Namespace** `drone-logbook`, tier `4-aux`, label `keel.sh/enrolled=true` → Kyverno injects
Keel poll annotations → auto-upgrades as upstream releases (project is actively maintained).
- **Deployment** (1 replica, `Recreate` — DuckDB is single-writer/embedded):
- image `ghcr.io/arpanghosh8453/open-dronelog:latest` (nginx frontend + Axum REST backend, port 80)
- memory request=limit **512Mi** (DuckDB import/analytics spikes), cpu request 25m, no cpu limit
- standard `KYVERNO_LIFECYCLE_V1` / `KEEL_IGNORE_IMAGE` / `KEEL_LIFECYCLE_V1` lifecycle ignores
- **App data** `/data/drone-logbook` (DuckDB db, cached DJI decryption keys, uploaded originals):
**`proxmox-lvm-encrypted` block PVC** `drone-logbook-data-encrypted`, 2Gi, topolvm autoresize →
10Gi ceiling. Encrypted class because flight logs are GPS traces of home/travel — sensitive data
defaults to `proxmox-lvm-encrypted` per the storage decision rule (`.claude/CLAUDE.md`).
Embedded DBs stay off NFS (same rationale documented in the freshrss stack: NFS only for static files).
- **Backup CronJob** `drone-logbook-backup` (mandatory for every proxmox-lvm app): daily 01:30
file copy of the data volume → NFS `/srv/nfs/drone-logbook-backup` (dated dirs, 30-day retention,
Pushgateway metrics), pod-affinity co-scheduled with the app pod (RWO volume). 01:30 sits outside
the 00:00/08:00/16:00 sync-import windows so the DuckDB file is quiescent; retained upload
originals make even a torn copy recoverable by re-import. `nfs-mirror` (02:00) ships it to sda →
Synology offsite. Vaultwarden pattern.
- **Sync drop folder**: static NFS volume (`modules/kubernetes/nfs_volume`)
`192.168.1.127:/srv/nfs/drone-logbook/sync-logs`, mounted **read-only** at `/sync-logs`;
`SYNC_LOGS_PATH=/sync-logs`, `SYNC_INTERVAL="0 0 */8 * * *"` (every 8 h).
Any producer (Nextcloud sync, scp, a future phone pipeline) drops `.txt` logs there; the app
imports them automatically. `KEEP_UPLOADED_FILES=true` keeps re-importable originals in the PVC.
- **Ingress** via `ingress_factory`: `name = "dronelog"`, `auth = "required"` (Authentik
forward-auth), `dns_type = "proxied"`. External Uptime Kuma HTTPS monitor comes automatically
with the ingress annotation. Homepage tile (group "Media & Entertainment", icon `mdi-quadcopter`).
- **Secrets**: Vault KV `secret/drone-logbook` (`profile_creation_pass`) → ExternalSecret
(`vault-kv` ClusterSecretStore) → k8s secret `drone-logbook-secrets` → env
`PROFILE_CREATION_PASS`. Gates profile create/delete even for other Authentik-logged-in users.
No plan-time secret reads needed (no `data "kubernetes_secret"`).
No `DJI_API_KEY` — bundled default is fine at personal import volume; add later if rate-limited.
## Operational notes
- **DJI egress dependency**: importing a *new* log file requires the pod to reach DJI's servers
once (flight-log decryption key fetch; keys are then cached in the data dir). Remember this when
egress enforcement lands (Security wave 1, beads `code-8ywc`).
- The web UI is desktop-first; mobile is functional but basic.
- NFS host prerequisite: `/srv/nfs/drone-logbook/sync-logs` (root:www-data, 2775 — same shape as
sibling dirs) and `/srv/nfs/drone-logbook-backup` created on 192.168.1.127 and recorded in
`secrets/nfs_directories.txt`. `/srv/nfs` is exported whole-tree, so no `/etc/exports`
(`scripts/pve-nfs-exports`) change.
- Backup story = the daily app-level backup CronJob (above) + the host `daily-backup` LVM-snapshot
leg + original log files retained both in the drop folder and in the data volume
(`KEEP_UPLOADED_FILES=true`).
## Alternatives considered
- **Build from the fork** (`ghcr.io/viktorbarzin/...` via GHA, ADR-0002): rejected for now — fork
has zero custom commits; a build chain adds maintenance for no benefit. Revisit if code changes
are wanted.
- **`auth = "app"` + app profile passwords** (would enable the `opendronelog-sync` native uploader
from anywhere): rejected — a single app password guarding GPS traces of home/travel on the open
internet is weaker than Authentik; the sync drop folder covers automated ingestion instead.
- **Internal-only (.lan + VPN)**: rejected — Authentik-gated public matches the rest of the
homelab and works without VPN while traveling.
- **NFS for the DuckDB data**: rejected — embedded-DB-on-NFS locking risk; freshrss precedent
keeps app DB data on proxmox-lvm.
## Implementation
See `2026-07-04-drone-logbook-plan.md`.