经常有朋友问我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这一支。下面这张表是当时我自己的对比记录维度就是最常见的几个痛点。对比项svnserveApache 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 grw /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或者600sudo 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和HTTPSsudo 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 /CCN/STBeijing/LBeijing/OIT/CNsvn.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 adminexample.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-failuresunknown-ca,cn-mismatch和--non-interactive参数一次性接受证书svn list https://服务器IP/svn/demo --username admin --non-interactive --trust-server-cert-failuresunknown-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弹窗里填仓库URLIDEA就会像管理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 cleanupsvn: 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或重新checkoutsvn 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 grw /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服务会越用越顺手。