Actual 配置参数详解

Actual sync-server(v26.10.0)配置参数详解:环境变量全集与 config.json 键位、三层优先级实测(env>config>默认)、数据目录派生树实测、password/header/openid 登录体系与 OpenID 全套变量、上传限制三变量,并澄清 ACTUAL_UPLOAD_DIR 不存在等 6 条 FAQ。

Actual财务记账自托管Node.jsSQLite2026/10/28

Actual 配置参数详解

目标:系统讲清 Actual sync-server(同步服务器)的配置参数体系——环境变量全集、config.json 键位、三层优先级、参数如何改变数据目录派生结构,以及 OpenID 登录等进阶配置的落地方式。全部参数行为均在 v26.10.0 上实测验证(实测日期 2026-10-28),每个关键结论附实测回显;部署流程本身见 《Actual Docker Compose 部署实战》。

一、环境信息

  • 组件版本:Actual sync-server v26.10.0(官方最新稳定版,latest 标签)
  • 实测环境:Ubuntu 24.04(x86_64),Node.js 24(服务端运行时)
  • 配置加载源码:packages/sync-server/src/load-config.js(官方文档明确提示:环境变量与 config.json 键并非一一对应,存疑时以该 schema 为准)
  • 实测方法:以 CLI 方式启动 sync-server 进程,注入不同环境变量组合,观察启动日志监听行与数据目录派生结构;探活用 /health 端点(返回 {"status":"UP"})
  • 示例密码:q1w2e3r4(演示口令)

💡 说明:sync-server 是一个 Node.js 单进程服务,没有独立配置数据库——所有配置都是进程启动时一次性读取(环境变量 / config.json / CLI 参数),改配置即重启进程。理解"启动时读定,之后不再变更"这一点,可以避免大量"改了配置为什么不生效"的困惑。

二、配置参数体系总览

2.1 三个配置入口与优先级

官方文档口径,sync-server 启动时按以下顺序查找并合并配置,优先级从高到低:

层级入口说明
1(最高)环境变量(ACTUAL_*)容器编排、systemd 单元、CLI 内联注入的首选方式
2config.json按需 ACTUAL_CONFIG_PATH 指定路径,否则找 /data/config.json(存在时)或安装目录
3(默认)内置默认值端口 5006、监听 ::、数据目录 /data(存在时)等

实测验证优先级:环境变量 ACTUAL_PORT=5007 与 config.json 的 "port": 5006 同时存在时,监听端口取环境变量值:

Listening on :::5007...

去掉环境变量、仅保留 config.json 后重启:

Listening on :::5006...

两组回显证明:环境变量覆盖 config.json,config.json 覆盖默认值。

2.2 键名映射规则与 _FILE 密钥后缀

  • config.json 键为 camelCase(dataDir、serverFiles、upload.fileSizeLimitMB),对应环境变量为 ACTUAL_ + 大写下划线(ACTUAL_DATA_DIR、ACTUAL_SERVER_FILES、ACTUAL_UPLOAD_FILE_SIZE_LIMIT_MB);
  • 并非所有键都有环境变量形态(如 https 是嵌套对象,通常只能写 config.json),也并非所有环境变量都有一一对应的键——官方把权威 schema 放在 load-config.js;
  • Secrets 文件化:ACTUAL_OPENID_CLIENT_SECRET_FILE、ACTUAL_GITHUB_TOKEN_FILE 等 _FILE 后缀变量可指向含密钥的文件(适配 Docker secrets / Kubernetes secret 卷),且 _FILE 后缀优先于普通变量。CLI 自身变量同样支持该后缀。

2.3 生效方式

所有配置在进程启动时读定。修改配置的标准流程:

# CLI 方式:改环境变量或 config.json 后重启进程
# systemd 部署:systemctl restart actual
# 容器部署:改 compose 的 environment/volumes 后 docker compose up -d

验证生效最直接的信号是启动日志的监听行(Listening on <host>:<port>...)与数据目录里新增的子目录(见第四章)。

