Files
Mujoco_WASM/deploy/cadworld/install-model-keys.py
T
chenlin 0d986f60bd
web-platform-ci / Standalone decision service (no cloud credentials) (push) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled
lekiwi-compatibility / cpu-compatibility (push) Has been cancelled
feat: release v1.0.2 LeKiwi 语言控制与网站嵌入
集成服务器托管模型、自然语言移动与有界抓放、内置 LeKiwi URL 导入和双摄像头;同步部署契约与指定域名 iframe 白名单,保留原有物理安全、会话及调用预算防护。

更新 npm 包及锁文件版本、CHANGELOG 与发布文档。提交前 typecheck、120 项定向前端测试和 44 项后端测试通过(3 项可选跳过);真实 v2 云模型抓放仍待单独验收,不包含运行密钥或构建产物。
2026-09-24 15:29:49 +08:00

83 lines
2.9 KiB
Python

"""Operator-only provisioning. Never package keys, pass them in argv, or print them."""
import argparse
import json
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from decision_server.credentials import deepseek_llm, openrouter_jev # noqa: E402
REMOTE = r"""
import json, os, pathlib, re, sys
values = json.loads(sys.stdin.read(16384))
assert set(values) == {'Deepseek_API_KEY', 'OPENROUTER_API_KEY'}
assert all(re.fullmatch(r'[A-Za-z0-9_-]{16,4096}', v) for v in values.values())
root = pathlib.Path('/opt/cadworld-sim')
secrets = root / 'secrets'
secrets.mkdir(mode=0o700, exist_ok=True)
os.chmod(str(secrets), 0o700)
path = secrets / 'model-keys.env'
payload = ''.join(k+'='+v+'\n' for k,v in sorted(values.items())).encode()
if path.exists():
if path.is_symlink() or path.read_bytes() != payload:
raise SystemExit('Existing credentials differ; explicit rotation required')
else:
fd = os.open(str(path), os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o400)
with os.fdopen(fd, 'wb') as stream:
stream.write(payload)
os.chown(str(path), 10001, 10001)
os.chmod(str(path), 0o400)
budget = root / 'budget'
budget.mkdir(mode=0o700, exist_ok=True)
os.chown(str(budget), 10001, 10001)
os.chmod(str(budget), 0o700)
print('Server-only credentials provisioned; values not displayed.')
"""
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--confirm-server-keys", action="store_true")
args = parser.parse_args()
if not args.confirm_server_keys:
parser.error("Explicit authorization required before reading the two .env variables")
keys = {
"Deepseek_API_KEY": deepseek_llm(ROOT / ".env").key,
"OPENROUTER_API_KEY": openrouter_jev(ROOT / ".env").key,
}
if not (ROOT / "website-dist/index.html").is_file():
raise SystemExit("Build website before provisioning")
for path in (ROOT / "website-dist").rglob("*"):
if path.is_file():
content = path.read_bytes()
if any(value.encode() in content for value in keys.values()):
raise SystemExit("Credential found in static build; provisioning refused")
# The remote source contains no values. Only SSH's encrypted stdin carries keys.
import shlex
result = subprocess.run(
[
"ssh",
"-o",
"BatchMode=yes",
"-o",
"StrictHostKeyChecking=yes",
"-o",
"UpdateHostKeys=no",
"root@47.93.31.109",
"python3 -c " + shlex.quote(REMOTE),
],
input=json.dumps(keys).encode(),
capture_output=True,
)
if result.returncode:
raise SystemExit("Credential provisioning failed; values/logs withheld, no automatic retry")
print("Static secret scan passed; server-only credentials provisioned (0400, read-only mount).")
if __name__ == "__main__":
main()