feat: add multi-device provisioning and local remote-config UI

- add multi-device portal device management, key rotation, and sync request APIs
- add Pi settings UI for remote server URL, device name, and API key
- add local test-connection and sync-now actions
- make upload and config sync reuse dynamic shared credentials
- add Phase 25.5c migration and docs updates

Co-authored-by: Copilot <[email protected]>
This commit is contained in:
Mathieu Moran
2026-08-10 18:02:09 -04:00
co-authored by Copilot
parent fbc41a5db5
commit ba682fbf3a
23 changed files with 1357 additions and 167 deletions
+8 -20
View File
@@ -9,13 +9,11 @@ vars already configured by docker-compose, e.g.:
The raw API key is only ever shown once here - copy it into the Pi's
upload-client config immediately. Only its sha256 hash is stored server-side.
"""
import hashlib
import secrets
import sys
sys.path.insert(0, "/app")
from modules import db # noqa: E402
from modules import device_registry # noqa: E402
def main():
@@ -23,26 +21,16 @@ def main():
print(f"usage: {sys.argv[0]} <device-name>", file=sys.stderr)
sys.exit(1)
device_name = sys.argv[1]
raw_key = secrets.token_urlsafe(32)
key_hash = hashlib.sha256(raw_key.encode("utf-8")).hexdigest()
result, errors = device_registry.create_device(sys.argv[1])
if errors:
print(f"error: {errors}", file=sys.stderr)
sys.exit(1)
with db.get_cursor(commit=True) as cur:
cur.execute(
"""
INSERT INTO devices (name, api_key_hash) VALUES (%s, %s)
ON CONFLICT (name) DO UPDATE SET api_key_hash = EXCLUDED.api_key_hash
RETURNING id
""",
(device_name, key_hash),
)
device_id = cur.fetchone()[0]
print(f"Device '{device_name}' registered (id={device_id}).")
print(f"Device '{result['device']['name']}' registered (id={result['device']['id']}).")
print()
print("Save these on the Pi (e.g. in db/upload_creds.env, gitignored):")
print(f" YARIS_DEVICE_NAME={device_name}")
print(f" YARIS_API_KEY={raw_key}")
print(f" YARIS_DEVICE_NAME={result['device']['name']}")
print(f" YARIS_API_KEY={result['api_key']}")
print()
print("This key will not be shown again - only its hash is stored server-side.")