三、网络监听参数

参数默认值说明
ACTUAL_PORT(config: port)5006HTTP(S) 监听端口
ACTUAL_HOSTNAME(config: hostname)::监听地址(多数系统上同时覆盖 IPv4/IPv6)
ACTUAL_HTTPS_KEY(config: https.key)无TLS 私钥文件路径
ACTUAL_HTTPS_CERT(config: https.cert)无TLS 证书文件路径
ACTUAL_TRUSTED_PROXIES10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7, ::1/128信任的反向代理网段(从客户端 IP 链中去掉代理 IP,影响限流判定)
ACTUAL_TRUSTED_AUTH_PROXIES同上允许以 HTTP 头方式认证的客户端(header 登录方式配套)

实测:改端口。注入 ACTUAL_PORT=5007 启动:

启动日志实测回显:

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:5007...

/health 探活实测回显:

{"status":"UP"}

💡 说明:ACTUAL_HOSTNAME 不设置时监听 ::(回显里表现为 Listening on :::5006...);显式设为 0.0.0.0 时回显为 Listening on 0.0.0.0:5007...。两者在常见 Linux 上对外可达性一致,生产建议显式指定以便审计。

HTTPS 说明:同时提供 key 与 cert 后服务直接以 HTTPS 监听(证书需自备,自签场景官方文档有专章);多数生产形态是反向代理终结 TLS、后端保持 HTTP,此时只需关注 ACTUAL_TRUSTED_PROXIES 把代理网段配对。

四、数据目录参数(含目录树实测)

4.1 参数定义

参数默认值说明
ACTUAL_DATA_DIR(config: dataDir)/data(该目录存在时),否则当前目录数据总目录
ACTUAL_CONFIG_PATH无config.json 路径(默认在数据目录或安装目录找;不能写进 config.json 本身——鸡生蛋问题)
ACTUAL_SERVER_FILES(config: serverFiles)<dataDir>/server-files服务器账号库目录(account.sqlite:密码哈希/预算清单/会话)
ACTUAL_USER_FILES(config: userFiles)<dataDir>/user-files预算文件目录(全部预算 blob 与同步组库)
ACTUAL_WEB_ROOT(config: webRoot)包内 @actual-app/web前端静态资源目录(自定义前端才需要)

4.2 默认派生结构实测

全新数据目录首次启动后实查:

/data
├── .migrate
├── server-files
│   └── account.sqlite
└── user-files

创建预算文件后再实查(du -b 实测大小):

/data
├── .migrate
├── server-files
│   └── account.sqlite                      (69632 B)
└── user-files
    ├── file-29b70409-25fa-47ba-b410-6c553ed0a32a.blob   (12017 B)
    └── group-caa22e62-9d2e-48d9-a781-b2b1455e38a1.sqlite (24576 B)

命名规律:预算文件本体是 file-<cloudFileId>.blob,同步组库是 group-<groupId>.sqlite。.migrate 与 account.sqlite 落在 server-files,而 .migrate 跟随数据总目录。

4.3 自定义路径派生实测

三个变量拆开指定(ACTUAL_DATA_DIR=/srv/actual-store、ACTUAL_SERVER_FILES=/srv/actual-store/accounts-db、ACTUAL_USER_FILES=/srv/actual-store/budget-blobs)后首次启动,实查目录树:

/srv/actual-store
├── accounts-db
│   └── account.sqlite
├── budget-blobs
└── .migrate

实测结论:目录自动创建(无需手工 mkdir 预建),account.sqlite 精确落在 ACTUAL_SERVER_FILES 指定路径,budget-blobs 目录按 ACTUAL_USER_FILES 创建。该形态适用于"账号库与预算文件分区存放"或"预算文件单独挂大容量盘"的运维需求。

⚠️ 注意:拆分存放后,备份必须覆盖两个目录(账号库与预算文件),漏掉任何一个都无法完整恢复(账号库没有预算文件、预算文件没有账号密码)。

五、认证与登录参数

