跳到正文

在服务器(VPS)上部署 Chimera

Chimera 作为一个长期运行的网关(gateway)进程运行。加上 --cron,它还会按照真实时钟触发 计划任务,因此它是到点自己行动,而不仅仅是被消息触发才响应。本指南介绍两种在一台 5 美元 VPS 上的部署方式:Docker Compose(推荐)或 systemd。

状态——长期记忆、定时任务(cron)、轨迹(trajectories)、审计日志——都保存在 CHIMERA_HOME (一个目录)里。只要把它持久化下来(一个 Docker 卷,或一个真实路径),agent 重启后状态也能保留。


0. 前置条件

  • 一台 Linux VPS(单个 agent 的话,1 vCPU / 1 GB 内存就足够)。
  • 至少一个 provider 密钥。最便宜的起步方式是使用 OpenRouter 密钥。
  • 如果要接收公网入站 webhook(WhatsApp Cloud API,POST /webhook/<hook>),需要一个域名 + 带 TLS 的反向代理(Caddy 或 nginx)。Discord/Telegram/Slack/Signal 是出站连接,不需要这些。

从模板创建你的 env 文件并填入一个密钥:

cp .env.example .env
# edit .env — e.g. set CHIMERA_OPENROUTER_KEYS=sk-or-...

1. Docker Compose(推荐)

docker compose up -d       # build + run; restarts on crash and on reboot
docker compose logs -f     # watch it
docker compose ps          # status + health

这会运行 chimera serve --host 0.0.0.0 --cron:即 HTTP 网关(/chat、/webhook/<hook>、 /health)加上 cron 守护进程。状态会持久化保存在 chimera-data 卷中。

接入一个聊天平台(以 Discord 为例)——在 .env 中设置好 token,然后在 docker-compose.yml 中覆盖启动命令:

    command: ["serve", "--host", "0.0.0.0", "--cron", "--discord"]

再执行一次 docker compose up -d。(Telegram/Slack/Signal 的接法一样,通过各自的标志启用;每 个平台都需要对应的 CHIMERA_* token——参见 .env.example。)

升级到新版本:

git pull && docker compose up -d --build

2. systemd(不使用 Docker)

在宿主机上安装到一个虚拟环境(virtualenv)中:

git clone https://github.com/brcampidelli/chimera-agent.git /opt/chimera
cd /opt/chimera
python3 -m venv .venv && . .venv/bin/activate
pip install '.[messaging,mcp]'
cp .env.example .env   # then edit it

创建 /etc/systemd/system/chimera.service:

[Unit]
Description=Chimera Agent gateway + cron daemon
After=network-online.target

[Service]
Type=simple
WorkingDirectory=/opt/chimera
EnvironmentFile=/opt/chimera/.env
Environment=CHIMERA_HOME=/opt/chimera/state
ExecStart=/opt/chimera/.venv/bin/chimera serve --host 0.0.0.0 --cron
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now chimera
sudo systemctl status chimera
journalctl -u chimera -f

3. 安排主动任务(--cron 守护进程)

--cron 只会运行你已经安排好的任务。用 CLI 添加它们(会持久化保存在 CHIMERA_HOME 中):

chimera cron add "morning-brief" "0 8 * * *" "Summarize overnight news and post it."
chimera cron add "nightly-backup" "0 3 * * *" "Back up the important files."
chimera cron list

在 Docker 内部:

docker compose exec chimera chimera cron add "morning-brief" "0 8 * * *" "..."

守护进程每 --cron-tick 秒(默认 30 秒)检查一次,到点就把对应任务的动作通过 agent 派发执行。 某个任务失败只会被记录下来,不会让守护进程停止运行。

向计划表问出它没有告诉你的事

chimera cron doctor

计划表安静下来有两种方式,而在你开口问之前,两者看起来一模一样——都像是一份眼下没有任何任务到点 的计划表:

  • 什么都没跑过。 守护进程死了,容器一直没被重启,宿主机睡着了。没有异常,没有日志行,没有 判定。这里其他所有的诚实机制都位于一次执行确实发生过的下游,所以谁都轮不上。
  • 全都跑了,也全都输了。 守护进程活着,last_run 就在一分钟前,计划表也在往前推进——可每一 次派发都已经失败了整整一个月。这种情况读起来反而比第一种更健康,因为那个看上去像健康指标的 字段,记录的是尝试,而不是结果。

