Actual 常用命令与运维实战
Actual sync-server(v26.10.0)运维实战:启停与日志监控、版本查询与升级流程、多预算文件管理(fileId/groupId/Sync ID 辨析)、双客户端端到端同步实测闭环、目录级备份与恢复演练、exportBudget 导出、API/CLI/HTTP 接口速查与 6 条排障实录。
Actual 常用命令与运维实战
目标:给出 Actual sync-server(v26.10.0)日常运维的完整命令集——服务启停与日志、版本升级、多预算文件管理、双客户端端到端同步实测、备份与恢复演练,以及 API/CLI 命令速查与排障实录。全部命令均在实测环境逐条验证(实测日期 2026-10-28);部署与配置细节见 《Actual Docker Compose 部署实战》、《Actual 配置参数详解》。
一、环境信息
- 组件版本:Actual sync-server v26.10.0(
latest标签,服务器与 API 客户端库版本严格一致) - 实测环境:Ubuntu 24.04(x86_64),Node.js 24(API 客户端运行时)
- 服务地址:
http://<服务器IP>:8129(默认端口 5006,本环境映射为 8129) - 示例预算:「家庭预算2026」(含账户"现金钱包"、分类"日常支出/餐饮、交通")
- 示例密码:
q1w2e3r4(演示口令) - API 客户端:
@actual-app/api@26.10.0(npm 安装,版本须与服务器一致)
💡 说明:运维 Actual 的抓手有两层——服务进程运维(日志/升级/数据目录)与预算数据运维(多预算管理/备份/同步验证)。数据层的黄金法则是:
user-files目录(预算文件 blob + 同步组库)加server-files/account.sqlite(账号密码库)就是全部状态,备份即拷贝目录。
二、服务启停与状态命令
2.1 CLI 部署形态(systemd / 裸进程)
sync-server 以 NPM 包 @actual-app/sync-server 分发(要求 Node.js ≥ 22),进程级运维命令如下:
# 启动(前台验证配置时)
actual-server --config /srv/actual/config.json
# systemd 部署时的标准生命周期
systemctl start actual
systemctl stop actual
systemctl restart actual # 改配置后重启生效
systemctl status actual # 运行状态(active/running 即健康)
# 版本查询
actual-server --version
服务就绪的判定信号是启动日志监听行与健康端点(两章命令均实测可用):
curl -sS http://127.0.0.1:8129/health
{"status":"UP"}
2.2 Compose 部署形态
按 《Actual Docker Compose 部署实战》 部署时,启停与状态命令为:
cd /opt/actual
docker compose ps # STATUS 列 healthy 即健康
docker compose restart # 重启服务
docker compose stop && docker compose start
docker compose ps 实测回显形态:
NAME IMAGE STATUS
actual-v26.10.0-server actualbudget/actual-server:latest Up 22 minutes (healthy)
三、日志与监控
3.1 日志观测点
sync-server 日志行与含义(实测归纳):
| 日志行 | 含义 | 运维关注点 |
|---|---|---|
Checking if there are any migrations to run for direction "up"... | 启动时检查数据库迁移 | 每次升级后必然出现 |
Migrations: DONE | 迁移完成 | 卡在此行之前说明迁移失败,查数据目录可写性 |
Running in production mode - Serving static React app | 生产模式 + 前端静态资源 | 出现即为正常 |
Listening on 0.0.0.0:5006... | 监听就绪 | 核对端口/地址是否符合预期 |
Syncing since <时间戳>(客户端侧) | 客户端与服务器增量同步 | 同步排障时观察游标推进 |
运行期日志的核心健康信号是安静——正常同步流量几乎不产生服务端日志;大量重复的同步错误日志通常意味着客户端与服务端版本不匹配。
3.2 健康监控命令
适合放监控定时任务的三个探针(全部实测回显):
# 1. 存活与健康
curl -sf http://127.0.0.1:8129/health
# {"status":"UP"}
# 2. 引导状态(可顺带验证登录方式配置)
curl -sf http://127.0.0.1:8129/account/needs-bootstrap
# {"status":"ok","data":{"bootstrapped":true,...}}
# 3. 数据目录增长监控(预算文件 blob 大小变化即数据在增长)
du -sb /srv/actual/user-files | awk '{print $1}'
四、版本查询与升级
4.1 版本核对(服务端 + 客户端双侧)
升级前后都要核对两侧版本一致:
# 服务端:CLI 形态
actual-server --version
# Compose 形态:探活后用 API 核对(见下)
# 客户端库版本
npm ls @actual-app/api
API 实测核对服务端版本(脚本片段):
await actual.init({ serverURL: 'http://127.0.0.1:8129', password: 'q1w2e3r4', dataDir: '...' });
console.log(JSON.stringify(await actual.getServerVersion()));
实测回显:
{"version":"26.10.0"}
4.2 升级流程
CLI 形态(官方文档口径):
npm update -g @actual-app/sync-server
systemctl restart actual
Compose 形态(官方升级姿势,实测执行):
docker compose pull && docker compose up -d
实测回显(已是最新版时的形态):
Image actualbudget/actual-server:latest Pulled
Container actual-v26.10.0-server Running
{"status":"UP"}
升级后验证三步(实测清单):
curl http://127.0.0.1:8129/health→{"status":"UP"};- 启动日志出现
Migrations: DONE(新版本迁移自动执行); - API
getServerVersion()返回新版本号,且@actual-app/api同步升级到相同版本。
⚠️ 注意:升级前必做备份(第六章目录打包一条命令即可);
@actual-app/api与服务器版本错配是升级后最常见的故障(同步协议错误),两侧一起升。
五、多预算文件管理
Actual 支持一个服务器上管理多个预算文件(如"家庭预算2026""2027 年度预算"并存)。实测环境中有两个同名预算(运维演练产物),正好用于管理演示。
5.1 查看预算清单
API 实测(getBudgets,输出节选):
console.log(JSON.stringify(await actual.getBudgets(), null, 1));
[
{
"cloudFileId": "29b70409-25fa-47ba-b410-6c553ed0a32a",
"groupId": "caa22e62-9d2e-48d9-a781-b2b1455e38a1",
"name": "家庭预算2026",
...
},
{
"cloudFileId": "29ee16e1-2e38-41b6-9138-edcb9db4920e",
"name": "家庭预算2026",
"state": "remote",
...
}
]
服务端等价 HTTP 接口实测(GET + 两个头):
curl -sS http://127.0.0.1:8129/sync/list-user-files -H 'X-ACTUAL-TOKEN: <token>'
{"status":"ok","data":[{"deleted":0,"fileId":"29ee16e1-...","groupId":"c74f8121-...","name":"家庭预算2026",...},{"deleted":0,"fileId":"29b70409-...","groupId":"caa22e62-...","name":"家庭预算2026",...}]}
三个 ID 的角色辨析(实测结论,重要):
| ID | 作用 | 用在哪 |
|---|---|---|
fileId(cloudFileId) | 预算文件在服务器侧的文件 ID | 服务器文件管理(如 get-user-file-info) |
groupId(同步组 ID) | 多端同步的组标识,即 Web 界面设置里显示的 Sync ID | 客户端 downloadBudget 的参数 |
本地 id | 客户端本地预算目录名 | 仅本地 |
⚠️ 注意:
downloadBudget的参数名是syncId,匹配的是groupId而非 cloudFileId——传 cloudFileId 会报 not found(实测踩中,见第十章排障实录)。这是文档级易错点。
5.2 新建与切换预算
- 新建:Web 界面「创建新预算文件」或 API
runImport(name, fn)(每次调用都新建,重复执行会产生多个同名预算); - 切换:客户端
downloadBudget(groupId)拉取目标预算;Web 界面右上角文件菜单切换; - 删除:v26.10.0 的
@actual-app/api没有删除预算的方法(65 个导出方法实测核对),删除走 Web 界面「设置 → 高级 → 预算管理」。删除只摘除服务器记录,user-files下的 blob 物理文件建议运维定期清点。
5.3 数据目录视角的预算管理
# 实查预算文件与同步组库(-h 人类可读)
ls -lh /srv/actual/user-files/
实测回显:
-rw-r--r-- 1 root root 12K Oct 28 11:05 file-29b70409-25fa-47ba-b410-6c553ed0a32a.blob
-rw-r--r-- 1 root root 12K Oct 28 11:04 file-29ee16e1-2e38-41b6-9138-edcb9db4920e.blob
-rw-r--r-- 1 root root 24K Oct 28 11:07 group-caa22e62-9d2e-48d9-a781-b2b1455e38a1.sqlite
file-*.blob 与预算一一对应;group-*.sqlite 每个同步组一个。孤儿 blob(服务器清单里没有对应 fileId)可用脚本比对清理。
六、端到端同步实测(第二客户端接入)
local-first 架构的核心运维验证:第二个客户端能否完整接入并双向同步。实测按"下载 → 一致性 → 反向变更 → 回流验证"四步闭环。
6.1 第二客户端下载同一预算
全新客户端(独立本地缓存目录)接入实测脚本 client2-sync.js:
const actual = require('@actual-app/api');
const SYNC_ID = 'caa22e62-9d2e-48d9-a781-b2b1455e38a1'; // 预算的 groupId(Web 设置页可见)
(async () => {
await actual.init({
serverURL: 'http://127.0.0.1:8129',
password: 'q1w2e3r4',
dataDir: '/srv/actual/client2-data', // 全新目录,需预先创建
});
await actual.downloadBudget(SYNC_ID);
const accs = await actual.getAccounts();
console.log('accounts:', accs.map(a => a.name));
const txns = await actual.getTransactions(accs[0].id, '2026-10-01', '2026-10-31');
console.log('txn count:', txns.length, '| balance:', await actual.getAccountBalance(accs[0].id));
await actual.shutdown();
})().catch(e => { console.error('FATAL:', e.message); process.exit(1); });
实测回显:
accounts: [ '现金钱包' ]
txn count: 2 | balance: 7150
第二客户端下载到的数据与第一客户端逐字节一致(账户、交易数、余额 7150 全对上)——同步基础设施正常。
6.2 第二客户端记账 → 第一客户端验证回流
在第二客户端追加一笔交易(-5.00 元地铁):
await actual.addTransactions(accs[0].id, [
{ date: '2026-10-28', payee_name: '地铁', amount: -500, notes: 'client2 记一笔' },
]);
console.log('new balance:', await actual.getAccountBalance(accs[0].id));
实测回显:
new balance: 6650
第一客户端(原目录)重新连接并拉取增量,实测回显(交易列表节选):
client1 txn count: 3
[
{ "payee": "地铁", "amount": -500, "notes": "client2 记一笔" },
{ "payee": "楼下超市", "amount": -2850, "notes": "买菜" },
...
]
client1 balance: 6650
闭环结论:client-2 记的「地铁 -500」完整回流到 client-1(金额、收款人、备注原样传递),余额两端一致(6650)。这就是"本地优先 + 消息式同步"的运维验证形态——Web 界面与桌面 App 接入自建服务器的行为与此等价(填服务器地址 + 密码 → 下载预算 → 自动增量同步)。
💡 说明:日常运维中验证同步是否健康,用同样思路即可:两端各改一笔,看另一端余额是否一致。同步游标异常时优先核对两端版本号。
七、备份策略与恢复演练
7.1 备份对象与粒度
| 粒度 | 内容 | 命令 | 适用 |
|---|---|---|---|
| 目录级(推荐主备份) | server-files + user-files 全部状态 | tar czf ... 一条命令 | 服务器整体备份/迁移/升级前 |
| 单预算导出 | 单个预算的 zip(db.sqlite + metadata.json) | API exportBudget() | 单预算版本化、跨服务器迁移、给人肉管报 |
目录级备份实测:
cd /srv/actual && tar czf data-backup-$(date +%Y%m%d).tar.gz server-files user-files
实测回显(产物):
-rw-r--r-- 1 root root 27870 Oct 28 11:27 data-backup-20261028.tar.gz
💡 说明:备份前先
stop或确保无写入(或接受文件级快照的微小不一致风险——SQLite blob 在无写入的瞬间是自洽的)。生产建议加--warning=no-file-changed并在低峰执行;有条件用文件系统快照更稳。
单预算导出实测(exportBudget 返回 zip 字节流):
const fs = require('fs');
await actual.downloadBudget(SYNC_ID);
const data = await actual.exportBudget();
fs.writeFileSync('/srv/actual/backup-家庭预算2026.zip', Buffer.from(data));
实测回显(产物核验):
exported bytes: 18152
zip entries: ['db.sqlite', 'metadata.json']
导出的 zip 与 Web 界面「导出备份」产物同构,可在任何 Actual 实例(含官方演示站)导入恢复。
7.2 恢复演练(实测)
演练设计(零风险方式):把 7.1 的备份包解压到独立目录,以独立实例挂载启动,验证账号、密码与预算完整可用——效果等价于"原目录丢失后从备份重建",且不触碰生产数据。
mkdir -p /srv/restore-drill && tar xzf data-backup-20261028.tar.gz -C /srv/restore-drill
find /srv/restore-drill -maxdepth 2 | sort
实测回显:
/srv/restore-drill
/srv/restore-drill/.migrate
/srv/restore-drill/server-files
/srv/restore-drill/server-files/account.sqlite
/srv/restore-drill/user-files
/srv/restore-drill/user-files/file-29b70409-25fa-47ba-b410-6c553ed0a32a.blob
/srv/restore-drill/user-files/file-29ee16e1-2e38-41b6-9138-edcb9db4920e.blob
/srv/restore-drill/user-files/group-caa22e62-9d2e-48d9-a781-b2b1455e38a1.sqlite
以恢复目录启动验证实例后,逐项验证(实测回显):
# 1. 引导状态 —— 账号库随备份回来了
curl -sS http://127.0.0.1:8135/account/needs-bootstrap
{"status":"ok","data":{"bootstrapped":true,...}}
# 2. 原密码登录 —— 密码哈希完好,token 与原实例一致
curl -sS -X POST http://127.0.0.1:8135/account/login \
-H 'Content-Type: application/json' -d '{"password":"q1w2e3r4"}'
{"status":"ok","data":{"token":"d43b7717-e7a7-409e-a74b-9df059ee78ae"}}
# 3. 预算清单完整
curl -sS http://127.0.0.1:8135/sync/list-user-files -H 'X-ACTUAL-TOKEN: <token>'
[('家庭预算2026', '29ee16e1-...'), ('家庭预算2026', '29b70409-...')]
演练结论:目录级备份完整覆盖"账号 + 密码 + 预算 + 同步组"全部状态;恢复 = 解包 + 启动,无需重新 bootstrap、无需重建预算。生产恢复流程:停服务 → 备份目录解压回数据目录(属主对齐)→ 启动 → 走 7.2 三步验证。
八、API / CLI 命令速查
8.1 API 客户端常用命令(@actual-app/api,实测可用)
const actual = require('@actual-app/api');
// 连接(dataDir 须预先存在)
await actual.init({ serverURL, password, dataDir });
// 版本/清单
await actual.getServerVersion(); // {"version":"26.10.0"}
await actual.getBudgets(); // 预算清单(本地+remote)
await actual.downloadBudget(groupId); // 按 Sync ID 下载/切预算
await actual.runImport(name, fn); // 新建预算
await actual.exportBudget(); // 导出当前预算 zip
// 数据操作(金额单位:分)
await actual.getAccounts();
await actual.getAccountBalance(accountId);
await actual.addTransactions(accountId, [ { date, payee_name, category, amount, notes } ]);
await actual.getTransactions(accountId, 'YYYY-MM-DD', 'YYYY-MM-DD');
await actual.getBudgetMonth('YYYY-MM'); // 月度预算视图
await actual.createCategoryGroup({ name });
await actual.createCategory({ name, group_id, is_income: false, hidden: false });
await actual.shutdown();
8.2 服务端 HTTP 接口速查(实测核对)
| 接口 | 方法 | 用途 |
|---|---|---|
/health、/api/health | GET | 存活探针 |
/account/needs-bootstrap | GET | 引导状态/登录方式 |
/account/bootstrap | POST | 首次设密码 {password} |
/account/login | POST | 登录换 token {password} |
/sync/list-user-files | GET(头 X-ACTUAL-TOKEN) | 预算文件清单 |
/sync/get-user-file-info | GET(头 X-ACTUAL-FILE-ID) | 单文件元信息 |
/sync/download-user-file | GET | 下载预算 blob |
/sync/upload-user-file | POST | 上传/新建预算 |
⚠️ 注意:文件类接口在
/sync前缀下(/sync/list-user-files),直接打根路径会命中前端 SPA fallback 返回 HTML(实测踩中,见第十章)。
8.3 CLI 工具命令(actual-server)
actual-server # 默认配置启动
actual-server --config <path> # 指定 config.json
actual-server --reset-password # 交互式重置服务器密码
actual-server --version # 版本号
npm update -g @actual-app/sync-server # 升级
npm uninstall -g @actual-app/sync-server # 卸载
九、排障实录
9.1 downloadBudget 报 Budget not found,但预算明明存在
现象(实测原文):
Budget "29b70409-25fa-47ba-b410-6c553ed0a32a" not found. Check the sync id of your budget in the Advanced section of the settings page.
定位:用 /sync/list-user-files 确认预算在服务器上完好;核对 API 源码发现 downloadBudget 内部用 find(f => f.groupId === syncId) 匹配——参数要的是 groupId(同步组 ID),传入的却是 cloudFileId。
解法:改传 groupId(即 Web 界面设置页 Advanced 区显示的 Sync ID)后下载成功(第六章 6.1 实测)。记忆口诀:文件 ID 管服务器文件,Sync ID 管客户端接入。
9.2 手动 curl 文件接口返回 HTML 而非 JSON
现象:curl http://127.0.0.1:8129/list-user-files 返回 <title>Actual</title> 的完整前端页面;POST /get-user-file-info 返回 Cannot POST。
原因:v26 起同步文件接口挂在 /sync 前缀下,根路径同名路径被前端 SPA fallback 兜底成 HTML。
解法:统一走 /sync/list-user-files、/sync/get-user-file-info、/sync/download-user-file(第八章 8.2 已核对全表)。
9.3 API 客户端 init 报 ENOENT scandir
现象:init({dataDir}) 报 ENOENT ... scandir <dataDir>。
原因:API 库要求本地缓存目录预先存在,不自动创建。
解法:mkdir -p <dataDir> 后重跑(服务端数据目录会自动建,客户端缓存目录不会——两者行为不同)。
9.4 API 库加载报 GLIBCXX_3.4.29 not found
现象:
Error: libstdc++.so.6: version `GLIBCXX_3.4.29' not found (required by better-sqlite3/prebuilds/linux-x64.node) code: 'ERR_DLOPEN_FAILED'
原因:@actual-app/api 依赖 better-sqlite3 原生模块,运行环境 libstdc++ 过旧或被旧库路径抢占。
解法:确认系统库含该符号(strings /usr/lib/x86_64-linux-gnu/libstdc++.so.6 | grep GLIBCXX_3.4.29)后以系统库运行 LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu node 脚本.js;系统库确实过旧则升级 libstdc++6 或 npm rebuild better-sqlite3 源码编译。
9.5 升级后客户端同步报协议错误
现象:服务器升级新版本后,API 脚本或桌面端同步报错。
原因:客户端(@actual-app/api / 桌面 App)与服务器版本错配,同步消息协议不兼容。
解法:客户端库升级到与服务器相同版本(npm install @actual-app/api@<服务器版本>);桌面 App 用其内置更新。getServerVersion() 与 npm ls @actual-app/api 双侧核对是标准排查动作。
9.6 服务器上出现多个同名预算
原因:runImport/界面"创建新预算"每次都会新建预算文件,重跑初始化脚本必然重复。
解法:getBudgets() 比对各预算 groupId,确认保留项后在 Web 界面删除多余预算;初始化脚本只在空服务器跑一次,后续一律 downloadBudget 拉取。
十、参考链接
- 官方备份恢复文档:https://actualbudget.org/docs/backup-restore/backup
- 官方 Server CLI:https://actualbudget.org/docs/install/cli-tool
- 官方 API 文档:https://actualbudget.org/docs/api/
- sync-server 配置体系:https://actualbudget.org/docs/config/
- 站内延伸阅读:《Actual 介绍与选型指南》、《Actual Docker Compose 部署实战》、《Actual 配置参数详解》。