fix: Persist login credentials for stable device ID

- Save user_id/device_id/access_token to crypto store on first login
- restore_login() on subsequent starts (no new device each restart)
- Enables proper Olm session persistence across restarts

CF-1147

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Christian Gick
2026-02-15 08:03:51 +02:00
parent d7044b613c
commit a7b55a1696

33
bot.py
View File

@@ -1,4 +1,5 @@
import os import os
import json
import asyncio import asyncio
import logging import logging
@@ -27,6 +28,7 @@ LK_KEY = os.environ["LIVEKIT_API_KEY"]
LK_SECRET = os.environ["LIVEKIT_API_SECRET"] LK_SECRET = os.environ["LIVEKIT_API_SECRET"]
AGENT_NAME = os.environ.get("AGENT_NAME", "matrix-ai") AGENT_NAME = os.environ.get("AGENT_NAME", "matrix-ai")
STORE_PATH = os.environ.get("CRYPTO_STORE_PATH", "/data/crypto_store") STORE_PATH = os.environ.get("CRYPTO_STORE_PATH", "/data/crypto_store")
CREDS_FILE = os.path.join(STORE_PATH, "credentials.json")
class Bot: class Bot:
@@ -47,13 +49,31 @@ class Bot:
self.dispatched_rooms = set() self.dispatched_rooms = set()
async def start(self): async def start(self):
# Restore existing session or create new one
if os.path.exists(CREDS_FILE):
with open(CREDS_FILE) as f:
creds = json.load(f)
self.client.restore_login(
user_id=creds["user_id"],
device_id=creds["device_id"],
access_token=creds["access_token"],
)
self.client.load_store()
logger.info("Restored session as %s (device %s)", creds["user_id"], creds["device_id"])
else:
resp = await self.client.login(BOT_PASS, device_name="ai-voice-bot") resp = await self.client.login(BOT_PASS, device_name="ai-voice-bot")
if not isinstance(resp, LoginResponse): if not isinstance(resp, LoginResponse):
logger.error("Login failed: %s", resp) logger.error("Login failed: %s", resp)
return return
logger.info("Logged in as %s (device %s)", resp.user_id, resp.device_id) # Persist credentials for next restart
with open(CREDS_FILE, "w") as f:
json.dump({
"user_id": resp.user_id,
"device_id": resp.device_id,
"access_token": resp.access_token,
}, f)
logger.info("Logged in as %s (device %s) — credentials saved", resp.user_id, resp.device_id)
# Trust our own device keys
if self.client.should_upload_keys: if self.client.should_upload_keys:
await self.client.keys_upload() await self.client.keys_upload()
@@ -74,7 +94,6 @@ class Bot:
logger.info("Invited to %s", room.room_id) logger.info("Invited to %s", room.room_id)
await self.client.join(room.room_id) await self.client.join(room.room_id)
# LiveKit room name = Matrix room ID (Element Call convention)
lk_room_name = room.room_id lk_room_name = room.room_id
try: try:
await self.lkapi.agent_dispatch.create_dispatch( await self.lkapi.agent_dispatch.create_dispatch(
@@ -90,17 +109,17 @@ class Bot:
async def on_sync(self, response: SyncResponse): async def on_sync(self, response: SyncResponse):
"""After each sync, trust all devices in our rooms.""" """After each sync, trust all devices in our rooms."""
for user_id in self.client.device_store.users: for user_id in list(self.client.device_store.users):
for device in self.client.device_store.active_user_devices(user_id): for device in self.client.device_store.active_user_devices(user_id):
if not device.verified: if not device.verified:
self.client.verify_device(device) self.client.verify_device(device)
logger.info("Auto-trusted device %s of %s", device.device_id, user_id) logger.info("Auto-trusted device %s of %s", device.device_id, user_id)
async def on_megolm(self, room, event: MegolmEvent): async def on_megolm(self, room, event: MegolmEvent):
"""Handle undecryptable messages by requesting keys.""" """Log undecryptable messages."""
logger.warning( logger.warning(
"Can't decrypt event %s in %s from %s (session %s)", "Undecryptable event %s in %s from %s",
event.event_id, room.room_id, event.sender, event.session_id, event.event_id, room.room_id, event.sender,
) )
async def on_key_verification(self, event): async def on_key_verification(self, event):