自托管 Macro:本地把整套栈跑起来的前置条件、命令,以及每一步卡住时怎么判断

2026-08-17

Macro 这个仓库以 AGPLv3 开源,后端是 Rust,前端是 SolidJS,官方在 README 里明确写了「你可以在 AGPLv3 条款下自托管 Macro」。于是很多人 clone 下来的第一反应是:先在本机跑起来看看。

问题是它跑起来的方式跟大多数 TypeScript 开源项目不一样。仓库结构里写着 42 个可部署的服务、167 个 Rust crate,本地栈还要在 Docker 里同时拉起 Postgres、Redis、LocalStack、OpenSearch、Kafka 和 FusionAuth 六个基础设施。这不是 npm install && npm run dev 能糊弄过去的规模,中间任何一环没起来,你看到的现象往往不是明确报错,而是「页面能打开但登录不了」「接口返回了一坨 HTML」。

官方 docs/RUNNING_LOCALLY.md 其实把这条链路写得相当完整,包括踩坑之后该怎么判断。下面按执行顺序拆一遍,每一步都标清楚「正常应该是什么样」和「不对劲时先看哪里」。

前置只有两样,别自己额外装

文档要求你在开始前只装两个东西:

前置说明常见误区
Nix 包管理器提供整个开发壳不要绕过它手装工具链
Docker(带 Compose v2 插件)跑六个基础设施容器Docker Desktop、OrbStack、Colima 都可以

这里最容易走弯路的是第二条以外的部分——很多人习惯性地先去装 Rust、装 just、装 Bun。文档写得很直白:Nix 壳里已经提供了 just、Cargo、Rust 工具链、Bun、sqlx、zig 和 cargo-zigbuild,你不需要单独安装 just 或 Cargo。手动装一套反而可能和壳里的版本打架。

拉代码:

git clone https://github.com/macro-inc/macro.git
cd macro

进 Nix 壳:nix develop 报错基本只有一个原因

nix develop

如果这一步失败,文档给的判断非常明确:Nix 需要开启实验性特性。先用这条命令单次开启验证一下:

nix develop --extra-experimental-features nix-command --extra-experimental-features flakes

如果加了这两个开关就能进壳,说明问题确实在这里,把它写进 ~/.config/nix/nix.conf 永久生效:

experimental-features = nix-command flakes

还有一个容易误判的点:默认壳不包含 Tauri 平台依赖。这些依赖体积大,官方把它们拆到了单独的壳里。做 Linux 桌面端开发用 nix develop .#tauri-linux,在 x86_64 Linux 上做 Android 开发用 nix develop .#tauri-android。所以如果你在默认壳里构建桌面端缺依赖,不是环境坏了,是走错壳了。

拉起整套栈:绝大多数人该用 --no-doppler

just run_local --no-doppler

这条命令是给「没有 Doppler 访问权限」的人用的,文档自己说了:大多数贡献者不在团队里,所以这是常见路径。它用代码里定义的本地配置,配一套假的 AWS 凭据和固定的测试密钥。

这条命令实际做了四件事:构建 Rust 后端服务、启动本地基础设施(Postgres、Redis、LocalStack、OpenSearch、Kafka、FusionAuth)、启动后端服务、启动本地代理和前端。启动完成后它会把前端 URL 和几个重要服务 URL 打印出来。

边界要认清:这套栈会给每个服务需要的配置项填上桩值,包括第三方集成(Google、GitHub、Stripe、CloudFront)。这些集成的流程在桩值下是走不通的。但文档同时明确写了,栈的其余部分是完全可用的——认证、文档、邮件、搜索都能跑。所以如果你的目的是看产品形态、读代码链路,--no-doppler 足够了;如果你要调 Gmail 同步或者 Stripe 结账,那是另一回事。

有 Doppler 访问权限的人才用不带参数的 just run_local,它会拉 lcl_personal 配置再叠加本地默认值,集成值都是真的。

没有预置账号,验证码在 Mailpit 里

这一步是新手最容易卡住的地方,而且卡住的时候很难自己想明白。文档写得很清楚:这套栈不会预先创建账号。它用的是免密登录,用户是按需创建的——你拿任意一个邮箱地址去注册就行,FusionAuth 会给你发一次性验证码。

那封邮件不会进你真实的收件箱,而是落在 Mailpit,地址是 http://localhost:8025。等真邮箱是等不到的。

空栈没东西可点,先 seed 一份数据

裸栈起来之后界面是空的,没有内容可点。官方提供了 seed CLI,会造出一套带真实权限关系的世界:用户、团队、频道、项目、文档、任务、聊天、通话、邮件和消息。

栈起来之后在仓库根目录执行:

just seed-scenario apply --file seed/scenarios/team-perms.json

apply 会为每个 persona 建一个 FusionAuth 账号,并为每个 persona 打印一条登录链接,形如 http://alice.localhost:3000/app/login?email=alice@seed.macro.local。用普通浏览器标签页逐个打开即可——每个 persona 的主机名有自己独立的 cookie 罐,所以你可以在同一套栈上同时驱动好几个身份,这对验证权限行为很有用。

