b3ba0fd431
Co-authored-by: Cursor <cursoragent@cursor.com>
125 lines
3.9 KiB
Python
125 lines
3.9 KiB
Python
"""读写仓库根 .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_SSE_INTERVAL_SEC": "1",
|
|
"CONTROL_SSE_PROBE_TIMEOUT_SEC": "2.5",
|
|
"CONTROL_HTTP_TIMEOUT_SEC": "12",
|
|
"CONTROL_AUTH_TOKEN_VERSION": "1",
|
|
"CONTROL_LAN_AUTH_BYPASS": "0",
|
|
}
|
|
|
|
|
|
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()
|