运维

备份与恢复

元数据库自动快照、从快照恢复、主机全损后的灾难恢复、元数据导出与对账 GC。

需要备份什么

渠道里的对象用内容哈希命名,图片与短链、用户、Token 的对应关系全在元数据库里。元数据库丢了,渠道里的图片就无法再对应回来;主密钥丢了,实例拒绝启动,渠道凭证与加密渠道的数据也都无法解密。

内容位置如何备份
主密钥<数据目录>/master.key,或环境变量 IMGYOUNG_MASTER_KEY首次启动后复制一份离线保存,与快照分开存放,见配置
元数据库<数据目录>/imgyoung.db自动快照(写到每个健康渠道),另外定期下载一份存到别处
图片各渠道配置多个渠道,由副本补齐保证每张图不止一份;内置 local 渠道的数据在 <数据目录>/blobs/
加密渠道的 rclone 配置后台「存储渠道」页的「导出 rclone 配置」每个加密渠道导出一份,和主密钥一样离线保存,见渠道存储加密
部署配置环境变量、systemd drop-in、compose.yaml、反代配置纳入你自己的配置管理

<数据目录>/variants/ 是衍生图缓存,丢失后按需重新生成,不必备份。服务运行中不要直接复制 imgyoung.db:数据库处于 WAL 模式,最近的写入可能还在 imgyoung.db-wal 里。要拿一份一致的副本,用后台下载快照,或先停服再复制。

元数据库自动快照

  • 生成:每轮用 SQLite 的 VACUUM INTO 生成一份一致的快照,先写到 <数据目录>/backup-snapshot.tmp(需要约一个数据库大小的空闲空间),写完各渠道后删除。
  • 存放位置:同一份快照写到每个健康且不在清理中的渠道,对象键为 _imgyoung/backup/imgyoung-<UTC 时刻>.db(如 imgyoung-20261015-083000.123.db),位于渠道路径之下;内置 local 渠道上即 <数据目录>/blobs/_imgyoung/backup/。
  • 加密:写到加密渠道上的快照随渠道一起加密,渠道商看不到内容。
  • 节奏:每隔「备份间隔」(backup_interval_hours,出厂 24 小时)一次,间隔从进程启动起算,修改间隔后从修改时刻重新计时。定时备份与手动「立即备份」依次执行,同时发生时排队。
  • 启动时:任一健康渠道上没有备份间隔内的快照,就立即备份一次,否则跳过。新接入渠道后的下一次启动会补上一份;频繁重启也不会用同一天的快照挤掉轮换下来的历史。
  • 保留:每个渠道写入成功后,各自只保留最新的「备份保留份数」(backup_keep,出厂 7)份,更旧的删除。
  • 失败:单个渠道写失败不影响其余渠道继续写,但只要有渠道没写成,这一轮就算失败,开启 backup_failed 告警;写成的渠道保留这份快照。没有健康渠道、生成快照失败同样算失败。下一次成功后告警自动恢复,见监控与告警。
  • 删除渠道时:「删除并清理」会连同渠道上的快照一起删除;「仅移除」不动远端对象,快照留在原处。

数据只存在这台主机上

本机磁盘以外没有健康的渠道时(只有内置 local 渠道,或其他渠道都不健康),图片副本和快照都只在这台主机上,主机损坏即全部丢失,总览页会提示「数据只存在这台主机上」。接入一个对象存储或网盘渠道,快照就会同时写到它上面,不需要调整优先级。

加密渠道上的快照

写到加密渠道上的快照是密文,解密用的 crypt 密码与盐由主密钥与该渠道在元数据库里的编号、创建时刻派生。元数据库整体丢失后,只要还有未加密的渠道持有快照,直接从它取回即可;快照只存在加密渠道上时,要靠事先用「导出 rclone 配置」下载并离线保存的配置文件读出,做法见渠道存储加密。

在后台查看与管理快照

「设置 → 备份与导出 → 元数据库快照」列出各健康渠道上快照的并集,最新在前;「所在渠道」列出持有每份快照的渠道,按优先级排列。

  • 「立即备份」:马上做一次快照,写到每个健康渠道,同样会清理各渠道超出保留份数的旧快照。有渠道没写成时返回错误并列出失败的渠道与原因,形如 备份失败:快照 imgyoung-20261016-083000.123.db 有 1/3 个渠道未写成: 渠道 "r2-main": 上传快照: …,写成的渠道保留这份快照;没有健康渠道时返回 没有健康渠道可存放快照。
  • 「下载」:按优先级依次从持有该快照的健康渠道读取,读不到换下一个。下载的文件就是一个完整的 SQLite 数据库。
  • 「恢复」:同样按优先级读取,见下一节。

