前阵子接手一台内网隔离的Linux服务器,需求很直接:在这台机器上把SVN服务端搭起来,供团队提交代码和文档。原本以为半小时能搞定,结果踩了不少坑——没有外网、yum源连不通、rpm包依赖一环扣一环,如果直接盲目rpm安装,几乎必然报依赖缺失。折腾完整个流程之后,我想把这套“离线安装SVN”的完整操作记录下来:从准备安装包、梳理依赖,到配置用户权限、注册系统服务,一路上会遇到什么问题、怎么排查,全部捋清楚。这套方法不依赖特定发行版,只要你的机器是CentOS 7/8、Rocky、AlmaLinux这类RHEL系,基本可以照着复现,真正适合内网环境和离线部署场景。
1. 离线安装的整体思路:先解决“包”的问题,再谈SVN配置
1.1 离线环境装软件,最怕的不是软件本身,而是依赖链
很多人第一次接触离线安装,下意识会去网上找一个subversion的rpm包或者tar.gz源码包,然后拷到服务器上直接装。这个过程通常会碰到三个问题:
- 单个rpm包往往不完整,SVN依赖apr、apr-util、sqlite、neon等一长串库,少一个都装不上。
- 源码编译虽然能绕开rpm依赖,但需要gcc、make等编译工具链,离线环境下连这些工具都未必齐全,而且编译耗时长、容易因为缺少某个开发库报警告。
- 版本不匹配问题,尤其是Java相关工具链场景下,SVN版本差异会直接影响客户端兼容性。
我推荐的方案是:在一台能联网的同版本Linux机器上,用包管理器把SVN及其全部依赖rpm包拉下来,然后拷到内网机器,做成本地yum源来安装。这个方案的核心思路是“把在线安装的依赖解析结果原封不动搬到离线环境”,本质上和在线yum install一模一样,只是把下载环节提前了。
1.2 对比一下几种常用离线方案,选哪种更省事
我实际中评估过三类做法,这里直接说结论:
| 方案 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| 下载rpm包 + 本地yum源 | 依赖自动解析,安装快,可复现 | 需要提前准备包,制作yum源需要createrepo工具 | 大多数内网服务器,强烈推荐 |
| rpm -ivh 手动逐个安装 | 简单粗暴 | 依赖关系处理非常痛苦,经常需要加--nodeps,容易留下隐患 | 只装单个无依赖的rpm时应急 |
| 源码编译安装 | 灵活、可定制 | 编译时间长,依赖编译环境,后续维护麻烦 | 没有对应rpm包或需要定制功能时 |
从运维角度说,本地yum源是性价比最高的做法。它不仅能用于SVN,以后内网机器要装其他软件、打安全补丁,都可以复用这套机制。所以本篇主线就用“下载rpm包—制作本地yum源—yum安装”这条路径。
2. 安装前准备:先摸清系统底细,再梳理依赖清单
2.1 确认系统版本、架构和自带软件情况
在动手之前,先要在目标机器上执行几条命令,确认基础信息。这一步千万别省,因为不同系统版本对应的rpm包来源差别很大,架构不同更是完全不能通用。
# 查看操作系统版本 cat /etc/redhat-release # 查看内核和CPU架构 uname -m # 查看是否已经装了SVN相关组件 rpm -qa | grep subversion svnserve --version我那次操作的机器是CentOS 7.9,x86_64架构,系统里没有任何subversion相关包。如果你的机器已经装过旧版本,建议先rpm -e统一卸载干净,再走离线安装流程,否则可能出现库文件版本冲突。
2.2 梳理Subversion的完整依赖链
这是离线安装里最重要的功课。SVN不是单打独斗的软件,它依赖一套底层的运行时库。用yum安装的时候系统会自动解决这些依赖,但离线环境下你必须提前准备好,这里列出CentOS 7上最常见的依赖清单:
- subversion:主程序
- subversion-libs:核心运行库,几乎所有SVN功能都依赖它
- apr:Apache可移植运行库,提供跨平台底层接口
- apr-util:apr的扩展库
- sqlite:SVN用于存储FSFS格式元数据的数据库引擎
- cyrus-sasl:当使用svnserve的SASL认证时会用到
- expat:XML解析库,SVN的配置解析依赖它
- neon:WebDAV客户端库,使用http://协议访问仓库时需要
其中subversion、subversion-libs、apr、apr-util、sqlite是核心链路,缺一个都起不来。这些包在联网机器上通过repotrack命令可以一次性全部拉取,不需要逐个去网上找。
2.3 确定安装包的获取来源:联网机器“下载包”这一步怎么做
准备离线rpm包,最稳定的方式是在一台与目标机器系统版本一致的联网机器上执行repotrack或yumdownloader。repotrack在yum-utils工具包里,可以递归地下载包及其全部依赖;yumdownloader配合--resolve参数也能达到类似效果,但有时会漏掉一些“推荐安装”的依赖,所以优先用repotrack。
# 在联网机器上,先安装yum-utils yum install -y yum-utils # 创建存放rpm包的目录 mkdir -p /root/svn-offline-rpms cd /root/svn-offline-rpms # 下载subversion及其全部依赖包 repotrack subversion执行完之后,这个目录下会有一大堆rpm文件。这时候把它们打包成一个tar.gz,传到内网机器上就可以了:
tar czf svn-offline-rpms.tar.gz /root/svn-offline-rpms内部传文件的办法常见有scp、ftp、U盘拷贝、内网共享目录,怎么方便怎么来。我那次是打包后拷到跳板机再上传,注意文件权限即可。
注意:下载rpm包时,一定确认联网机器和目标机器的系统大版本一致。CentOS 7的包不要在CentOS 8上装,反之亦然,否则会遇到glibc版本不兼容的问题。
3. 核心操作:制作本地yum源并完成离线安装
3.1 目标机器上创建本地yum仓库
rpm包到位之后,接下来要把它们变成yum能识别的仓库。如果目标机器之前没有安装createrepo命令,可以在下载rpm包时顺手把createrepo这个包也一起拉下来,或者直接在临时目录里用rpm强制安装一个独立的createrepo rpm包。
准备本地仓库的步骤:
# 1. 把离线包解压到目标机器,比如/opt/svn-offline-rpms目录 mkdir -p /opt/svn-offline-rpms tar zxf svn-offline-rpms.tar.gz -C /opt/svn-offline-rpms # 2. 如果系统没有createrepo,先用rpm方式装一个 # 在联网机器上提前下载 createrepo 包及其依赖,拷过来 rpm -ivh createrepo-*.rpm # 3. 生成仓库元数据 cd /opt/svn-offline-rpms createrepo .createrepo执行完之后,目录下会出现一个repodata文件夹,这就是yum检索元数据的地方。此时可以新建一个yum源配置文件来指向它:
vi /etc/yum.repos.d/local-svn.repo # 文件内容如下 [local-svn] name=Local SVN Repository baseurl=file:///opt/svn-offline-rpms enabled=1 gpgcheck=0保存后执行yum clean all和yum makecache,让yum重新读取仓库信息。
提示:gpgcheck建议设成0,因为我们用的是自己下载的rpm包,签名校验没有实际意义;如果强行设成1而缺少公钥,反而会报GPG key相关错误。
3.2 用yum命令执行离线安装,让依赖自动解析
本地源就绪之后,安装就变得非常简单了:
yum install -y subversionyum会从/opt/svn-offline-rpms这个本地源里把所有依赖包一次性装完,不需要再额外处理依赖问题。安装完成后验证一下:
svnserve --version正常会输出版本信息和编译参数,比如:
svnserve, version 1.7.14 (r1542130) compiled Apr 29 2018, 20:45:40到这一步,SVN本体已经安装成功。整个过程的核心就是把在线安装时的依赖下载环节前置,然后用本地源“欺骗”yum,让它在离线状态下也能正常解析依赖。
3.3 如果不想做yum源,能不能直接rpm安装?
有读者会问,既然rpm包都在目录里了,我能不能rpm -ivh *.rpm一把梭?理论上可以,但我踩过一次坑:rpm安装有顺序问题,必须先装底层依赖库,再装subversion主体。直接rpm -ivh *.rpm时,如果shell通配符展开的顺序不对,非常容易报“需要被依赖”的错误。
如果实在不想做yum源,有一个折中办法:
rpm -ivh apr-*.rpm apr-util-*.rpm sqlite-*.rpm subversion-libs-*.rpm subversion-*.rpm也就是手动控制安装顺序。但这样做一旦某个包版本不匹配,排查起来很痛苦。做本地yum源才是正统做法,多花5分钟,省下后面一堆麻烦。
4. 配置SVN服务端:创建仓库和用户权限体系
4.1 创建代码仓库并确认权限归属
SVN装好之后,第一件事是规划仓库目录。我习惯把仓库统一放在/opt/svn下,每个项目一个仓库,或者用一个大仓库再分目录。两种方式各有优劣,这里我先用“仓库目录”方式演示:
# 创建仓库根目录 mkdir -p /opt/svn # 创建项目仓库,这里以demo为例 svnadmin create /opt/svn/demo创建完成后,/opt/svn/demo目录下会出现conf、db、hooks等子目录。如果SVN服务是以root身份启动的,那么仓库目录权限默认没问题;如果以后要切换为普通用户启动svnserve,必须chown -R svnuser:svnuser /opt/svn/demo,否则客户端提交时会疯狂报Permission denied。
注意:svnadmin create这个命令创建的仓库默认是FSFS格式,这是SVN官方推荐的存储模式,不要随便改成BDB格式,维护难度大且稳定性差。
4.2 核心配置文件:svnserve.conf到底要改哪些参数
仓库的配置目录是/opt/svn/demo/conf,里面有三个核心文件:svnserve.conf、passwd、authz。svnserve.conf是整个服务端的“总开关”,里面很多配置项默认被注释掉,必须手动打开并指定authz和passwd路径。
我一般把svnserve.conf改成这样:
[general] anon-access = none auth-access = write password-db = passwd authz-db = authz realm = my-svn-realm逐行解释一下:
- anon-access = none:禁止匿名访问。不设成none的话,别人不输密码也能读取仓库,这对代码仓库来说很危险。
- auth-access = write:认证用户有读写权限。
- password-db = passwd:账号密码文件指向当前目录下的passwd文件。
- authz-db = authz:授权规则文件指向当前目录下的authz文件。
- realm = my-svn-realm:认证域名称,只要是纯svnserve方式,这个名字通常不会影响使用,但多个仓库时建议区分开,避免缓存串台。
改完之后重启svnserve服务才生效。注意svnserve.conf里的配置项等号两边不要乱加空格,有些SVN版本解析非常严格,空格会导致配置项被忽略。
4.3 用户密码文件和权限文件:最简单的授权实践
passwd文件格式很直白,就是“用户名 = 密码”的列表。我建议至少创建一个管理员账号和一个普通成员账号,方便测试不同权限:
[users] admin = Admin@123456 zhangsan = zhangsan@123456 lisi = lisi@123456authz文件控制“谁能访问哪个仓库的哪个目录”。SVN的权限核心是“先写继承规则,再写例外规则”,常用的写法如下:
[groups] devteam = zhangsan,lisi [/] admin = rw @devteam = r * = [/trunk] @devteam = rw这个规则含义是:根目录下admin可读写,devteam组只读,其他人没有任何权限;但trunk目录下devteam组提升为读写权限。注意* =这行,星号表示“所有人”,等号右边留空表示无权限,这是安全基线写法,建议每一段都加上。
保存好passwd和authz之后,再手动启动svnserve进行第一次验证:
svnserve -d -r /opt/svn-r参数指定仓库根目录,这样客户端访问路径不用带上完整的物理路径,直接用svn://服务器IP/demo就能连上demo仓库。
5. 把SVN注册为系统服务并放行防火墙
5.1 使用systemd管理svnserve进程,告别手动启动
前面用svnserve -d启动只是临时进程,一旦机器重启进程就没了。要让它随开机自启,必须写一个systemd服务文件。我通常在/etc/systemd/system/svnserve.service路径下创建:
[Unit] Description=Subversion Server After=network.target [Service] Type=forking ExecStart=/usr/bin/svnserve --daemon --root /opt/svn ExecReload=/bin/kill -HUP $MAINPID PIDFile=/run/svnserve.pid Restart=on-failure [Install] WantedBy=multi-user.target写完之后依次执行:
systemctl daemon-reload systemctl enable svnserve systemctl start svnserve systemctl status svnserve如果status显示active (running),就说明服务已经跑起来了。注意Type=forking是因为svnserve加--daemon参数后会fork出后台进程,所以systemd需要PIDFile来定位主进程;如果不想写PIDFile,可以把启动方式改成ExecStart=/usr/bin/svnserve -d -r /opt/svn,并且去掉Type=forking。
5.2 防火墙端口放行与客户端连通性检查
SVN默认走3690端口,内网环境通常开着firewalld或iptables,不放行的话客户端连接必失败。CentOS 7用firewalld的放行命令:
firewall-cmd --permanent --add-port=3690/tcp firewall-cmd --reload firewall-cmd --list-ports如果用的是旧版iptables,可以临时加一条规则:
iptables -I INPUT -p tcp --dport 3690 -j ACCEPT service iptables save之后在另一台机器上用svn客户端测试基本的仓库访问:
svn list svn://服务器IP/demo --username admin --password 'Admin@123456'如果配置正确,会列出仓库当前的空目录(第一次创建时什么内容都没有)。这一步能通,说明服务端、权限、防火墙全链路已经打通。
5.3 客户端导入项目与日常提交命令
服务端通路没问题后,最后一步就是把代码导入仓库。假设本地有一个项目目录/root/myproject,要提交到demo仓库的trunk目录下:
# 1. 初始化项目目录结构 cd /root/myproject mkdir -p trunk branches tags # 2. 将项目导入仓库 svn import /root/myproject svn://服务器IP/demo --username admin --password 'Admin@123456' -m "初始化项目结构"这时再执行svn list svn://服务器IP/demo,就能看到trunk、branches、tags三个目录。团队其他同事则可以用svn checkout把仓库拉下来:
svn checkout svn://服务器IP/demo/trunk --username zhangsan关于客户端,Windows上常用TortoiseSVN,Linux终端直接用svn命令,IDEA里配置Subversion时填入svn://服务器IP/demo即可。这几种方式都是走同一个svnserve服务,只要服务端配置好,客户端几乎不用额外设置。
6. 排障与避坑:离线安装SVN常见的几类问题
6.1 依赖错误:rpm工具本身缺createrepo怎么办
很多时候目标机器是一个精简系统,连createrepo都没装。解决办法是在联网机器上提前用yumdownloader --resolve --destdir=/root/createrepo-rpms createrepo把createrepo及其依赖全拉下来,再拷到内网机器上rpm -ivh一次性安装。这是我在“离线装离线工具”时最常用的方法,本质上仍是“提前下载依赖包”的思路。
6.2 E000013 Permission denied:根源往往在目录权限
svnserve启动之后,客户端checkout时报svn: E000013: Can't open file '/opt/svn/demo/format': Permission denied,这一般不是SVN配置问题,而是svnserve运行用户对仓库目录没有写权限。排查方法:
- 先用ps -ef | grep svnserve查看svnserve是什么用户启动的。
- 再用ls -ld /opt/svn/demo查看仓库目录owner。
- 如果svnserve是root启动,但仓库目录被chown成了别的用户,root反而可能因为SELinux策略限制而报权限问题。最稳妥的做法是统一:用root启动就把仓库owner设成root,或者干脆创建一个svn用户管理仓库。
如果是SELinux导致的权限问题,可以先看一下/var/log/audit/audit.log有没有相关记录,临时用setenforce 0验证一下;确认是SELinux拦截,再用chcon或semanage放行。
6.3 authentication failed:密码文件与权限配置不同步
客户端提示认证失败,90%是以下三种原因:
- passwd文件里用户被误加了空格或中文字符。
- authz文件中用户名大小写与passwd不一致。
- svnserve.conf里password-db路径不对,导致SVN没读到passwd文件,直接拒绝认证。
排错时最简单的办法是把svnserve.conf里的anon-access改成read,auth-access改成write,暂时注释authz-db,然后客户端用svn list试一次。如果能匿名访问,说明问题出在认证配置;如果仍然失败,那就是服务端没读到配置,重启svnserve再看。
6.4 防火墙/SELinux导致连不上端口
客户端报svn: E670008: Cannot connect to host,服务端却显示进程正常,这通常是防火墙或SELinux的问题。先用telnet 127.0.0.1 3690在服务器本机测一下端口,如果本机通、远程不通,那就是防火墙没放行;如果本机都不通,检查服务监听地址:ss -lntp | grep 3690,确认svnserve监听的是0.0.0.0:3690而不是只有127.0.0.1。
6.5 快速排查速查表
| 现象 | 可能原因 | 排查命令/操作 |
|---|---|---|
| yum install找不到包 | 本地yum源没生效 | yum repolist,检查repo文件baseurl |
| rpm安装有依赖冲突 | 使用方式不对 | 改用本地yum源安装 |
| svnserve启动报错 | 配置文件语法问题 | svnserve -d -r /opt/svn -X 前台运行看输出 |
| 客户端无法连接 | 防火墙/服务未启动 | systemctl status svnserve,firewall-cmd --list-ports |
| 提交时Permission denied | 目录权限或SELinux | ls -ld /opt/svn,setenforce 0临时验证 |
| 认证失败 | passwd/authz配置问题 | 检查空格、大小写、路径 |
7. 我实际维护中的几点经验
7.1 保留离线rpm包,后续版本升级和补丁全靠它们
离线环境最怕反复“找包”。这次为SVN准备的rpm目录,我建议单独放一份到内网公共存储上,同时把这个local-svn.repo文件保存好。以后内网机器再加节点,直接把这个repo配置和rpm目录拷过去就能复用,不用重新上网找。
7.2 仓库备份用hotcopy,随手就能做
SVN仓库的备份,首推svnadmin hotcopy,它能在仓库运行状态下做出一份一致性的物理备份:
svnadmin hotcopy /opt/svn/demo /backup/svn/demo-$(date +%F)这个命令比直接cp -r更安全,因为它会把当前仓库的db目录状态强制落盘,避免复制过程中产生不一致。如果是日常定时备份,放在crontab里执行即可。
7.3 仓库目录规划越早想清楚,后面越省心
我最初图简单,把SVN根目录直接当作仓库,后来项目多了才发现问题:权限控制、备份、迁移都不方便。后来统一改成/opt/svn/仓库名这种结构,每个仓库独立svnadmin create,权限用authz统筹管理,备份时按仓库粒度操作,清晰得多。如果你预计团队会有多个项目并行,强烈建议一开始就用“多仓库”结构。
7.4 关于版本选择
我在这篇记录里以CentOS 7 + SVN 1.7为例,因为这是内网存量系统里最常见的组合之一。如果你的系统是Rocky Linux 9或Ubuntu 22.04,原理完全一样,只是包管理器命令不同:Ubuntu离线安装可以用apt-offline或下载.deb包,配置方式依然是svnserve.conf/passwd/authz那套。SVN版本不必追求最新,稳定和兼容性优先,很多老项目的客户端工具链仍然停留在1.7/1.8时代,贸然升级高版本服务端反而容易引入兼容性问题。
离线安装SVN这件事本身并不难,真正考验人的是对依赖链的梳理和对配置细节的耐心。把“提前准备rpm包”和“本地yum源”这两步做扎实,后面就是svnadmin create和改三个配置文件的事。希望这篇记录能让你在遇到同样场景时,少走几步弯路。