2026-07-03 13:24:20 +08:00
|
|
|
|
# MNote control-plane Turso / libSQL 运行手册
|
|
|
|
|
|
|
|
|
|
|
|
> 状态:首轮 Rust control-plane cutover 手册。
|
|
|
|
|
|
> 范围:只覆盖 MNote Rust control-plane;本地 `.md`、附件、mindmap、OnlyOffice 文件仍是 local-first 文件真相,不迁入 Turso。
|
|
|
|
|
|
|
|
|
|
|
|
## 后端选择
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-07-03 23:20:16 +08:00
|
|
|
|
# 默认本地 libSQL 运行时(无云依赖)
|
2026-07-03 13:24:20 +08:00
|
|
|
|
export MNOTE_CONTROL_PLANE_BACKEND=libsql-local
|
|
|
|
|
|
export MNOTE_TURSO_LOCAL_PATH=/mnt/Data1T/Mnote_data/control-plane/control-plane-libsql.db
|
|
|
|
|
|
|
|
|
|
|
|
# 真实 Turso remote
|
|
|
|
|
|
export MNOTE_CONTROL_PLANE_BACKEND=turso-remote
|
|
|
|
|
|
export MNOTE_TURSO_DATABASE_URL=libsql://...
|
|
|
|
|
|
export MNOTE_TURSO_AUTH_TOKEN=...
|
|
|
|
|
|
|
|
|
|
|
|
# Turso local replica
|
|
|
|
|
|
export MNOTE_CONTROL_PLANE_BACKEND=turso-local-replica
|
|
|
|
|
|
export MNOTE_TURSO_LOCAL_REPLICA_PATH=/mnt/Data1T/Mnote_data/control-plane/control-plane-replica.db
|
|
|
|
|
|
export MNOTE_TURSO_DATABASE_URL=libsql://...
|
|
|
|
|
|
export MNOTE_TURSO_AUTH_TOKEN=...
|
|
|
|
|
|
|
|
|
|
|
|
# libSQL synced database
|
|
|
|
|
|
export MNOTE_CONTROL_PLANE_BACKEND=turso-synced
|
|
|
|
|
|
export MNOTE_TURSO_SYNCED_PATH=/mnt/Data1T/Mnote_data/control-plane/control-plane-synced.db
|
|
|
|
|
|
export MNOTE_TURSO_DATABASE_URL=libsql://...
|
|
|
|
|
|
export MNOTE_TURSO_AUTH_TOKEN=...
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Sync interval for turso-local-replica / turso-synced
|
|
|
|
|
|
export MNOTE_TURSO_SYNC_INTERVAL_MS=5000
|
|
|
|
|
|
## 当前决策边界
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-03 已配置真实 Turso dev DB/token,但当前不把 Turso Cloud 作为 MNote 本地功能测试的前置条件,也不把它当成本地文件网盘。
|
|
|
|
|
|
|
|
|
|
|
|
- 本地 `.md`、附件、mindmap、OnlyOffice 文件仍是 local-first 文件真相,不迁入 Turso。
|
|
|
|
|
|
- Turso 只承接 Rust control-plane 元数据:auth、session、workspace、grant、AI policy/runtime、audit/outbox 等。
|
2026-07-28 17:04:27 +08:00
|
|
|
|
- `dev-hot`、`mnote-web-hot`、prod runtime 默认使用 `libsql-local`;`sqlite` 不再是 mnote-web 运行时后端。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
- `turso-remote` / `turso-local-replica` / `turso-synced` 只作为 dev DB dry-run、迁移演练、回滚演练和后续受保护 smoke。
|
|
|
|
|
|
- remote 成为长期候选默认前,必须先完成 Argon2id password hash migration、remote smoke、rollback export 和 token 轮换策略。
|
|
|
|
|
|
|
|
|
|
|
|
## 本机私有 Turso env
|
|
|
|
|
|
|
|
|
|
|
|
真实 URL/token 不写入仓库。当前本机私有配置文件:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
/mnt/Data1T/Mnote_data/control-plane/turso.env
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
要求:
|
|
|
|
|
|
|
|
|
|
|
|
- 文件权限必须是 `0600`。
|
|
|
|
|
|
- 只在本机 shell 中 `source`,不要复制到设计文档、日志、issue、截图或 commit。
|
|
|
|
|
|
- repo 内不得出现 JWT 明文、`MNOTE_TURSO_AUTH_TOKEN=<jwt>` 或带 token 的 libSQL URL。
|
|
|
|
|
|
- `tmp/turso.md` 仅作为本机临时凭据来源,已被 `tmp/` ignore,不纳入版本管理。
|
|
|
|
|
|
|
|
|
|
|
|
加载方式:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
set -a
|
|
|
|
|
|
source /mnt/Data1T/Mnote_data/control-plane/turso.env
|
|
|
|
|
|
set +a
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
确认不打印 token:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
printf 'backend=%s\n' "$MNOTE_CONTROL_PLANE_BACKEND"
|
|
|
|
|
|
printf 'url_host=%s\n' "$(printf '%s' "$MNOTE_TURSO_DATABASE_URL" | sed -E 's#^libsql://([^/]+).*#\1#')"
|
|
|
|
|
|
test -n "$MNOTE_TURSO_AUTH_TOKEN" && echo 'token_present=yes'
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 初始化
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
cargo run --manifest-path rust/Cargo.toml -p control-plane --bin control-plane-admin -- \
|
|
|
|
|
|
init \
|
|
|
|
|
|
--backend "$MNOTE_CONTROL_PLANE_BACKEND"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
默认会创建测试账号:
|
|
|
|
|
|
|
|
|
|
|
|
- 邮箱:`mnote.e2e@example.com`
|
|
|
|
|
|
- 用户名:`mnote-e2e`
|
|
|
|
|
|
- 默认 workspace、directory grant、AI policy、UI preference、audit/outbox 初始化记录
|
|
|
|
|
|
|
|
|
|
|
|
可先 dry-run:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
cargo run --manifest-path rust/Cargo.toml -p control-plane --bin control-plane-admin -- \
|
|
|
|
|
|
init \
|
|
|
|
|
|
--backend "$MNOTE_CONTROL_PLANE_BACKEND" \
|
|
|
|
|
|
--dry-run
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## SQLite 到 libSQL / Turso 迁移
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
cargo run --manifest-path rust/Cargo.toml -p control-plane --bin control-plane-admin -- \
|
|
|
|
|
|
migrate-sqlite-to-target \
|
|
|
|
|
|
--source /mnt/Data1T/Mnote_data/control-plane/control-plane.db \
|
|
|
|
|
|
--backend "$MNOTE_CONTROL_PLANE_BACKEND" \
|
|
|
|
|
|
--dry-run
|
|
|
|
|
|
|
|
|
|
|
|
cargo run --manifest-path rust/Cargo.toml -p control-plane --bin control-plane-admin -- \
|
|
|
|
|
|
migrate-sqlite-to-target \
|
|
|
|
|
|
--source /mnt/Data1T/Mnote_data/control-plane/control-plane.db \
|
|
|
|
|
|
--backend "$MNOTE_CONTROL_PLANE_BACKEND" \
|
|
|
|
|
|
--reset-target
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-07-03 23:20:16 +08:00
|
|
|
|
## 导出到 SQLite 备份
|
|
|
|
|
|
|
|
|
|
|
|
SQLite 仅作为 control-plane-admin 迁移 / 导出格式和测试隔离实现,不再作为 mnote-web runtime fallback。需要离线备份时执行:
|
2026-07-03 13:24:20 +08:00
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
cargo run --manifest-path rust/Cargo.toml -p control-plane --bin control-plane-admin -- \
|
|
|
|
|
|
export-target-to-sqlite \
|
|
|
|
|
|
--backend "$MNOTE_CONTROL_PLANE_BACKEND" \
|
2026-07-03 23:20:16 +08:00
|
|
|
|
--output /mnt/Data1T/Mnote_data/control-plane/control-plane.backup.sqlite \
|
2026-07-03 13:24:20 +08:00
|
|
|
|
--backup-existing
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-07-28 17:04:27 +08:00
|
|
|
|
备份文件仅用于审计、对照和后续手工恢复,不作为 `npm run dev:hot` / `npm run dev:hot` 启动后端。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
|
|
|
|
|
|
## 验证命令
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
cargo check --manifest-path rust/Cargo.toml -p mnote-web
|
|
|
|
|
|
cargo test --manifest-path rust/Cargo.toml -p control-plane
|
|
|
|
|
|
cargo test --manifest-path rust/Cargo.toml -p control-plane --features turso-unit-tests -- --test-threads=1
|
2026-07-03 23:20:16 +08:00
|
|
|
|
cargo test --manifest-path rust/Cargo.toml -p mnote-web app_state_initializes_control_plane_store_for_tests
|
2026-07-03 13:24:20 +08:00
|
|
|
|
cargo test --manifest-path rust/Cargo.toml -p mnote-web dev_seed -- --test-threads=1
|
|
|
|
|
|
cargo test --manifest-path rust/Cargo.toml -p mnote-web local_folder_watcher -- --test-threads=1
|
2026-07-28 17:04:27 +08:00
|
|
|
|
node --test scripts/mnote-web-hot.test.js
|
2026-07-03 13:24:20 +08:00
|
|
|
|
node scripts/task-dev-hot-plan-test.js
|
|
|
|
|
|
node scripts/task-control-plane-admin-libsql-roundtrip-smoke.js
|
|
|
|
|
|
codegraph sync .
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
脚本审计:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
rg -n "sqlite3|better-sqlite3|node:sqlite|MNOTE_CONTROL_PLANE_DB_PATH|control-plane\\.(sqlite|db)" scripts rust/crates \
|
|
|
|
|
|
-g '!target' -g '!node_modules' -g '!recycle'
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
允许命中范围:
|
|
|
|
|
|
|
|
|
|
|
|
- `control-plane-admin` CLI
|
2026-07-03 23:20:16 +08:00
|
|
|
|
- `control-plane-admin` 的 SQLite 迁移 / 导出参数
|
|
|
|
|
|
- `scripts/lib/control-plane-test-env.js` 的显式测试后端参数
|
2026-07-28 17:04:27 +08:00
|
|
|
|
- `mnote-web-hot` / `dev-hot` / `prod-build-start` 对 `sqlite` runtime 的拒绝逻辑
|
2026-07-03 13:24:20 +08:00
|
|
|
|
- legacy `evidence.sqlite` / local search 测试边界
|
|
|
|
|
|
|
|
|
|
|
|
不允许命中范围:
|
|
|
|
|
|
|
|
|
|
|
|
- smoke 脚本直接调用 `sqlite3` CLI 写 control-plane DB
|
|
|
|
|
|
- Node 脚本用 SQLite 库写生产 control-plane DB
|
|
|
|
|
|
|
|
|
|
|
|
## 故障边界
|
|
|
|
|
|
|
|
|
|
|
|
- 缺 URL/token:应返回 `InvalidInput` 或启动时报清晰错误,不允许 panic。
|
|
|
|
|
|
- 本地 libSQL path 不可写:应返回 storage error,不允许 panic。
|
|
|
|
|
|
- remote 网络不可达、5xx、限流、replica 同步失败:属于后续云端同步/备份链路验收,必须在真实 Turso dev DB 配好后补注入测试。
|
2026-07-03 23:20:16 +08:00
|
|
|
|
- 真实 remote / replica / synced 未验证前,本地功能测试优先使用 `libsql-local`;不再回落到 SQLite runtime。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
|
|
|
|
|
|
## Turso dev DB 后续云端验证项
|
|
|
|
|
|
|
|
|
|
|
|
remote / local-replica / synced 更接近云端同步、备份和多设备准备层;它们不影响当前 local-first 本地功能测试。真实 Turso 验证需要:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
export MNOTE_TURSO_DATABASE_URL=libsql://...
|
|
|
|
|
|
export MNOTE_TURSO_AUTH_TOKEN=...
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
然后执行:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
set -a
|
|
|
|
|
|
source /mnt/Data1T/Mnote_data/control-plane/turso.env
|
|
|
|
|
|
set +a
|
|
|
|
|
|
|
|
|
|
|
|
MNOTE_CONTROL_PLANE_BACKEND=turso-remote \
|
|
|
|
|
|
cargo run --manifest-path rust/Cargo.toml -p control-plane --bin control-plane-admin -- init --backend turso-remote
|
|
|
|
|
|
|
|
|
|
|
|
MNOTE_CONTROL_PLANE_BACKEND=turso-remote \
|
|
|
|
|
|
cargo run --manifest-path rust/Cargo.toml -p control-plane --bin control-plane-admin -- \
|
|
|
|
|
|
migrate-sqlite-to-target \
|
|
|
|
|
|
--source /mnt/Data1T/Mnote_data/control-plane/control-plane.db \
|
|
|
|
|
|
--backend turso-remote \
|
|
|
|
|
|
--dry-run
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
当前已完成的非破坏性 remote dry-run:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
set -a
|
|
|
|
|
|
source /mnt/Data1T/Mnote_data/control-plane/turso.env
|
|
|
|
|
|
set +a
|
|
|
|
|
|
|
|
|
|
|
|
cargo run --manifest-path rust/Cargo.toml -q -p control-plane --bin control-plane-admin -- \
|
|
|
|
|
|
init --backend turso-remote --dry-run
|
|
|
|
|
|
|
|
|
|
|
|
cargo run --manifest-path rust/Cargo.toml -q -p control-plane --bin control-plane-admin -- \
|
|
|
|
|
|
migrate-sqlite-to-target \
|
|
|
|
|
|
--source /mnt/Data1T/Mnote_data/control-plane/control-plane.db \
|
|
|
|
|
|
--backend turso-remote \
|
|
|
|
|
|
--dry-run
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-03 结果:
|
|
|
|
|
|
|
|
|
|
|
|
- `init --backend turso-remote --dry-run` 返回 `ok=true`。
|
|
|
|
|
|
- `migrate-sqlite-to-target --backend turso-remote --dry-run` 返回 `ok=true`,并成功读取本地 SQLite 源库表计数,`targetBackend=TursoRemote`。
|
|
|
|
|
|
- 这只证明 URL/token 和 admin CLI 配置可用,不代表 remote 已成为默认后端。
|
|
|
|
|
|
|
|
|
|
|
|
通过后再进入 remote / replica 默认切换;不要把 OpenHub SQLite 和 Rust control-plane 首轮本地 cutover 绑死。
|
|
|
|
|
|
|
|
|
|
|
|
## 后续优化 checklist
|
|
|
|
|
|
|
|
|
|
|
|
- [x] 增加 `MNOTE_TURSO_SYNC_INTERVAL_MS`,让 remote replica / synced 同步间隔可配置。
|
|
|
|
|
|
- [ ] remote replica 启动后做 explicit initial sync,并验证 `read_your_writes` 行为。
|
|
|
|
|
|
- [ ] 新增 control-plane health endpoint,仅暴露 backend/mode/schema/sync 状态,不暴露 token。
|
|
|
|
|
|
- [ ] Argon2id password migration 完成前,不把 remote Turso 作为长期默认。
|
|
|
|
|
|
- [ ] secret-gated `turso-dev` store contract smoke:init、migration、session、grant、audit/outbox、export rollback。
|
|
|
|
|
|
- [ ] browser QA 覆盖 `turso-remote` 和 `turso-local-replica`,但失败不阻塞本地 `libsql-local` smoke。
|
|
|
|
|
|
|
|
|
|
|
|
## P0 / P1 / P2 状态
|
|
|
|
|
|
|
|
|
|
|
|
当前已完成的是 local cutover 和安全基线,不是所有 Turso 优势都已经落地:
|
|
|
|
|
|
|
|
|
|
|
|
- P0 已完成:唯一写入口、脚本直写清退、`libsql-local` store、migration、admin roundtrip、browser QA、local 多线程并发防损坏测试、remote dry-run 凭据验证、`MNOTE_TURSO_SYNC_INTERVAL_MS`。
|
|
|
|
|
|
- P0 未完成:explicit initial sync、control-plane health endpoint、Argon2id password migration。
|
|
|
|
|
|
- P1 未完成:读写并发性能优化。当前 `TursoControlPlaneStore` 仍是单 `Mutex<TursoConnection>`,读写都串行;这是故意保守的安全基线,不是最终性能设计。
|
|
|
|
|
|
- P2/P3 暂缓:CDC、Tantivy FTS、vector/hybrid search、Browser WASM/OPFS、OpenHub SQLite 迁移。
|
|
|
|
|
|
|
|
|
|
|
|
## 读写并发设计
|
|
|
|
|
|
|
|
|
|
|
|
当前实现优先保证一致性和可回滚:
|
|
|
|
|
|
|
|
|
|
|
|
- 单进程内通过 `Mutex<TursoConnection>` 串行化 control-plane 操作。
|
|
|
|
|
|
- 脚本和 smoke 不再直接写生产 control-plane DB,降低多进程直写损坏风险。
|
|
|
|
|
|
- local 多线程测试已覆盖 session、sidebar shortcut、AI runtime event、audit/outbox 并发写。
|
|
|
|
|
|
|
|
|
|
|
|
后续性能优化需要单独实施:
|
|
|
|
|
|
|
|
|
|
|
|
- 写入路径保持单写者或独占写锁;多表写入必须显式事务。
|
|
|
|
|
|
- 读路径可以评估 read connection pool 或 `RwLock`,但必须先按 store 方法分类只读/写入,并跑压力测试。
|
|
|
|
|
|
- remote replica 只适合本地读副本;写仍转发 remote primary,不把 embedded replica 当成高并发本地写主路径。
|
|
|
|
|
|
- 在同步 trait 未改 async 前,不扩大 `block_in_place` 使用面。
|
|
|
|
|
|
|
|
|
|
|
|
## CDC / Tantivy 评估
|
|
|
|
|
|
|
|
|
|
|
|
CDC 当前不进入默认路线:
|
|
|
|
|
|
|
|
|
|
|
|
- Turso CDC 通过 `PRAGMA capture_data_changes_conn(...)` 把变更写入 CDC 表,按连接开启,尊重事务边界。
|
|
|
|
|
|
- CDC 可以用于后续审计归档、外部消息桥接、实时分析或同步 spike。
|
|
|
|
|
|
- MNote control-plane 当前已有 `audit_log` 和 `outbox_events`,这些记录业务语义,比通用表级 CDC 更适合当前主链。
|
|
|
|
|
|
- CDC 可能捕获敏感字段,启用前必须定义脱敏、保留期、权限和导出边界。
|
|
|
|
|
|
|
|
|
|
|
|
Tantivy FTS 当前不进入 control-plane:
|
|
|
|
|
|
|
|
|
|
|
|
- Turso FTS 是 `CREATE INDEX ... USING fts` + `fts_match` / `fts_score` / `fts_highlight`,不是 SQLite FTS5 的无成本替换。
|
|
|
|
|
|
- 当前 control-plane 只存轻量元数据,全文搜索收益有限。
|
2026-07-28 17:04:27 +08:00
|
|
|
|
- 页面正文、知识库和 RAG 仍走 local-first 文件 / OpenHub / LightRAG,不迁入 Turso control-plane。
|
2026-07-03 13:24:20 +08:00
|
|
|
|
- 后续如需搜索页面标题、路径、AI 会话标题或轻量摘要,可单独做 P2/P3 spike。
|
|
|
|
|
|
|
|
|
|
|
|
Browser WASM / OPFS 当前不进入主线:
|
|
|
|
|
|
|
|
|
|
|
|
- 它适合纯浏览器 / PWA 离线 SQL 存储,但当前 MNote 主路径是本地文件夹 + Rust mnote-web + watcher。
|
|
|
|
|
|
- 浏览器 OPFS 不能替代本地 `.md`、附件、mindmap、OnlyOffice 文件真相,否则会制造第三份正文真相。
|
|
|
|
|
|
- 浏览器内数据库也不能作为 auth、grant、AI policy 的权限真源;这些仍必须由 Rust control-plane 校验。
|
|
|
|
|
|
- 后续可作为 P3 spike:只存可重建 cache、草稿、最近访问索引或纯 Web 离线收集箱,不存不可恢复正文唯一副本和长期 secret。
|
|
|
|
|
|
|
|
|
|
|
|
## 密钥泄露检查
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
rg -n "eyJhbGciOi|MNOTE_TURSO_AUTH_TOKEN=.*ey|authToken=.*ey|mnote-liaibo\.aws-ap-northeast-1\.turso\.io" . \
|
|
|
|
|
|
-g '!target/**' \
|
|
|
|
|
|
-g '!node_modules/**' \
|
|
|
|
|
|
-g '!recycle/**' \
|
|
|
|
|
|
-g '!*.db' \
|
|
|
|
|
|
-g '!*.sqlite' \
|
|
|
|
|
|
-g '!*.sqlite3' \
|
|
|
|
|
|
-g '!*.png' \
|
|
|
|
|
|
-g '!*.jpg' \
|
|
|
|
|
|
-g '!*.jpeg'
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
允许结果:无输出;或者只命中文档中不含 token 的 host 示例。任何 JWT 明文命中都必须立即移出仓库路径并轮换 token。
|