5.1 参数定义

参数默认值说明
ACTUAL_LOGIN_METHOD(config: loginMethod)password默认登录方式:password / header / openid
ACTUAL_ALLOWED_LOGIN_METHODSpassword,header,openid允许的登录方式白名单(逗号分隔)
ACTUAL_SIMPLEFIN_ALLOWED_NETWORKS无(默认禁止私网)允许 SimpleFIN 访问的内网桥 IP/CIDR(自建 SimpleFIN 桥才需要)

5.2 password 模式(默认)实测闭环

服务器首次上线需先"引导"设置密码,全套实测回显如下。

查询引导状态:

curl -sS http://127.0.0.1:8129/account/needs-bootstrap
{"status":"ok","data":{"bootstrapped":false,"loginMethod":"password","availableLoginMethods":[],"multiuser":false}}

引导设置密码(bootstrap):

curl -sS -X POST http://127.0.0.1:8129/account/bootstrap \
  -H 'Content-Type: application/json' -d '{"password":"q1w2e3r4"}'
{"status":"ok","data":{"token":"d43b7717-e7a7-409e-a74b-9df059ee78ae"}}

登录换会话 token:

curl -sS -X POST http://127.0.0.1:8129/account/login \
  -H 'Content-Type: application/json' -d '{"password":"q1w2e3r4"}'
{"status":"ok","data":{"token":"d43b7717-e7a7-409e-a74b-9df059ee78ae"}}

引导后状态复核:

{"status":"ok","data":{"bootstrapped":true,"loginMethod":"password","availableLoginMethods":[{"method":"password","active":1,"displayName":"Password"}],"multiuser":false}}

💡 说明:availableLoginMethods 是运行时可用的登录方式列表,由 ACTUAL_ALLOWED_LOGIN_METHODS 与实际配置共同决定——这是排查"登录方式怎么不生效"的第一观察点。密码哈希存于 server-files/account.sqlite,所以改密码/重置密码的本质是动这个库(CLI 提供 --reset-password,见第八章)。

5.3 header 模式要点

header 方式让网关在请求头 x-actual-password 注入密码实现自动登录,仅建议"已有统一认证入口"的场景使用;且必须配合 ACTUAL_TRUSTED_AUTH_PROXIES 限定哪些来源可以带认证头,否则任何能直连服务的客户端都可以伪造该头登入(官方文档明确提示安全风险)。收敛白名单的标准做法:

ACTUAL_TRUSTED_AUTH_PROXIES=10.89.0.0/16

5.4 loginMethod 三种方式对比

方式机制适用风险点
password(默认)服务端校验密码,签发 token个人自托管标准形态弱密码可被爆破,建议强密码 + 反代限流
header网关注入 x-actual-password 自动登录已有 SSO 网关注入凭据信任来源配置错误即任意登入
openid对接 OIDC/OAuth2 身份提供方多用户、要 MFA 的家庭/团队见第六章配置项较多

六、OpenID 登录参数详解

openid 是官方多用户与 MFA 的推荐方案(预览特性),支持两种配置通道:环境变量或 config.json(亦可在 Web 界面预算内 Settings 中图形化配置)。核心环境变量如下:

发现(discovery)模式——身份提供方支持 .well-known/openid-configuration 时只需四项:

变量示例值
ACTUAL_OPENID_DISCOVERY_URLhttps://<provider>/.well-known/openid-configuration
ACTUAL_OPENID_CLIENT_IDmy-actual-app
ACTUAL_OPENID_CLIENT_SECRETsuper-secret-value
ACTUAL_OPENID_SERVER_HOSTNAMEhttps://actual.example.com(服务公网地址,回调用)

手动端点模式(提供方不支持 discovery 时):以 ACTUAL_OPENID_AUTHORIZATION_ENDPOINT、ACTUAL_OPENID_TOKEN_ENDPOINT、ACTUAL_OPENID_USERINFO_ENDPOINT 三个端点变量替代 discovery URL。

行为与安全控制项:

变量可选值说明
ACTUAL_OPENID_AUTH_METHODopenid(默认)/ oauth2GitHub 这类不完全支持 OIDC discovery 的提供方用 oauth2
ACTUAL_OPENID_ENFORCEtrue/false(默认)true 时 OpenID 成为唯一登录方式
ACTUAL_TOKEN_EXPIRATIONnever(当前默认)/ openid-provider / 秒数(如 3600)会话 token 过期策略
ACTUAL_USER_CREATION_MODEmanual(默认)/ loginmanual:用户须先在 Actual 侧建好同名账号;login:首次认证自动建户
ACTUAL_OPENID_CLIENT_SECRET_FILE文件路径密钥文件化(优先于普通变量)

落地要点(官方文档口径 + 配置实践):

  • 身份提供方需登记回调地址:https://<actual服务器>/openid/callback;
  • 第一个用 OpenID 登录的用户自动成为服务器 owner(且 UI 不可更改)——全新实例上线时谁先登录谁是管理员,要有预期;
  • 官方列出已验证提供方:Auth0、Authentik、GitHub、Google Accounts、Keycloak、Microsoft Entra、Passwordless.id、Pocket ID、Rauthy(Rauthy 需将 token/ID token 算法改为 RS256);
  • config.json 形态的键(openId 对象:discoveryURL/client_id/client_secret/server_hostname/authMethod)与环境变量二选一即可,键名风格与主配置一致(camelCase/snake_case 混用以官方示例为准)。

七、上传限制参数

参数默认值说明
ACTUAL_UPLOAD_FILE_SIZE_LIMIT_MB(config: upload.fileSizeLimitMB)20普通上传文件上限(导入 OFX/CSV 等走这里)
ACTUAL_UPLOAD_FILE_SYNC_SIZE_LIMIT_MB(config: upload.fileSyncSizeLimitMB)20同步通道文件上限
ACTUAL_UPLOAD_SYNC_ENCRYPTED_FILE_SYNC_SIZE_LIMIT_MB(config: upload.syncEncryptedFileSizeLimitMB)50加密同步文件上限(加密膨胀所以默认更宽)

实测澄清:不存在 ACTUAL_UPLOAD_DIR 这个配置项。实测向进程注入 ACTUAL_UPLOAD_DIR=/data/uploads-dir 后:

启动日志(正常启动,无任何关于 upload 的告警——未知变量被静默忽略):

Migrations: DONE
Running in production mode - Serving static React app
Listening on :::5007...

数据目录实查(uploads-dir 并未被创建,上传落点仍是默认的 user-files):

/data
├── server-files
└── user-files

结论:上传目录没有独立开关——导入/上传的文件统一落在 ACTUAL_USER_FILES 目录,控制其位置用 ACTUAL_USER_FILES,控制大小用上表三个变量。官方 load-config.js schema 中没有 upload 目录键位,各类旧教程中的 ACTUAL_UPLOAD_DIR 写法对 v26.10.0 无效(未知 ACTUAL_* 变量静默忽略,不报错——排障时容易被误导)。

💡 技术拓展:为何三个上限要分开?普通上传是一次性 HTTP POST(导入文件);同步是高频小文件通道,限制更敏感;加密同步因密文有固定膨胀率(认证标签、分块填充),同样的明文预算加密后更大,所以官方把加密上限单独放宽到 50MB。若开启 E2EE 后同步大预算报 413,应调第三个变量。

八、config.json 完整配置与 CLI 配套

8.1 完整示例

/srv/actual/config.json(键名 camelCase,与第七章环境变量一一对应):

{
  "port": 5006,
  "hostname": "0.0.0.0",
  "serverFiles": "/srv/actual/server-files",
  "userFiles": "/srv/actual/user-files",
  "upload": {
    "fileSizeLimitMB": 25,
    "fileSyncSizeLimitMB": 20,
    "syncEncryptedFileSizeLimitMB": 50
  },
  "loginMethod": "password",
  "allowedLoginMethods": ["password"]
}

