"""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()}" )