From 5a5058adcaf4dc332fd37b30c3ac3b3bd1753dae Mon Sep 17 00:00:00 2001 From: Mathieu Moran Date: Mon, 10 Aug 2026 17:37:52 -0400 Subject: [PATCH] fix: reuse upload credentials for device config sync - load server sync creds from db/upload_creds.env like uploads do - fall back from YARIS_DEVICE_KEY to existing YARIS_API_KEY - document shared credential flow for server-synced identity - add test coverage for sync credential fallback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- PLAN.md | 5 +++-- deploy/readme.md | 6 ++++++ modules/app_device_sync.py | 16 +++++++++++++--- tests/test_app_device_config_runtime.py | 8 ++++++++ 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/PLAN.md b/PLAN.md index a08b0cb..4fc3a9b 100644 --- a/PLAN.md +++ b/PLAN.md @@ -1504,7 +1504,8 @@ phase roadmap. - Fetches config from `/api/device-config` endpoint (device_key auth) - Detects changes via config_version - Stores synced config in SQLite - - Environment vars: `YARIS_SERVER_URL`, `YARIS_DEVICE_KEY` + - Reuses the existing upload credentials from `db/upload_creds.env` + (`YARIS_SERVER_URL` + `YARIS_API_KEY`; `YARIS_DEVICE_KEY` also accepted as an override) - Ready for hot-reload on config change (future: signal handler) - **Pi startup** (`app.py`): @@ -1525,7 +1526,7 @@ phase roadmap. #### Testing - Server endpoints accessible via `/api/device-config` - Pi thread starts on boot (check logs for connectivity attempts) -- Sync disabled if `YARIS_SERVER_URL` or `YARIS_DEVICE_KEY` not set +- Sync disabled if `YARIS_SERVER_URL` or an API key (`YARIS_API_KEY` / `YARIS_DEVICE_KEY`) is not set - **2026-08-10 access verification**: SSH access confirmed to Pi at `yaris@192.168.50.213` (new WiFi card/subnet) and to unraid via `ssh unraid` (`root@192.168.10.10`). Follow-up deployment succeeded the same day: Pi fast-forwarded from `d7b9bb6` to `cd004fb`, diff --git a/deploy/readme.md b/deploy/readme.md index 23495ed..d378720 100644 --- a/deploy/readme.md +++ b/deploy/readme.md @@ -203,6 +203,12 @@ nano /home/yaris/app/yaris-obd/config/device.json - **`device_name`**: Reserved for server-side display (Phase 25.5 feature, not yet used) - **`avatar_path`**: Avatar image shown in topbar; must exist in `static/img/` (replace `static/img/yaris.png` with your own image, or point to a different file) +If you want the remote server to become the source of truth instead, keep the Pi +upload credentials in `db/upload_creds.env` (`YARIS_SERVER_URL` + `YARIS_API_KEY`) +and enable **Sync with server** in the local Settings page. The same creds file is +reused for config sync; when the server pushes new identity values, the running Pi +UI hot-reloads them on the next successful sync. + After editing, restart the app: ```bash diff --git a/modules/app_device_sync.py b/modules/app_device_sync.py index 3598270..d335101 100644 --- a/modules/app_device_sync.py +++ b/modules/app_device_sync.py @@ -10,6 +10,7 @@ import threading import time import requests +from dotenv import load_dotenv from . import app_network, app_sql @@ -18,6 +19,9 @@ _SYNC_THREAD = None _SYNC_STOP = False _SYNC_CONFIG_VERSION = 0 # Track last-synced version to detect changes _RELOAD_CALLBACK = None +_BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +load_dotenv(os.path.join(_BASE_DIR, "db", "upload_creds.env")) def _has_internet(): @@ -34,6 +38,12 @@ def _has_internet(): return False +def _server_credentials(): + server_url = os.getenv("YARIS_SERVER_URL", "").rstrip("/") + device_key = os.getenv("YARIS_DEVICE_KEY") or os.getenv("YARIS_API_KEY", "") + return server_url, device_key + + def start_sync(): """Start background device config sync thread.""" global _SYNC_THREAD, _SYNC_STOP @@ -61,8 +71,7 @@ def _sync_loop(): """Periodically sync device config from server when internet available.""" global _SYNC_CONFIG_VERSION - server_url = os.getenv("YARIS_SERVER_URL", "") - device_key = os.getenv("YARIS_DEVICE_KEY", "") + server_url, device_key = _server_credentials() if not server_url or not device_key: return # Sync disabled if server URL or device key not configured @@ -125,7 +134,8 @@ def _on_config_changed(): def is_sync_enabled(): """Check if device config sync from server is enabled.""" - return bool(os.getenv("YARIS_SERVER_URL")) and bool(os.getenv("YARIS_DEVICE_KEY")) + server_url, device_key = _server_credentials() + return bool(server_url) and bool(device_key) def get_synced_config_version(): diff --git a/tests/test_app_device_config_runtime.py b/tests/test_app_device_config_runtime.py index af0a1a8..926e1f7 100644 --- a/tests/test_app_device_config_runtime.py +++ b/tests/test_app_device_config_runtime.py @@ -1,4 +1,5 @@ import unittest +import os from unittest.mock import Mock, patch from modules import app_account, app_car, app_device_sync @@ -55,6 +56,13 @@ class DeviceSyncReloadCallbackTest(unittest.TestCase): finally: app_device_sync._RELOAD_CALLBACK = original + @patch.dict(os.environ, {"YARIS_SERVER_URL": "https://example.test", "YARIS_API_KEY": "upload-key"}, clear=True) + def test_sync_credentials_fall_back_to_upload_api_key(self): + server_url, device_key = app_device_sync._server_credentials() + + self.assertEqual(server_url, "https://example.test") + self.assertEqual(device_key, "upload-key") + if __name__ == "__main__": unittest.main()