Actual sync-server 自托管部署与 CLI 实战:从 npm 安装到密码重置的完整指南
【免费下载链接】actualA local-first personal finance app项目地址: https://gitcode.com/GitHub_Trending/ac/actual
导读
本文围绕 Actual 开源仓库中的 packages/sync-server/README.md 展开,系统讲解 Actual 官方同步服务器(@actual-app/sync-server)的定位、CLI 工具的安装与使用、配置体系、Docker 部署方式及底层实现。读完本文,你将掌握如何在自己的服务器上运行 Actual 同步服务、通过--config定制运行参数、使用--reset-password重置密码,并能从源码层面理解端口、数据目录、HTTPS、登录方式等关键配置的真实生效机制。
Actual 同步服务器:local-first 理念的落地载体
Actual 是一款本地优先(local-first)的个人财务管理应用,完全免费开源,使用 Node.js 编写。所谓"本地优先",意味着你的账本数据默认保存在本地,应用无需依赖云端即可完成记账、预算等核心操作;而sync-server包(对应 packages/sync-server/package.json 中名为@actual-app/sync-server的发布包)则在此基础上增加了一个同步服务器组件,用于持久化变更并把数据同步到所有设备,让手机、平板、电脑之间的数据保持一致,"不需要任何繁重操作即可在设备间移动变更"。
从仓库结构看,sync-server 不是独立于 Actual 的旁支模块,而是整个 monorepo 的组成部分:它的依赖列表中包含了@actual-app/crdt(同步所需的 CRDT 实现,见 packages/crdt/src/crdt)与@actual-app/web(Actual 的前端构建产物,用于服务器直接托管 Web 客户端)。因此,运行 sync-server 就等于同时获得了最新版的 Actual Web 应用与跨设备同步能力。
环境要求与 CLI 安装
官方 README 明确要求:使用@actual-app/sync-servernpm 包需要 Node.js v22 或更高版本。这一约束在 packages/sync-server/package.json 的engines字段中同样有声明("node": ">=22"),属于硬性前提。
安装方式为全局安装:
npm install --location=global @actual-app/sync-server安装完成后,npm 会根据 packages/sync-server/package.json 中bin字段的映射("actual-server": "./build/bin/actual-server.js")在 PATH 中注册actual-server可执行命令,之后便可以在终端中直接调用。
如果想从源码自行构建运行,也可以使用仓库提供的 npm 脚本(见 packages/sync-server/package.json 的scripts):
yarn build && node build/app.js提示:从源码启动时,入口 packages/sync-server/app.ts 会先执行数据库迁移再启动应用——它先调用
runMigrations(),成功后才动态import('./src/app.js')并运行,避免因迁移未完成导致应用在缺表状态下启动。
actual-server 命令行:四个核心选项
actual-server的命令行用法非常简洁:
actual-server [options]官方 README 提供的选项如下表:
| 命令 | 说明 |
|---|---|
-h或--help | 打印帮助列表并退出 |
-v或--version | 打印版本号并退出 |
--config | 指定配置文件路径 |
--reset-password | 重置你的密码 |
场景一:默认配置直接运行
actual-server不传任何参数时,服务器以默认配置启动:端口5006、监听地址::(IPv6 通配,通常同时覆盖 IPv4)、数据目录优先使用/data(若存在),否则使用项目根目录。这些默认值来自 packages/sync-server/src/load-config.js 的 convict schema(port默认 5006、hostname默认::、dataDir默认取ACTUAL_DATA_DIR或/data)。
场景二:自定义配置文件
actual-server --config ./config.json通过--config传入配置文件路径。从 packages/sync-server/src/load-config.js 的加载逻辑可以看到完整的配置来源优先级:
- 若设置了环境变量
ACTUAL_CONFIG_PATH,直接使用其指向的路径; - 否则尝试
<项目根目录>/config.json; - 若该文件不存在,回退到
<数据目录>/config.json; - 配置加载后,
<VAR>_FILE形式的环境变量(见下文"挂载式密钥")会覆盖 config.json 与普通环境变量; - 最后通过
configSchema.validate({ allowed: 'strict' })做严格校验,未知字段会直接报错。
因此,--config本质上是在默认路径之外显式指定 config.json 的位置,便于把配置放在与数据分离的目录,或在一个机器上跑多份实例。
场景三:重置密码
actual-server --reset-password密码重置的底层逻辑在 packages/sync-server/src/scripts/reset-password.js 中:
- 若服务器尚未设置密码(
needsBootstrap()为真),交互式提示你输入新密码并执行bootstrap({ password })完成初始化,输出Password set!; - 若已存在密码,则调用
changePassword(password)完成重置,并输出提示:你需要在所有当前已登录的浏览器或设备上使用新密码重新登录。
该脚本对应的 npm 命令为yarn reset-password(见 packages/sync-server/package.json)。README 中关于 CLI 的全部命令与示例均由此脚本支撑,属于"开箱即用"的运维入口。
配置体系:config.json 与环境变量
sync-server 的配置采用 convict 库管理(convict出现在 packages/sync-server/package.json 的 dependencies 中),所有配置项均可在 config.json 与同名环境变量之间二选一。下面列出 packages/sync-server/src/load-config.js 中 schema 的核心配置项及其默认值:
| 配置项 | 环境变量 | 默认值 | 说明 |
|---|---|---|---|
port | ACTUAL_PORT | 5006 | 服务监听端口 |
hostname | ACTUAL_HOSTNAME | :: | 监听地址,默认 IPv6 通配 |
dataDir | ACTUAL_DATA_DIR | /data(不存在则用项目根目录) | 数据目录 |
serverFiles | ACTUAL_SERVER_FILES | <dataDir>/server-files | 服务器端文件(含同步数据) |
userFiles | ACTUAL_USER_FILES | <dataDir>/user-files | 用户文件 |
webRoot | ACTUAL_WEB_ROOT | @actual-app/web构建目录 | Web 前端静态资源位置 |
loginMethod | ACTUAL_LOGIN_METHOD | password | 登录方式:password/header/openid |
trustedProxies | ACTUAL_TRUSTED_PROXIES | 内网网段列表(10.0.0.0/8等) | 可信反向代理 IP 网段 |
https.key | ACTUAL_HTTPS_KEY | 空 | HTTPS 私钥(证书内容或路径) |
https.cert | ACTUAL_HTTPS_CERT | 空 | HTTPS 证书(证书内容或路径) |
upload.fileSizeLimitMB | ACTUAL_UPLOAD_FILE_SIZE_LIMIT_MB | 20 | JSON 请求体大小上限(MB) |
upload.fileSizeSyncLimitMB | ACTUAL_UPLOAD_FILE_SYNC_SIZE_LIMIT_MB | 20 | 同步请求体上限(MB) |
upload.syncEncryptedFileSizeLimitMB | ACTUAL_UPLOAD_SYNC_ENCRYPTED_FILE_SYNC_SIZE_LIMIT_MB | 50 | 加密文件同步上限(MB) |
token_expiration | ACTUAL_TOKEN_EXPIRATION | never | 登录令牌过期时间,支持never、openid-provider或毫秒数 |
userCreationMode | ACTUAL_USER_CREATION_MODE | manual | 用户创建模式:manual/login |
corsProxy.enabled | ACTUAL_CORS_PROXY_ENABLED | false | 是否开启 CORS 代理端点(供前端插件使用) |
github.token | ACTUAL_GITHUB_TOKEN | 空 | GitHub API 令牌 |
这些环境变量的命名与取值直接对应 README 之外、config.json的字段,例如在 config.json 中可写作:
{ "port": 5006, "hostname": "::", "https": { "key": "/data/selfhost.key", "cert": "/data/selfhost.crt" }, "upload": { "fileSizeLimitMB": 20, "fileSizeSyncLimitMB": 20, "syncEncryptedFileSizeLimitMB": 50 } }上传限制与请求体的关联
三个上传限制并非摆设:在 packages/sync-server/src/app.ts 中,Express 分别针对三种 body 类型设置了对应上限——express.json({ limit: ...fileSizeLimitMB })处理普通 JSON、application/actual-sync类型使用fileSizeSyncLimitMB、application/encrypted-file类型使用syncEncryptedFileSizeLimitMB。也就是说,配置项直接决定了同步大数据量账本时服务器能否接受请求。
挂载式密钥:_FILE环境变量
对于ACTUAL_OPENID_CLIENT_SECRET与ACTUAL_GITHUB_TOKEN这类敏感配置,packages/sync-server/src/config-file-env.ts 提供了_FILE变体(如ACTUAL_OPENID_CLIENT_SECRET_FILE、ACTUAL_GITHUB_TOKEN_FILE):值指向一个文件路径,运行时读取文件内容(自动trim()去除首尾空白)作为配置值。文件不可读时直接抛错,避免静默失败。这一机制非常适合 Docker 中通过挂载 secret 文件注入凭据,仓库的 upcoming-release-notes/support-file-mounted-secrets.md 也印证了该能力是近期重点演进方向。
Docker 一键部署
除了 npm 全局安装,仓库提供了官方 Docker Compose 编排文件 packages/sync-server/docker-compose.yml,适合生产环境或不想污染本机 Node 环境的用户:
services: actual_server: image: docker.io/actualbudget/actual-server:latest ports: - '5006:5006' # 前面数字可改,后面 5006 是容器内端口 environment: # - ACTUAL_HTTPS_KEY=/data/selfhost.key # - ACTUAL_HTTPS_CERT=/data/selfhost.crt # - ACTUAL_PORT=5006 # - ACTUAL_UPLOAD_FILE_SYNC_SIZE_LIMIT_MB=20 # - ACTUAL_UPLOAD_SYNC_ENCRYPTED_FILE_SYNC_SIZE_LIMIT_MB=50 # - ACTUAL_UPLOAD_FILE_SIZE_LIMIT_MB=20 volumes: - ./actual-data:/data # 宿主机目录挂载到容器内 /data healthcheck: test: ['CMD-SHELL', 'node scripts/health-check.js'] interval: 60s timeout: 10s retries: 3 start_period: 20s restart: unless-stopped几个关键点:
- 端口映射:宿主机端口(第一个数字)可随意修改,容器内固定为
5006; - 数据持久化:容器内的
/data目录是 Actual 默认的数据存放位置,务必挂载到宿主机目录(如./actual-data)防止容器重建导致数据丢失;这也与load-config.js中dataDir默认取/data的逻辑完全对应; - HTTPS:若使用自签证书,healthcheck 需要额外注入
NODE_EXTRA_CA_CERTS=/data/selfhost.crt(Compose 文件中有对应注释行); - 健康检查:通过
node scripts/health-check.js验证实例存活,对应 packages/sync-server/package.json 中的health-check脚本。
启动方式:
docker compose up -d随后浏览器访问http://localhost:5006即可进入 Actual Web 客户端。
服务器内部:路由、限流与安全头
理解 sync-server 的行为,有助于判断自托管时的网络与安全配置。packages/sync-server/src/app.ts 展示了完整的 Express 应用骨架:
核心路由挂载
app.use('/sync', syncApp.handlers); // 数据同步 app.use('/account', accountApp.handlers); // 账户/登录 app.use('/gocardless', goCardlessApp.handlers); // GoCardless 银行同步 app.use('/simplefin', simpleFinApp.handlers); // SimpleFIN app.use('/pluggyai', pluggai.handlers); // Pluggy.ai app.use('/akahu', akahuApp.handlers); // Akahu app.use('/enablebanking', enableBankingApp.handlers); // Enable Banking app.use('/secret', secretApp.handlers); // 密钥管理 app.use('/admin', adminApp.handlers); // 管理端 app.use('/openid', openidApp.handlers); // OpenID从路径结构可以推断,sync-server 不仅承担同步职责,还集成了多家银行数据源提供商的接入端点(GoCardless、SimpleFIN、Pluggy、Akahu、Enable Banking),这也是 Actual 自动对账/拉取交易功能的服务器侧基础。
限流与安全
- 非
development环境下启用express-rate-limit:每分钟窗口最多 500 次请求(见 packages/sync-server/src/app.ts); - 禁用
x-powered-by头,设置trust proxy为trustedProxies配置,方便在反向代理(Nginx/Caddy)后正确识别客户端 IP; - 统一附加
Cross-Origin-Opener-Policy: same-origin、Cross-Origin-Embedder-Policy: require-corp与 CSP 响应头。
运维端点
GET /health:返回{ status: 'UP' },供负载均衡器或 Docker healthcheck 使用;GET /metrics:返回内存使用与进程运行时长;GET /info:返回构建包名、描述与版本号(从最近的@actual-app/sync-serverpackage.json 向上查找);GET /mode:返回当前运行模式(development/test)。
生产/开发双模式:development模式将前端请求代理到本地 Vite 开发服务器(http://localhost:3001,含 HMR WebSocket);生产模式则直接以静态文件方式托管webRoot下的 React 构建产物,并对任意未匹配路径回退到index.html,即单页应用路由。
HTTPS 支持:当同时配置了https.key与https.cert时,run()使用node:https创建 HTTPS 服务器;parseHTTPSConfig支持直接传入 PEM 内容(以-----BEGIN开头)或证书文件路径两种写法。
密码机制与首次引导
第一次使用自托管实例时,需要设置管理员密码,这是所有后续登录的基础。密码引导/重置的完整流程在 packages/sync-server/src/scripts/reset-password.js 中:
- 运行
actual-server --reset-password; - 脚本调用
needsBootstrap()判断是否已初始化; - 未初始化 → 提示"还没有设置密码,现在来设置一个吧!",交互输入密码后调用
bootstrap({ password }); - 已初始化 → 提示"已经有密码了,现在来重置它!",输入新密码后调用
changePassword(password); - 成功后会明确提醒:所有已登录的浏览器和设备都需要使用新密码重新登录。
密码通过 argon2/bcrypt 等库进行哈希存储(二者均出现在 packages/sync-server/package.json 的 dependencies 中),不会以明文落盘。忘记密码时,这是官方提供的唯一自助恢复途径。
数据目录与数据库迁移
服务器运行会产生两类关键数据,默认都位于dataDir下:
server-files/:同步的账本文件数据;user-files/:用户文件。
首次启动(含升级后启动)会自动执行数据库迁移。迁移逻辑位于 packages/sync-server/src/migrations.ts:
- 通过 Vite 的
import.meta.glob在构建期静态收集migrations/目录下全部迁移脚本(见 packages/sync-server/migrations),每个迁移独立成 chunk,运行时按文件名排序后逐个执行; - 迁移状态记录在
<dataDir>/.migrate(测试模式下为.migrate-test),支持up/down双向操作; - 对应的运维命令为
yarn db:migrate与yarn db:downgrade(见 packages/sync-server/package.json)。
由于入口 packages/sync-server/app.ts 保证"先迁移、后启动",升级版本时无需手工干预;只有显式回滚时才需要执行db:downgrade。
自托管实践建议
结合上述实现,给出几条可直接落地的运维建议:
- 数据安全第一:无论采用 npm 还是 Docker 部署,务必把
server-files、user-files(即整个数据目录)纳入定期备份,同时保护好.migrate迁移状态文件; - 公网访问务必上 HTTPS:通过
https.key/https.cert配置或前置反向代理终结 TLS;trustedProxies默认已覆盖常见内网网段,若反向代理位于其他网段需追加; - 按需调整上传限制:账本文件较大或多设备高频同步时,可将
ACTUAL_UPLOAD_FILE_SYNC_SIZE_LIMIT_MB从默认 20 调大(Docker Compose 文件中已预留注释示例); - 健康检查接入编排:使用
/health端点或官方 health-check 脚本纳入监控告警; - 开放 OpenID 时先看 enforce:
ACTUAL_OPENID_ENFORCE(对应enforceOpenId,默认false)决定是否强制所有用户走 OpenID,开启前需确认discoveryURL或各 endpoint 均已配置(openId配置完整时,run()会在启动阶段调用bootstrap({ openId })预配置提供方)。
小结
@actual-app/sync-server用极简的 CLI 面(四个选项)封装了完整的自托管同步方案:npm 全局安装后一条actual-server即可运行,--config对接 convict 驱动的丰富配置体系,--reset-password提供密码恢复通道,Docker Compose 文件则给出生产级的一键部署参考。底层上,load-config.js 定义了全部可调参数与优先级,app.ts 承载了同步、银行对接、健康检查与 HTTPS 等全部路由,migrations.ts 保障了版本升级的平滑性——这正是"本地优先 + 跨设备同步"这一设计在服务器端的完整实现。
【免费下载链接】actualA local-first personal finance app项目地址: https://gitcode.com/GitHub_Trending/ac/actual
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考