不健康或清理中的渠道上的快照不在列表里,也不参与下载与恢复。任一健康渠道列举失败时列表整体报错(500)。没有健康渠道时各项操作都返回 503:列表为 没有健康渠道,无法列举快照,下载与恢复为 没有健康渠道,无法读取快照,立即备份为 没有健康渠道可存放快照。健康渠道上都没有这份快照时,下载与恢复返回 404 快照不存在;有渠道读取失败、又没有一个读得出时返回 500。

从快照恢复

运行中的 SQLite 文件无法安全替换,所以恢复分两步:先暂存,再在下一次启动、打开数据库之前换入。

暂存并停机:在快照行点「恢复」,确认「恢复并重启」。服务把快照下载到 <数据目录>/imgyoung.db.restore(先写临时文件,完整后再改名),记一条 backup.restore 审计日志,返回后开始优雅停机:先停止接收新请求,在途请求最多等 6 秒,再停后台循环与补齐任务,最多等 2 秒。

由进程管理器拉起:进程正常退出,需要进程管理器把它重新拉起。Docker 用 --restart unless-stopped、Compose 用 restart: unless-stopped、systemd 用 Restart=always(deploy/ 里的示例都已这样配置)。直接在终端运行的二进制会就此退出,暂存文件留到你下次手动启动时换入。

换入:启动时、打开数据库之前,先在数据目录写下换入标记 imgyoung.db.restored,再把现有的 imgyoung.db 连同 -wal、-shm 改名为 imgyoung.db.pre-restore-<UTC 时刻> 留存,暂存文件改名为 imgyoung.db。随后照常执行数据库迁移,较早版本的快照会被迁移到当前版本。

重置绑定时刻:迁移之后、加载主密钥之前,见到换入标记就把全部渠道的绑定时刻(见对账 GC)重置为当前时刻,随后删除标记,日志 换入快照后已把渠道绑定时刻重置为当前时刻,快照之后写入渠道的对象不会被对账回收,字段 channels 为重置的渠道数。这一步失败时标记保留,下次启动会再做。

重新登录:后台页面在服务恢复后自动刷新。会话与密码都回到快照时刻的状态,要用那时的密码登录。

留存的 imgyoung.db.pre-restore-* 不会自动删除,确认恢复无误后手动清理。

恢复把元数据整体退回快照时刻,在此之后发生的变化与渠道上的实际对象不再一致:

  • 快照之后上传的图片:元数据里没有它们。它们在渠道上的对象早于重置后的绑定时刻,对账 GC 只计数(foreign)、不回收;对象留在渠道上,但无法再通过 ImgYoung 访问;
  • 快照之后删除的图片:元数据里还在,但渠道对象已删,访问失败,并以「副本补齐失败」告警出现,在后台删掉即可;
  • 快照之后的其他改动:新建的渠道、用户、Token,修改过的运营配置与防盗链规则,全部退回快照时刻。

主机全损后的灾难恢复

前提:你有快照所属实例的 master.key 备份,并且能拿到一份快照文件,来源可以是:

  • 自己下载保存的快照;
  • 任一未加密渠道上的快照:用渠道服务商的控制台或 rclone 下载渠道路径下 _imgyoung/backup/ 里最新的 imgyoung-*.db;
  • 加密渠道上的快照:用事先导出的 rclone 配置文件经 crypt 远端读出,见渠道存储加密。

在新机器上安装同一或更新版本,见安装。已经启动过也没关系:换入快照时,全新实例的数据库会被改名留存;只是它首次启动时生成的 master.key 必须在下一步换成原来那把。元数据库里记着主密钥的指纹,换入快照后,主密钥缺失或与快照不符时进程拒绝启动,不会生成新密钥。

停服,放入主密钥与快照:在数据目录里放入原来的 master.key,把快照文件放为 imgyoung.db.restore。

快照必须放成 imgyoung.db.restore,不要直接覆盖 imgyoung.db:只有经暂存文件换入,启动时才会写下换入标记、重置渠道绑定时刻。直接覆盖的话,快照之后写进渠道的对象在对账 GC 看来就是本库的孤儿,过了宽限期会被删除。

终端
mkdir -p data
cp imgyoung-master.key data/master.key && chmod 600 data/master.key
cp imgyoung-20261015-083000.123.db data/imgyoung.db.restore

主密钥用环境变量 IMGYOUNG_MASTER_KEY 提供的,改设环境变量即可,不必放文件。

启动并检查:二进制方式此时启动 imgyoung。日志里依次出现 已用暂存快照换入元数据库,原库留存 与 换入快照后已把渠道绑定时刻重置为当前时刻,快照之后写入渠道的对象不会被对账回收,即换入完成。用快照时刻的账号密码登录,在「存储渠道」页确认各渠道恢复健康。

如果启动失败并提示主密钥与数据库不符,或找不到主密钥,说明放进去的不是快照所属实例的主密钥,或者没有放。此时快照已经换入,换上正确的主密钥再启动即可,不必重新放快照。

恢复后,原来只存在 local 渠道上的图片已随主机丢失;其他渠道上有副本的图片照常可访问,local 渠道上缺失的副本会被对账 GC 发现并从其他渠道补回。

