做版本管理的人最怕什么?不是改坏了代码,而是仓库没了。代码写在本地,开发机硬盘挂掉最多丢一天工作;SVN 仓库整个没了,丢的是从第一行代码到现在的全部历史、所有分支标签、所有提交记录和权限配置。标题里只有“svn备份”四个字,但这个词背后是一整套仓库安全的底线工程。这篇文章围绕 SVN 备份展开,覆盖 svnadmin dump、svnadmin hotcopy、配置文件备份、自动化脚本、恢复演练和常见问题排查,适合刚搭好 SVN 服务器的小团队,也适合已经跑了几百个仓库但备份一直靠手动的运维同学。看完至少能搭出一套“每天自动备份、定期恢复演练、出事能落地”的仓库保护方案。
1. 备份前必须想清楚的事:备份什么、多频繁、放哪里
1.1 SVN仓库里到底有哪些值得备份的内容
很多新手对 SVN 备份的理解是“把项目文件夹复制一份”,这是最常见的误区。项目目录在本地通常是一个 working copy,里面带着 .svn 隐藏目录,它只是仓库某个版本的工作副本,不是一个完整仓库。真正需要备份的是服务器端仓库目录,也就是通过 svnadmin create 创建的那个路径,里面通常有 conf、db、hooks、locks、format 这些核心内容。
具体来说,仓库目录里最重要的是 db 子目录,它存放所有版本历史、文件内容、属性修改记录。conf 目录是认证和授权配置,比如 svnserve.conf、passwd、authz,这些文件决定了谁能访问仓库、能访问哪些路径。hooks 目录是钩子脚本,很多团队在 post-commit 里挂自动构建、自动通知、自动同步,丢了就断了自动化链路。所以备份 SVN 仓库时,至少要覆盖 db、conf、hooks 这三块,缺一不可。
还有一种情况需要特别提醒:不要把带有 .svn 目录的网站根目录直接当备份源。.svn 目录本身会记录工作副本的元数据、原始 URL、文件状态,历史上还出现过通过访问 .svn 目录泄露源码的信息安全问题。对备份来说,它既不是完整历史,也可能给人“我已经备份了”的错觉,隐患很大。
1.2 确定备份策略:全量、增量、异地三个维度
备份策略不需要一上来就做得特别复杂,但至少要回答三个问题:备份哪些东西、多长时间备份一次、备份放到哪里。
备份哪些东西,上面已经说明,至少是仓库核心数据加配置。多长时间备份一次,取决于团队对丢失数据的容忍度。如果日均提交量大、历史记录很重要,建议做“每周一次全量 + 每日一次增量”;如果仓库规模小、提交不频繁,每周一次全量也可以接受。备份放到哪里,一定不能和 SVN 仓库在同一块磁盘上。机器磁盘坏了、机房断电、误删仓库目录,这些事故都会连累同盘备份。现实中小团队最常用的做法是:仓库在 /var/svn,备份到另一块数据盘 /backup/svn,再把 /backup/svn 里的备份文件通过 rsync 同步到另一台机器或对象存储。哪怕是手动拷到移动硬盘,也比放在同一台机器上强得多。
从恢复速度角度,备份分为冷备和热备两类。svnadmin dump 导出的是纯文本格式的仓库快照,文件可以压缩、可以异地传输,但恢复时需要新建仓库再 load,过程稍慢。svnadmin hotcopy 是直接的仓库复制,恢复最快,但它一般只适合放在本机或同一文件系统环境里。两者不是互斥关系,小团队可以全量 dump 为主,hotcopy 为辅;大团队可以 hotcopy 做即时恢复镜像,dump 做异地归档。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 最稳妥的冷备方案:svnadmin dump 与 restore
2.1 为什么说 dump 是“仓库级保险单”
svnadmin dump 是 SVN 自带的导出命令,它会把仓库里的所有版本历史、文件属性、提交日志、分支标签信息按顺序输出到一个文件里。这个文件不依赖仓库后端是 FSFS 还是 Berkeley DB,换机器、换版本、换平台都可以通过 load 恢复,因此非常通用。
最基本的完整备份命令是:
bash复制svnadmin dump /var/svn/repos/myproject > /backup/svn/myproject_20250601.dump
如果仓库比较大,可以直接用 gzip 压缩导出,能省不少磁盘:
bash复制svnadmin dump /var/svn/repos/myproject | gzip -9 > /backup/svn/myproject_20250601.dump.gz
如果只想备份某一个版本区间的增量,比如将上次全量备份之后的 1001 到 1050 版本导入,可以这样:
bash复制svnadmin dump /var/svn/repos/myproject -r 1001:1050 --incremental > /backup/svn/myproject_inc_1001_1050.dump
这里 --incremental 参数很关键,它会把备份文件标记为增量包。恢复时先在空仓库里加载全量备份,再按顺序加载每一个增量包。需要特别注意的是,增量备份的恢复顺序不能乱,一旦某个中间包损坏,后面的增量包就全部作废。所以增量备份不是越多越好,一般建议每天增量、每周全量重做一次,把恢复链条控制在七环以内。
2.2 从 dump 文件恢复仓库的完整流程
恢复操作不复杂,但步骤顺序记错很容易出问题。完整流程是:
bash复制svnadmin create /var/svn/repos/myproject_restore
svnadmin load /var/svn/repos/myproject_restore < /backup/svn/myproject_20250601.dump
如果备份文件是 gzip 压缩过的,需要先解压再 load,或者用管道直接恢复:
bash复制gunzip -c /backup/svn/myproject_20250601.dump.gz | svnadmin load /var/svn/repos/myproject_restore
如果后面还有增量包,接着用同样的 load 命令依次加载增量文件,顺序不可颠倒。加载完成后,用 svnadmin verify 验证一下仓库完整性:
bash复制svnadmin verify /var/svn/repos/myproject_restore
verify 会逐版本检查仓库数据是否完整。如果输出没有报错,再从中 checkout 一个工作副本看两层:一层是看最新代码是否正常,另一层是看历史日志是否都在。只有项目文件能正常拉下来、svn log 能看到历史提交,备份才算真正验证通过。
2.3 自动定期做全量备份和清理的参考脚本
手动敲命令备份一两次容易,坚持天天敲不现实。建议直接写好计划任务,让系统自动执行。下面是一个比较通用的全量备份脚本,适合 Linux 环境,逻辑简单、改动成本低:
bash复制#!/bin/bash
REPO_BASE=/var/svn/repos
BACKUP_BASE=/backup/svn
DATE=$(date +%Y%m%d_%H%M%S)
KEEP_DAYS=14
LOG=/var/log/svn_backup.log
mkdir -p "$BACKUP_BASE/full"
for repo in "$REPO_BASE"/*; do
[ -d "$repo" ] || continue
name=$(basename "$repo")
latest=$(svnlook youngest "$repo")
if [ -z "$latest" ]; then
latest=0
fi
svnadmin dump -q "$repo" | gzip -9 > "$BACKUP_BASE/full/${name}_${DATE}_r${latest}.dump.gz"
if [ $? -eq 0 ]; then
size=$(du -h "$BACKUP_BASE/full/${name}_${DATE}_r${latest}.dump.gz" | awk '{print $1}')
echo "$(date '+%F %T') $name r$latest backup ok, size $size" >> "$LOG"
else
echo "$(date '+%F %T') $name backup FAILED" >> "$LOG"
fi
done
find "$BACKUP_BASE/full" -name "*.dump.gz" -mtime +$KEEP_DAYS -delete
echo "$(date '+%F %T') clean old backups done" >> "$LOG"
脚本里用了 svnlook youngest 获取仓库当前最新版本号,是为了在备份文件名里标记版本范围,方便之后做增量备份和恢复时对版本。find ... -mtime +$KEEP_DAYS -delete 会删除 14 天前的旧备份,保留多少天按自己的磁盘容量来。Windows 环境下类似的思路是打开任务计划程序,定时执行一行 svnadmin dump 命令,注意把 svnadmin.exe 的完整路径写清楚,并配置好输出文件重定向,不然定时任务执行时可能找不到命令。
3. 更省事的镜像备份方式:svnadmin hotcopy
3.1 hotcopy 和 dump 的区别,该如何选择
svnadmin hotcopy 跟 dump 最大的区别是,它直接把整个仓库目录复制成一份可用的仓库副本,而不是导出成中间格式。它最大的优点是恢复速度非常快,不需要 create 再 load,副本目录本身就是完整仓库,把原仓库目录改名后,把 hotcopy 副本改名成正式仓库就能立刻提供服务。
命令格式很简洁:
bash复制svnadmin hotcopy /var/svn/repos/myproject /backup/svn/hotcopy/myproject_20250601
执行后,目标目录就是一个结构完整、可以直接被 SVN 服务加载的仓库。因为它复制的是底层仓库结构,对相同 SVN 版本的兼容性最好,日常做本机镜像备份比较合适。不过它不像 dump 文件那样适合跨大版本迁移,比如从 SVN 1.6 升级到 1.14,用 dump/load 通常是更稳的路径。
这里要纠正一个常见误解:hotcopy 不等于“趁服务关着的时候直接 copy 目录”。直接复制正在使用的仓库目录,可能会复制到写入一半的数据,轻则备份不可用,重则原仓库因为复制过程中抢锁而出问题。hotcopy 则是在不中断服务的前提下,通过 SVN 内部机制保证复制出来的数据一致性,比手动 cp -r 可靠得多。如果特别看重一致性,还可以加上 --clean-logs 参数,复制成功后顺手清理源仓库里的多余日志文件,减少磁盘占用。
3.2 hotcopy 和 dump 配合使用才是完整方案
hotcopy 虽然恢复快,但它不适合当作唯一的备份手段,因为它通常留在本机或同一环境,很难替代异地归档。实际操作中我更推荐这样搭配:
- 每天凌晨用
svnadmin dump做全量备份,压缩后放到独立备份盘,并同步到异地机器。 - 每周挑一个提交不频繁的时间点,做一次 hotcopy,保留最近的可用仓库副本。
- 每季度做一次恢复演练,把最新 dump 恢复到临时目录,测试启动和 checkout。
这样组合的好处是显而易见的:dump 文件负责“留底”,保证跨版本、跨机器都能恢复;hotcopy 负责“快速”,遇到仓库坏掉时,几分钟内就能切回最近的镜像。两者各有侧重,缺一不可。
热备还有一个相对高级的方案是 svnsync,它的思路是建一个只读的从仓库,通过同步命令把主仓库的提交实时推送到从仓库。配置起来比 dump/hotcopy 复杂,但能用在一主一备的容灾场景里。如果团队规模不大,建议先把 dump + hotcopy 玩熟,再考虑 svnsync,不要一开始就上复杂架构。
3.3 用 post-commit 钩子做提交后即时备份
有些仓库提交量不大,但团队希望每次提交后都能立刻有一次备份,这个时候可以在仓库的 hooks 目录里配置 post-commit 钩子。post-commit 是提交完成后自动执行的脚本,里面写一条备份命令,提交动作一结束就会被调用。
一个简单版本的 post-commit 脚本可以这样写:
bash复制#!/bin/bash
REPO="$1"
REV="$2"
BACKUP_DIR="/backup/svn/latest/${REPO##*/}"
mkdir -p "$BACKUP_DIR"
svnadmin hotcopy "$REPO" "$BACKUP_DIR" --clean-logs >/dev/null 2>&1
把脚本存到 /var/svn/repos/myproject/hooks/post-commit 后,需要加上执行权限:
bash复制chmod +x /var/svn/repos/myproject/hooks/post-commit
注意,钩子脚本里的输出最好重定向到文件或直接丢弃,因为 SVN 会尝试把钩子的 stdout/stderr 返回给客户端,如果钩子执行太久或输出太多,客户端会感觉提交卡顿。还有一个容易踩的坑:钩子脚本执行环境很干净,PATH 里可能没有 svnadmin 所在的目录,所以脚本里尽量使用 svnadmin 的绝对路径,例如 /usr/bin/svnadmin,免得定时任务或钩子运行时找不到命令。
4. 容易被忽略的配置文件备份:conf、hooks、authz
4.1 配置数据丢了比代码库丢了还麻烦
很多团队做备份只盯仓库里的版本数据,忽略 conf 和 hooks。但真正出过事故的人都会告诉你,配置的恢复往往比代码恢复更耗时。代码丢了,仓库还在,最多是历史回滚;配置丢了,账号密码映射、目录权限规则、钩子脚本全部没了,所有人不是看到“认证失败”就是“路径不允许访问”,场面会非常混乱。
比如 conf/svnserve.conf 里定义了认证方式和是否开启匿名访问,conf/passwd 保存用户明文密码,conf/authz 决定谁有权限读写哪些路径。这些文件一旦损坏,整个仓库等于无法被正常访问。hooks 目录里可能有十几个脚本,平时不觉得重要,但里面往往积累了很多只存在于别人那台机器上的逻辑,丢了很难再原样写回来。
所以备份仓库的时候,务必把配置和脚本一起纳入备份范围。dump 只备份仓库版本数据,不包含这些配置,不能以为 dump 完就万事大吉。
4.2 把配置和仓库一起打包备份
合并备份最直接的办法是在备份脚本里把仓库目录整体打成一个 tar 包,但这里要注意一个操作顺序:最好先做 svnadmin dump 或 hotcopy,再打包配置文件,不要在 SVN 服务运行期间直接 tar 仓库目录,否则同样会遇到数据一致性问题。
更稳妥的做法是分两步:第一步用 dump/hotcopy 备份版本数据,第二步单独打包每个仓库的 conf 和 hooks。参考脚本:
bash复制BACKUP_CONF_DIR=/backup/svn/configs
DATE=$(date +%Y%m%d%H%M%S)
mkdir -p "$BACKUP_CONF_DIR"
for repo in /var/svn/repos/*/; do
name=$(basename "$repo")
tar -czf "$BACKUP_CONF_DIR/${name}_conf_${DATE}.tar.gz" \
-C "$repo" conf hooks
done
这样即使 dump 文件本身不包含权限配置,恢复仓库时也能快速把配置脚本放回原位。打包好后,同样建议把 $BACKUP_CONF_DIR 同步到异地。配置备份文件通常很小,不需要特别复杂的清理策略,保留最近 30 天就够。
4.3 把权限配置纳入版本管理
配置备份还有一种更优雅的做法:把 conf 和 hooks 单独放在一个管理仓库里,用 SVN 自己管理这些配置文件的版本历史。也就是建立专门的 admin-repo,把各个仓库的 authz、passwd、svnserve.conf、post-commit 脚本都提交进去。这样配置发生变更时会有历史记录,万一某次改坏了,可以直接回滚到上一个版本。
我第一次尝试这种做法时,发现它对事故恢复的帮助非常大。有一次 authz 文件被人改动,导致某个目录所有人都没权限。当时我不记得历史版本内容,也没有原始备份,花了半个多小时排查。后来把配置纳入版本管理,遇到同样问题只需要 svn log 找出最近一次提交,再 svn export 出上一版本覆盖回去就行。建议所有使用 SVN 的团队都收下这个习惯,成本很低,收益却很大。
5. 备份做完不算完,恢复演练与问题排查实录
5.1 恢复演练是唯一能验证备份真实性的方式
备份文件生成了,不代表备份就是可用的。真正验证备份的唯一方式,是把它恢复到一台临时机器或临时目录,完整走一遍服务启动、checkout、历史查看的流程。现实中有太多团队,备份脚本跑了大半年,日志里全是“backup ok”,结果服务器真正宕机时才发现 dump 文件损坏、备份盘没挂上、或恢复版本和当前环境不兼容。
恢复演练不需要很频繁,但一定要定期做。我自己习惯是每个季度抽一个仓库做完整恢复测试,流程是这样的:
- 把最新的全量 dump 文件恢复到临时仓库目录,比如
/tmp/restore_test。 - 用
svnadmin verify检查完整仓库。 - 启动一个临时 SVN 服务,或者直接用
svn命令访问file:///tmp/restore_test。 - 从恢复后的仓库 checkout 一个工作副本。
- 对比最新版本号和提交时间,确认数据没有丢失。
- 测试结束,删除临时目录。
这套流程看起来不复杂,但实际执行时经常能发现备份脚本里的隐性 bug,比如备份路径写错、磁盘满了导致 dump 文件只有一半、gzip 压缩中途断掉但脚本没报错等。所以“备份成功”这四个字,要看日志和文件校验,不能只看命令返回 0。
5.2 常见问题速查表
下面是我实际运维中遇到过的一些典型问题,整理成表格方便照着排查。
| 现象 | 可能原因 | 处理思路 |
|---|---|---|
dump 命令报错 Unable to open repos |
仓库路径写错,或当前用户无权限读取 | 用绝对路径,确认运行用户对仓库目录有读权限 |
| 备份文件大小一直为 0 | 命令输出被重定向到错误文件,或磁盘已满 | 检查目标路径和 inode 余量,先 df -h 看磁盘 |
| dump 过程中仓库还在写入 | 可能导致版本集不一致 | 尽量在低峰期备份,或改用 hotcopy,不要直接 copy 目录 |
load 增量包时报错 Revision not found |
增量包加载顺序错误或缺少前序版本 | 确认全量包先加载,增量包按版本号升序依次加载 |
| hotcopy 复制很慢 | 仓库太大或目标盘性能差 | 拆分仓库、改用 dump+gzip、或把目标盘换成 SSD |
| 备份脚本定时任务没执行 | 脚本里命令路径不在 PATH 中 | 在脚本开头写死 SVNADMIN=/usr/bin/svnadmin,并看日志 |
| 恢复后 checkout 正常但看不到历史日志 | 备份时只复制了工作副本,不是仓库数据 | 确认备份源是服务器仓库目录,而不是本地项目文件夹 |
| 权限文件丢失导致所有人无法登录 | 备份没有包含 conf 目录 | 使用仓库目录整体归档或单独备份 conf/hooks |
这个表里的问题大多是操作层面的事,很多不是命令不会写,而是没留意“备份源对不对”“路径对不对”“权限够不够”这些基本功。
5.3 一次备份恢复事故给我的教训
之前我参与过一个项目,SVN 仓库用了三年,团队人数从三个人涨到十几个人。当时的“备份”方案是管理员偶尔用 zip 把整个仓库目录压缩一下,放在同一台服务器的另一个分区。后来有一天仓库所在的磁盘出现坏道,仓库目录结构损坏,我们用 zip 包里的仓库目录尝试启动服务,结果各种报错,因为那是运行中的目录直接打包出来的,里面可能本身就带着损坏状态。
那次我用 dump 文件恢复了所有版本数据,花费一整天,过程中发现最大的问题不是技术,而是没有可用的验证流程。如果当时有定期恢复演练,仓库磁盘坏道其实不会造成那么大影响,顶多十分钟就能切换。从那以后,我给自己定了一条规矩:任何备份方案,必须配一个恢复清单,清单里写清楚备份文件在哪里、需要执行哪些命令、恢复后如何验证。没有恢复清单的备份,等于没有备份。
在备份上的几个实操建议
最后再分享几个我自己用惯了的建议。第一,备份脚本不要只写“执行成功”的日志,一定要把备份文件大小、版本号、耗时都记录下来,靠 size 落差往往能提前发现备份异常。第二,数据库或代码仓库的备份,最好不要用同一个用户名跑,单独建一个备份专用账号,只给读权限,降低误操作风险。第三,SCM 这类核心服务,备份内容一定要包含三份东西:仓库数据、配置参数、恢复文档,少一样都让人睡不踏实。真到事故那天,你会发现能拉住你的不是当时用了多高级的命令,而是平时有没有把“备份”当一回事、提前把恢复路径跑通。