cron doctor 会把这两个问题都问一遍,并给出不同的建议,因为这两者的修法毫无共同之处:逾期是守 护进程的事,失败是任务本身的事。只要有任何东西在失败,chimera cron list 就会打印出一行,所以 你不必事先知道有这条命令才能发现问题。

它不是什么。 它是一个问题,不是一个监视者:进程躺下的这段时间里,这里没有任何东西会察觉,原 因和一个已经崩溃的进程无法记录自己的崩溃是一样的。只要有任何东西来问——一个 shell、桌面应用、下 一次启动——它就会诚实地回答。真正的看门狗需要自己的时钟和自己的存活性,那是另一个独立的决定, 已作为 issue #26 跟踪。如果你要的是告 警而不是回答,就从宿主机自己的 cron 里运行它:

*/30 * * * * cd /opt/chimera && out=$(.venv/bin/chimera cron doctor --check) || echo "$out" | mail -s "chimera" you@example.com

这之所以有效,正是因为看着它的是 Chimera 之外的东西——这就是全部要点。

--check 只在有任务延迟或失败时以 1 退出——现在守护进程的心跳过期时也会以 1 退出,所以上面的 host-cron 行无论任务的计划如何,都会在下一次运行时抓到死掉的守护进程;邮件只在那时才到。不带该选项的 cron doctor 总是以 0 退出,因为回答不是失败。

守护进程的心跳补上了任务补不上的窗口。 每个 tick,守护进程都会把生命迹象写入 <CHIMERA_HOME>/scheduler/heartbeat.json(原子写,与 jobs.json 并排)。doctor 先读它,回答一个单靠任务回答不了的问题:一个死掉的守护进程带着每日任务,从任务的角度看 ~23 小时内依然“健康”——任务还没到期——而心跳在守护进程死亡的那一刻就停了。判定以心跳自己记录的间隔的三个 tick 为上限,并连同上限一起打印出来,让你可以对这个数字提出异议,而不是猜测它从哪来。三种诚实的读数:alive/stale 已判定;unknown 表示有心跳但没记录tick 间隔(更老的写入方),无法在不编造数字的情况下判断新鲜度——读者拒绝编造;none 表示完全没有心跳,这是“没有信号”,不是“死了”——从未运行过的守护进程根本没留下任何证据。这是 liveness,不是 taint:调度器表面仍然不追踪 taint(见 SECURITY.md)。

4. 健康检查、备份、安全

  • 健康检查: GET /health 会返回 {"ok": true}。Compose 已经接好了健康检查。
  • 备份: 备份 chimera-data 卷(Docker 方式)或 CHIMERA_HOME 目录(systemd 方式)——这就 是全部的持久化状态。示例:docker run --rm -v chimera-data:/d -v $PWD:/b busybox tar czf /b/chimera-state.tgz -C /d .
  • 密钥: 把密钥留在 .env 中(已被 git 忽略);绝不要把它们打进镜像里。
  • 暴露面: 只在防火墙/反向代理之后才把网关绑定到 0.0.0.0。设置 CHIMERA_SERVER_TOKEN,要求 HTTP 网关和桌面端 API 都必须带上 Authorization: Bearer <token>(桌面 UI 只会在回环/loopback 客户端上自动拿到这个 token,因此 一个暴露在公网上的实例仍然会受到你自己的鉴权保护)。鉴权是可选启用的,默认为空,因此不设置 这个变量就等于没有鉴权——请务必限制端口访问,或者只暴露 webhook 路径。要从桌面应用连到这个实 例,参见 §5。
  • 沙箱化: 设置 CHIMERA_SANDBOX=docker,让 shell/代码工具在一个用后即弃的容器中运行,而 不是直接在宿主机上运行。
  • 无人值守的宿主机执行: 自 2026-07-20 起,在默认的 CHIMERA_HOST_EXEC=ask 设置下,无头 (headless)运行会拒绝执行宿主机命令(因为没有 TTY 可供确认)。如果某个部署确实需要 agent 在宿主机上运行 shell,需要有意地设置 CHIMERA_HOST_EXEC=allow;更安全的做法是使用 CHIMERA_SANDBOX=docker,此时这道关卡会被跳过,因为容器本身就已经真正做到了隔离。同样地, API 服务器还会启用污点收紧(CHIMERA_TAINT_NARROW=1):agent 一旦读取了不可信内容,后续的 执行/写入/外发类工具就会失败并采取保守(fail-closed)策略。设为 0 则可以让它继续自主行动。

