Spaces:
Running
Running
File size: 7,077 Bytes
c47ec10 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | #!/usr/bin/env python3
"""Idempotently merge WARP/FlareSolverr proxy_runtime defaults into config.json."""
from __future__ import annotations
import copy
import json
import os
import re
import sys
from pathlib import Path
from typing import Any
DEFAULT_USER_AGENT = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/145.0.0.0 Safari/537.36"
)
DEFAULT_PROXY_RUNTIME: dict[str, Any] = {
"enabled": False,
"egress_mode": "direct",
"proxy_url": "",
"resource_proxy_url": "",
"skip_ssl_verify": False,
"reset_session_status_codes": [403],
"clearance": {
"enabled": False,
"mode": "none",
"cf_cookies": "",
"cf_clearance": "",
"user_agent": DEFAULT_USER_AGENT,
"browser": "chrome",
"flaresolverr_url": "",
"timeout_sec": 60,
"refresh_interval": 3600,
"warm_up_on_start": False,
},
}
def _env_bool(name: str, default: bool) -> bool:
value = os.getenv(name)
if value is None:
return default
lowered = value.strip().lower()
if lowered in {"1", "true", "yes", "on"}:
return True
if lowered in {"0", "false", "no", "off"}:
return False
return default
def _env_int(name: str, default: int, minimum: int = 0) -> int:
try:
return max(minimum, int(os.getenv(name, str(default))))
except (TypeError, ValueError, OverflowError):
return default
def _warp_runtime_defaults() -> dict[str, Any]:
clearance_enabled = _env_bool("CHATGPT2API_PROXY_RUNTIME_CLEARANCE_ENABLED", True)
clearance_mode = os.getenv("CHATGPT2API_PROXY_RUNTIME_CLEARANCE_MODE", "flaresolverr").strip().lower()
if clearance_mode not in {"none", "manual", "flaresolverr"}:
clearance_mode = "flaresolverr" if clearance_enabled else "none"
if not clearance_enabled:
clearance_mode = "none"
egress_mode = os.getenv("CHATGPT2API_PROXY_RUNTIME_EGRESS_MODE", "single_proxy").strip().lower()
if egress_mode not in {"direct", "single_proxy"}:
egress_mode = "single_proxy"
runtime = copy.deepcopy(DEFAULT_PROXY_RUNTIME)
runtime.update(
{
"enabled": _env_bool("CHATGPT2API_PROXY_RUNTIME_ENABLED", True),
"egress_mode": egress_mode,
"proxy_url": os.getenv("CHATGPT2API_PROXY_RUNTIME_PROXY_URL", "http://privoxy:8118").strip(),
"resource_proxy_url": os.getenv("CHATGPT2API_PROXY_RUNTIME_RESOURCE_PROXY_URL", "").strip(),
"skip_ssl_verify": _env_bool("CHATGPT2API_PROXY_RUNTIME_SKIP_SSL_VERIFY", False),
"reset_session_status_codes": [
int(part.strip())
for part in os.getenv("CHATGPT2API_PROXY_RUNTIME_RESET_STATUS_CODES", "403").split(",")
if part.strip().isdigit() and 100 <= int(part.strip()) <= 599
] or [403],
}
)
runtime["clearance"].update(
{
"enabled": clearance_enabled,
"mode": clearance_mode,
"user_agent": os.getenv("CHATGPT2API_PROXY_RUNTIME_USER_AGENT", DEFAULT_USER_AGENT).strip() or DEFAULT_USER_AGENT,
"browser": os.getenv("CHATGPT2API_PROXY_RUNTIME_BROWSER", "chrome").strip() or "chrome",
"flaresolverr_url": os.getenv("CHATGPT2API_FLARESOLVERR_URL", "http://flaresolverr:8191").strip(),
"timeout_sec": _env_int("CHATGPT2API_PROXY_RUNTIME_CLEARANCE_TIMEOUT_SEC", 60, 1),
"refresh_interval": _env_int("CHATGPT2API_PROXY_RUNTIME_CLEARANCE_REFRESH_INTERVAL", 3600, 60),
"warm_up_on_start": _env_bool("CHATGPT2API_PROXY_RUNTIME_WARM_UP_ON_START", False),
}
)
return runtime
def _deep_fill_missing(target: dict[str, Any], defaults: dict[str, Any]) -> bool:
changed = False
for key, value in defaults.items():
if key not in target:
target[key] = copy.deepcopy(value)
changed = True
continue
if isinstance(target[key], dict) and isinstance(value, dict):
changed = _deep_fill_missing(target[key], value) or changed
return changed
def _looks_like_repository_default(runtime: Any) -> bool:
if not isinstance(runtime, dict):
return True
candidate = copy.deepcopy(runtime)
_deep_fill_missing(candidate, DEFAULT_PROXY_RUNTIME)
return candidate == DEFAULT_PROXY_RUNTIME
def _mask_url(value: str) -> str:
return re.sub(r"(https?://)([^\s/@:]+):([^\s/@]+)@", r"\1[REDACTED]@", value or "", flags=re.I)
def main() -> int:
config_path = Path(os.getenv("CHATGPT2API_CONFIG_FILE", "/app/config.json"))
if not config_path.exists():
print(f"Config file not found, creating {config_path}")
data: dict[str, Any] = {}
else:
try:
data = json.loads(config_path.read_text(encoding="utf-8"))
except json.JSONDecodeError as exc:
print(f"Invalid JSON in {config_path}: {exc}", file=sys.stderr)
return 1
if not isinstance(data, dict):
print(f"Config root must be an object: {config_path}", file=sys.stderr)
return 1
desired = _warp_runtime_defaults()
existing = data.get("proxy_runtime")
changed = False
if _looks_like_repository_default(existing) or _env_bool("CHATGPT2API_PROXY_RUNTIME_FORCE", False):
data["proxy_runtime"] = desired
changed = True
print("Created proxy_runtime defaults")
else:
runtime = existing if isinstance(existing, dict) else {}
changed = _deep_fill_missing(runtime, DEFAULT_PROXY_RUNTIME)
data["proxy_runtime"] = runtime
print("Proxy runtime already configured")
if changed:
config_path.parent.mkdir(parents=True, exist_ok=True)
payload = json.dumps(data, ensure_ascii=False, indent=2) + "\n"
tmp_path = config_path.with_suffix(config_path.suffix + ".tmp")
try:
tmp_path.write_text(payload, encoding="utf-8")
tmp_path.replace(config_path)
except OSError as exc:
# Docker bind-mounted single files can reject atomic rename with EBUSY.
# Fall back to in-place write so the init job works with both file and
# directory mounts.
if getattr(exc, "errno", None) != 16:
raise
config_path.write_text(payload, encoding="utf-8")
tmp_path.unlink(missing_ok=True)
runtime = data.get("proxy_runtime") if isinstance(data.get("proxy_runtime"), dict) else {}
clearance = runtime.get("clearance") if isinstance(runtime.get("clearance"), dict) else {}
print(
"Proxy runtime summary: "
f"enabled={bool(runtime.get('enabled'))}, "
f"egress_mode={runtime.get('egress_mode')}, "
f"proxy_url={_mask_url(str(runtime.get('proxy_url') or ''))}, "
f"clearance_mode={clearance.get('mode')}, "
f"flaresolverr_url={_mask_url(str(clearance.get('flaresolverr_url') or ''))}"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())
|