96 lines
3.0 KiB
Python
96 lines
3.0 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
|
|
def _read_secret(file_var: str, value_var: str) -> str | None:
|
|
secret_file = os.getenv(file_var)
|
|
if secret_file:
|
|
path = Path(secret_file)
|
|
if path.is_file():
|
|
return path.read_text(encoding="utf-8").strip()
|
|
value = os.getenv(value_var)
|
|
return value.strip() if value else None
|
|
|
|
|
|
def _as_bool(name: str, default: bool) -> bool:
|
|
value = os.getenv(name)
|
|
if value is None:
|
|
return default
|
|
return value.lower() in {"1", "true", "yes", "on"}
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Settings:
|
|
data_dir: Path
|
|
database_path: Path
|
|
users: tuple["UserSeed", ...]
|
|
session_secret: str | None
|
|
deepseek_api_key: str | None
|
|
deepseek_base_url: str
|
|
deepseek_model: str
|
|
cookie_secure: bool
|
|
auth_disabled: bool
|
|
|
|
@classmethod
|
|
def load(cls) -> "Settings":
|
|
data_dir = Path(os.getenv("DATA_DIR", "./data")).resolve()
|
|
data_dir.mkdir(parents=True, exist_ok=True)
|
|
return cls(
|
|
data_dir=data_dir,
|
|
database_path=data_dir / "notes.sqlite3",
|
|
users=_load_users(),
|
|
session_secret=_read_secret("SESSION_SECRET_FILE", "SESSION_SECRET"),
|
|
deepseek_api_key=_read_secret(
|
|
"DEEPSEEK_API_KEY_FILE", "DEEPSEEK_API_KEY"
|
|
),
|
|
deepseek_base_url=os.getenv(
|
|
"DEEPSEEK_BASE_URL", "https://api.deepseek.com"
|
|
).rstrip("/"),
|
|
deepseek_model=os.getenv("DEEPSEEK_MODEL", "deepseek-v4-pro"),
|
|
cookie_secure=_as_bool("COOKIE_SECURE", True),
|
|
auth_disabled=_as_bool("AUTH_DISABLED", False),
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class UserSeed:
|
|
id: str
|
|
label: str
|
|
role: str
|
|
access_key_hash: str
|
|
debug_sharing: bool = False
|
|
|
|
|
|
def _load_users() -> tuple[UserSeed, ...]:
|
|
raw = _read_secret("USERS_FILE", "USERS_JSON")
|
|
if not raw:
|
|
return ()
|
|
payload = json.loads(raw)
|
|
if not isinstance(payload, list):
|
|
raise ValueError("users configuration must be a JSON array")
|
|
users: list[UserSeed] = []
|
|
seen: set[str] = set()
|
|
for item in payload:
|
|
if not isinstance(item, dict):
|
|
raise ValueError("each configured user must be an object")
|
|
user = UserSeed(
|
|
id=str(item["id"]),
|
|
label=str(item["label"]).strip()[:40],
|
|
role=str(item["role"]),
|
|
access_key_hash=str(item["access_key_hash"]),
|
|
debug_sharing=bool(item.get("debug_sharing", False)),
|
|
)
|
|
if not user.id or user.id in seen:
|
|
raise ValueError("configured user ids must be unique")
|
|
if user.role not in {"admin", "member"}:
|
|
raise ValueError("configured user role must be admin or member")
|
|
if not user.label:
|
|
raise ValueError("configured user label cannot be empty")
|
|
seen.add(user.id)
|
|
users.append(user)
|
|
return tuple(users)
|