WeKan 在 Sandstorm 上的 Meteor 3.5 / Node.js 24 构建与 MongoDB 3 → FerretDB 迁移方案【免费下载链接】wekanThe Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . PR source translation to imports/i18n/data/en.i18n.json, other translations at https://app.transifex.com/wekan/wekan项目地址: https://gitcode.com/GitHub_Trending/we/wekan本文档对应仓库中的 docs/Platforms/FOSS/Container/Sandstorm/Meteor3/Migration.md该方案目前处于design / not yet implemented状态文中标注TEST的步骤都必须在真实 Sandstorm 实例理想情况是现有 WeKan grain 的副本上验证后才可发布。导读WeKan 的 Sandstorm.spk构建长期依赖 2021 年的 meteor-spk 0.6.0 打包器其内部捆绑的是 2015 年设计的 Node 14 / MongoDB 3.0 运行时。随着 WeKan 主版本升级到 Meteor 3.5要求 Node.js 24旧构建方式已经无法复用。本篇技术指南完整讲解如何打造一个现代化的 WeKan.spk用 Meteor 3.5 Node.js 24 替代旧运行时将打包的 MongoDB 3.0 数据库替换为FerretDB v1内嵌 SQLite并在 grain 首次启动时完成存量数据的自动迁移——直接复用 WeKan snap 已随附的迁移逻辑。读完本文你将掌握Sandstorm grain 的运行时替换与 seccomp 兼容性评估、meteor-spk.deps载荷payload的现代化改造、两阶段迁移niscu 2.x → MongoDB 3.0 → FerretDB的完整启动器设计、迁移导入器importer的内部机制以及管理面板中迁移状态 / 磁盘占用 / 删除旧数据的 Sandstorm 专属 UI 实现。1. 为什么需要重写 Sandstorm 构建当前 Sandstorm 构建使用meteor-spk 0.6.0https://dl.sandstorm.io/meteor-spk-0.6.0.tar.xz构建于 2021-10-23。其meteor-spk.deps载荷是一个 2015 年时代的设计与 WeKan 现在所需的运行时存在巨大落差组件meteor-spk 0.6.0 自带WeKan 需要bin/nodeNode14.17.5nodejs.org 官方原版Node 24.xMeteor 3.5 要求bin/mongodMongoDB3.0.7WiredTiger保留——仅作为只读迁移数据源bin/niscudMongoDB2.xKenton 的 Niscu fork保留用于 niscu→3.0 迁移lib/glibc2.31Ubuntu 20.04在 22.04glibc 2.35上重新生成以支撑 node24/ferretdbstart.js启动 Mongo 3.0 niscu→3.0 迁移重写为面向 FerretDB0.6.0 中的start.js与 0.5.1 功能上完全相同仅做了重新排版。它仍然以--nohttpinterface启动 mongod 3.0——该参数在 MongoDB 3.6 中已被移除因此即使不修改start.js也无法直接塞入更新的 mongod。关于 Node fork 的说明sandstorm-io/nodefork并不需要。它冻结在 Node 8.11.42018 年其唯一的实质补丁是为node-fibers做的 V8 线程表优化——而 Meteor 3 已彻底移除 fibers。meteor-spk 本身已经打包了上游原版Node0.6.0 的bin/node正是官方 nodejs.org 14.17.5 构建。因此直接使用原版 Node 24即可。2. 目标架构迁移完成后的稳态WeKanNode 24通过MongoDB wire 协议与FerretDB v1SQLite通信——整个 grain 中不再运行任何 MongoDB 服务器。mongod 3.0 仅在一次性的迁移过程中被临时使用一次用于读取存量 grain 的旧数据。grain单一沙箱可写目录 /var ├─ /var/wiredTigerDb ← 现有 MongoDB 3.0 数据旧 grain 才有只读迁移源 ├─ /var/files/ ← WRITABLE_PATH新增 │ ├─ attachments/ ← GridFS 附件在此落盘Meteor-Files 存于文件系统 │ ├─ avatars/ ← GridFS 头像在此落盘 │ └─ db/ ← FerretDB SQLite 目录wekan.sqlite← 稳态数据库 └─ /var/.migration-to-ferretdb-done ← 幂等性标记文件grain 内环回端口端口角色4000应用 HTTP位于sandstorm-http-bridge之后4001FerretDB v1稳态——MONGO_URLmongodb://127.0.0.1:4001/wekan4003临时 mongod 3.0迁移只读源从 sandstorm-src/start.js 的实现看实际布局与文档略有差异但保持一致新增了NISCU_PORT 4004专供 niscudniscu→3.0 阶段的数据源以及STATE_DIR /var/ferretdbFerretDB 进程状态state.json的存放位置。这些细节在下面的启动器分析中会展开。3. Sandbox 兼容性基于 sandstorm 源码验证文档作者检查了sandstorm/src/sandstorm/seccomp-bpf/filter.s。grain 的 seccomp 过滤器是一个白名单其默认动作是ENOSYS而非 SIGSYS 杀进程因此 Go/glibc 遇到缺失的系统调用时会优雅地回退。相关结论允许Node 24 / Go FerretDB 所需getrandom、statfs/fstatfs、sched_getaffinity、clone、futex、epoll_*、eventfd2、fork、mmap、mprotect。拒绝 → ENOSYS通过回退容忍membarrier、rseq、clone3、restart_syscall。结论grain 可以同时运行 Node 24 FerretDBGo mongod 3.0——这与当前构建中 mongodnode 的多进程模型完全相同。无需从 sandstorm 平台仓库额外索取任何东西。TEST最高风险项Node 24 的 glibc 在创建线程时使用clone3依赖 ENOSYS→clone回退glibc ≥ 2.34。在投入其余工作之前务必先验证一个最简单的 Node 24 grain 能否成功创建线程。4.meteor-spk.deps载荷变更以 0.6.0 为基础然后替换bin/node为Node 24——使用 Meteor 3.5 dev bundle 中的精确构建通过meteor node -e console.log(process.version)确认版本确保原生插件native addonABI 与 WeKan bundle 匹配。保留bin/mongod3.0.7——作为只读迁移数据源。保留bin/niscudMongoDB 2.x以及旧的捆绑node_modules/{mongodb,bson,mongodb-core,es6-promise,readable-stream}——供start.js的niscu → MongoDB 3.0阶段Stage 1处理非常古老的 grain。对旧版本的迁移支持是永久性的不会被移除。0.6.0 已同时携带二者无需额外添加。新增migratemongo/bin/{mongoexport,mongo}migratemongo/lib/这些 3.x CLI 所需的旧 glibc。导入器用这些工具读取 3.0 数据——现代的 Node 驱动无法与 3.0/3.2 服务器对话。复用与 snap 的migratemongoamd64、x86_64相同的工具。新增ferretdb——直接下载ferretdb-amd64二进制取 wekan/FerretDB 的最新发布与release-all.yml内嵌的ferretdb-amd64资产一致。新增migrate-mongo3-to-ferretdb.mjs——从 snap-src/bin/migrate-mongo3-to-ferretdb.mjs原样复制该脚本完全由环境变量/路径驱动无需任何修改。导入器Stage 2通过NODE_PATHbundle/programs/server/node_modules从 WeKan bundle 解析其mongodb/bson现代驱动独立进程与 Stage 1 使用的旧驱动不冲突。在 Ubuntu 22.04/24.04glibc 2.35上为 node24 ferretdb重新生成lib/、lib64/、usr/lib。将旧库单独保留在migratemongo/lib下供 mongod3/mongoexport 使用双库模式与 snap 用MM_LIB的做法完全一致。0.5.1 fork 的lib/x86_64-linux-gnu/*是指向/home/user/Latauket/...下载文件夹的悬空符号链接。不要继承这些——依赖树必须自包含并在干净的现代基础环境上用gather-deps重新生成。仅支持 amd64Sandstorm 的 arm64 不在本文范围内migratemongo 3.x 二进制是 x86_64 的。5. 重写后的启动器start.js规范启动器已提交在 sandstorm-src/start.jsspk 构建会将其复制到meteor-spk.deps/start.js。它是一个 CJS 启动器由 pkgdef 以... node start.js形式派生。首次启动时它会针对 grain 持有的数据执行迁移链——niscu2.x→ MongoDB 3.0Stage 1保留的遗留路径供非常古老的 grain 使用然后MongoDB 3.0 → FerretDBStage 2——之后在 FerretDB 上运行 WeKan。全新 grain 和已迁移的 grain 直接跳过迁移直接在 FerretDB 上运行。迁移逻辑是 snap 的migration-controlmigrate-mongo3-to-ferretdb.mjs的重新定向re-pathed移植。所有应用载荷路径都从__dirnamegrain 挂载根解析并且在运行导入器之前会等待 mongod 3.0 回应 ping。核心结构精简版完整文件见 sandstorm-src/start.js// meteor-spk.deps/start.js 完整文件见 sandstorm-src/start.js const { spawn, spawnSync } require(child_process); const fs require(fs); const path require(path); const APP_PORT process.env.PORT || 4000; const DB_PORT 4001; // FerretDB 稳态端口 const SRC_PORT 4003; // 临时 mongod 3.0迁移只读 const FILES_DIR /var/files; const SQLITE_DIR path.join(FILES_DIR, db); const OLD_MONGO /var/wiredTigerDb; const MARKER /var/.migration-to-ferretdb-done; const STATUS path.join(FILES_DIR, db, migration-status.json); const APPROOT __dirname; // .meteor-spk/bundle 挂载处 const MM_LIB path.join(APPROOT, migratemongo/lib); for (const d of [FILES_DIR, SQLITE_DIR, path.join(FILES_DIR, attachments), path.join(FILES_DIR, avatars)]) { fs.mkdirSync(d, { recursive: true }); } function startFerret(port) { return spawn(path.join(APPROOT, ferretdb), [--handlersqlite, --sqlite-urlfile:${SQLITE_DIR}/, --listen-addr127.0.0.1:${port}, --telemetrydisable], { stdio: inherit, env: { ...process.env, DO_NOT_TRACK: 1, FERRETDB_TELEMETRY: disable } }); } function migrateIfNeeded() { const hasOld fs.existsSync(path.join(OLD_MONGO, WiredTiger)); if (fs.existsSync(MARKER) || !hasOld) return; // 全新安装或已迁移 console.log(** Migrating MongoDB 3 - FerretDB (one-time) ...); const oldLibEnv { ...process.env, LD_LIBRARY_PATH: ${MM_LIB}:${process.env.LD_LIBRARY_PATH || } }; // 1. 旧 mongod 3.0 服务现有数据只读迁移源 spawnSync(/bin/mongod, [--dbpath, OLD_MONGO, --bind_ip, 127.0.0.1, --port, SRC_PORT, --storageEngine, wiredTiger, --fork, --logpath, /var/migration-mongod.log], { stdio: inherit, env: oldLibEnv }); // 2. FerretDB 目标永久 SQLite 目录——无需临时切换 const ferret startFerret(DB_PORT); // 3. 导入器CLI 读 3.0 - 驱动写 FerretDBGridFS - /var/files/{attachments,avatars} const rc spawnSync(path.join(APPROOT, node), [path.join(APPROOT, migrate-mongo3-to-ferretdb.mjs)], { stdio: inherit, env: { ...process.env, NODE_PATH: path.join(APPROOT, programs/server/node_modules), MONGO_BIN_DIR: path.join(APPROOT, migratemongo/bin), MONGO_LIB: MM_LIB, SRC_PORT, SRC_DB: wekan, TARGET_MONGO_URL: mongodb://127.0.0.1:${DB_PORT}/wekan, FILES_DIR, MIGRATION_PORT: APP_PORT, // 进度仪表盘显示在 grain URL 上 STATUS_FILE: STATUS }, // 导入器将最终状态写入此处见 §6 }).status; try { ferret.kill(); } catch {} spawnSync(/bin/mongo, [--port, SRC_PORT, --quiet, --eval, db.getSiblingDB(admin).shutdownServer()], { env: oldLibEnv }); if (rc 0) { fs.writeFileSync(MARKER, new Date().toISOString()); // 绝不重新迁移旧数据保留 } else { console.error(** Migration failed; will retry on next grain start. Old data kept.); process.exit(1); } } function runApp() { const ferret startFerret(DB_PORT); // 稳态数据库 process.env.MONGO_URL mongodb://127.0.0.1:${DB_PORT}/wekan; process.env.ROOT_URL process.env.ROOT_URL || http://127.0.0.1:${APP_PORT}; process.env.PORT APP_PORT; process.env.WRITABLE_PATH FILES_DIR; process.on(exit, () { try { ferret.kill(); } catch {} }); setTimeout(() require(./main.js), 1500); // 等待 FerretDB 开始监听 } migrateIfNeeded(); runApp();与 snap 相比的设计决策不做临时目录再切换no temp-then-switch迁移直接写入永久 FerretDB SQLite 目录/var/files/db因为在 grain 中从此只有一个数据库。幂等标记文件阻止重复迁移旧/var/wiredTigerDb永远不会被自动删除——由管理员在 UI 中显式删除§7。实际仓库实现中的增强相对设计文档对照 sandstorm-src/start.js 的完整实现可以确认设计文档中描述的骨架已经落地并补充了若干关键细节niscu → MongoDB 3.0 的 WiredTiger 缓存问题在 grain 沙箱中 mongod 3.0 的 RAM 探测返回 0导致其按cache_size0G计算并触发 WiredTiger 中止Fatal Assertion 28561 / Value too small for key cache_size。两个迁移阶段都用--wiredTigerCacheSizeGB 1将缓存钉在 1 GB——mongod 3.0.7 将该选项解析为整数 GB小数如 0.25 会解析失败。它只是缓存上限而非预分配短时迁移不会真正用到这么多但从此不再依赖 RAM 探测。LD_LIBRARY_PATH双库隔离oldLibEnv()把migratemongo/lib旧 glibc前置到LD_LIBRARY_PATH只用于 Mongo 3.x CLI 工具mongoexport/mongonode24/ferretdb 始终使用重新生成的新 glibc 2.35 库互不污染。cpu-exec特征安全启动[#6458]当 bundle 中有bin/cpu-exec时所有捆绑二进制都经它启动因此落在 CPU 缺少某些特性如 AVX 被 QEMU/KVM/Proxmox 等虚拟化屏蔽的主机上的 grain会透明回退到捆绑的 qemu-user而不是以 SIGILL退出码 132瞬间死亡。这些二进制当前都不声明必需特性所以 cpu-exec 目前是零开销直执行但启动路径从此具备特性安全。migration-bridge.js等待页Sandstorm 通过/sandstorm-http-bridge把 grain 嵌入 iframe 并代理到 APP_PORT。在首次启动迁移期间以及向 WeKan 交接的窗口内若该端口上没有任何服务浏览器会显示 This page can not be displayed embedded in another page。因此 sandstorm-src/migration-bridge.js 作为一个子进程HTTP 服务器仅用node:http无外部依赖占用 APP_PORT向所有 URL 返回带自动刷新的 please wait 页面HTTP 503保证 grain 始终保持可嵌入状态。之所以必须是子进程是因为start.js在spawnSync阻塞执行迁移无法在进程内服务监听——杀掉子进程才能确定性地释放端口。FerretDB 的--state-dir即使关闭遥测FerretDB 也会通过其 state provider 持久化state.json其默认--state-dir是.在 grain 中是只读的/若不指向可写目录会报 open /state.json: read-only file system 并导致 grain 崩溃循环。因此启动器显式传入--state-dir/var/ferretdb并设置FERRETDB_STATE_DIR。OpLog 与轮询策略[#6503/#6480/#6481]FerretDB v1 可以 tail OpLog但在 SQLite 后端上 tailableawaitData 的 tail 会让 CPU 空转保持在约 190–390%所以默认采用仅轮询polling-only。通过WEKAN_FERRETDB_OPLOGtrue显式启用 OpLog tail启用后设置MONGO_OPLOG_URL与METEOR_REACTIVITY_ORDERoplog,polling否则删除MONGO_OPLOG_URL并把 reactivity order 设为polling[#6498]仅仅设置了该变量就会让 Meteor 在启动时持续轮询 FerretDB 造成高 CPU。迁移期临时 FerretDB 一律不启用 OpLog不带--repl-set-name以免批量插入被 oplog 记录拖慢。Sandstorm 认证环境变量process.env.SANDSTORM 1必须在require(./main.js)之前设置——packages/wekan-accounts-sandstorm 只在process.env.SANDSTORM存在时才把__meteor_runtime_config__.SANDSTORM注入运行时配置触发客户端基于 header 的自动登录否则 WeKan 能启动但每个页面都显示 Must be logged in。SRC_DB关键细节Sandstorm WeKan grain 的数据存放在 Meteor 默认数据库meteor中niscu→3.0 阶段读写的就是meteor现有 grain 的 WiredTiger catalog 也显示meteor.*集合。因此启动器给导入器传的是SRC_DB: meteor只有 FerretDB目标才是wekan——若按设计文档早期版本传SRC_DBwekan真实 grain 会迁移到零文档。6. 迁移导入器做了什么migrate-mongo3-to-ferretdb.mjs与 snap 版本一致文本集合——逐个用mongoexportExtended JSON从 mongod 3.0 导出再通过现代 Node 驱动insertMany写入 FerretDB批次 200冲突时 upsert。附件 头像——来自 CollectionFS GridFScfs_gridfs.bucket.{files,chunks}cfs.bucket.filerecord每个文件从 chunks 重组后写入/var/files/attachments或/var/files/avatars并在 FerretDB 中插入一条指向该文件系统路径的Meteor-Files记录。这本质上就是 WeKan 的MongoDB → 文件系统附件迁移在此内联完成。Schema 原样保留——由 WeKan 自身的 Board Settings / Migrations 在首次使用时升级。进度通过 HTTP 在MIGRATION_PORT即应用端口上提供服务迁移期间打开 grain 的用户可以看到实时仪表盘。对照 snap-src/bin/migrate-mongo3-to-ferretdb.mjs 的实现还能看到更多细节为何不能用 mongodump/mongorestore现代 Node MongoDB 驱动无法连接 3.2 服务器因此导入器用遗留 MongoDB CLImongoexport能与 3.2 对话读取源用现代 Node 驱动写入 FerretDB——不需要 mongodump/mongorestore也不需要中间的 MongoDB 7。CJS require 锚定anchorMongoDB 和 bson 在 WeKan bundle 中是 CommonJS在 Node 24 的 ESM loader 下import x from cjs的默认互操作会丢失 EJSONEJSON.parse unavailable。因此用createRequire锚定 require 解析并构建多个根候选$SNAP/BUNDLE_ROOT/NODE_PATH/脚本父目录按现代优先顺序遍历已知 bundle 子路径npm-mongo嵌套的 mongodb v6 排在普通 node_modules 之前确保绝不误用古老的 mongodb v2 驱动旧驱动走 legacy OP_QUERYinsert 会报 Unsupported OP_QUERY command: update。进度状态对象state对象包含startedAt、phase、collections、filesattachments/avatars 的 done/bytes、current正在提取的单文件实时进度用于大附件的进度条以及diskFree文件卷实时剩余空间。磁盘空间不足时会中止迁移——磁盘写满会损坏 MongoDB中止时会回滚已部分迁移的文件以归还空间。文档建议的STATUS_FILE写入{ startedAt, finishedAt, phase, success, collections, files, errors }正是管理面板读取的状态 JSON。脚本还支持FILES_ONLYtrue[#6473]做增量附件/头像修复不动文本集合验证并跳过已迁移文件只补缺失的二进制与记录。7. 管理面板 / 附件 / Sandstorm新增仅isSandstorm已实现——服务端server/methods/sandstormMigration.js客户端位于 client/components/settings/attachments.jade / attachments.js 的Sandstorm标签页文案在 imports/i18n/data/en.i18n.json。导入器写入的migration-status.jsonSTATUS_FILE由面板读取。当isSandstorm trueMeteor.settings.public.sandstorm见 models/settings.js时管理面板 / 附件中会出现新的Sandstorm分区位于 Backup 旁边展示迁移状态——成功 / 待处理 / 失败 时间戳与按集合 / 文件统计从/var/files/db/migration-status.json和标记文件读取。原始 MongoDB 磁盘占用——/var/wiredTigerDb下旧 MongoDB 3.0 原始文件当前占用的字节数可回收空间以及 FerretDB SQLite 大小和附件/头像大小作参考。删除原始 MongoDB 文件——一个有防护的按钮删除/var/wiredTigerDb以释放 grain 磁盘。仅当迁移成功标记存在且success:true且 FerretDB 确实持有数据时才启用。服务端方法对照 server/methods/sandstormMigration.js 的完整实现与 server/methods/backup.js 相同的管理员门控方式ReactiveCache.getCurrentUser()isAdminconst OLD_MONGO /var/wiredTigerDb; const STATUS (process.env.WRITABLE_PATH || /var/files) /db/migration-status.json; const MARKER /var/.migration-to-ferretdb-done; function dirBytes(dir) { // 递归统计大小目录缺失时容错 let total 0; const walk d { for (const e of fs.readdirSync(d, { withFileTypes: true })) { const p path.join(d, e.name); if (e.isDirectory()) walk(p); else { try { total fs.statSync(p).size; } catch {} } } }; try { if (fs.existsSync(dir)) walk(dir); } catch {} return total; } Meteor.methods({ async sandstormMigrationStatus() { const user await ReactiveCache.getCurrentUser(); if (!user || !user.isAdmin) return false; let status null; try { status JSON.parse(fs.readFileSync(STATUS, utf8)); } catch {} return { isSandstorm: true, migrationDone: fs.existsSync(MARKER), status, rawMongoBytes: dirBytes(OLD_MONGO), rawMongoExists: fs.existsSync(OLD_MONGO), ferretBytes: dirBytes(path.dirname(STATUS)), }; }, async sandstormDeleteRawMongo() { const user await ReactiveCache.getCurrentUser(); if (!user || !user.isAdmin) throw new Meteor.Error(not-authorized); // 安全防护仅在确认迁移成功之后 if (!fs.existsSync(MARKER)) throw new Meteor.Error(migration-not-done); let ok false; try { ok JSON.parse(fs.readFileSync(STATUS, utf8)).success true; } catch {} if (!ok) throw new Meteor.Error(migration-not-successful); const freed dirBytes(OLD_MONGO); fs.rmSync(OLD_MONGO, { recursive: true, force: true }); return { deleted: true, freedBytes: freed }; }, });实际实现比设计文档还多返回了migrationSuccess仅在记录的迁移以success:true结束时才为 true、attachmentsBytes、avatarsBytes非 Sandstorm 环境返回{ isSandstorm: false }删除方法在非 Sandstorm 下抛not-sandstorm并支持通过SANDSTORM_RAW_MONGO_PATH、SANDSTORM_MIGRATION_MARKER等环境变量覆盖路径以便在 grain 外测试。删除原始文件的操作不可逆其门控条件为迁移success:true 显式确认 推荐输入型确认。客户端client/components/settings/attachments.js 中新增的 tab 仅在isSandstorm时渲染轮询sandstormMigrationStatus类似 Backup 标签页轮询backupStatus渲染状态 人类可读的大小filesize以及一个在确认对话框之后调用sandstormDeleteRawMongo并刷新的Delete raw MongoDB files按钮。从当前仓库快照看该 tab 相关代码大部分以注释形式保留在 attachments.js 中sandstormStatus、sandstormDeleteDisabled、click .js-sandstorm-delete-raw-mongodb等可推断为客户端集成仍在按此设计落地中。8. pkgdef / 环境变量变更sandstorm-pkgdef.capnpWRITABLE_PATH/var/wekan-uploads→/var/files。不要在environ中设置MONGO_URL由 start.js 设为 FerretDB 端口。递增appVersion和appMarketingVersion。保留argv [/sandstorm-http-bridge, 4000, --, node, start.js]。SANDSTORM1和METEOR_SETTINGS{public:{sandstorm:true}}已存在——驱动新管理面板的isSandstorm。对照当前 sandstorm-pkgdef.capnp 的environ可看到迁移方案已部分落地WRITABLE_PATH/var/files注释明确说明 files root 服务于 Node24/FerretDB 构建附件/头像与 FerretDB SQLitedb/都在/var/files下、SANDSTORM1、METEOR_SETTINGS{public: {sandstorm: true}}均已就位同时还保留了DDP_TRANSPORTsockjs——该值使 grain 通过 sockjs 走 DDP从而允许 bundle 裁剪releases/bundle-trim.mjs移除全部 121 MB 的 uWebSockets.js 预编译二进制这是让包体积符合 Sandstorm 1 GiB 限制的重要一环tests/bundleTrim.test.cjs 将二者绑定防止漂移。注意当前 capnp 的argv仍是./start-memory.sh旧启动路径与设计文档目标node start.js存在差异这属于设计未完全落地清单的一部分。9. 构建 / CI.github/workflows/sandstorm.yml基础 上游 meteor-spk 0.6.0从https://dl.sandstorm.io/meteor-spk-0.6.0.tar.xz下载。它已包含bin/mongod3.0.7、bin/niscud2.x以及 niscu→3.0 阶段所需的旧node_modules。不需要旧的projects.7z它只含 0.4.1/0.5.0/0.5.1且其替换的 node 过于古老——反正我们要装 Node 24。死掉的releases.wekan.team/dev/meteor-spk/projects.7z拉取被移除。装配脚本sandstorm-src/build-deps.sh现代化meteor-spk.deps换入 Node 24添加ferretdb-amd64来自 wekan/FerretDB 发布的按架构资产添加migratemongo/{bin,lib}mongoexportmongo 旧库复制 sandstorm-src/start.js →meteor-spk.deps/start.js、snap-src/bin/migrate-mongo3-to-ferretdb.mjs →meteor-spk.deps/保留niscud并在 ubuntu-24.04 上用gather-deps重新生成库树。额外的二进制Node 24、ferretdb-amd64、migratemongo CLI从GitHub release 资产获取——releases.wekan.team已不存在所以任何所需构建文件都放在 GitHub releases 上。对照 .github/workflows/sandstorm.yml 的实际 CI工作流在ubuntu-24.04上运行需要显式放宽 unprivileged user namespacesUbuntu 24.04 默认kernel.apparmor_restrict_unprivileged_userns1会阻断 Sandstorm 安装与 spk supervisor 使用的沙箱这也是文档中fragile part的出处。装配步骤注释明确写明了Base upstream meteor-spk 0.6.0 ... 无依赖已退役的 releases.wekan.team 或旧 projects.7z且build-deps.sh的 [bridge] 步骤会用/opt/sandstorm/latest/bin/sandstorm-http-bridge覆盖捆绑的古老桥接二进制因此Sandstorm 必须先于装配步骤安装。打包阶段以meteor-spk pack生成wekan-sandstorm-YYYY_MM_DD-HH_MM_SS.spk并警告超过 Cloudflare 100 MB 上传上限时应通过 DNS-only 主机 / SSH 隧道安装或继续裁剪 bundle。签名密钥来自SANDSTORM_KEYRINGsecret写入$HOME/.sandstorm-keyring。10. 风险与测试清单TESTNode 24 在 seccomp 下的线程创建clone3→clone ENOSYS 回退。最高风险。TESTmongod 3.0.7 在旧库环境下于 grain 内打开/var/wiredTigerDb。TESTFerretDBGo在 grain 内启动并开始监听。TEST在真实旧 WeKan grain 的副本上跑完整迁移文本 附件 头像。TEST全新安装无/var/wiredTigerDb跳过迁移直接在空 FerretDB 上运行。TESTgrain 磁盘余量——迁移期间会同时持有旧 Mongo 新 SQLite 提取出的文件需在 grain 配额内。TESTLD_LIBRARY_PATH隔离——node24/ferretdb 用新的 2.35 库mongo-3.0 CLI 用旧库无交叉污染。TEST管理面板 / 附件 / Sandstorm状态准确性、磁盘数字、受防护的删除按钮。11. 开放问题FerretDB v1 SQLite 文件名已确认——对于wekan库后端写入sqlite-dir/wekan.sqlite外加 SQLite WAL 伴生文件wekan.sqlite-shm/wekan.sqlite-wal。因此对db/目录的磁盘用量求和即可覆盖全部。Bundle 在 grain 中的挂载根sandstorm-src/start.js 从__dirname解析应用载荷路径因此bin/mongod、ferretdb、migratemongo/、main.js都相对于启动器。需要从打包后的 spk 确认新增二进制落在start.js期望的位置例如migratemongo/和ferretdb在挂载根、CLI 在migratemongo/bin。旧版本niscu 2.x 和 MongoDB 3.0的迁移支持是永久性的——不会移除因此niscud、mongod 3.0 和基于 CLI 的导入器会一直留在包里。总结这份设计方案的核心价值在于它没有为 Sandstorm 平台另起炉灶而是最大化复用 WeKan 生态已有的迁移资产——migrate-mongo3-to-ferretdb.mjs导入器与migratemongo工具直接从 snap 移植start.js采用双库 幂等标记 失败重试的模式管理面板提供受防护的旧数据回收入口。当文档中标注的TEST项全部通过后Sandstorm 上的 WeKan grain 将告别 2015 年的 Node 14 / MongoDB 3.0 组合平稳过渡到 Meteor 3.5 / Node 24 / FerretDB(SQLite) 的现代架构同时保持对最古老 grainniscu 2.x 时代的永久迁移兼容。【免费下载链接】wekanThe Open Source kanban, built with Meteor. GitHub issues/PRs are only for FLOSS Developers, not for support, support is at https://wekan.fi/commercial-support/ . PR source translation to imports/i18n/data/en.i18n.json, other translations at https://app.transifex.com/wekan/wekan项目地址: https://gitcode.com/GitHub_Trending/we/wekan创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考