5. 从桌面应用连到这个实例

桌面应用默认只和它自己在你机器上启动的那个 Chimera 对话。从 v0.44 起,它也可以指向一个由你自己 运行的实例——就是这台 VPS——于是这个应用就成了一扇窗,让你看见那个整夜都在替你跑定时任务的 agent。

在开放端口之前,请先读这一段。 你暴露出去的并不是一块仪表盘。这个应用的每一个界面都是操作 面:它运行 shell、编辑文件、派发一整块自主任务看板,还会修改设置。一个没有 token、却能从公网访 问到的实例,不是「一个别人只能看看的 Chimera」——它是一台机器,任何找到地址的人都能在上面执行命 令,而账单记在你的 provider 密钥上。

有三件事必须成立,而前两件不成立时,应用会直接拒绝连接:

1 —— TLS。 把它放到一个带有真实证书的反向代理后面(Caddy 会替你申请一张):

chimera.seudominio.com {
    reverse_proxy 127.0.0.1:8765
}

只要地址不在你自己的机器上,应用就会拒绝非 https 的地址,因为 token 会随每一个请求放在 Authorization 头里一路走过去——走明文 http 的话,那就是一份交给你与服务器之间每一跳的凭据,而 这一切发生时,屏幕上看不出任何不对劲。

2 —— 一个 token。 鉴权是可选启用的,默认为空:

CHIMERA_SERVER_TOKEN=$(openssl rand -hex 32)

把它写进 .env,重启,再把同一个值粘贴到应用里。应用之所以拒绝没有 token 的远程地址,就是上面 那个理由:没有 token 的实例对任何找到它的人都是敞开的。

请注意服务器刻意不做的一件事:当远程客户端请求界面时,它送出的页面里不带 token。token 从 不会经由网络发放——你在带外把它复制到自己的客户端里,只此一次。这就是应用里有那个输入框的原因。

3 —— 你这个应用的来源。 应用是由它自己的本地 sidecar 提供的,所以它发往这个实例的请求属于跨 来源请求;只要这个实例没有点名那个来源,浏览器就会把响应丢掉:

CHIMERA_ALLOWED_ORIGINS=http://127.0.0.1:45813

连接失败时,应用会把确切的值显示给你——它就在那条错误信息里,可以直接复制。端口对每一次安装都是 固定的(自 v0.43 起会在多次启动之间记住),所以每台你用来连接的机器只需设置一次。多个来源之间用 逗号分隔。

这项设置不是安全边界,也绝不能被当成安全边界来读。 CORS 决定的是哪个页面可以读取响应;它 对谁可以发起调用不作任何决定。关卡是 token。只点名来源而不设置 token,什么都保护不了——那只会 让一个没有防护的实例除了 curl 之外,也能从浏览器访问到。

默认为空,所以一个没人配置过的实例,行为和以前完全一样。

失败时应用会告诉你什么

  • 「token 被拒绝了」——地址和来源都对,是值错了。
  • 「连不上」——要么地址错了,要么来源没被允许。浏览器故意不肯说是哪一种,所以应用不去猜,而 是把两者都点出来,并把需要放行的来源直接交给你。
  • 版本警告——应用会拿它自己后端的版本和这一端比较,并把两个数字都说出来。它不会拒绝:落后一 个版本的服务器通常照样能用,而拒绝只会把你挡在恰好用来修这件事的界面之外。较旧的一端可能没有 某些端点。

还可以更安全

干脆完全跳过公网端口:通过 WireGuard 或者 Tailscale 的 tailnet 连到这台 VPS,再把应用指向内网地 址。token 依然重要——tailnet 只是一间更小的屋子,不是一间空屋子。


6. 代理、自定义 CA,以及查看正在运行的实例内部

代理与自定义 CA

出站流量——提供商调用和各项集成——会从 Chimera 进程的环境中读取标准的代理和证书变量。目前可以核实的情况(httpx 0.28、LiteLLM 1.99):httpx 遵循 HTTP_PROXY、HTTPS_PROXY 和 NO_PROXY,并从 SSL_CERT_FILE / SSL_CERT_DIR 获取 CA 证书包;LiteLLM 读取 SSL_CERT_FILE。两者都不读取 REQUESTS_CA_BUNDLE,它只影响基于 requests 库构建的代码——只有当你的某个集成使用 requests 时才需要额外设置它。LiteLLM 内部的各个提供商适配器并不一致,所以请用下面的探测来证明你所依赖的那条路径,而不是想当然。以上这些都不会改变 Chimera 的入站监听器,也不会改变其 API 的授权。

