mirror of
https://github.com/prdlk/vortex.git
synced 2026-08-02 09:21:40 +00:00
One-command local stack: cp .env.example .env && make up. Idempotent bootstrap renders Synapse + bridge configs from pristine upstream examples, provisions databases, registrations, double puppeting, and the admin user. Textual TUI with bridge manager, in-terminal QR login, and drafts panel. MCP server exposes read tools and a human-gated draft send workflow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
83 lines
2.9 KiB
Python
83 lines
2.9 KiB
Python
"""Credentials loading and Matrix client construction."""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
from nio import AsyncClient, AsyncClientConfig, LoginResponse
|
|
|
|
|
|
def store_dir() -> Path:
|
|
return Path(os.environ.get("STORE_DIR", "/shared/tui-store"))
|
|
|
|
|
|
def load_credentials(retries: int = 10, delay: float = 3.0) -> dict:
|
|
"""Read CREDENTIALS_FILE, waiting a bit in case bootstrap is still provisioning."""
|
|
path = Path(os.environ.get("CREDENTIALS_FILE", "/shared/credentials.json"))
|
|
for attempt in range(retries):
|
|
try:
|
|
return json.loads(path.read_text())
|
|
except (OSError, json.JSONDecodeError):
|
|
if attempt < retries - 1:
|
|
print(
|
|
f"vortex-tui: waiting for {path} (bootstrap still provisioning?)...",
|
|
file=sys.stderr,
|
|
)
|
|
time.sleep(delay)
|
|
sys.exit(
|
|
f"vortex-tui: credentials file {path} missing or invalid after "
|
|
f"{retries} attempts. Run the bootstrap first, or set CREDENTIALS_FILE."
|
|
)
|
|
|
|
|
|
async def build_client(creds: dict) -> AsyncClient:
|
|
"""Return a logged-in AsyncClient with its own persisted E2EE device.
|
|
|
|
Order: STORE_DIR/session.json (our own device) -> password login (persist
|
|
new device) -> bootstrap access_token fallback.
|
|
"""
|
|
store = store_dir()
|
|
store.mkdir(parents=True, exist_ok=True)
|
|
homeserver = (
|
|
os.environ.get("MATRIX_HOMESERVER")
|
|
or creds.get("homeserver")
|
|
or "http://localhost:8008"
|
|
)
|
|
user_id = creds["user_id"]
|
|
# encryption_enabled defaults to True only when olm is importable,
|
|
# so this works with or without the [e2e] extra installed.
|
|
client = AsyncClient(
|
|
homeserver,
|
|
user_id,
|
|
store_path=str(store),
|
|
config=AsyncClientConfig(store_sync_tokens=True),
|
|
)
|
|
session_file = store / "session.json"
|
|
if session_file.exists():
|
|
s = json.loads(session_file.read_text())
|
|
client.restore_login(s["user_id"], s["device_id"], s["access_token"])
|
|
return client
|
|
if creds.get("password"):
|
|
resp = await client.login(creds["password"], device_name="vortex-tui")
|
|
if isinstance(resp, LoginResponse):
|
|
session_file.write_text(
|
|
json.dumps(
|
|
{
|
|
"user_id": resp.user_id,
|
|
"device_id": resp.device_id,
|
|
"access_token": resp.access_token,
|
|
}
|
|
)
|
|
)
|
|
return client
|
|
if creds.get("access_token"):
|
|
# Fall back to the bootstrap token (shares the bootstrap device).
|
|
client.restore_login(
|
|
user_id, creds.get("device_id") or "VORTEXTUI", creds["access_token"]
|
|
)
|
|
return client
|
|
await client.close()
|
|
raise RuntimeError("no usable password or access_token in credentials file")
|