91 lines
2.9 KiB
Python
91 lines
2.9 KiB
Python
import re
|
|
import secrets
|
|
import sys
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
try:
|
|
import tomllib
|
|
except ImportError:
|
|
try:
|
|
import tomli as tomllib # type: ignore[no-redef]
|
|
except ImportError:
|
|
print("Error: Python 3.11+ is required (for tomllib), or install tomli: pip install tomli")
|
|
sys.exit(1)
|
|
|
|
CONFIG_PATH = Path("config.toml")
|
|
|
|
|
|
@dataclass
|
|
class AppConfig:
|
|
width: int | None
|
|
height: int | None
|
|
fps: int
|
|
clock_font_path: str
|
|
message_font_path: str
|
|
port: int
|
|
default_duration: float
|
|
api_token: str
|
|
rate_limit: int
|
|
password_hash: str
|
|
session_timeout_hours: int
|
|
config_path: Path
|
|
|
|
@classmethod
|
|
def load(cls, path: str | Path = CONFIG_PATH) -> "AppConfig":
|
|
p = Path(path)
|
|
if not p.exists():
|
|
print(f"Error: Config file not found: {p}")
|
|
print("Copy config.toml to your working directory and edit it.")
|
|
sys.exit(1)
|
|
|
|
with open(p, "rb") as f:
|
|
raw = tomllib.load(f)
|
|
|
|
display = raw.get("display", {})
|
|
server = raw.get("server", {})
|
|
api = raw.get("api", {})
|
|
rate = raw.get("rate_limit", {})
|
|
dashboard = raw.get("dashboard", {})
|
|
|
|
token = api.get("token", "").strip()
|
|
if not token:
|
|
token = secrets.token_hex(32)
|
|
_update_config_field(p, "token", token)
|
|
print(f"\n[KH-clock] Generated API bearer token (saved to config.toml):")
|
|
print(f" {token}\n")
|
|
|
|
return cls(
|
|
width=display.get("width"),
|
|
height=display.get("height"),
|
|
fps=display.get("fps", 10),
|
|
clock_font_path=display.get("clock_font_path", "").strip(),
|
|
message_font_path=display.get("message_font_path", "").strip(),
|
|
port=server.get("port", 8080),
|
|
default_duration=float(server.get("default_duration_seconds", 20)),
|
|
api_token=token,
|
|
rate_limit=rate.get("requests_per_minute", 20),
|
|
password_hash=dashboard.get("password_hash", "").strip(),
|
|
session_timeout_hours=dashboard.get("session_timeout_hours", 8),
|
|
config_path=p,
|
|
)
|
|
|
|
def save_password_hash(self, hashed: str) -> None:
|
|
_update_config_field(self.config_path, "password_hash", hashed)
|
|
self.password_hash = hashed
|
|
|
|
|
|
def _update_config_field(config_path: Path, key: str, value: str) -> None:
|
|
"""Update a quoted string field in config.toml using regex replacement."""
|
|
content = config_path.read_text()
|
|
pattern = rf'^({re.escape(key)}\s*=\s*)"[^"]*"'
|
|
|
|
def replacer(m: re.Match) -> str:
|
|
return f'{m.group(1)}"{value}"'
|
|
|
|
new_content = re.sub(pattern, replacer, content, flags=re.MULTILINE)
|
|
if new_content == content:
|
|
print(f"Warning: Could not update '{key}' in {config_path}. Field not found.")
|
|
return
|
|
config_path.write_text(new_content)
|