Actual Budget 23.12.1 解析:actual-server 迁移状态存储(migrations statestore)迁入数据目录的修复与实现
Actual Budget 23.12.1 解析actual-server 迁移状态存储migrations statestore迁入数据目录的修复与实现【免费下载链接】actualA local-first personal finance app项目地址: https://gitcode.com/GitHub_Trending/ac/actualActual Budgetlocal-first 个人财务管理应用在 23.12.1 版本中发布了一次小而关键的服务器端修复将 actual-server 的数据库迁移状态存储migrations statestore从应用根目录移动到数据目录datadir。本指南以该发布说明为核心结合当前仓库源码逐层拆解修复动机、底层实现、数据目录布局与验证方法帮助读者理解为何一个看似只有一行改动的补丁对 Synology NAS 等容器化部署场景至关重要并掌握迁移机制的运行原理。发布背景一次纯粹的服务器端 Bugfix2023-12-07-release-23.12.1.md 明确指出This release does not have any UI changes or feature improvements. It features onlyactual-serverfix for Synology NAS users.也就是说23.12.1 版本没有任何 UI 变化或功能改进是纯actual-server的修复版本其 Docker 镜像标签为23.12.1。虽然该文档发布于 2023 年 12 月但其中修复的问题与当前的 migrations.ts 实现一脉相承——我们可以通过阅读当前仓库源码验证该修复在今天代码库中的最终形态并理解其完整的工程上下文。修复的核心对象迁移状态存储migrations statestore迁移机制与 stateStore 的作用actual-server 使用 npm 的migrate库管理数据库结构变更schema migration。与大多数迁移工具一样它除了要按顺序执行迁移脚本外还必须记录哪些迁移已经执行过这个记录位置就是stateStore——迁移状态存储。迁移状态存储通常是一个 JSON 文件保存已完成迁移的清单与当前指针。如果它丢失或不可写服务端会无法判断当前数据库处于哪个版本可能导致重复执行迁移、启动失败或数据一致性问题。修复前后行为对比修复前stateStore 写入应用根目录application root。在容器化部署中应用根目录是镜像内部路径如/app一旦容器重建、镜像更新或根文件系统被重置该状态文件随之丢失或不可写。修复后23.12.1 起stateStore 写入数据目录datadir。数据目录通常通过 Docker 卷持久化容器重建后迁移状态依然保留服务端能够正确判断已完成的迁移。这正是该修复专为 Synology NAS 用户发布的原因NAS 部署场景中用户数据如/data被挂载为持久化存储而应用目录在套件/容器更新时会被重建。将迁移状态与用户数据放在同一持久卷中可以推断是让迁移状态跟随数据长期存活的稳妥做法。源码级解读当前实现中的 stateStore 构建迁移入口与状态存储路径当前仓库中迁移的完整实现位于 packages/sync-server/src/migrations.ts。核心逻辑如下// packages/sync-server/src/migrations.ts import path from node:path; import { load } from migrate; import { config } from ./load-config; export async function run(direction: up | down up): Promisevoid { // ... return new Promisevoid((resolve, reject) { load( { stateStore: ${path.join(config.get(dataDir), .migrate)}${config.get(mode) test ? -test : }, migrations: migrationsModules, }, (err, set) { if (err) return reject(err); setdirection return reject(err); console.log(Migrations: DONE); resolve(); }); }, ); }); }关键点stateStore指向dataDir下的.migrate文件即path.join(config.get(dataDir), .migrate)。数据目录通常解析为/data因此实际状态文件为/data/.migrate——这印证了 23.12.1 发布说明中Store the migrations statestore in the datadir instead of the application root的描述。测试模式使用独立状态文件当mode test时路径追加-test后缀即/data/.migrate-test避免测试执行迁移污染生产状态文件。load()由migrate库提供set[direction]执行up应用迁移或down回滚迁移。迁移模块的加载方式同一个文件顶部使用 Vite 的import.meta.glob批量加载迁移脚本const migrationsLoaders import.meta.globMigrationModule( ../migrations/*.{ts,js}, );这意味着 Vite 在构建时会将 packages/sync-server/migrations 目录下的所有*.ts/*.js文件内联为按文件名排序的动态 import 映射运行时不再依赖对迁移目录的fs读取——每次构建后新增的迁移文件都会被打包进产物与 23.12.1 时代的文件系统扫描方案相比迁移发现机制更稳定、更易在打包后的单文件部署中工作。数据目录datadir的解析逻辑stateStore 落在数据目录中那么数据目录本身是如何确定的这由 packages/sync-server/src/load-config.js 中的 convict 配置 schema 决定const projectRoot path.dirname(__dirname).replace(/[\\/]build$/, ); const defaultDataDir process.env.ACTUAL_DATA_DIR ? process.env.ACTUAL_DATA_DIR : fs.existsSync(/data) ? /data : projectRoot;解析优先级如下若设置了环境变量ACTUAL_DATA_DIR则优先采用其值否则若文件系统存在/data目录Docker 镜像中的标准数据挂载点则使用/data兜底回退到projectRoot应用项目根目录。对应的 convict 配置项为dataDir: { doc: Default data directory., format: String, default: process.env.NODE_ENV test ? projectRoot : defaultDataDir, env: ACTUAL_DATA_DIR, },在测试环境NODE_ENVtest下dataDir默认回退到projectRoot确保测试不会向宿主机的/data写入任何内容。配置文件的加载顺序同样值得注意load-config.js若设置了ACTUAL_CONFIG_PATH从该路径加载config.json否则依次尝试projectRoot/config.json与dataDir/config.json找到后通过configSchema.loadFile()载入并执行configSchema.validate({ allowed: strict })严格校验。也就是说配置文件同样支持放在数据目录中这与 stateStore 迁移到数据目录的设计思路一致数据目录是部署实例的单一事实来源容器重建不影响配置与迁移状态。数据目录内的完整布局从当前源码可以还原出数据目录默认/data下的典型结构路径内容来源/data/.migrate迁移状态存储stateStoremigrations.ts/data/config.json服务器配置文件可选load-config.js/data/server-files/服务端文件用户密钥、同步元数据等load-config.js/data/user-files/用户数据库文件load-config.js其中user-files目录内的用户数据由 packages/sync-server/src/util/paths.ts 管理export function getPathForUserFile(fileId: FileId) { return join(resolve(config.get(userFiles)), file-${fileId}.blob); } export function getPathForGroupFile(groupId: GroupId) { return join(resolve(config.get(userFiles)), group-${groupId}.sqlite); }每个用户文件对应file-id.blob同步块每组用户数据库对应group-id.sqlite。这些路径与迁移状态一样均以dataDir为根——这就是为什么把.migrate也放入dataDir能够实现整个实例只需持久化一个目录的目标。第一个初始化迁移 1694360000000-create-folders.js 正是负责在首次启动时创建这两个目录export const up async function () { await ensureExists(config.get(serverFiles)); await ensureExists(config.get(userFiles)); };修复的验证与部署实践验证迁移状态已写入数据目录修复生效后可按下述方式验证确保 Docker 部署中将数据目录挂载为持久卷如-v /path/to/actual-data:/data首次启动 actual-server让其完成初始化迁移检查数据卷中是否存在状态文件ls -la /path/to/actual-data/.migrate若该文件存在且内容记录了已完成迁移清单说明 stateStore 已按 23.12.1 的设计落到数据目录中随后重建容器该文件应依然保留服务端能基于它正确跳过已执行的迁移。常用配置项速查以下配置项与数据目录/迁移直接相关均可通过环境变量或config.json设置详见 load-config.js环境变量配置项默认值说明ACTUAL_DATA_DIRdataDir/data存在时否则projectRoot数据目录根迁移状态、用户文件、配置文件的存放位置ACTUAL_SERVER_FILESserverFilesdataDir/server-files服务端文件目录ACTUAL_USER_FILESuserFilesdataDir/user-files用户数据目录ACTUAL_CONFIG_PATH—projectRoot/config.json→dataDir/config.json显式指定配置文件路径ACTUAL_PORTport5006服务监听端口ACTUAL_HOSTNAMEhostname::服务监听地址ACTUAL_LOGIN_METHODloginMethodpassword登录方式password/header/openid手动执行迁移除服务启动时的自动迁移外仓库还提供了手动迁移入口 packages/sync-server/src/scripts/run-migrations.jsimport { run } from #migrations; const direction process.argv[2] || up; run(direction).catch(err { console.error(Migration failed:, err); process.exit(1); });对应的 npm 脚本定义在 packages/sync-server/package.jsondb:migrate: yarn build cross-env NODE_ENVdevelopment node build/scripts/run-migrations.js up, db:downgrade: yarn build cross-env NODE_ENVdevelopment node build/scripts/run-migrations.js down, db:test-migrate: yarn build cross-env NODE_ENVtest node build/scripts/run-migrations.js up, db:test-downgrade: yarn build cross-env NODE_ENVtest node build/scripts/run-migrations.js down手动迁移时通过第二个参数指定方向up应用所有未执行的迁移down回滚。注意测试与开发环境使用独立的 stateStore 文件.migrate-test避免相互干扰。迁移框架的整体运行链路综合当前源码actual-server 的迁移体系由以下环节构成配置解析load-config.js 通过 convict 解析环境变量与config.json确定dataDir、mode等关键配置迁移发现migrations.ts 用import.meta.glob在构建期收集 migrations 目录 下全部迁移模块并按文件名排序状态读取migrate库从dataDir/.migrate测试模式为.migrate-test读取已完成迁移记录执行与记录依次执行未完成的迁移up/down完成后再将状态写回同一 stateStore 文件结果输出控制台打印Migrations: DONE或抛出异常导致进程以非零码退出见 run-migrations.js。对迁移执行结果的断言同样可以在测试中验证仓库的同步服务测试 app-sync.test.ts 覆盖了用户文件列表等接口行为而迁移本身通过db:test-migrate脚本在NODE_ENVtest下运行确保数据库结构在测试环境中始终处于最新状态。结语23.12.1 虽然只是 actual-server 的一次纯修复发布却揭示了一个重要的部署原则凡是容器重建后必须保留的状态都必须放在持久化的数据目录中。将迁移状态存储从应用根目录迁入数据目录/data/.migrate使迁移记录与用户数据、配置文件共同构成完整的持久层彻底规避了 Synology NAS 等容器化场景下应用目录被重置导致迁移状态丢失的风险。这一设计在当前仓库中依然完整保留并演进出更成熟的形态构建期静态收集迁移模块、测试/开发环境状态隔离、支持up/down双向迁移与手动触发脚本。理解这层机制有助于在实际部署 actual-server 时正确规划数据卷挂载、排查迁移异常以及安全地执行数据库结构升级。相关阅读发布说明原文packages/docs/blog/2023-12-07-release-23.12.1.md迁移实现packages/sync-server/src/migrations.ts配置加载packages/sync-server/src/load-config.js迁移脚本目录packages/sync-server/migrations同步服务测试packages/sync-server/src/app-sync.test.ts【免费下载链接】actualA local-first personal finance app项目地址: https://gitcode.com/GitHub_Trending/ac/actual创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考