经常有朋友问我,Ubuntu上到底怎么搭一套正经能用的SVN服务器。网上教程一搜一大把,但大多要么只讲svnserve那套最原始的方案,要么就三行命令带过,权限怎么配、Apache怎么接、客户端怎么绕过各种坑,全靠自己踩。这篇文章我把前前后后折腾过、帮人修过的经验完整捋一遍,从选型、安装、建仓、配权限到接HTTPS、配客户端、跑备份和钩子,一次性交代清楚,照着做就能搭出一套规范的SVN服务。
先说结论,这套方案我推荐用Apache + mod_dav_svn的方式,而不是单独跑svnserve。原因后面细说,核心就一条:Apache方式能直接获得Web访问、更灵活的路径级权限控制,还能顺手用HTTPS把认证加密掉,而这些恰恰是团队用SVN时最在意的几个点。
1. 方案选型与环境准备
1.1 为什么还选SVN,为什么用Apache而不是svnserve
很多新项目已经全面拥抱Git了,但SVN在一些场景里并没有退场:老项目历史仓库沉淀在SVN里,团队习惯集中式模型,或者需要非常直观的目录级授权,SVN都比Git更省心。尤其是二进制文件多、且团队不想折腾子模块的场景,SVN的检出模式和锁定机制反而更简单直接。
具体到服务器方案,SVN官方提供了两种服务形态:一是自带svnserve,默认监听3690端口,配置文件简单、部署快;二是走Apache的mod_dav_svn模块,让SVN直接挂在HTTP服务器上。我早期图省事用过svnserve,但很快就撞到几面墙:权限只能靠authz控制访问,却没法方便地让新人通过浏览器直接看代码;而且svnserve默认不加密传输,用户名口令在网络上是明文的;想搞多仓库、备份、Web界面也都不如Apache那边生态成熟。所以给团队用、且打算长期维护的仓库,我强烈建议走Apache这一支。
下面这张表是当时我自己的对比记录,维度就是最常见的几个痛点。
| 对比项 | svnserve | Apache + mod_dav_svn |
|---|---|---|
| 部署复杂度 | 低,基本改3个配置文件 | 中,需要管理Apache配置 |
| 路径级权限控制 | 支持authz | 支持authz,颗粒度更灵活 |
| 浏览器浏览代码 | 不支持 | 直接能看目录和文件 |
| 传输加密 | 需要额外隧道方案 | 配合HTTPS非常顺手 |
| 多仓库管理 | 靠多个root目录,稍麻烦 | SVNParentPath一个配置全搞定 |
| 团队协作扩展性 | 够用但很简陋 | 可加ViewVC、钩子、备份脚本等 |
所以我的最终建议是:个人临时用,svnserve没问题;但凡仓库要服务一个小团队、且以后会长期迭代,直接上Apache + DAV方案,免得来回迁移。
1.2 Ubuntu版本选择与基础环境
我用过Ubuntu 20.04、22.04和24.04 LTS来搭,结论是这几代LTS操作基本一致,命令差异很小。本文以22.04/24.04的路径为例,如果你还在用18.04,个别包名(比如libapache2-mod-svn)可能不同,需要留意。
系统装好后,先把软件源和基础包刷新一遍,保证后续安装不会被旧索引卡住:
sudo apt update sudo apt upgrade -y然后安装Apache和SVN相关组件。Ubuntu 20.04起SVN的Apache模块包就已经合并改名了,直接装这个组合:
sudo apt install -y apache2 subversion libapache2-mod-svn装完后用命令确认模块是否就位:
ls /usr/lib/apache2/modules/ | grep svn apache2ctl -M | grep svn正常情况下能看到dav_svn和authz_svn两个模块。如果apache2ctl那行输出为空白,多半是模块还没启用,手动启用一下:
sudo a2enmod dav dav_svn authz_svn sudo systemctl restart apache2这个地方很多人踩过坑,提醒一句:不同教程让你装libapache2-mod-dav-svn,那是老版本Ubuntu的包名,新系统里装libapache2-mod-svn就行,装完再看看模块是否真的加载了,不要装完就急着建仓。
2. 仓库规划与创建
2.1 目录结构和仓库怎么规划
SVN仓库一旦上线,目录结构基本就固定了,所以开始之前先把根目录规划好。我习惯把所有仓库统一放在/var/svn下,每个仓库一个独立目录,仓库内部按标准的trunk、branches、tags结构预留好。这种约定不是SVN强制要求的,但团队协作时必须遵守,否则后续分支和发布混乱是迟早的事。
sudo mkdir -p /var/svn sudo chown -R www-data:www-data /var/svn sudo chmod -R g+rw /var/svn这里解释一下权限用意:如果你用Apache的DAV方案,实际读写仓库的是Apache的子进程(以www-data用户运行),所以仓库目录必须让www-data能写。之前我偷懒用默认root权限建目录,结果提交代码时报E200033、E170001之类的权限错误,查了半天才发现仓库目录不带写权限。所以一开始就把属主和组权限定对,后面会省掉一堆排查时间。
2.2 创建仓库并初始化标准目录
创建仓库用svnadmin,这是SVN服务端的专属命令,别跟svn客户端命令搞混。一个仓库执行一次:
sudo svnadmin create /var/svn/demo创建完可以在/var/svn/demo下看到conf、hooks、db等目录。然后手动建立三个标准子目录,这是顺手的加分操作,方便之后直接用标准流程管理分支和标签:
sudo mkdir -p /var/svn/demo/trunk /var/svn/demo/branches /var/svn/demo/tags之后把标准结构导入版本库,第一次导入时建议用svn import,而不是直接在仓库目录里svn add,因为仓库物理目录和版本库的目录树并不完全是同一回事。具体操作是先在本地准备一个目录骨架,然后导入:
mkdir temp-skeleton && cd temp-skeleton mkdir -p trunk branches tags svn import . http://localhost/svn/demo -m "初始化仓库目录结构" cd .. && rm -rf temp-skeleton如果你还没配好Apache和认证,这一步导入可能会报无法连接,也是正常的。不过实际上很多团队不导入骨架,直接svn mkdir也是可以的。我自己的习惯是导入一次,保证历史和目录结构从开头就是清晰的。
2.3 多仓库场景下的目录布局
如果你的团队不止一个项目,没必要为每个项目单独启动一套服务。用Apache的SVNParentPath指向/var/svn,那么/var/svn下的每一个子目录都会自动作为一个独立仓库对外提供服务。也就是说,我只需要在/var/svn下再创建第二个仓库:
sudo svnadmin create /var/svn/website sudo mkdir -p /var/svn/website/trunk /var/svn/website/branches /var/svn/website/tags sudo chown -R www-data:www-data /var/svn/website然后访问路径自动变成http://服务器/svn/website。这种设计比一个仓库一个服务、每个服务单独改监听端口要清爽太多了,无论是维护成本还是权限管理,都优雅一个数量级。
3. 认证与权限配置
3.1 用htpasswd创建SVN登录账号
Apache DAV方式下,用户的认证直接复用Apache的Basic Auth机制,所以创建账号用的是维护Apache认证文件的htpasswd命令,而不是SVN自己的passwd文件。
创建第一个用户时加-c参数,表示新建认证文件:
sudo htpasswd -c /etc/apache2/dav_svn.passwd admin追加后续用户时,千万去掉-c,否则会把之前的用户文件整个覆盖掉:
sudo htpasswd /etc/apache2/dav_svn.passwd zhangsan sudo htpasswd /etc/apache2/dav_svn.passwd lisi建议用-B参数指定bcrypt散列方式,比默认的MD5更安全:
sudo htpasswd -B /etc/apache2/dav_svn.passwd zhangsan顺便说一个权限细节:这个passwd文件对Apache进程需要可读,但绝不能让普通用户随便读。所以创建完后最好确认权限是640或者600:
sudo chown root:www-data /etc/apache2/dav_svn.passwd sudo chmod 640 /etc/apache2/dav_svn.passwd3.2 配置authz实现细化权限控制
Basic Auth只解决“谁能登录”,但解决不了“谁能看哪些目录、谁能写哪些目录”。路径级权限靠的是authz配置文件,这也是SVN权限管理的核心。先创建authz文件:
sudo touch /etc/apache2/dav_svn.authz一个典型的authz文件长这样:
[groups] admin = admin,zhangsan dev = lisi [/] * = r @admin = rw [/demo/trunk] @dev = rw [/demo/branches] @admin = rw @dev = r [/website] @dev = rw简单解读一下:中括号里写的是版本库路径,/ 表示所有仓库的根;* = r表示所有认证用户默认只有读权限;@admin表示组名,rw就是可读可写。这样能实现有的组只管自己项目、有的组能全仓读写。
这里有个容易弄混的地方:如果启用了SVNParentPath多仓库模式,authz中日志路径要写[仓库名:/相对路径]这种格式,比如[demo:/trunk],而不是[demo/trunk]。我第一次配的时候就栽在这里,规则写错了,结果权限完全不生效,报错也模棱两可。上面示例是最简单的情况,实际生产我建议写成带仓库名前缀的格式,更明确、更不容易串仓库。
3.3 修改Apache的DAV配置
Ubuntu安装libapache2-mod-svn后,会自动在/etc/apache2/mods-available目录下生成dav_svn.conf文件,我们需要把自己的配置填进去:
sudo vim /etc/apache2/mods-available/dav_svn.conf把默认内容替换成下面这份:
<Location /svn> DAV svn SVNParentPath /var/svn AuthType Basic AuthName "SVN Server" AuthUserFile /etc/apache2/dav_svn.passwd AuthzSVNAccessFile /etc/apache2/dav_svn.authz Require valid-user </Location>这里解释几个关键行:
- SVNParentPath指向/var/svn,表示目录下每个子目录都是一个仓库,适合多仓库管理模式。如果你只有一个仓库,也可以改成SVNPath /var/svn/demo,但后续扩展仓库就麻烦。
- Require valid-user表示所有访问必须过Basic认证,没有匿名访问。
- AuthzSVNAccessFile就是上面配置的权限文件,它会在认证通过后再做路径级权限检查。
配置完成后重载Apache,让配置生效:
sudo apache2ctl configtest sudo systemctl reload apache2如果configtest报语法错误,99%是Location路径或配置文件里多空格、少行了,仔细检查即可。
3.4 防火墙放行与浏览器验证
服务器如果启用了UFW防火墙,记得放行HTTP和HTTPS:
sudo ufw allow 80/tcp sudo ufw allow 443/tcp然后随便找一台能访问该服务器的机器,浏览器打开:
http://服务器IP/svn/demo/此时应该弹出用户名密码框,输入admin账号后,能看到demo仓库下的目录列表,再试试访问没有被授权的路径,应该会被拒绝。这一步验证通过,说明认证和权限链路已经通了。
4. 配置HTTPS加密访问
4.1 自签名证书的生成
Basic Auth本身不加密,用户名密码会在网络上明文传输。公司内网用可能没人太较真,但只要仓库会被跨网段访问、或者你自己对安全有一点追求,都应该把HTTPS开起来。这里先介绍自签名证书做快速加密,生产环境建议申请受信任的CA证书。
生成证书用openssl一条命令就够:
sudo mkdir -p /etc/apache2/ssl sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout /etc/apache2/ssl/svn-server.key \ -out /etc/apache2/ssl/svn-server.crt \ -subj "/C=CN/ST=Beijing/L=Beijing/O=IT/CN=svn.example.com"生成的.crt证书文件和.key私钥文件,分别交给Apache的SSL虚拟主机使用。
4.2 让Apache加载SSL模块并挂载证书
先启用SSL模块:
sudo a2enmod ssl然后编辑/default-ssl.conf或者新建一个专门的虚拟主机配置:
<IfModule mod_ssl.c> <VirtualHost _default_:443> ServerAdmin admin@example.com ServerName svn.example.com SSLEngine on SSLCertificateFile /etc/apache2/ssl/svn-server.crt SSLCertificateKeyFile /etc/apache2/ssl/svn-server.key ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined <Location /svn> DAV svn SVNParentPath /var/svn AuthType Basic AuthName "SVN Server" AuthUserFile /etc/apache2/dav_svn.passwd AuthzSVNAccessFile /etc/apache2/dav_svn.authz Require valid-user </Location> </VirtualHost> </IfModule>把这个配置放到/etc/apache2/sites-available/svn-ssl.conf里,然后启用站点:
sudo a2ensite svn-ssl.conf sudo apache2ctl configtest sudo systemctl reload apache2配置完成后,访问地址就变成:
https://服务器IP/svn/demo/这里说一下热词里常见的“Apache SVN证书告警”问题:浏览器第一次访问自签名证书会提示不安全,客户端(如TortoiseSVN)也会弹证书验证窗口,需要手动确认接受。对内网工具来说这可以接受,但关键是之后访问路径要用https://,避免继续走http漏密码。
4.3 让SVN客户端不烦人地接受自签名证书
如果你不想每次连接都确认证书,可以在客户端做一次性信任。拿TortoiseSVN举例,首次连接时弹窗里把“信任这家证书颁发机构”勾上,之后就不会再问。命令行svn客户端则可以用svn列表时加上--trust-server-cert-failures=unknown-ca,cn-mismatch和--non-interactive参数,一次性接受证书:
svn list https://服务器IP/svn/demo --username admin --non-interactive --trust-server-cert-failures=unknown-ca这里提一句,自签名证书如果CN和访问的域名或IP不一致,TortoiseSVN这类客户端会反复报警,所以签名时尽量用实际访问的主机名或IP做CN。
5. 客户端接入与常用操作
5.1 TortoiseSVN小乌龟的安装和设置
Windows用户基本都听说过TortoiseSVN,就是那个文件管理器右键菜单里出现一堆SVN操作的小乌龟。下载安装时有个极其关键的选项:是否安装命令行客户端工具。默认是不装的,但IDEA、VS Code要通过svn.exe调用SVN,没有它插件会直接报找不到svn。
所以装TortoiseSVN时,在自定义安装那一步,一定要把“command line client tools”选上。装完之后确认:
svn --version如果命令行找不到,重新运行安装包,选择修改安装,把命令行工具补装上,并把TortoiseSVN的bin目录加入PATH。
日常操作逻辑是这样的:先在本地右键选择“SVN Checkout”,填上https://服务器IP/svn/demo,然后目录里就有带绿勾的文件。后续提交、更新、回滚菜单都非常直观,不需要记命令。
5.2 IDEA和VS Code怎么接上去
IDEA接入SVN分两步:先设置本机svn.exe路径,再关联具体仓库。菜单路径是Settings -> Version Control -> Subversion,把“Use command line client”勾上并选择svn.exe的正确路径。接着在VCS菜单里选择“Checkout from Version Control” -> Subversion,弹窗里填仓库URL,IDEA就会像管理Git仓库一样管理SVN。
VS Code则需要装一个SVN插件。在扩展市场搜索SVN,建议装下载量最大的那个,比如过去几年的“svn”插件。装完后用命令面板输入“SVN: Checkout”或者直接在源代码管理面板里操作。注意SVN插件依赖工作副本这个概念,你需要先用svn checkout把一个目录绑定成工作副本,VS Code才能识别里面的SVN状态。这里也是热词里“vscode使用svn标记文件”怎么用的问题:打开代码文件后,编辑器左侧会自动显示M(已修改)、A(新增)、?(未纳管)、C(冲突)等标记,这些标记和svn status命令输出的状态是一一对应的。
5.3 日常命令速查
命令行方式依然是排查问题最直接的手段,哪怕平时用图形客户端,也建议掌握这几个:
# 检出 svn checkout http://服务器IP/svn/demo demo # 更新 svn update # 查看状态 svn status # 添加新文件 svn add filename # 提交 svn commit -m "提交说明" # 查看日志 svn log -l 10 # 回滚到指定版本 svn merge -r HEAD:100 . # 把当前工作副本合并回版本100 svn commit -m "回滚到版本100"关于热词里经常出现的“svn回滚到指定日期版本”,这里展开一下。如果只知道日期不知道版本号,可以先用svn log定位:
svn log -r {2024-12-31}:{2025-01-31} http://服务器IP/svn/demo查到目标版本号后用上面两条命令合并回去并提交。SVN没有类似Git的reset概念,正确的回滚方式就是把旧版本反向合并到当前,再提交一次,历史记录里能看到一条“回滚”提交,这样做可追溯、不影响其他人的副本。
5.4 svn 推送 提示仓库不存在的排查
这个报错集中出现在客户端checkout或commit时。如果你已经确认URL没拼错,那原因基本集中在三个地方:一是Apache的SVNParentPath指向的目录和实际仓库目录不一致;二是仓库创建后没做chown,导致Apache无法读取仓库目录,它读不到就直接报“仓库不存在”;三是URL路径层级对不上,比如仓库在/var/svn/demo,但URL写成/svn。顺着这三个方向排查,绝大多数问题都能解决。
6. 常见问题与排错实录
6.1 常见客户端报错速查表
建一个群,把SVN常见报错贴进去的时候,很多人第一反应是抓瞎。我整理了一份比较高频的速查表,你可以直接存一份。
| 报错信息 | 大概率原因 | 解决路径 |
|---|---|---|
| svn: E170013 Unable to connect to a repository | 网络不通、防火墙拦截、URL写错 | 检查服务器和端口,确认URL层级 |
| svn: E200033 Another process is holding a lock | 其他客户端中断导致锁残留 | 在出错目录svn cleanup |
| svn: E170001 Authentication failed | 帐号密码错误或authz权限不足 | 重置密码,确认用户名有对应权限 |
| svn: E200009 Error while post-commit hook | 钩子脚本执行失败 | 查看钩子输出和服务器端日志 |
| svn: E155004 Working copy locked | 客户端非正常退出 | svn cleanup后重试 |
| svn: E155010 The node is not present | 工作副本损坏 | 用svn revert或重新checkout |
| svn is not a working copy | 目录本身未checkout,或.svn目录缺失 | 确认目录来源,重新checkout |
这里特别强调一下svn cleanup这个大杀器。SVN这种集中式架构,最怕客户端中途断网、断电,导致工作副本出现锁标记。遇到很多奇奇怪怪的状态和提交失败,第一步永远是svn cleanup,再不行才考虑revert和re-checkout,不要一上来就把整个副本删了重拉。
6.2 TortoiseSVN绿勾消失与缓存问题
热词里有一个很常见的疑问:“svn 文件上的绿勾没了”。大部分情况不是文件冲突或版本问题,而是TortoiseSVN的状态缓存挂掉了。解决办法是:在TortoiseSVN的Settings -> Icon Overlays里把Status cache设为“Shell”,然后重启explorer.exe进程,或者干脆注销重登,绿勾就会恢复。这个问题不影响真实的代码状态,只是图标显示问题,不必恐慌。
另外VisualSVN、TortoiseSVN这类的商业工具的License过期问题,我个人的经验是:如果公司用的License过期续不上,可以考虑把客户端收编到命令行+TortoiseSVN的开源方案里,或者直接迁移到Ubuntu服务器上的开源SVN栈,授权问题瞬间消失。当然这牵涉团队习惯,不强求。
6.3 服务端权限与文件属主问题
在服务端排错,重点看Apache的错误日志和访问日志:
sudo tail -f /var/log/apache2/error.log sudo tail -f /var/log/apache2/access.log经常能看到的权限错误长这样:
[client 192.168.1.10] Access denied: 'demo' '/trunk'这种前半段是认证错误,后半段则是authz权限命中失败。前者建议直接看htpasswd文件是否对应;后者则去检查dav_svn.authz中的路径格式,尤其是前面提到的带不带仓库名前缀的区别。
如果出现“Could not open the requested SVN filesystem”这类信息,基本就是仓库目录属主问题,确认一下/var/svn/demo是否属于www-data,再给仓库目录加组读写权限即可:
sudo chown -R www-data:www-data /var/svn sudo chmod -R g+rw /var/svn6.4 提交代码时的冲突处理
SVN集中式仓库下,多人改同一文件几乎是必然的,冲突处理是每个玩SVN的人都会遇到的日常。客户端在update时会提示冲突文件,同时生成三个临时文件:.mine、.r旧版本号、.r新版本号。操作上我的建议是:不要急着点“Edit conflict”,先看一下.mine和你印象中的改动范围,然后在冲突编辑界面里逐行比对,保留正确内容后标记为已解决,再commit。
比较省心的习惯是:每次commit前先svn update,测试通过后再commit,别把大改动攒着一次性提交,也别裸提交不带更新。这个习惯能让冲突率低很多。
7. 备份、迁移与钩子
7.1 svnadmin dump备份和增量思路
SVN服务端备份的官方正解是svnadmin dump,它把版本库完整导出为一个档案文件。全量备份我一般这样做:
sudo svnadmin dump /var/svn/demo > /backup/demo-$(date +%F).dump如果仓库很大,可以加上--incremental做增量备份,配合cron定时任务,每日凌晨dump一次。恢复时:
sudo svnadmin create /var/svn/demo-restore sudo svnadmin load /var/svn/demo-restore < /backup/demo-2025-01-01.dump另一种更快的方式是svnadmin hotcopy,它直接把仓库目录的当前状态完整复制一份,恢复时更省事。但hotcopy是同步快照,更适合做快速现场备份,dump则更适合长期归档和跨版本迁移。
我的个人建议是:备份脚本里dump和hotcopy结合,hotcopy做每日现场快照,dump做每周归档,双保险。备份这件事在SVN运维里其实非常关键,很多人搭好服务就忘了备份,等硬盘坏了才后悔。
7.2 post-commit钩子做自动同步
SVN钩子脚本是目前团队自动化里最轻量实用的方式,post-commit提交后钩子就是最典型的应用。典型场景是:提交代码后,服务器自动把最新代码同步到测试环境目录。
先在目标服务器上checkout一份工作副本:
svn checkout http://服务器IP/svn/demo /var/www/demo --username svnbot --password 你的密码 --non-interactive然后编辑仓库的钩子文件:
sudo vim /var/svn/demo/hooks/post-commit写入内容:
#!/bin/sh /usr/bin/svn update /var/www/demo --username svnbot --password 你的密码 --non-interactive保存后要给钩子加执行权限,否则钩子永远不会被调用:
sudo chmod +x /var/svn/demo/hooks/post-commit这里有个坑必须提醒:钩子脚本里最好不要用工作副本的实际用户名(比如admin)直接做同步账号,因为提交者在提交后触发钩子时,钩子进程可能拿不到原用户的完整上下文。单独建一个只读或写权限受限的svnbot账号来做同步,既清晰又安全。
7.3 结合备份策略维护长期稳定
我们把上面两件事合到一起,可以整理成一个比较健康的维护节奏:每日cron跑一次dump备份,每周做一次离线归档,提交钩子负责代码同步,再把Apache日志定期rotate。这套组合拳下来,一个中等规模团队的SVN服务已经相当皮实了。我在生产环境跑过两年,除了坏过一块硬盘(靠备份恢复了),基本没出过幺蛾子。
8. 经验总结和几个小建议
搭Ubuntu SVN服务器这件事,单看步骤并不难,难的是选择一条适合团队的路。svnserve虽然适合快速验证,但长期服务团队还是建议Apache + mod_dav_svn配合HTTPS,权限清晰、扩展性强、Web访问也方便。仓库规划、目录属主、备份脚本这些在最开始就做好,后面运维会轻松非常多。
从实际使用中我有几个小建议:一是在最初的仓库规划里就把目录结构建好,后面分支管理会很顺手;二是SVN的钩子非常适合做轻量自动化,post-commit同步代码、发通知邮件都可以实现,不必额外上太重CI系统;三是TortoiseSVN这类工具装的时候顺手把命令行组件选上,很多编辑器插件的坑就提前避掉了。
如果这篇文章里的信息能帮你把Ubuntu上的SVN服务跑起来,少踩几个我当年踩过的坑,那就值了。后续如果团队分支和标签管理玩得深了,再把authz的细致规则和钩子脚本逐步迭代起来,你们的SVN服务会越用越顺手。