恢复点必须能够恢复完整的数据库。EmDash 的 JSON 备份可用于检查和自定义工具,但 EmDash 无法恢复它们。在进行数据库迁移或破坏性操作之前,请使用原始数据库备份或时间点恢复。
请单独备份媒体二进制文件。如果站点存储了加密的插件设置,请把完整的 EMDASH_ENCRYPTION_KEY 轮换列表保存在单独的密钥管理器备份中。数据库备份和导出不包含这些文件或密钥。
选择恢复方法
请使用与站点所配置的数据库适配器相对应的方法:
| 数据库 | 恢复点 |
|---|---|
| Cloudflare D1 | 记录一个 Time Travel 书签。当所需的保留期超过 Time Travel,或需要演练恢复时,创建异地 SQL 转储。 |
| SQLite 或本地 libSQL | 使用 SQLite 的在线备份命令,然后对副本运行完整性检查。 |
| PostgreSQL | 使用服务商的时间点恢复,或用 pg_dump 创建自定义格式的归档。 |
| Hyperdrive | 通过直接连接源库来备份其 PostgreSQL 源。Hyperdrive 绑定不是备份连接。 |
| Turso 托管的 libSQL | 使用 Turso 的时间点恢复,或创建并演练 SQL 转储。 |
| 其他远程 libSQL 服务商 | 使用服务商的完整数据库导出或时间点恢复流程,并在隔离的数据库上测试。 |
请把恢复点与对应的应用构建产物、媒体备份和密钥列表版本放在一起保存。在计划的迁移之前,记录确切的数据库和环境;如果服务商的流程要求,请停止应用写入;并确认恢复点在预期的回滚窗口内一直可用。
备份与恢复媒体对象
R2 和兼容 S3 的存储桶除数据库之外,还需要对象级备份。下面的 AWS CLI 命令会把所有对象(包括 backups/ 下的 EmDash JSON 归档)复制到本地备份目录。对于 AWS S3,请省略 --endpoint-url。
aws s3 sync s3://emdash-media ./emdash-media-backup \
--endpoint-url https://<account-id>.r2.cloudflarestorage.com
请把 <account-id> 替换为 Cloudflare 账户 ID。日常备份任务请使用只读的存储桶凭据。把备份存放在生产账户或故障域之外,并记录同一时间创建的数据库恢复点。
请恢复到一个空的恢复存储桶,而不是在生产环境处理请求时覆盖它:
aws s3 sync ./emdash-media-backup s3://emdash-media-recovery \
--endpoint-url https://<account-id>.r2.cloudflarestorage.com
只给恢复任务恢复存储桶的写入权限。让一个非生产部署指向已恢复的数据库和存储桶,打开几个已知的媒体 URL,并上传、删除一个临时文件。只有在数据库和媒体一起通过检查之后,才修改生产绑定或存储桶配置。
对于本地存储,请复制由 local() 存储适配器配置的目录。复制时请停止媒体写入,然后验证副本中有代表性的文件。
用 Time Travel 恢复 D1 数据库
迁移之前,向 Time Travel 查询数据库当前的书签,并把完整输出与部署一起记录下来:
npx wrangler d1 time-travel info my-database
Time Travel 恢复点会过期。Cloudflare 目前在 Workers Free 套餐中保留 7 天,在 Workers Paid 套餐中保留 30 天。如果回滚窗口需要更长,请创建异地 D1 转储。
如果需要恢复,请停止对站点的写入,并恢复迁移之前记录的那个确切书签:
npx wrangler d1 time-travel restore my-database \
--bookmark="<pre-migration-bookmark>"
请把 <pre-migration-bookmark> 替换为记录下来的书签。该命令会就地覆盖数据库并取消正在执行的查询。请保存恢复命令输出的先前书签,以便撤销这次恢复本身。
Time Travel 会恢复内容、用户、设置、插件数据和迁移记录。它不会恢复 R2 媒体对象或 EMDASH_ENCRYPTION_KEY。在重新开放流量之前,请恢复对应的应用构建产物、媒体和密钥。验证登录、内容读取、模式、一项加密的插件设置以及一次写入。
有关当前的保留期、要求和恢复行为,请参阅 D1 Time Travel 文档。
创建异地 D1 转储
要获得原始数据库(包括用户和认证表)的 SQL 转储,请使用 Wrangler。一旦任何集合启用了搜索,单纯的 wrangler d1 export my-database --remote 就会因 D1 Export error: cannot export databases with Virtual Tables (fts5) 而失败,因为 EmDash 把搜索索引保存在 FTS5 虚拟表中。请按名称导出其他表,并把搜索索引排除在外;恢复后的站点会重建它。
转储被拆分为四个文件,以便恢复能够成功:
backup-schema.sql创建表。backup-folders.sql保存媒体文件夹。大型导入会分批执行,否则媒体项可能先于它所属的文件夹被插入,从而无法通过外键检查。backup-data.sql保存所有其他行。backup-indexes.sql保存索引和触发器,按表名导出时会漏掉它们。它们最后创建,这样导入行时触发器就不会触发。
这些命令需要 jq。下面的命令会写出这四个文件:
npx wrangler d1 execute my-database --remote --json \
--command "SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT GLOB '_emdash_fts_*' AND name NOT GLOB 'sqlite_*' AND name NOT GLOB '_cf_*' AND name != 'media_folders'" \
| jq -r '.[0].results[] | "--table=" + .name' > backup-tables.txt
npx wrangler d1 export my-database --remote --no-data --output=backup-schema.sql \
--table=media_folders $(cat backup-tables.txt)
npx wrangler d1 export my-database --remote --no-schema --output=backup-folders.sql \
--table=media_folders
npx wrangler d1 export my-database --remote --no-schema --output=backup-data.sql \
$(cat backup-tables.txt)
npx wrangler d1 execute my-database --remote --json \
--command "SELECT sql FROM sqlite_master WHERE type IN ('index', 'trigger') AND sql IS NOT NULL AND name NOT GLOB '_emdash_fts_*' AND tbl_name NOT GLOB '_emdash_fts_*' AND tbl_name NOT GLOB '_cf_*'" \
| jq -r '.[0].results[] | .sql + ";"' > backup-indexes.sql
有关 Wrangler 的导出选项和限制,请参阅 D1 导入与导出文档。
请把 SQL 文件与对应的应用版本、同时创建的媒体备份,以及单独保存的 EMDASH_ENCRYPTION_KEY 轮换列表副本放在一起保存。测试恢复的方法是:把这些文件导入新创建的空 D1 数据库,恢复运行时密钥,把一个非生产绑定更新为指向该数据库,然后验证站点。
请按以下顺序把文件导入空的恢复数据库:
npx wrangler d1 execute my-recovery-database --remote --file=backup-schema.sql
npx wrangler d1 execute my-recovery-database --remote --file=backup-folders.sql
npx wrangler d1 execute my-recovery-database --remote --file=backup-data.sql
npx wrangler d1 execute my-recovery-database --remote --file=backup-indexes.sql
恢复后的数据库没有搜索索引。EmDash 会在恢复后的站点第一次调用搜索 API(/_emdash/api/search)时重建它,例如通过管理端的命令面板,或使用该 API 的搜索框。在此之前,直接调用 search() 的页面不会返回结果。若要立即重建,请在站点运行后请求一次 /_emdash/api/search?q=a。
备份 SQLite 与本地 libSQL
离线备份时,请停止所有写入数据库的进程,并连同数据库文件一起复制旁边所有的 -wal 和 -shm 文件。-wal 文件可能包含尚未写入主文件的已提交更改。
要获得一致的在线备份,请对数据库适配器所配置的路径使用 SQLite 的备份命令:
sqlite3 emdash.db ".backup backup.db"
检查复制出的数据库。该命令必须输出 ok:
sqlite3 backup.db "PRAGMA integrity_check"
在非生产环境中,用对应的应用版本打开该副本,并验证登录、公开内容、一项加密的插件设置、一次编辑和一次媒体读取。
要恢复,请停止所有写入数据库的进程,保留损坏的数据库及其 -wal 和 -shm 文件的副本,用验证过的备份替换它们,并在重新开放流量之前恢复对应的应用构建产物、媒体和密钥。
备份 PostgreSQL 与 Hyperdrive
如果 PostgreSQL 服务商提供可在修改生产环境之前测试的隔离恢复,请使用其时间点恢复或快照功能。记录确切的时间戳或快照 ID 以及保留期。
要获得可移植的备份,请直接连接 PostgreSQL 源库,并创建自定义格式的归档:
pg_dump --format=custom --file=emdash.dump "$DATABASE_URL"
pg_dump 会在数据库保持使用的同时创建事务一致的归档。在依赖该归档之前,请先检查其目录:
pg_restore --list emdash.dump > emdash.dump.list
准备一个空的恢复数据库。以规范的 EmDash 应用角色连接,并在恢复时不把源库的所有权带入恢复数据库:
pg_restore --exit-on-error --no-owner \
--dbname="$RECOVERY_DATABASE_URL" emdash.dump
RECOVERY_DATABASE_URL 中的角色会成为已恢复的 EmDash 对象的所有者。请使用恢复后的应用将要使用的同一个稳定角色。让一个非生产部署指向恢复数据库,并验证登录、内容读取、模式、一项加密的插件设置以及一次写入。
对于 Hyperdrive,DATABASE_URL 必须是 migrationConnectionStringEnv 所指定的、直连 PostgreSQL 源库的连接;Worker 绑定无法传给 PostgreSQL 备份工具。请遵循源库服务商的恢复流程,并把 Hyperdrive 配置与应用构建产物一起保留。
有关服务商托管的恢复、大型数据库和持续归档,请参阅 PostgreSQL 备份文档。
备份 Turso 托管的 libSQL
Turso 的时间点恢复会根据较早的时间戳创建一个独立的数据库。请在迁移之前立即记录一个 RFC 3339 时间戳。如果需要恢复,请从该时间点创建一个隔离的数据库:
turso db create emdash-recovery \
--from-db emdash-production \
--timestamp <pre-migration-rfc3339-timestamp>
请把 <pre-migration-rfc3339-timestamp> 替换为记录下来的时间戳。为恢复后的数据库创建令牌,让一个非生产部署指向它,并在更改生产连接之前验证站点。有关站点套餐所提供的保留期,请查看 Turso 时间点恢复。
要获得异地副本,请转储整个数据库:
turso db shell emdash-production .dump > emdash.sql
通过用转储创建一个隔离的数据库来测试该转储:
turso db create emdash-recovery --from-dump ./emdash.sql
对于其他远程 libSQL 服务商,请遵循其完整数据库导出或时间点恢复流程。在该流程生成恢复点并且测试过隔离恢复之前,不要应用远程迁移。
将 JSON 备份用于检查
JSON 备份包含:
- 所有内容条目,包括草稿、已计划的文章和回收站中的项目
- 构成内容模型的集合和字段定义
- 分类法定义、术语,以及分配给每个条目的术语
- 菜单和菜单项、区块、小部件区域和小部件、SEO 记录、修订历史、媒体元数据和数据库迁移历史
- 站点设置,例如来自
site:设置组以及emdash:site_title、emdash:site_tagline和emdash:locale设置的标题、标语、URL、语言环境、徽标、显示偏好、社交资料和 SEO 默认值
它不包含:
- 用户账户、会话、通行密钥、OAuth 数据、API 令牌和其他认证数据
- 插件存储和插件设置,包括插件密钥
- 评论和互动回应、重定向和 404 日志、署名、内容关系与引用、审计日志、速率限制以及计划任务状态
- 媒体文件夹、记录媒体使用位置的数据、未完成的上传以及媒体文件本身
在 Settings → Backups 下,Download backup 会生成一个新的 JSON 文件。配置了存储时,Back up now 会在 backups/ 前缀下存储一个。这些文件使用与 EmDash 预览相同的带版本快照格式。
要计划 JSON 备份,请在 Settings → Backups 下启用 Daily automatic backups,并选择保留 1–30 个文件。它们在计划维护期间运行。在 Cloudflare 上,Worker 需要一个 cron 触发器;在 Node 上,内置调度器会运行该任务。
如果存储桶有公开的 R2 域名或内容分发网络源站,请把该源站限制为媒体对象,或通过 EmDash 提供媒体。EmDash 的媒体路由拒绝提供 backups/,但公开的存储桶源站可能通过 URL 暴露每一个对象。
要把内容、设置和媒体复制到新的 EmDash 站点,请使用站点包。站点包不会恢复用户、API 令牌、插件数据或密钥,因此不能替代数据库恢复点。