11

多模型协作命令行 Agent —— 设计评审

评审对象:《多模型协作命令行 Agent:GLM 规划 → DeepSeek 审查 → GPT 实施》设计稿
结论一句话:骨架不用改,但第 5 节「七个内置工具」和第 6 节「四道安全护栏」需要按本评审重写一遍。
当前护栏写法会给人「已经安全了」的错觉,而实际上 python 白名单 + 可能的 shell=True + 无 SSRF 防护三者叠加,等于没有边界。


0. 总评

值得保留的设计

设计点 为什么对
工具注册表 TOOLS = {name: {schema, func}} 加工具 = 加条目,能力边界清晰,是整份方案最好的决策
三阶段接力(规划 / 审查 / 实施) 把「想清楚」和「动手」分开,对长任务确实有效
finish 作为显式结束工具 比「让模型输出特定结束文本」可靠得多
审查循环带轮数上限后放行进实施 避免死循环,工程上是正确的取舍
transcript 全量落盘 出问题可复盘,调提示词的唯一依据
模型名走 .env 可覆盖 换模型不改代码

需要重写的部分

位置 问题定性
第 6 节 护栏 1(文件沙箱) 实现细节有 Windows 正确性缺陷,且被护栏 2 抵消
第 6 节 护栏 2(命令白名单) 设计矛盾:放行 python 等于放弃沙箱
第 5 节 web_search 接口形态描述不准确(不是 SDK 调用)
第 5 节 web_fetch 缺 SSRF 防护,可被诱导读内网/本机服务
第 1 节 llm.py 统一封装 被 gpt-5 参数约束打穿,需参数适配层
第 3 节 审查循环 缺严重度分级,必然耗满 3 轮
流程整体 没人审「实现」,GLM 定的验收标准是死的
MCP 演进说明 过于乐观,低估了异步与生命周期成本

1. 阻断级问题(P0:不修就跑不通)

1.1 统一 chat() 会被 gpt-5 的参数约束打穿

设计稿说「用 openai SDK 一套代码调三家」。但 gpt-5 系列不支持 max_tokens,也不接受 temperature != 1(传了直接 400)。
参考:litellm 针对该问题的修复 PR、GPT-5 新参数与工具 cookbook。

如果 llm.py 里写死 temperature=0.2, max_tokens=4096 三家共用,第三阶段第一次调用就炸。

修法:加一层模型能力表,调用前按能力裁剪参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# config.py 或 llm.py
MODEL_CAPS = {
# 是否支持 temperature、token 上限用哪个字段、是否支持 json_object
"glm": {"temperature": True, "token_param": "max_tokens", "json_mode": False},
"deepseek": {"temperature": True, "token_param": "max_tokens", "json_mode": True},
"openai": {"temperature": False, "token_param": "max_completion_tokens", "json_mode": True},
}

def build_kwargs(provider, *, temperature=None, max_tokens=None, tools=None, json_mode=False):
caps = MODEL_CAPS[provider]
kw = {}
if max_tokens is not None:
kw[caps["token_param"]] = max_tokens
if temperature is not None and caps["temperature"]:
kw["temperature"] = temperature
if json_mode and caps["json_mode"]:
kw["response_format"] = {"type": "json_object"}
if tools:
kw["tools"] = tools
return kw

这一步应该第一个写,不是最后一个补。

1.2 智谱 web_search 不是 openai SDK 能调的接口

设计稿写「web_search 也走智谱,复用同一个 key」——复用 key 没问题,但它和 chat 是两条完全不同的路。官方 OpenAPI 描述的是独立 REST 端点:

1
2
POST https://open.bigmodel.cn/api/paas/v4/web_search
Authorization: Bearer <ZHIPU_API_KEY>

出处:智谱网络搜索 API 文档

必踩的坑(逐条对着 OpenAPI schema 抄下来的):

  1. search_engine 和 search_intent 都是 required——只发 search_query 直接 400。search_engine 取值:search_std / search_pro / search_pro_sogou / search_pro_quark。
  2. search_query maxLength = 70——模型很爱生成一百多字的中文查询,必须程序侧截断,否则 400。
  3. count 范围 1–50,默认 10。
  4. 错误码要单独处理:1701 搜索并发已达上限、1702 无可用搜索引擎、1703 引擎未返回有效数据 → 都应重试而不是抛异常中断。
  5. 正文在 search_result[].content,不是 snippet;另有 title / link / media / publish_date。
  6. content_size 选 medium(摘要)或 high(长正文),search_recency_filter 可限定 oneDay/oneWeek/oneMonth/oneYear——调研类任务建议默认开 oneYear 增强时效性。

修法:llm.py 里给搜索单开一个 HTTP 分支(httpx/requests,别硬塞进 openai SDK)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def web_search(query: str, *, count: int = 10, engine: str = "search_pro",
recency: str = "noLimit", content_size: str = "medium") -> list[dict]:
q = query[:70] # 硬截断,否则 70 字符上限直接 400
payload = {
"search_query": q,
"search_engine": engine, # required
"search_intent": False, # required
"count": max(1, min(int(count), 50)),
"content_size": content_size,
}
if recency != "noLimit":
payload["search_recency_filter"] = recency
for attempt in range(3):
r = httpx.post(SEARCH_URL, json=payload,
headers={"Authorization": f"Bearer {ZHIPU_API_KEY}"}, timeout=30)
if r.status_code == 200:
return r.json().get("search_result") or []
code = (r.json().get("error") or {}).get("code")
if code not in {"1701", "1702", "1703"}: # 非可重试错误直接返回
return [{"error": f"{code}: {r.text[:200]}"}]
time.sleep(1.5 * (attempt + 1))
return [{"error": "web_search failed after 3 attempts"}]

