Files
grok-free-register-oss/xai_enroller/config.py
chaos d10009d639 Initial commit: grok-free-register-oss
Open-source Grok free registration CLI, xai_enroller auth pipeline,
local auth service, tests and docs.
2026-07-16 21:04:05 +08:00

140 lines
5.7 KiB
Python

import os
import stat
from dataclasses import dataclass
from pathlib import Path
from urllib.parse import urlparse
def _int(env, key, default):
try:
return int(env.get(key, str(default)))
except ValueError as exc:
raise ValueError(f"{key} must be an integer") from exc
def _float(env, key, default):
try:
return float(env.get(key, str(default)))
except ValueError as exc:
raise ValueError(f"{key} must be numeric") from exc
@dataclass(frozen=True)
class Settings:
source_kind: str
source_file: Path | None
source_db: Path | None
source_salt: bytes
ledger_path: Path
target: int = 1
concurrency: int = 1
retry_attempts: int = 0
timeout_sec: float = 1800.0
poll_interval: float = 5.0
executor: str = "http"
sink: str | None = None
cpa_base_url: str | None = None
cpa_management_secret: str | None = None
local_auth_dir: Path | None = None
@classmethod
def from_environ(cls, env=None):
env = dict(os.environ if env is None else env)
source_kind = env.get("XAI_ENROLLER_SOURCE_KIND", "file")
if source_kind not in {"file", "sqlite", "remote"}:
raise ValueError("source kind must be file, sqlite, or remote")
source_file = Path(env["XAI_ENROLLER_SOURCE_FILE"]) if env.get("XAI_ENROLLER_SOURCE_FILE") else None
source_db = Path(env["XAI_ENROLLER_SOURCE_DB"]) if env.get("XAI_ENROLLER_SOURCE_DB") else None
if source_kind == "file" and source_file is None:
raise ValueError("source file is required")
if source_kind == "sqlite" and source_db is None:
raise ValueError("source db is required")
salt = env.get("XAI_ENROLLER_SOURCE_SALT")
if not salt:
raise ValueError("source salt is required")
ledger_path = Path(env.get("XAI_ENROLLER_LEDGER_PATH", "xai-enroller-ledger.db"))
target = _int(env, "XAI_ENROLLER_TARGET", 1)
concurrency = _int(env, "XAI_ENROLLER_CONCURRENCY", 1)
retry_attempts = _int(env, "XAI_ENROLLER_RETRY_ATTEMPTS", 0)
timeout_sec = _float(env, "XAI_ENROLLER_TIMEOUT_SEC", 1800)
poll_interval = _float(env, "XAI_ENROLLER_POLL_SEC", 5)
executor = env.get("XAI_ENROLLER_AUTH_EXECUTOR", "http")
sink = env.get("XAI_ENROLLER_SINK") or None
if not 1 <= target <= 100:
raise ValueError("target must be between 1 and 100")
if not 1 <= concurrency <= 4:
raise ValueError("concurrency must be between 1 and 4")
if not 0 <= retry_attempts <= 3:
raise ValueError("retry attempts must be between 0 and 3")
if timeout_sec <= 0:
raise ValueError("timeout must be positive")
if poll_interval <= 0:
raise ValueError("poll interval must be positive")
if executor not in {"http", "playwright"}:
raise ValueError("executor must be http or playwright")
if sink not in {None, "cpa", "local"}:
raise ValueError("sink must be cpa or local")
cpa_url = env.get("XAI_ENROLLER_CPA_BASE_URL") or None
cpa_secret = env.get("XAI_ENROLLER_CPA_MANAGEMENT_SECRET") or None
local_auth_dir = (
Path(env["XAI_ENROLLER_LOCAL_AUTH_DIR"]).expanduser()
if env.get("XAI_ENROLLER_LOCAL_AUTH_DIR")
else None
)
if sink == "cpa":
if not cpa_url or not cpa_secret:
raise ValueError("CPA base URL and management secret are required")
parsed = urlparse(cpa_url)
is_private = parsed.hostname in {"localhost", "127.0.0.1", "::1"}
if parsed.scheme != "https" and not is_private:
raise ValueError("CPA base URL must use HTTPS")
if sink == "local" and local_auth_dir is None:
raise ValueError("local auth directory is required")
return cls(
source_kind=source_kind,
source_file=source_file,
source_db=source_db,
source_salt=salt.encode(),
ledger_path=ledger_path,
target=target,
concurrency=concurrency,
retry_attempts=retry_attempts,
timeout_sec=timeout_sec,
poll_interval=poll_interval,
executor=executor,
sink=sink,
cpa_base_url=cpa_url,
cpa_management_secret=cpa_secret,
local_auth_dir=local_auth_dir,
)
def redacted_dict(self):
return {
"source_kind": self.source_kind,
"source_file": str(self.source_file) if self.source_file else None,
"source_db": str(self.source_db) if self.source_db else None,
"ledger_path": str(self.ledger_path),
"target": self.target,
"concurrency": self.concurrency,
"retry_attempts": self.retry_attempts,
"timeout_sec": self.timeout_sec,
"poll_interval": self.poll_interval,
"executor": self.executor,
"sink": self.sink,
"cpa_base_url": self.cpa_base_url,
"local_auth_dir": str(self.local_auth_dir) if self.local_auth_dir else None,
}
def require_private_regular_file(path: Path, *, require_0600=False):
try:
mode = path.stat()
except OSError as exc:
raise ValueError(f"source path is unavailable: {path}") from exc
if not stat.S_ISREG(mode.st_mode):
raise ValueError("source path must be a regular file")
if mode.st_mode & (stat.S_IWGRP | stat.S_IWOTH):
raise ValueError("source path must not be group/world writable")
if require_0600 and mode.st_mode & (stat.S_IRGRP | stat.S_IROTH):
raise ValueError("file source requires mode 0600 or stricter")