From 595c1cadf558fadc5e9270749f2033740a731289 Mon Sep 17 00:00:00 2001 From: Kayos Date: Sat, 14 Mar 2026 20:51:26 -0700 Subject: [PATCH 1/3] fix: GPS from SQLite framekms (confirmed live device schema) odc-api.db confirmed present on device. framekms table has: latitude, longitude, altitude, hdop, satellites_used, time NOT lat_deg/lon_deg/alt_m/num_satellites as previously assumed. Redis fallback retained, supports both field naming conventions. API response format unchanged (still returns lat_deg/lon_deg for Varroa compat). --- adacam_api/redis_client.py | 101 +++++++++++++++++++++++++++---------- 1 file changed, 74 insertions(+), 27 deletions(-) diff --git a/adacam_api/redis_client.py b/adacam_api/redis_client.py index 78a2f53..80162b9 100644 --- a/adacam_api/redis_client.py +++ b/adacam_api/redis_client.py @@ -1,40 +1,87 @@ -"""Redis client for GPS and IMU data.""" +"""GPS data reader for adacam-api. +Primary source: /data/recording/odc-api.db framekms table (confirmed schema from live device). +Fallback: Redis (keys may appear when device has outdoor GPS fix post-liberation). + +Confirmed field names from live device: + latitude, longitude, altitude, hdop, satellites_used, speed, heading, time +""" import json -import redis +import os +import sqlite3 +import time -_client = None +ODC_DB = '/data/recording/odc-api.db' +GNSS_REDIS_KEYS = ['GNSSFusion30Hz', 'GnssData', 'GnssPvt', 'GnssPosition'] + +_redis_client = None -def get_client(): - """Get Redis connection.""" - global _client - if _client is None: - _client = redis.Redis(host="localhost", port=6379, decode_responses=True) - return _client +def _get_redis(): + global _redis_client + try: + import redis + if _redis_client is None: + _redis_client = redis.Redis(host='localhost', port=6379, decode_responses=True) + _redis_client.ping() + return _redis_client + except Exception: + _redis_client = None + return None def get_latest_gnss(): - """Get latest GPS fix from GNSSFusion30Hz ZSET.""" - client = get_client() - try: - # Get the most recent entry (highest score = most recent timestamp) - results = client.zrevrange("GNSSFusion30Hz", 0, 0, withscores=True) - if results: - data = json.loads(results[0][0]) - return { - "lat_deg": data.get("lat_deg"), - "lon_deg": data.get("lon_deg"), - "alt_m": data.get("alt_m"), - "unix_milliseconds": int(data.get("unix_milliseconds", 0)), - "hdop": data.get("hdop"), - "num_satellites": data.get("num_satellites", 0), - } - except (redis.RedisError, json.JSONDecodeError): - pass + """Get latest GPS fix. Returns dict with keys: lat_deg, lon_deg, alt_m, hdop, num_satellites. + Field names kept compatible with original API response format.""" + + # Primary: SQLite framekms (confirmed live device schema) + if os.path.exists(ODC_DB): + try: + conn = sqlite3.connect(ODC_DB) + row = conn.execute( + 'SELECT latitude, longitude, altitude, hdop, satellites_used, time ' + 'FROM framekms WHERE latitude IS NOT NULL AND latitude != 0 ' + 'ORDER BY time DESC LIMIT 1' + ).fetchone() + conn.close() + if row: + return { + 'lat_deg': row[0], + 'lon_deg': row[1], + 'alt_m': row[2], + 'hdop': row[3], + 'num_satellites': row[4], + 'unix_milliseconds': int(row[5]) if row[5] else int(time.time() * 1000), + } + except Exception: + pass + + # Fallback: Redis (may appear post-liberation with outdoor GPS fix) + r = _get_redis() + if r: + for key in GNSS_REDIS_KEYS: + try: + for getter in [lambda k: r.zrevrange(k, 0, 0), lambda k: [r.get(k)]]: + items = getter(key) + item = items[0] if items else None + if item: + data = json.loads(item) + lat = data.get('latitude') or data.get('lat_deg') + lon = data.get('longitude') or data.get('lon_deg') + if lat and lon: + return { + 'lat_deg': lat, + 'lon_deg': lon, + 'alt_m': data.get('altitude') or data.get('alt_m', 0), + 'hdop': data.get('hdop', 99), + 'num_satellites': data.get('satellites_used') or data.get('num_satellites', 0), + 'unix_milliseconds': int(data.get('unix_milliseconds', time.time() * 1000)), + } + except Exception: + continue return None def has_gps_lock(): """Check if we have a valid GPS lock.""" gnss = get_latest_gnss() - return gnss is not None and gnss.get("num_satellites", 0) >= 4 + return gnss is not None and gnss.get('num_satellites', 0) >= 4 From 81a6d18c1ddad1b1336cf1a6b1beb2fd708e5169 Mon Sep 17 00:00:00 2001 From: Cobb Hayes Date: Wed, 27 May 2026 09:17:22 -0700 Subject: [PATCH 2/3] Rotate AdaMaps ingest+read keys (env-required, no inline default) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previous values (adamaps-ingest-2026, adamaps-read-2026, mapnet-ingest-2026) were inline defaults across adamaps + adacam-api + varroa. The ingest key was briefly anon-visible during the 2026-05-27 Forgejo public-flip when adacam-api + varroa were public for a short window before the leak was spotted. New values live in Vaultwarden: - AdaMaps — API_KEY (ingest) - AdaMaps — READ_KEY Validators now hard-fail at boot if the env var is missing. Service is on hold today; when it resumes, both env vars must be set. --- README.md | 2 +- adacam_api/config.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 413c7c7..6770372 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Config file: `/data/adacam/config.json` ```json { "device_id": "auto-generated UUID", - "adamaps_key": "adamaps-ingest-2026", + "adamaps_key": "", "adamaps_api": "https://api.adamaps.org", "ap_interface": "wlp1s0f0", "tunnel_host": "", diff --git a/adacam_api/config.py b/adacam_api/config.py index b425e69..2898690 100644 --- a/adacam_api/config.py +++ b/adacam_api/config.py @@ -8,7 +8,7 @@ FIRMWARE_VERSION = "adacam-1.0.0" _defaults = { "device_id": None, - "adamaps_key": "adamaps-ingest-2026", + "adamaps_key": "", "adamaps_api": "https://api.adamaps.org", "ap_interface": "wlp1s0f0", "tunnel_host": "", From beead1d6b04c3b0b0a15fbef45e0164d78eb90bf Mon Sep 17 00:00:00 2001 From: Cobb Hayes Date: Wed, 27 May 2026 09:22:12 -0700 Subject: [PATCH 3/3] audit follow-ups: deps floor, LICENSE, gate /debug/redis-keys - requirements.txt: bump floors past known CVEs (flask>=2.3.2 fixes CVE-2023-30861, requests>=2.32.0 fixes CVE-2023-32681 + CVE-2024-35195, redis>=5.0 fixes CVE-2023-28858/9). - LICENSE: add MIT text (README claimed MIT but the file was missing). - /api/1/debug/redis-keys: require auth. Was unauthenticated info-disclosure on the LAN/AP side. --- LICENSE | 21 +++++++++++++++++++++ adacam_api/app.py | 1 + requirements.txt | 6 +++--- 3 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..77c4caf --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Sulkta Coop + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/adacam_api/app.py b/adacam_api/app.py index 03f0afb..be0384b 100644 --- a/adacam_api/app.py +++ b/adacam_api/app.py @@ -112,6 +112,7 @@ def create_app(): # ── DEBUG ENDPOINTS ──────────────────────────────────────────────────── @app.route('/api/1/debug/redis-keys') + @require_auth def redis_keys(): """Debug endpoint — list Redis keys for GPS/IMU troubleshooting.""" try: diff --git a/requirements.txt b/requirements.txt index 6f89858..4ec13a8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -flask>=2.0 -redis>=4.0 -requests>=2.25 +flask>=2.3.2 +redis>=5.0 +requests>=2.32.0