1.3 DeepSeek JSON 模式有「官方免责声明」

DeepSeek 官方 JSON Output 文档明确写了两条设计稿没接住的要求:

  1. Include the word “json” in the system or user prompt…
  2. When using the JSON Output feature, the API may occasionally return empty content. We are actively working on optimizing this issue.

含义:

  • 提示词里必须出现 “json” 字样,否则报错;
  • 返回空 content 是已知行为,不是异常;
  • 还要「设置合理的 max_tokens 防止 JSON 被中途截断」。

修法:把策略写死进代码(这是设计稿最大的逻辑漏洞之一——「解析失败降级为关键字提取」后面没有下文了):

1
2
3
4
5
尝试解析:
1. 空 content → 重试(最多 3 次,逐次提高 max_tokens)
2. 截断/非法 JSON → 尝试补齐尾部括号 → 仍失败则正则提取 verdict/issues
3. 仍未拿到 verdict → 降级为关键词提取(PASS/FAIL 关键词)
4. 完全失败 → 默认放行(fail-open)+ transcript 打醒目警告

并且必须在文档里显式写明「审查器故障时 fail-open 还是 fail-closed」。 建议 fail-open:审查器是辅助环节,不该阻塞整个任务。

1.4 缺「启动冒烟检查」

gpt-5 需要组织验证,key 很可能没权限;模型名(如 deepseek-chat)也可能随时间变动——官方文档示例里已经出现 deepseek-flash 这类新名字。现在是跑到第三阶段才 401,前两阶段的钱白烧。

修法:开跑前给三个模型各发一个 1-token 的最小请求(可用 max_tokens=1 且不传 tools),任何一家失败就立即退出并给出中文明确提示。约 30 秒换一次「快速失败」,非常值。


2. 安全护栏:第 6 节需要重写

2.1 python 自动放行 = 文件沙箱形同虚设(设计内部矛盾)

设计稿写「python / pytest / git status 等自动放行」。问题在于:

1
2
# 护栏 1 精心守护的沙箱,被护栏 2 一条命令绕过
run_command('python -c "import shutil; shutil.rmtree(\'C:/Users/...\')"')

pytest 同理(会执行工作目录里的 conftest.py,即任意代码);git 的部分子命令也会触发 hooks。

修法:白名单分级。

级别 命令 策略
只读安全 git status、git diff、ls/dir、cat 类 自动放行
受限执行 python script.py(必须带已存在的 .py 路径参数) 自动放行,但校验脚本路径在沙箱内
任意执行 python -c、python -m、pip install、pytest、npm 一律人工确认
其他 一切未列出的 确认,或直接拒绝

如果嫌麻烦,至少要在 README 里诚实写明:沙箱只防误写、不防恶意。但不能不说。

2.2 如果实现用 shell=True,整个白名单被一个 && 绕过

1
git status && del /f /q *        # 第一个 token 是白名单,shell=True 下全执行

