zcbot/core/sandbox/network.py

54 lines
2.2 KiB
Python

"""Sandbox Docker network 管理。
`zcbot-sandbox-net` 是 docker bridge,**默有 outbound NAT**(走 host 默 bridge 路由)。
sandbox 容器同接此 net + iptables OUTPUT 红线段 DROP(init.sh)挡 cloud metadata /
loopback / 内网 / PG IP。
**dogfood 阶段**(当前):容器可访问公网(让模型能 `pip install` / `curl` 公开域名),
iptables 仍挡内网 + cloud metadata。
**外部用户开放时**(§7.7 Stage C Step 4,DESIGN §7.5 #2):
network 改 `--internal`(完全禁 outbound)+ 起 zcbot-proxy 容器接此 net + sandbox
容器 env `HTTP_PROXY` 指向 proxy + proxy 做 allowlist / 字节计量 / audit。届时
network 从 bridge 改 internal,需手动 rm + recreate(已 running 的容器先全停)。
操作幂等:create 前 inspect 探测,已存在直接返;若已存在但 Internal=true(上一版
遗留),打 warn 提示 ── 不自动 rm 避免破坏现有连着的容器(详 RUN.md "Sandbox
网络迁移"段)。
"""
from __future__ import annotations
import json
import subprocess
NETWORK_NAME = "zcbot-sandbox-net"
def ensure_network() -> None:
"""创建 `zcbot-sandbox-net`(若不存在);若已存在且 Internal=True 仅 warn。失败 raise。"""
inspect = subprocess.run(
["docker", "network", "inspect", NETWORK_NAME],
capture_output=True, text=True,
)
if inspect.returncode == 0:
# 已存在 ── 检测 Internal 属性,若 true 给迁移提示
try:
data = json.loads(inspect.stdout)
if data and isinstance(data, list) and data[0].get("Internal") is True:
print(
f"[warn] network {NETWORK_NAME} is --internal (legacy);"
f" sandbox 容器将无法 outbound。手动 `docker network rm {NETWORK_NAME}`"
f" 后重启 web,会自动 recreate 为非 internal(详 RUN.md)"
)
except (json.JSONDecodeError, IndexError, AttributeError):
pass
return
r = subprocess.run(
["docker", "network", "create", NETWORK_NAME],
capture_output=True, text=True,
)
if r.returncode != 0:
raise RuntimeError(
f"docker network create {NETWORK_NAME} failed: {r.stderr.strip()}"
)