配套的几条命令:

命令作用
just seed-scenario status --file ...看已经 seed 了什么,并重新打印登录链接
just seed-scenario reset --file ...按邮箱删掉该场景的数据行与用户账号
just seed-scenario matrix --file ...拿「用户 × 实体」的预期访问级别去比对实时数据库

安全性上文档给了个说法:apply 只碰带有场景 5eed id 标记的行,加上它自己创建的 persona 账号,所以在你已经手动测过的栈上跑它是安全的。

起不来先跑体检,而不是猜

just doctor-local

这是首次启动前就该跑的预检。它会测 Docker 守护进程、工具链和需要的端口,报告问题并给出修复建议。文档的建议很实用:启动失败就再跑一次这个检查,别急着改配置。

端口被占的典型症状:接口返回 HTML 而不是 JSON

这一节值得单拎出来,因为它的症状极具误导性。默认实例绑定一组固定的宿主机端口,而 macOS 会给自己的服务保留其中一些。文档点名了两个最常见的冲突:

  • 8080 端口——macOS 的 WebDriver 服务(com.apple.WebDriver.HTTPService),开启远程自动化时它会监听这个端口,导致认证服务绑不上。
  • 8090 端口——别的项目的开发服务器,比如带 --port 8090 的 Expo 服务,导致代理绑不上。

表现出来就是:前端能加载,但登录和 API 调用打到了另一个进程上,你看到的是 HTML 或者控制台报错,而不是 JSON。文档说 just doctor-local 会在启动前就报出这些忙碌端口。

解决办法不是去杀掉别人的进程,而是把整套栈挪到一个空闲的端口窗口:

just doctor-local                                      # 先看默认端口哪些被占
just doctor-local --instance test --port-base 31000    # 确认新窗口是空的
just run_local --no-doppler --instance test --port-base 31000

命名实例会把每个服务绑到 port-base + 偏移量,选一个像 31000 这样空闲的基址,整套栈就搬进一段连续窗口。

这里有个必须记住的一致性要求:后续所有命令都要带上同样的两个参数。

just run_local --no-doppler --instance test --port-base 31000
just seed-scenario --instance test --port-base 31000 apply --file seed/scenarios/team-perms.json
just status_local --instance test --port-base 31000

原因是:如果你省掉 --port-base,命名实例会拿到一个由名字推导出来的确定性端口窗口,那跟你手动选的窗口不是一个。用显式 --port-base 启的栈,如果 seed 时不带同样的参数,seed CLI 会去看错误的数据库。另外 seed 出来的 persona 登录链接里嵌了前端端口,换了端口就得重新跑一次 apply 才能拿到匹配新窗口的链接。默认实例(不带 --instance)用固定端口,不需要额外参数。

顺带一提,多实例本身就是个正经用法。跨 worktree 同时跑几套栈时:

just run_local --instance agent-a
just run_local --instance agent-b

每个实例有自己的 Compose 项目、卷与网络、env 文件,以及各自的代理端口、前端端口和后端端口。生成的文件在 infra/local/generated/<instance> 下。同一个实例名每次都拿到同一段端口窗口。

改了代码没生效?先分清是哪一类服务

栈在前台跑着的时候有两个热键:按 r 重新构建改动过的 Rust 服务并重载,按 q 停止并退出。文档专门叮嘱了一句:q,不要直接关终端窗口,因为 q 会一次性停止并删除容器,下次启动就不用先清理残留的栈。

而「改了代码没生效」这个现象,需要先分清服务类型。Rust 服务是在宿主机上用 cargo zigbuild 构建的,二进制被挂载进一个共享运行时镜像,正常的 run_local 过程中 Docker 并不编译这些服务;按 r 重建二进制,只有二进制变了的服务会重启。

但有三个服务是 Docker 构建镜像的,默认不重建:

  • sync_service
  • lexical_service
  • websocket_service

改这三个的话,运行中的栈可能一直在用旧镜像。要强制重建:

just run_local --build-aux-services

带这个标志启动之后,按 r 才会重建这些镜像并重新创建容器。这条路更慢,所以不做这几个服务时就别开。如果你已经在没带标志的情况下启动了,又怀疑镜像是旧的,就按 q 退出,带标志重新起。

不想占着终端:just stack

just stack 跑的是同一套栈,但不占着终端:没有热键循环,也没有开发服务器,前端只构建一次,由代理静态托管,整个产品在同一个 origin 下。up 完成后只剩 Docker 容器在跑。

just stack up                  # 全部起来、打印 URL、返回
just stack status --json       # 机器可读状态(容器、健康、URL)
just stack update              # 只重建并重载改动过的服务(相当于 r 热键)
just stack update --frontend   # 顺带重建前端产物
just stack down                # 删除容器、卷和状态