对于 shell 或 systemd 环境:

export HTTP_PROXY=http://proxy.example:8080
export HTTPS_PROXY=http://proxy.example:8080
export NO_PROXY=localhost,127.0.0.1,.internal.example
export SSL_CERT_FILE=/etc/ssl/certs/company-ca.pem
# Optional, only for requests-based code:
# export REQUESTS_CA_BUNDLE=/etc/ssl/certs/company-ca.pem
chimera doctor --probe

在 docker-compose.yml 中,写在服务的 environment: 下(CA 文件必须存在于容器内):

    environment:
      HTTP_PROXY: http://proxy.example:8080
      HTTPS_PROXY: http://proxy.example:8080
      NO_PROXY: localhost,127.0.0.1,.internal.example
      SSL_CERT_FILE: /etc/ssl/certs/company-ca.pem
      # REQUESTS_CA_BUNDLE: /etc/ssl/certs/company-ca.pem   # optional, requests-based code only
    volumes:
      - ./company-ca.pem:/etc/ssl/certs/company-ca.pem:ro

chimera doctor --probe 会通过所配置的路由和证书链发起一次真实的提供商调用。探测通过,验证的是那个提供商和那一次调用;它并不为所有集成背书。尽可能不要把凭据放进代理 URL:任何能查看该进程的人都能看到环境变量的值。

chimera api 与 chimera logs

两个面向正在运行实例的终端命令,二者都不会启动服务器:

chimera api GET /api/health                 # the app's API: status and body, token never printed
chimera api POST /api/... -d '{}' --yes     # changing state needs --yes
docker exec chimera chimera api GET /health # the HTTP gateway of plain `chimera serve`
docker exec chimera chimera logs --level ERROR --lines 50
docker exec chimera chimera logs --follow --run <run-id>

chimera api 会发送环境中的 CHIMERA_SERVER_TOKEN。它通过应用在 CHIMERA_HOME 中公布的 URL 找到 chimera app;否则使用 http://127.0.0.1:8765,即 chimera serve 的默认端口,其网关响应 GET /health 和 POST /chat(那里没有 /api)。serve --discord/--telegram/--slack/--signal 根本不开放任何 HTTP 端口,所以在那种部署下 chimera api 没有可连接的对象;chimera logs 依然可用。--url 可覆盖地址。回环调用会忽略上面的代理变量,因此 token 永远不会到达代理,而且该命令拒绝把 token 发送到远程的 http:// URL。每个请求仍由服务器自身的授权和审批来决定。

chimera logs 读取 CHIMERA_HOME/logs/backend.log:由 chimera serve 和 chimera app 写入的警告和错误,达到 1 MB 时轮转,且密钥已被遮蔽。在 --level 和 --run 下,traceback 会和它所属的记录保持在一起,--follow 会跨越轮转继续读取。


7. 诚实的现状说明

Chimera 目前处于 alpha 阶段。它可以完成部署并正常运行,cron 守护进程也让它具备了主动行为能力。它的生产运行里程只有一个部署:作者自己的 VPS,自 2026 年 7 月起全天候运行定时任务和一个 Discord 机器人。这是真实使用,而不是一个集群,也没有经历过你的工作负载。请先从低风险的定时任务开始,密切关注 logs,并在处理任何涉及真实系统的任务时,牢记这些治理护栏(solve 上的 --guard、CHIMERA_SANDBOX=docker)。

这些页面发布在哪里

这些文件就是 chimeraagent.space 网站文档的源文件,该网站在构建时会直接从这个目录渲染出 页面。在这里编辑 markdown,网站内容就会随之更新;不存在需要另外同步的第二份副本。

曾经放在 mkdocs.yml 里的 MkDocs 配置已被移除。它本身是完整的——主题、导航、十个页面齐全—— 但从未真正发布过:既没有对应的工作流(workflow),也没有 gh-pages 分支,所以原本写在这个 位置的部署说明,描述的其实是一个并不存在的网站。没有人在运行的配置,比压根没有配置还糟糕, 因为下一个接手的人会去改它的导航结构,却怎么也想不明白为什么什么都没有变化。

在 GitHub 上编辑本页