修法:

  • 必须 shell=False + 参数列表;
  • Windows 上别用默认 posix 模式的 shlex.split(会把 C:\path 的反斜杠吃掉),改用参数列表直传或 shlex.split(cmd, posix=False);
  • 禁掉 | & ; > < ` $( 等元字符,或直接要求模型传数组参数。

这条比白名单内容本身更重要。

2.3 路径沙箱的 Windows 正确性缺陷

resolve() 之后用 str.startswith(workdir) 比较是错的:

1
2
C:\ws        ← 工作目录
C:\ws-evil ← startswith("C:\\ws") == True,逃逸成功

还要一并处理(你在 D:\ 上跑,这些都真实存在):

坑 说明
大小写不敏感 C:\WS 与 C:\ws 是同一目录
8.3 短名 PROGRA~1 绕过字符串比较
NTFS 数据流 file.txt:evil 写到备用数据流
保留设备名 CON、NUL、COM1
UNC 路径 \\?\C:\...、\\server\share
symlink / junction 必须在解析后再比较
TOCTOU 检查通过后、写入前目标被替换成软链
目标不存在 此时要 resolve 父目录再拼文件名

修法:

1
2
3
4
5
6
7
8
def safe_path(root: Path, user_path: str, *, for_write: bool = False) -> Path:
root = root.resolve()
p = (root / user_path).resolve() if not Path(user_path).is_absolute() else Path(user_path).resolve()
if for_write and not p.exists():
p = p.parent.resolve() / p.name # 父目录已解析,防止 symlink 逃逸
if os.path.commonpath([str(root).lower(), str(p).lower()]) != str(root).lower():
raise PermissionError(f"路径越界:{user_path}")
return p

Path.is_relative_to(Python 3.9+)也可以,但同样要先 normcase/lower 处理大小写。

2.4 web_fetch 完全没有 SSRF 防护(最该补的一条)

设计稿对 web_fetch 的描述只有「抓网页 → BeautifulSoup 抽正文」。当前形态下,模型可以叫它抓:

  • http://169.254.169.254/ —— 云元数据(拿临时凭证)
  • http://127.0.0.1:3080 —— 就是你现在这个 DSH GUI
  • http://192.168.x.x:6379 —— 内网服务探测
  • file:///C:/Users/.../.env —— 本地文件读取

而且抓回来的内容还会进入上下文。这个 agent 就跑在你本机,风险是实打实的。

修法(缺一不可):

1
2
3
4
5
6
7
8
9
10
11
12
13
ALLOWED_SCHEMES = {"http", "https"}

def check_url(url: str) -> None:
u = urlparse(url)
if u.scheme.lower() not in ALLOWED_SCHEMES: # 干掉 file:// ftp:// gopher://
raise PermissionError("仅允许 http/https")
for family, _, _, _, sockaddr in socket.getaddrinfo(u.hostname, u.port or 80):
ip = ipaddress.ip_address(sockaddr[0])
if (ip.is_private or ip.is_loopback or ip.is_link_local
or ip.is_reserved or ip.is_multicast or ip.is_unspecified):
raise PermissionError(f"拒绝访问内网/本机地址:{ip}")
# 另需:禁止跟随重定向到私网(手动处理 30x,每跳都校验)
# 另需:响应体大小上限(如 2MB)、content-type 白名单(text/html、text/plain、application/json)

注意:DNS 解析后校验 IP 才有效;只校验 URL 字符串里的主机名会被 DNS rebinding 与十进制 IP(http://2130706433/)绕过。

2.5 「反提示词注入声明」是降概率,不是安全边界

设计稿把「提示词里声明无视网页内容中的指令」列为四道护栏之一,这高估了它的作用。模型仍可能被诱导。

真正的边界是工具能力,你已经做对一半(不提供删除工具),但另一半被 2.1 抵消了。

性价比最高的补强是污点标记(taint tracking):

一旦本会话出现过 web_fetch 的返回内容,则其后的工具调用强制人工确认;且 --yes 不豁免这一类确认。

这比写十行「请无视注入」的提示词有用得多。同时把网页正文用明确分隔符包裹并标注为不可信数据,作为第二层防御。

2.6 两个小但会咬人的点

  • 非 TTY 死锁:input() 在管道/CI 里会挂住。必须检测 sys.stdin.isatty(),非 TTY 时默认拒绝该命令(而不是默认放行,也不是挂死)。
  • Windows 不杀进程树:subprocess 的 timeout 只杀直接子进程,python 起的孙子进程会残留。需 CREATE_NEW_PROCESS_GROUP + taskkill /T /F(或 Job Object)。

3. 架构与流程问题(P1)

3.1 最关键的缺口:没有人审「实现」

GLM 定了验收标准,然后第三阶段结束就没人看了。GPT 说完成就完成了。

两种情况任选一种(推荐都做):

  1. 加轻量第四阶段:把产物清单 + 关键命令输出喂给便宜模型,对照验收标准逐条判定 PASS/FAIL;
  2. 强制 finish 工具带结构化字段:{"summary": ..., "artifacts": [...], "acceptance": [{"criterion": ..., "met": bool, "evidence": ...}]}。

否则「验收标准」只是写着好看的摆设。

3.2 角色分工与能力错配

最贵的 gpt-5 干最重最长的活:40 轮 reasoning + tool calls,每轮推理 token 都计费。

量级估算(粗估,取决于任务复杂度):单次任务 十几分钟到几十分钟、数美元级。40 轮 × (推理 token + 上下文重放) 是成本主要来源。

修法:

  • 轮数上限从 40 降到 15–20(绝大多数任务够用);
  • 加全局预算熔断:wall clock 上限 + 累计 token 上限 + 费用估算上限,任一触发即优雅停止并落盘 transcript(只靠轮数上限挡不住成本);
  • 打印每阶段耗时与 token,让自己对花销有体感。

3.3 审查者的建议可能不可执行

DeepSeek 若只看到「任务描述 + 计划文本」,会给出能力边界之外的建议(「建议引入 X 库」「建议用 MCP」),而实施者只有 7 个工具。

修法:reviewer 的 prompt 必须注入:

  • 7 个工具的能力清单(含参数约束,如「无网络写权限」「无删除工具」「命令受白名单限制」);
  • 工作目录真实文件列表;
  • 明确约束:「不得提出超出上述工具能力范围的建议;每条 issue 必须指向计划中的原文片段」。

顺带说一句:审查质量主要来自给它可验证的事实,而不是换个模型。DeepSeek 审 GLM 计划,两家能力相近,靠「异源」防放水的效果有限。

3.4 审查 issue 缺严重度分级 → 必然耗满 3 轮

{"verdict","issues"} 把致命问题和措辞建议混在一起,前两轮很容易耗在细节上。

修法:

1
2
3
4
5
6
7
8
{
"verdict": "PASS" | "FAIL",
"issues": [
{"severity": "blocker" | "major" | "minor",
"location": "计划中的原文片段",
"problem": "...", "suggestion": "..."}
]
}

只有 blocker 才阻止进入实施;major/minor 记录后随计划一起下发,让实施者自行取舍。这样 3 轮上限才花在刀刃上。

3.5 没定义「实施阶段发现计划错了」怎么办

GPT 只有两个坏选择:硬着头皮实现错计划,或自作主张乱改。

修法:加一个 request_replan(reason) 工具(或允许它在输出里显式报告偏离),触发一次回到 GLM 的修订(计入总预算,最多 1 次)。

3.6 工具循环的防呆一个都没提

缺什么 后果 修法
重复调用检测 模型陷入「写同一文件 → 读 → 再写」死循环 同 args 连续 N(=3) 次即注入提醒,再犯则中止
模型不调 finish 只回纯文本 循环不知道怎么办 视为结束(或提醒一次后结束),并落盘该文本为结论
工具异常 程序直接崩 结构化回传 {"ok": false, "error": ...},让模型自己纠错
上下文膨胀 40 轮后撑爆 token 上限 历史裁剪/摘要:保留 system + 计划 + 最近 N 轮 + 工具结果摘要
工具结果过大 同上 单条结果硬截断(如 8KB)+ 标注「已截断」

3.7 建议加 --plan-only

调提示词时每次都烧三个阶段的钱非常痛苦。这个开关几乎零成本,收益极高。


4. MCP 演进说明需要修正

设计稿:「将来加一个从 MCP Server 拉取 schema 并转发的加载器,注册表和循环逻辑不变」——过于乐观。

MCP 的真实成本:

项 说明
异步 JSON-RPC over stdio / Streamable HTTP,func 得是 async
生命周期 initialize → tools/list → tools/call,还有 notifications
进程管理 stdio 型 MCP Server 要管理子进程启停、崩溃重启、超时
命名冲突 多个 Server 的工具名要加命名空间前缀
Schema 转换 MCP 的 inputSchema 与 OpenAI function 格式不完全一致,要写转换器
鉴权与配额 每个 Server 一套凭据、限流、超时

但现在有一个「花 10 分钟、将来省一天」的准备动作——把执行器签名直接定成异步 + 结构化返回:

1
2
3
4
5
6
7
8
9
10
11
@dataclass
class ToolResult:
ok: bool
content: str # 回传给模型的可读文本
meta: dict | None = None # 耗时/截断标记/退出码等,供 transcript 使用

async def write_file(args: dict) -> ToolResult: ...

TOOLS = {
"write_file": {"schema": {...}, "func": write_file},
}

循环里统一 await,内部实现该同步就同步(用 asyncio.to_thread 包一下即可)。这样将来接 MCP 只是多一个注册来源,循环一行不用改。

这是整份方案里性价比最高的一个改动建议。


5. 工程细节清单(零散但会咬人)

# 项 问题 修法
1 编码 Windows 上默认 GBK,写代码含中文/emoji 会崩 所有 open() 显式 encoding="utf-8"
2 子进程解码 控制台输出非 UTF-8 时 UnicodeDecodeError 直接崩程序 decode(errors="replace")
3 read_file 二进制防护 工作目录里全是 PDF,读 PDF 必抛异常 先嗅探二进制(\x00 检测),明确报错;v2 加 read_pdf(调研类任务刚需)
4 transcript 脱敏 命令输出/网页正文可能含 API key 落盘前正则打码 sk-、Bearer 后的串;runs/ 进 .gitignore
5 transcript 时间戳 只到秒,并发运行会撞 用 %Y%m%d-%H%M%S + 短随机后缀
6 write_file 原子性 中途失败留下半截文件 写临时文件 + os.replace
7 覆盖策略 没定义,重跑行为不确定 默认允许覆盖但记录 overwrote: true;或不含 --force 时拒绝覆盖已存在文件
8 key 校验 缺 key 要跑到第一次调用才 401 启动时校验,缺哪个报哪个(中文)
9 文件账 说「约 7 个源文件」,实际列了 8 项(py 只有 5 个) 建议 agent.py 拆出 executor.py(工具循环),py 变 6 个
10 agent.py 责任 CLI + 编排 + 两个循环 + 落盘,过于臃肿 内部至少严格分层:cli() / plan_stage() / review_loop() / execute_loop() / write_transcript()
11 缺目录约定 runs/、workspace/、.gitignore 都没提 启动时自动创建
12 缺测试 沙箱/白名单/JSON 降级都是纯函数,却没测试 加 tests/,沙箱必须覆盖逃逸用例(../、绝对路径、symlink、大小写、..\\、C:\ws-evil)
13 调研任务不可复现 联网结果的回归测试等于掷骰子 把 web_search/web_fetch 打桩(fixture)后做确定性测试
14 缺自动验收 「猜数字游戏」怎么算通过? 给示例任务配验收脚本(echo "50\n" | python game.py 断言输出)

6. 修订后的实施顺序

原设计稿的 7 步顺序需要调整——先把「容易炸的地基」写好,再写编排。

步 内容 完成判据(可验证)
1 requirements.txt / .env.example / config.py 缺 key 时报中文明确错误;MODEL_CAPS 能力表就位
2 llm.py 参数适配层 + 三客户端 + 冒烟检查 三家各发一次最小请求全部 200
3 llm.py 的 web_search(独立 HTTP 分支) 真实调用返回 ≥1 条结果;query 超 70 字符被正确截断
4 tools.py 骨架:注册表 + ToolResult + 异步签名 注册表可枚举、schema 可 dump
5 路径沙箱 + 命令白名单分级 + shell=False 通过全部逃逸单测(含 C:\ws-evil、symlink、大小写)
6 web_fetch + SSRF 校验 拒绝 127.0.0.1 / 169.254.169.254 / file://;正常网页可抓
7 prompts.py 三角色(含工具边界声明 + 反注入) reviewer 输出的 issue 均在工具能力范围内
8 agent.py 三阶段编排 + 审查循环(issue 分级) --plan-only 跑通,打印计划与分级意见
9 工具循环 executor.py + 重复检测 + 上下文裁剪 + 预算熔断 小任务能在 10 轮内 finish,超预算时优雅停止
10 transcript 落盘 + 脱敏 落盘文件里搜不到 key
11 端到端:编码类 + 调研类 编码类有自动验收脚本;调研类用 mock 复跑
12 按实跑结果调提示词 3 轮审查内通过率、平均轮数有记录

与原文最大的差异:把「护栏」和「参数适配」提到编排之前(原文是第 4、5 步才做工具,第 6 步才测试),避免返工。


7. 待你决策的开放问题

  1. 是否坚持 gpt-5 做实施者? reasoning 模型跑 15–20 轮工具循环,延迟和成本都偏高。若目标只是「能跑通且便宜」,gpt-5-mini 或「GLM 做实施、GPT 做验收」可能更划算。建议先按设计稿实现,跑一次真实任务后再看账单决定。
  2. --yes 是否豁免污点确认? 建议不豁免(见 2.5),但如果你要无人值守跑,就得接受这个风险——需要你明确。
  3. 是否现在就做第四阶段验收? 不做的话,「验收标准」形同虚设(见 3.1);做的话多一次 API 调用。
  4. read_pdf 是否进 v1? 你的工作目录全是财报 PDF,调研类任务大概率第一次就会碰到。若不进 v1,read_file 至少要能明确报错而不是崩。
  5. 命令白名单里 python 的最终等级:完全自动放行(方便但沙箱失效)/需确认(安全但打断流程)/仅允许「带沙箱内 .py 路径」的形式(折中,推荐)。

8. 附录:已核实的外部事实与出处

事实 出处
智谱网络搜索是独立端点 POST /paas/v4/web_search;search_engine、search_intent 必填;search_query ≤70 字符;错误码 1701/1702/1703 docs.bigmodel.cn 网络搜索
DeepSeek JSON Output:prompt 必须含 “json” 字样;可能偶发返回空 content;需合理设置 max_tokens 防截断 api-docs.deepseek.com/guides/json_mode
gpt-5 系列不支持 max_tokens,且不接受 temperature != 1 litellm PR #13390、GPT-5 参数与工具
GLM base_url https://open.bigmodel.cn/api/paas/v4、glm-4.6 可用 GLM-4.6 API

注:deepseek-chat 的可用性请在步骤 2 的冒烟检查里实测确认(官方文档示例中已出现 deepseek-flash 等新模型名,模型线会变),模型名走 .env 的设计因此是对的。

PentAGI 安装(Ubuntu Server + VMware)

记录了在 VMware 虚拟机中从零开始安装 PentAGI 的完整过程,包括遇到的所有坑及解决方法。

**环境信息**

  • 宿主机:Windows + VMware Workstation
  • 虚拟机:Ubuntu Server 24.04 LTS
  • 内存:8GB(推荐)
  • 磁盘:50GB(最终扩容后)
  • 网络:NAT + Clash 代理

-–

目录

  1. 环境准备:为什么需要新虚拟机
  2. 安装 Ubuntu Server
  3. 基础网络排查
  4. 安装 Docker 环境
  5. 下载 PentAGI 配置文件
  6. Docker 代理配置(核心难点)
  7. 磁盘扩容
  8. 启动 PentAGI
  9. 首次使用与后续维护

-–

一、环境准备:为什么需要新虚拟机

1.1 原环境的问题

原有 Ubuntu 桌面版虚拟机只有 3.8GB 内存,而 PentAGI 官方最低要求 4GB,推荐 8GB+。桌面版系统本身就要吃掉 500MB~1GB 内存,再加上 Docker 和各种容器,内存严重不足。

1.2 选择 Ubuntu Server 的理由

对比项 Ubuntu 桌面版 Ubuntu Server
图形界面 有(占资源) 无(纯命令行)
空载内存 ~800MB ~200MB
Docker 兼容性 好 更好
适合场景 日常办公 服务器/容器部署

结论:装 Ubuntu Server 24.04 LTS,分配 6~8GB 内存、30GB+ 磁盘。

1.3 安装过程中的代理选项

安装程序会询问 Proxy configuration:

1
Proxy address:

直接留空,按回车选 Done。绝大多数家庭/校园网不需要代理。如果后续真的连不上网,再在系统里配置。

-–

二、安装 Ubuntu Server

2.1 安装后首次登录

Server 版没有图形界面,登录后就是纯命令行。

2.2 查看 IP 地址(ifconfig 不存在)

1
2
3
ip addr
# 或简写
ip a

解释:

  • ip 是现代 Linux 的网络配置工具,取代了老旧的 ifconfig
  • ip addr 显示所有网卡的 IP 地址、MAC 地址、状态
  • 找 ens33(或类似名称)下面的 inet 字段,如 192.168.x.x

如果确实想用 ifconfig,可以安装:

1
sudo apt install net-tools

-–

三、基础网络排查

3.1 现象:网卡状态 DOWN,没有 IP

1
2: ens33: <NO-CARRIER,BROADCAST,MULTICAST,UP> ... state DOWN

3.2 排查 netplan 配置

Ubuntu 18.04+ 使用 netplan 管理网络,配置文件在:

1
cat /etc/netplan/00-installer-config.yaml

发现配置成了静态 IP + Host-Only 网络:

1
2
3
4
5
6
7
8
network:
ethernets:
ens33:
addresses:
- 192.168.56.155/24
nameservers:
addresses:
- 192.168.56.1

问题:192.168.56.x 是 VMware 的 **Host-Only(仅主机)**网段,只能和宿主机通信,不能访问外网。

3.3 修改为 DHCP 自动获取

1
2
3
4
5
6
7
sudo tee /etc/netplan/00-installer-config.yaml << EOF
network:
version: 2
ethernets:
ens33:
dhcp4: true
EOF

解释:

  • sudo tee:用 root 权限写入文件,同时屏幕回显内容
  • << EOF:here-document 语法,表示从下一行开始到 EOF 之间的内容全部写入文件
  • dhcp4: true:启用 IPv4 DHCP,自动从路由器/VMware 获取 IP

⚠️ YAML 缩进非常重要! 必须是空格,不能用 Tab。结构是:

  • network: 顶格
  • version:、ethernets: 缩进 2 空格
  • ens33: 缩进 4 空格
  • dhcp4: 缩进 6 空格

3.4 应用配置

1
sudo netplan apply

3.5 VMware 虚拟网络设置

如果还是拿不到 IP,检查 VMware:

  1. 关闭虚拟机
  2. 虚拟机 → 设置 → 网络适配器
  3. 勾选 “已连接” 和 “启动时连接”
  4. 选择 NAT 模式
  5. 如果列表为空,去 编辑 → 虚拟网络编辑器 → 更改设置 → 还原默认设置

3.6 验证网络

1
ping -c 3 baidu.com

-–

四、安装 Docker 环境

4.1 更新软件源

1
sudo apt update

注意拼写,不要打成 opadte。

4.2 安装 Docker

1
sudo apt install -y docker.io docker-compose

解释:

  • docker.io:Ubuntu 官方仓库里的 Docker 引擎
  • docker-compose:Docker Compose 独立包(Ubuntu 24.04 用这个)
  • -y:自动确认,不需要手动输入 Y

4.3 启动并设置开机自启

1
2
sudo systemctl start docker
sudo systemctl enable docker

4.4 将当前用户加入 docker 组

1
2
sudo usermod -aG docker $USER
newgrp docker

解释:

  • usermod -aG docker $USER:将当前用户追加(-a)到 docker 用户组(-G)
  • 加入 docker 组后,用户可以直接运行 docker 命令,不需要每次加 sudo
  • newgrp docker:立即刷新用户组权限,不用重新登录

4.5 验证安装

1
2
docker --version
docker compose version

-–

五、下载 PentAGI 配置文件

5.1 创建工作目录

1
2
cd \~
mkdir -p pentagi \&\& cd pentagi

建议放在用户目录下(\~/pentagi),不要放在根目录 /pentagi,否则会有权限问题。

5.2 下载官方配置文件

1
2
curl -o docker-compose.yml https://raw.githubusercontent.com/vxcontrol/pentagi/master/docker-compose.yml
curl -o .env https://raw.githubusercontent.com/vxcontrol/pentagi/master/.env.example

解释:

  • curl -o 文件名 URL:将远程文件下载保存为指定文件名
  • -o 参数必须在文件名和 URL 之间,顺序不能错

5.3 GitHub 被墙的问题

由于网络原因,raw.githubusercontent.com 经常连不上,表现为:

  • curl: (7) Failed to connect
  • 下载的文件只有几十行(被截断)
  • wget 重试多次后失败

正常文件大小:

  • docker-compose.yml:约 287 行
  • .env.example:约 525 行

验证文件完整性:

1
2
3
wc -l docker-compose.yml   # 应该显示 287
head -5 docker-compose.yml # 开头应该是 version: '3.8' 或 volumes:
grep -n "services:" docker-compose.yml # services 应该在第 24 行左右

如果下载不完整,需要配置代理(见下一章)。

5.4 docker-compose.yml 的端口映射陷阱

官方 docker-compose.yml 中,PentAGI 的端口绑定使用了环境变量:

1
2
ports:
- "${PENTAGI\_LISTEN\_IP:-127.0.0.1}:${PENTAGI\_LISTEN\_PORT:-8443}:8443"

问题:默认值 127.0.0.1 表示只监听容器内部回环地址,外部浏览器无法访问。

解决:修改 .env 文件:

1
nano .env

找到:

1
PENTAGI\_LISTEN\_IP=

改为:

1
PENTAGI\_LISTEN\_IP=0.0.0.0

同时修改:

1
2
PUBLIC\_URL=https://192.168.247.129:8443
CORS\_ORIGINS=https://192.168.247.129:8443

(将 IP 替换为你虚拟机的实际 IP)

解释:

  • 0.0.0.0:监听所有网络接口,允许外部访问
  • 127.0.0.1:仅监听本地回环,外部无法访问

-–

六、Docker 代理配置(核心难点)

这是整个安装过程中最折腾的部分。Docker Hub(registry-1.docker.io)在国内被墙,拉取镜像需要代理。

6.1 确认宿主机代理状态

假设你使用 Clash 作为代理工具:

  1. 查看 Clash 端口:默认 HTTP 代理端口是 7890
  2. 开启 Allow LAN:在 Clash General 页面勾选 “Allow LAN”(允许局域网连接)
  3. 确认监听地址:在 Windows CMD 执行:
1
netstat -ano | findstr ":7890"

应该看到 0.0.0.0:7890(而不是 127.0.0.1:7890)

  1. 确认 VMnet8 IP:
1
ipconfig

找 VMware Network Adapter VMnet8,记录 IP(如 192.168.247.1)

6.2 配置 Docker 代理(daemon.json 方式)

推荐方式,比 systemd drop-in 更稳定。

1
2
3
4
5
6
7
8
9
sudo tee /etc/docker/daemon.json << EOF
{
"proxies": {
"http-proxy": "http://192.168.247.1:7890",
"https-proxy": "http://192.168.247.1:7890",
"no-proxy": "localhost,127.0.0.1"
}
}
EOF

⚠️ 重要陷阱:https-proxy 的值也必须用 http:// 开头,不是 https://!

解释:

  • Docker 访问 HTTPS 仓库时,会通过 HTTP 代理的 CONNECT 隧道方式连接
  • 代理服务器本身跑的是 HTTP 协议(Clash 的 7890 端口是 HTTP 代理)
  • 如果写成 https://192.168.247.1:7890,Docker 会尝试用 TLS 连接代理服务器,代理不认识,直接断开(EOF 错误)

6.3 重启 Docker 生效

1
2
sudo systemctl restart docker
sudo docker info | grep -i proxy

应该显示:

1
2
3
HTTP Proxy: http://192.168.247.1:7890
HTTPS Proxy: http://192.168.247.1:7890
No Proxy: localhost,127.0.0.1

6.4 验证代理通路

1
curl -x http://192.168.247.1:7890 -I https://registry-1.docker.io/v2/

正常应该返回 HTTP/2 401(未授权,说明连上了)。

6.5 Clash 模式问题

如果 Docker pull 时还是报错 EOF 或 connection refused,检查 Clash:

  • Rule(规则)模式:可能把 registry-1.docker.io 判定为直连,但直连又被墙
  • Global(全局)模式:所有流量都走代理,最稳妥

建议:拉镜像时切到 Global 模式。

6.6 阿里云镜像加速器的坑

一开始配置了阿里云镜像加速器:

1
2
3
{
"registry-mirrors": \["https://xxx.mirror.aliyuncs.com"]
}

但对 vxcontrol/pentagi、vxcontrol/scraper 等私有/小众镜像,阿里云返回 403 Forbidden。

结论:小众镜像加速器没用,必须走代理直接连 Docker Hub。

6.7 完整的 daemon.json(代理 + 可选镜像加速)

1
2
3
4
5
6
7
8
{
"registry-mirrors": \["https://xxx.mirror.aliyuncs.com"],
"proxies": {
"http-proxy": "http://192.168.247.1:7890",
"https-proxy": "http://192.168.247.1:7890",
"no-proxy": "localhost,127.0.0.1"
}
}

Docker 会先尝试镜像加速器,失败后再走代理。

-–

七、磁盘扩容

7.1 现象

pgvector 容器报错:

1
initdb: error: could not create directory "/var/lib/postgresql/data/pg\_wal": No space left on device

7.2 查看磁盘使用

1
df -h

发现根分区只有 9.8GB,已用 100%。

7.3 原因

创建虚拟机时磁盘分配太小(如 20GB),Docker 镜像和容器数据占满空间。

7.4 扩容步骤

步骤 1:在 VMware 中扩展虚拟磁盘

  1. 关闭虚拟机(sudo shutdown now)
  2. VMware → 虚拟机 → 设置 → 硬盘 → 扩展
  3. 将大小从 20GB 调到 50GB
  4. 点击”扩展”,等待完成

步骤 2:在系统中扩展分区

启动虚拟机后依次执行:

1
2
# 1. 查看磁盘结构
sudo lsblk

应该看到 sda 是 50G,但 sda3 还是原来的大小。

1
2
# 2. 扩展物理分区(假设是 /dev/sda3)
sudo growpart /dev/sda 3

解释:

  • growpart:扩展分区工具,属于 cloud-guest-utils 包
  • /dev/sda:磁盘设备
  • 3:第 3 个分区(sda3)
  • 如果提示 command not found,先安装:sudo apt install -y cloud-guest-utils
1
2
# 3. 扩展物理卷(Physical Volume)
sudo pvresize /dev/sda3

解释:

  • Ubuntu Server 安装时默认使用 LVM(逻辑卷管理)
  • pvresize:将物理卷扩展到分区的新大小
1
2
# 4. 扩展逻辑卷,占满所有空闲空间
sudo lvextend -l +100%FREE /dev/mapper/ubuntu--vg-ubuntu--lv

解释:

  • lvextend:扩展逻辑卷
  • -l +100%FREE:将物理卷中所有剩余空闲空间分配给该逻辑卷
  • /dev/mapper/ubuntu--vg-ubuntu--lv:根分区对应的逻辑卷路径
1
2
# 5. 扩展文件系统
sudo resize2fs /dev/mapper/ubuntu--vg-ubuntu--lv

解释:

  • resize2fs:调整 ext4 文件系统大小,使其匹配新的逻辑卷大小
  • 这一步执行后,系统才能真正使用扩展后的空间
1
2
# 6. 验证
df -h

根分区应该变成 40G+。

-–

八、启动 PentAGI

8.1 启动容器

1
2
cd /pentagi
sudo docker compose up -d

-d 表示后台运行(detached 模式)。

8.2 检查容器状态

1
sudo docker ps

期望看到:

1
2
3
4
5
CONTAINER ID   IMAGE                        STATUS          PORTS
xxx vxcontrol/pentagi:latest Up 5 minutes 0.0.0.0:8443->8443/tcp
xxx vxcontrol/scraper:latest Up 5 minutes ...
xxx vxcontrol/pgvector:latest Up 5 minutes (healthy) ...
xxx postgres-exporter Up 5 minutes ...

8.3 查看日志(排错用)

1
2
3
4
5
6
7
8
# 查看 PentAGI 主服务日志
sudo docker logs pentagi

# 查看数据库日志
sudo docker logs pgvector

# 查看所有服务的实时日志
sudo docker compose logs -f

8.4 浏览器访问

在 Windows 浏览器打开:

1
https://192.168.247.129:8443

注意:

  • 必须是 https,不是 http
  • 端口是 8443
  • IP 是虚拟机的 IP(hostname -I 查看)

第一次访问会提示证书不安全,点击”高级” → “继续前往”即可(PentAGI 使用的是自签名证书)。

-–

九、首次使用与后续维护

9.1 首次创建 Flow 的额外镜像

点击 “New flow” 时,PentAGI 会动态创建终端容器,需要拉取 debian:latest 镜像。

如果报错拉取失败,手动拉取:

1
sudo docker pull debian:latest

9.2 强制关机后的恢复

如果直接强制关机或重启虚拟机,Docker 服务可能没自动启动,或者容器没自动运行。

恢复步骤:

1
2
3
4
5
6
7
8
9
10
11
# 1. 确保 Docker 在运行
sudo systemctl start docker

# 2. 进入目录
cd /pentagi

# 3. 启动所有容器
sudo docker compose up -d

# 4. 检查状态
sudo docker ps

9.3 日常维护建议

场景 推荐操作 命令
暂时不用(几小时内) 放着不管 -
隔夜/几天不用 挂起(Suspend) VMware 菜单:虚拟机 → 电源 → 挂起客户机
长期不用 关机 sudo shutdown now

为什么推荐挂起而不是关机?

  • 挂起:保存当前内存状态到硬盘,下次”继续运行”秒开,容器状态保留
  • 关机:完全断电,下次需要重新启动 Docker 和容器

9.4 常用命令速查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 查看容器状态
sudo docker ps

# 查看所有容器(包括停止的)
sudo docker ps -a

# 重启所有容器
cd /pentagi \&\& sudo docker compose restart

# 停止所有容器
cd /pentagi \&\& sudo docker compose down

# 查看容器日志
sudo docker logs -f 容器名

# 进入容器内部
sudo docker exec -it 容器名 /bin/bash

# 清理无用镜像和缓存
sudo docker system prune -a -f

-–

附录:完整问题清单

问题 原因 解决
ifconfig 不存在 新版 Linux 已弃用 使用 ip a
网卡状态 DOWN VMware 网络设置问题 检查 NAT + 还原虚拟网络编辑器
YAML 缩进错误 用了 Tab 或空格不对 严格用空格缩进
apt update 报错 拼写错误 是 update 不是 opadte
权限 denied 目录属于 root 用 \~/pentagi 而不是 /pentagi
GitHub 下载失败 被墙/限速 配置 Clash 代理或换镜像源
Docker pull 403 阿里云镜像不支持该镜像 走代理直连 Docker Hub
Docker pull EOF Clash 规则模式拦截 切到 Global 全局模式
代理配置不生效 Docker 29.x bug / 配置位置不对 改用 daemon.json proxies 块
https-proxy 写错 写成了 https:// 必须是 http://
磁盘满 分配太小 VMware 扩容 + LVM 扩展
浏览器拒绝连接 端口绑定 127.0.0.1 .env 里改 PENTAGI\_LISTEN\_IP=0.0.0.0
强制关机后打不开 容器没自动启动 cd /pentagi \&\& docker compose up -d

-–

**最后**:PentAGI 是一个功能强大的 AI 渗透测试平台,安装过程虽然曲折,但跑起来后非常香。祝挖洞愉快!🎯