run_local 的参数对 stack 同样有效,包括 --instance--no-doppler--no-build--binaries-dir。应用的访问路径是 <proxy>/app/,产物会从被托管的 origin 推断后端地址,所以同一套栈放在 localhost 还是预览主机名下都不用重新构建。

stack up 还会缓存代价最高的基础设施初始化——首次冷启动要迁移数据库、等 FusionAuth kickstart、创建搜索索引,这些卷会被存成一份初始化快照,按内容寻址放在 infra/local/generated/.snapshots 下,后续启动直接恢复快照跳过初始化。输入变了就缓存未命中,走一次完整初始化。just stack snapshot 看当前快照键,just stack up --no-snapshot 跳过缓存。

要打开真实集成时才需要 env 文件

--no-doppler 的栈用的是确定性桩值,够把服务启起来,但背后的第三方集成不通。文档列了各自的表现:

集成桩值下的行为
Google 登录 / GmailGoogle SSO 与 Gmail 收件箱关联不可用;本地注册照常,邮件服务报告没有 Gmail 授权并跳过收件箱同步
GitHub 登录用 GitHub 登录不可用
Stripe 计费结账与订阅接口失败;注册仍可用,创建用户的 webhook 检测到桩密钥后跳过真实 Stripe 调用,存一个占位客户 id
CloudFront 签名 URL文档下载 URL 不签名(对本地 S3 无影响)

要打开某一个,把真实值写进 local.env 再传进去:

just run_local --no-doppler --env-file ./local.env

文件里的键会覆盖代码里定义的默认值,所以只写你关心的那几个集成就够了。另外文档特别说明:REDIS_HOSTMACRO_DB_URLINTERNAL_API_KEYAUTHENTICATION_SERVICE_SECRET_KEYOPENSEARCH_USERNAMEOPENSEARCH_PASSWORD 这些是内部管道,本地值本来就是对的,永远不需要你去覆盖。看到它们是桩值不要慌着改。

收摊与排查用的几条命令

命令作用
just status_local看实例现状:带存活探测的端点、每个容器的状态与宿主机端口;不启动也不重建任何东西
just stop_local --instance X停掉实例但保留卷
just destroy_local --instance X删掉实例的容器、卷和命名实例网络
just reset_local --instance X删库、重建、重新迁移
just run_dev拿本地二进制打共享 dev 资源,需要 Doppler 和真实云访问,是给有团队权限的贡献者用的

默认实例省掉 --instance 即可。排查顺序上,just status_local 比翻容器日志更快看出「哪个端点没起来」,因为它带存活探测。

什么时候这条路不适用

第一,硬件与耐心成本。这套栈要在 Docker 里同时跑六个基础设施,加上宿主机上编译 Rust 后端。官方文档没有给出任何构建耗时、内存占用或磁盘占用的数字,我们也没有实际跑过,所以这里不给估算——但从组件规模上你应该有心理准备,这跟起一个前端 dev server 不是一个量级。

第二,--no-doppler 栈拿不到真实集成。如果你的目标就是评估 Gmail 同步质量,本地栈默认给不了,得自己准备 Google 的凭据走 --env-file

第三,本地跑通不等于可以对外提供服务。自托管涉及 AGPLv3 的义务边界,README 里同时提到了另一条路——如果想在不同许可证下基于 Macro 构建,或者需要托管与商业安排,官方给的是发邮件联系,具体条款请以 LICENSE.txt 和官方 FAQ 为准,这部分我们单独在 AGPL 协议与自托管的边界 里按条文讲。

第四,文档没覆盖的坑还有一些。端口冲突这一节官方只点名了 macOS 上的 8080 和 8090 两个,其他环境下的冲突只能靠 just doctor-local 自己发现;OAuth 回调、本地 MCP 的可用边界这类问题,社区 issue 里有人提过但官方文档未给出统一答复,我们把这些整理在 自托管的已知问题 里。

如果你只是想先搞清楚 Macro 到底把哪几块工作串成了一个系统,其实不必先跑本地栈,Macro 是什么 那篇按官方文档口径讲了数据模型;而这套「Rust 后端 + SolidJS 前端」的选型理由,官方 README 也有自己的说法,见 技术选型的官方口径

真要动手,建议的最短路径就是四条命令:nix develop 进壳,just doctor-local 体检,just run_local --no-doppler 起栈,just seed-scenario apply 造数据,然后去 http://localhost:8025 收验证码登录。中间任何一步卡住,先回到 doctor-localstatus_local,别急着去改配置文件。

延伸阅读


本文依据 Macro 官方仓库(github.com/macro-inc/macro,AGPL-3.0 协议)的 apps/docs/ 产品文档、 MCP 工具参考与自托管说明整理,核对日 2026-08-17。 我们没有注册或运行过 Macro,因此不涉及界面外观与操作手感; 官方标注为计划中的能力文中已如实标明,不代表当前可用。 价格与额度以官网 macro.com 最新页面为准;许可证相关问题请咨询专业人士并以官方许可证原文为准。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。