逐项说明:port/hostname 收敛监听;serverFiles/userFiles 拆分数据落点(备份面见第四章);upload 组放宽导入上限到 25MB;allowedLoginMethods 只留 password,显式关闭 header/openid 攻击面(未配 OpenID 时强烈建议)。

8.2 Server CLI 配套参数

sync-server 以 NPM 包 @actual-app/sync-server 分发(要求 Node.js ≥ 22),全局安装后得到 actual-server 命令:

npm install --location=global @actual-app/sync-server

CLI 选项(官方文档口径):

选项说明
actual-server以默认配置(或当前目录 config.json)启动
--config <path>指定配置文件路径
--reset-password重置服务器密码
-v / --version输出版本号
-h / --help帮助

环境变量与 CLI 混用示例(官方文档原文形态):

ACTUAL_DATA_DIR=./custom-directory actual-server --config ./config.json

--reset-password 的价值:password 模式忘记服务器密码时不必删库重建,一条命令交互式重置(密码哈希在 account.sqlite,该命令直接改写它)。

九、参数生效验证清单

配置变更后按此清单逐项验证(全部为实测可用命令):

# 1. 进程起来了且监听端口/地址正确 —— 看启动日志监听行
#    Listening on 0.0.0.0:5007...

# 2. 健康检查通过
curl -sS http://127.0.0.1:8129/health
# {"status":"UP"}

# 3. 登录方式与引导状态符合预期
curl -sS http://127.0.0.1:8129/account/needs-bootstrap

# 4. 数据落点符合预期
find <ACTUAL_DATA_DIR> -maxdepth 2 | sort

# 5. 版本与预期一致(API 侧核对)
# getServerVersion() -> {"version":"26.10.0"}

十、常见问题(FAQ)

10.1 改了环境变量/配置不生效

排查链:确认改在"正确的层"(环境变量会覆盖 config.json,若环境里残留旧变量,改 config.json 无效)→ 确认进程真的重启了(配置仅启动时读取)→ 看启动日志监听行是否变成本次期望值 → 用 /health 与数据目录树交叉验证。实测中环境变量优先级高于 config.json(第二章回显)。

10.2 设了 ACTUAL_UPLOAD_DIR 但上传目录没变

现象:注入该变量后启动正常,但目录树里没有 uploads-dir,上传文件仍落 user-files(本章第七章实测复现)。

原因:v26.10.0 的配置 schema 中不存在该变量,未知 ACTUAL_* 变量被静默忽略。

解法:用 ACTUAL_USER_FILES(或 config.json userFiles)控制上传/预算文件落点。

10.3 全新实例登录报 invalid-password

原因与解法:服务器未 bootstrap(/account/needs-bootstrap 回显 bootstrapped:false 可确认),先走 bootstrap 设置初始密码再登录。完整回显见第五章 5.2。

10.4 OpenID 配置后登录页没有 OpenID 入口

排查链:/account/needs-bootstrap 的 availableLoginMethods 是否含 openid → ACTUAL_OPENID_* 变量是否齐全(discovery 模式四项)→ ACTUAL_OPENID_SERVER_HOSTNAME 是否用了公网可达地址(回调重定向依赖它)→ 提供方回调 URI 是否登记为 https://<服务器>/openid/callback。GitHub 类提供方需 ACTUAL_OPENID_AUTH_METHOD=oauth2。

10.5 开了端到端加密后同步大文件报 413

原因:加密同步走 ACTUAL_UPLOAD_SYNC_ENCRYPTED_FILE_SYNC_SIZE_LIMIT_MB(默认 50MB),密文膨胀后超限。

解法:调大该变量并重启进程。

10.6 多台设备连接后配置互相"污染"?

不会。sync-server 配置(端口/目录/登录方式)只属于服务器进程;客户端侧只有服务器地址与密码两个连接要素。预算文件内容的多端一致性由同步组机制(user-files 下的 group-*.sqlite 消息库)保证,与服务器配置无关。

十一、参考链接