"""读写仓库根 .env.control(仅补缺,不覆盖已有值)。""" from __future__ import annotations import re from pathlib import Path from .config import _repo_root, get_control_settings def env_control_path() -> Path: return _repo_root() / ".env.control" def _read_text(path: Path) -> str: if not path.is_file(): return "" return path.read_text(encoding="utf-8") def _write_text(path: Path, text: str) -> None: path.parent.mkdir(parents=True, exist_ok=True) if text and not text.endswith("\n"): text += "\n" path.write_text(text, encoding="utf-8") try: path.chmod(0o600) except Exception: pass def get_env_value(key: str, text: str | None = None) -> str | None: raw = text if text is not None else _read_text(env_control_path()) m = re.search(rf"(?m)^{re.escape(key)}=(.*)$", raw) if not m: return None val = m.group(1).strip() if len(val) >= 2 and val[0] == val[-1] and val[0] in ("'", '"'): val = val[1:-1] return val def upsert_env_control(key: str, value: str, *, overwrite: bool = True) -> Path: """写入/更新单个键。overwrite=False 时若已有非空值则跳过。""" if "\n" in value or "\r" in value: raise ValueError(f"{key} 值不能包含换行") path = env_control_path() text = _read_text(path) existing = get_env_value(key, text) if not overwrite and existing is not None and existing.strip() != "": return path safe = value.replace("\\", "\\\\").replace('"', '\\"') line = f'{key}="{safe}"' pattern = re.compile(rf"(?m)^{re.escape(key)}=.*$") if pattern.search(text): if not overwrite: return path text = pattern.sub(line, text) else: if text and not text.endswith("\n"): text += "\n" text += line + "\n" _write_text(path, text) return path # 一键部署默认项:仅在缺失或为空时写入 _DEPLOY_DEFAULTS: dict[str, str] = { "CONTROL_AUTH_USERNAME": "admin", "CONTROL_AUTH_PASSWORD": "admin123", "CONTROL_AUTH_SECRET": "change-me-control-secret-please", "CONTROL_TOKEN_TTL_SEC": "604800", "CONTROL_POLL_INTERVAL_SEC": "8", "CONTROL_HTTP_TIMEOUT_SEC": "12", "CONTROL_AUTH_TOKEN_VERSION": "1", } def ensure_env_control_defaults() -> dict[str, bool]: """ 确保 .env.control 存在且关键键有值。 已有非空值绝不覆盖。返回 {key: written?}。 """ written: dict[str, bool] = {} path = env_control_path() before = _read_text(path) for key, default in _DEPLOY_DEFAULTS.items(): old = get_env_value(key, before) if old is not None and old.strip() != "": written[key] = False continue upsert_env_control(key, default, overwrite=False) # 若文件原先无该键,before 里也没有;重新读确认 after = get_env_value(key) written[key] = after == default or (old is None or old.strip() == "") get_control_settings.cache_clear() return written def update_control_credentials( *, new_username: str, new_password: str, bump_token_version: bool = True, ) -> None: user = new_username.strip() pwd = new_password if not user or not pwd: raise ValueError("用户名和密码不能为空") if len(pwd) < 6: raise ValueError("密码至少 6 位") upsert_env_control("CONTROL_AUTH_USERNAME", user, overwrite=True) upsert_env_control("CONTROL_AUTH_PASSWORD", pwd, overwrite=True) if bump_token_version: cur = get_env_value("CONTROL_AUTH_TOKEN_VERSION") or "1" try: ver = int(cur) + 1 except ValueError: ver = 2 upsert_env_control("CONTROL_AUTH_TOKEN_VERSION", str(ver), overwrite=True) get_control_settings.cache_clear()