Each configured GUI app (e.g. Nextcloud) gets its own dock icon and opens as a regular Atlus tab. Under the hood: Xvfb virtual display, ImageMagick captures individual window pixmaps as JPEG, streams over WebSocket to a canvas element, with xdotool forwarding mouse/keyboard input back to the X11 window. Apps persist in background when tab is closed, and streaming pauses when no viewers are attached. New files: backend/display.py (DisplayManager + ManagedGuiApp), backend/routers/display.py (WebSocket + REST), frontend display.js/css. Config: gui_apps array in settings for registered applications. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
104 lines
3.7 KiB
Python
104 lines
3.7 KiB
Python
"""Atlus configuration — paths, JWT settings, defaults."""
|
|
|
|
import os
|
|
import json
|
|
import secrets
|
|
from pathlib import Path
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Paths
|
|
# ---------------------------------------------------------------------------
|
|
BASE_DIR = Path(__file__).resolve().parent.parent # repo root
|
|
FRONTEND_DIR = BASE_DIR / "frontend"
|
|
_default_config = "/etc/atlus" if os.path.exists("/etc") and os.access("/etc", os.W_OK) else str(BASE_DIR / ".atlus_data")
|
|
_default_data = "/var/lib/atlus" if os.path.exists("/var/lib") and os.access("/var/lib", os.W_OK) else str(BASE_DIR / ".atlus_data")
|
|
CONFIG_DIR = Path(os.environ.get("ATLUS_CONFIG_DIR", _default_config))
|
|
DATA_DIR = Path(os.environ.get("ATLUS_DATA_DIR", _default_data))
|
|
USER_CONFIG_FILE = CONFIG_DIR / "atlus.json"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Server
|
|
# ---------------------------------------------------------------------------
|
|
HOST = os.environ.get("ATLUS_HOST", "0.0.0.0")
|
|
PORT = int(os.environ.get("ATLUS_PORT", "7779"))
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# JWT
|
|
# ---------------------------------------------------------------------------
|
|
JWT_SECRET_FILE = DATA_DIR / "jwt_secret"
|
|
JWT_ALGORITHM = "HS256"
|
|
JWT_EXPIRY_HOURS = int(os.environ.get("ATLUS_JWT_EXPIRY_HOURS", "24"))
|
|
|
|
# Revoked tokens (in-memory set, cleared on restart)
|
|
_revoked_tokens: set[str] = set()
|
|
|
|
|
|
def get_jwt_secret() -> str:
|
|
"""Return persistent JWT secret, generating one on first run."""
|
|
if JWT_SECRET_FILE.exists():
|
|
return JWT_SECRET_FILE.read_text().strip()
|
|
JWT_SECRET_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
secret = secrets.token_hex(32)
|
|
JWT_SECRET_FILE.write_text(secret)
|
|
JWT_SECRET_FILE.chmod(0o600)
|
|
return secret
|
|
|
|
|
|
def revoke_token(jti: str) -> None:
|
|
_revoked_tokens.add(jti)
|
|
|
|
|
|
def is_token_revoked(jti: str) -> bool:
|
|
return jti in _revoked_tokens
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# User config helpers (persisted JSON)
|
|
# ---------------------------------------------------------------------------
|
|
_DEFAULT_CONFIG: dict = {
|
|
"hostname_display": None, # override for panel display
|
|
"timezone": None, # e.g. "America/New_York", None=system
|
|
"ntp_enabled": True,
|
|
"dock_apps": [
|
|
"terminal", "files", "services", "tasks", "network", "settings"
|
|
],
|
|
"panel_services": [], # systemd unit names to show in panel
|
|
"gui_apps": [], # GUI apps: [{id, name, command, icon, args, target_fps}]
|
|
"asi_bridge": {
|
|
"cifs_share": "//192.168.10.120/share",
|
|
"mount_point": "/mnt/asiair",
|
|
"cifs_user": "anonymous",
|
|
"cifs_pass": "",
|
|
},
|
|
"session_timeout_minutes": 1440, # 24 h
|
|
"stats_interval_seconds": 2,
|
|
}
|
|
|
|
|
|
def load_config() -> dict:
|
|
"""Load user config from disk, merged over defaults."""
|
|
cfg = dict(_DEFAULT_CONFIG)
|
|
if USER_CONFIG_FILE.exists():
|
|
try:
|
|
with open(USER_CONFIG_FILE) as f:
|
|
user = json.load(f)
|
|
cfg.update(user)
|
|
except (json.JSONDecodeError, OSError):
|
|
pass
|
|
return cfg
|
|
|
|
|
|
def save_config(cfg: dict) -> None:
|
|
"""Persist user config to disk."""
|
|
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
with open(USER_CONFIG_FILE, "w") as f:
|
|
json.dump(cfg, f, indent=2)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Stats push interval
|
|
# ---------------------------------------------------------------------------
|
|
STATS_INTERVAL = float(os.environ.get(
|
|
"ATLUS_STATS_INTERVAL",
|
|
str(load_config().get("stats_interval_seconds", 2)),
|
|
))
|