快照在未加密渠道上时,也可以不手动取文件:先启动一个全新实例,在后台按原参数接入存放快照的渠道,再从「元数据库快照」里恢复;列表包含所有健康渠道上的快照,不需要调整优先级。这个全新实例必须从首次启动起就用原来的主密钥(首次启动前放入 master.key 或设置 IMGYOUNG_MASTER_KEY),否则恢复后重启时主密钥与快照不符,进程拒绝启动。已经用自动生成的主密钥启动过的,先停服,换上原来的 master.key,删掉数据目录里全新的 imgyoung.db(连同 -wal、-shm)后再启动,然后再接入渠道。新接入的渠道以接入时刻为绑定时刻,桶里原有的对象都早于它,对账 GC 只计数、不回收,等待期间不会被删。

接入旧渠道后尽快恢复

接入后、恢复前,不要点「立即备份」,也不要重启全新实例:全新实例的空数据库快照会写进每个健康渠道,包括刚接入的旧渠道,并按保留份数挤掉那里最旧的一份原有快照。点「恢复」引起的那次重启没有这个问题:启动时先换入快照,之后备份的是恢复后的数据库。

元数据导出

「设置 → 备份与导出 → 数据导出 → 导出 JSON」下载 imgyoung-export.json(每次导出记一条 data.export 审计日志),内容:

字段内容
users用户:ID、登录名、角色、配额、默认有效期、是否禁用、用量与图片数;不含密码哈希
channels渠道:ID、名称、驱动、优先级、健康状态、是否加密、是否允许直链、创建时刻、副本水位;不含任何配置参数与凭证
tokensToken:名称、归属用户、明文前缀、授权范围、吊销与签发时刻;不含哈希
images每条图片引用:短 ID、物理图、归属用户与 Token、原始文件名、对象键别名、有效期、访问统计、清理标记
blobs每个物理图:SHA-256、感知哈希、渠道内对象键、MIME、大小、宽高、引用计数、删除状态
blob_channels每份副本在哪个渠道、状态与最近错误

导出可读、自包含,即使不再运行 ImgYoung,也能凭它和渠道里的对象把图片对应回来。它不是恢复用的格式:恢复用快照。运营配置、防盗链规则、审计日志与告警不在导出内。

对账 GC

补齐子系统每 24 小时对每个健康渠道做一次双向对账(第一次在进程启动 24 小时后),渠道内 _imgyoung/ 前缀下的系统对象(含快照)不参与。

孤儿判定以渠道的绑定时刻为界:渠道记录开始对应当前存储位置的时刻,ImgYoung 只会在这之后往该位置写对象。

情形绑定时刻
新建渠道创建时刻
后台编辑渠道,后端名或任何配置参数有改动重置为编辑时刻;重新输入同一个密码也算改动(rclone 编码密码时带随机 IV,编码结果每次不同)
只改名称、优先级或直链开关;服务商刷新令牌后的写回不变
从快照恢复(经 imgyoung.db.restore 换入)全部渠道重置为换入后启动的时刻
  • 孤儿:同时满足以下条件的对象被删除,免得按量计费的渠道为无主对象付费:
    • 键名是本服务的对象键格式 <短 ID>_<SHA-256 前 8 位>;
    • 元数据库里没有它在该渠道上的任何副本记录(不论状态);
    • 修改时间不早于该渠道的绑定时刻;
    • 修改时间已超过 48 小时宽限期,宽限期覆盖「已写入渠道、尚未落库」的正常中间状态。
  • 深探测残留:深度健康探测写入后即删的临时对象 .imgyoung-probe-*,删除失败时才会留下。过了 48 小时宽限期即删除,不看写入时刻(测试连接时留下的残留会早于渠道记录),也不计入对象数与用量。
  • 外来对象:元数据库里没有记录、但键名不是本服务格式,或修改时间早于绑定时刻的对象,比如别的程序写进同一路径的文件、灾备时新实例接入的旧桶里原有的对象、从快照恢复后快照之后写入的对象。它们只计数,不回收。
  • 幻影:元数据库记为已就绪、渠道上实际缺失的副本。状态退回待补齐,重新入队从其他渠道复制。

每个渠道对账完成后记一条 渠道对账完成 日志,字段 objects(对象数)、bytes(字节数)、foreign(外来对象数)、orphans_deleted(回收的孤儿数,含深探测残留)、phantoms_requeued(重新入队的幻影数)。

两个实例不能共用同一渠道路径

两个 ImgYoung 实例接入同一个存储路径时,对方在本实例绑定之后写出的对象,在本库看来就是孤儿,过了宽限期会被删掉。每个实例要用各自的桶或路径。

此外每 10 分钟有一次副本对账(不列举渠道对象):把恢复健康的渠道上失败的副本重新置为待补齐,把未完成的副本重新入队。

本页目录