ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

轻量级Git仓库服务器自建指南:从SSH Bare到Gitea

轻量级Git仓库服务器自建指南:从SSH Bare到Gitea 说到底Git 仓库服务器这个东西我自己一开始是拒绝自建的。GitHub 私有仓库都免费了Gitee 也有免费额度个人项目直接丢上去完事何必再养一台服务器来伺候代码直到我连续踩了几次坑才老实内网开发环境下代码根本推不出去公司要求代码资产必须留在内部还有一次公有仓库存储满员我不得不把一批老项目备份到本地再删掉整个过程又憋屈又麻烦。那之后我花时间认真整理了一套轻量级 Git 仓库服务器的完整方案。所谓“轻量级”指的不是功能阉割而是资源占用和运维成本上的克制一台 1 核 1G 的云主机、一块 NAS、甚至一台树莓派都能稳定跑起来。这篇文章就把我整理的完整过程拆开讲清楚从最基础的 Git 安装到零依赖的 SSH bare 仓库方案再到带 Web 界面的 Gitea以及大家几乎必踩的 SSH 认证失败、分支合并冲突这些实战问题。适合看这篇文章的人很明确手里有一台闲置服务器或 NAS想给个人项目、小团队搭一个私有 Git 服务又不想上 GitLab 那种动辄占几个 G 内存的庞然大物或者你只是想把“自建 Git 仓库服务器”这条路完整走一遍搞清楚里面到底有哪些关键配置和坑。下文默认你在 Linux 上操作命令我会尽可能给出可以直接复制粘贴的版本。1. 先想明白你到底需不需要自建仓库服务器1.1 云托管服务的边界在哪里GitHub、Gitee、GitLab.com 这些托管平台的优势不用多说注册即用、生态完整、CI/CD 集成、Issue/PR 流程完善个人项目基本零成本。但它们的边界也很明显。私有性是第一道坎。云服务再怎么承诺隐私保护代码终究在别人的机器上。涉及商业项目、客户源码、核心技术积累的场景很多团队连试都不敢试。第二道坎是网络依赖。内网开发环境、隔离网络、没有稳定公网出口的情况下公有仓库天然不可用就算能连推一个大一点的仓库卡到超时也是经常的事。第三道坎是配额。GitHub 免费私有仓库虽然没有数量限制但存储有软限制单个仓库到 1GB 会收到警告超过 5GB 直接拒绝 push。如果你需要存放二进制资源、模型文件、构建产物公有仓库根本不够看。第四道坎是合规。部分行业明确要求代码数据本地存储、备份自主可控这不是说公有云不安全而是规章制度层面就卡死了。反过来说自建方案要付出的核心成本只是“一台长期在线的机器”。至于运维轻量级方案真的不需要多少运维这也是我写这篇文章的价值所在。1.2 从“能用”到“好用”的四档方案市面上的自建 Git 服务按重量级大致可以分四档我整理的时候反复权衡的就是它们。第一档是纯 SSH bare repository。服务器上只需要装 Git 本体建一个裸仓库客户端通过 SSH 协议读写。没有 Web 界面没有项目管理但资源占用约等于零128MB 内存的 VPS 都能跑。第二档是 Gitea 或 Gogs。Go 语言写的单二进制程序自带 Web 界面、用户体系、组织管理、Webhook、CI 基础能力官方推荐 512MB 内存即可流畅运行实际用 1 核 1G 的机器很舒服。第三档是 GitLab Community Edition。功能最接近 GitHub 全家桶有完整 CI/CD、容器仓库、依赖扫描但它是 Ruby on Rails 应用内存占用以 GB 计官方最低 4GB实际跑起来 8GB 才安心升级运维也重。第四档是各类特殊场景方案比如 Gerrit 这种偏代码评审的工具一般团队用到它们的场景很少。我的判断标准很简单团队超过 10 人、需要完善的权限审计和 MR 流程才考虑 GitLab CE10 人以内、想要 Web 界面但不想折腾选 Gitea只是自己用、追求最低成本和最强可控性SSH bare 仓库非常香。1.3 我最终选型的原则这篇文章我重点讲两个方案SSH bare 仓库和 Gitea。不是因为其他方案不好而是这两个覆盖了我见过的大多数真实场景——个人开发者、两三人的小团队、以及想把代码留在内网的实验室。选型时我遵循了三条原则。资源优先能用一个进程解决的事不引入数据库和中间件。Gitea 自带 SQLite默认配置零外部依赖这就是它轻量的底气。迁移成本可控bare 仓库的本质是一个目录打包拷贝就走Gitea 的数据就是一个仓库目录加一个 SQLite 文件整个实例迁移极其简单。不过度设计自建服务最容易翻车的地方就是装了一堆东西然后没人维护。轻量方案的内核是“少即是多”功能少攻击面小出问题也好排查。2. 服务器和本地的 Git 安装别在这一步留下坑不管最终采用哪个方案Git 本体是地基。安装看起来简单但有几个细节不处理好后面会很难受。2.1 Linux 服务端安装 GitDebian/Ubuntu 系sudo apt update sudo apt install -y git git --versionCentOS/RHEL 系sudo yum install -y git # 新版系统用 dnf sudo dnf install -y git git --version这里要注意版本策略。用系统包管理器装的是发行版维护的稳定版可能不是最新但胜在稳定、有安全补丁跟进。对仓库服务器来说稳定优先于新功能所以我一般不会手动编译安装最新版 Git除非有特定需求。装完先验证一下版本心里有数。2.2 创建专用运行账号强烈建议不要用 root 直接跑 Git 仓库服务。原因有两个一是权限过大一旦 SSH 私钥泄露攻击者直接获得服务器全部控制权二是仓库文件如果归属 root后面用 Gitea 或其他服务管理时会出现权限冲突。创建专用账号sudo adduser --system --group --home /home/git --shell /usr/bin/git-shell git这里用到了 git-shell它会把该用户的 shell 限制为只能执行 Git 相关命令连 SSH 登录都进不去这是裸仓库方案一个很关键的安全配置。后面 Gitea 场景下账号可以稍微放宽松但一样建议独立账号。目录规划我习惯这样sudo mkdir -p /srv/git sudo chown -R git:git /srv/git/srv 是 Linux 存放服务数据的标准目录比随手放在 /root 或者 /home 下更整洁。存储路径一旦定下来后续迁移的改动成本就在这个位置。2.3 客户端 Git 安装Windows 上直接装 Git for Windows官方下载页拿安装包即可也可以用命令行统一安装winget install --id Git.Git -e --source winget装完记得检查两个选项安装时选择 “Git from the command line and also from 3rd-party software”方便 IDEA 等工具直接识别换行符处理推荐选 “Checkout as-is, commit as-is”。如果选了默认的 autocrlf在 Windows 和 Linux 混合作业的团队里换行符问题会以非常隐蔽的方式折磨你。macOS 上brew install git或者装 Xcode Command Line Tools 也会自带 Git。但 brew 版本通常更新命令行工具版本经常停留在 Apple 维护的旧版遇到新格式文件可能不认。2.4 全局身份配置与换行符陷阱装好 Git先做全局配置git config --global user.name Your Name git config --global user.email youexample.comemail 不一定是真实邮箱但提交记录里要能联系到人建议用长期稳定地址。有个小坑如果用 GitHub 等平台平台会提供 noreply 隐藏邮箱本地提交如果用了不同邮箱commit 不会关联到你的账号上。换行符问题值得单独提醒。Linux 和 macOS 默认用 LFWindows 默认 CRLF。跨平台协作时仓库内最好统一为 LF然后靠客户端配置转换。推荐在仓库根目录放一个 .gitattributes* textauto *.sh text eollf *.bat text eolcrlf这比让每个人去设 autocrlf 靠谱得多。我见过太多项目因为一个 CRLF 导致整个文件被判定为改动diff 全红干掉半天时间。3. 方案一SSH Bare 仓库零依赖的朴素力量3.1 初始化 bare 仓库Bare 仓库就是没有工作区的仓库只存储 Git 对象和引用。它是服务器端仓库的标准形态——你不希望服务器上有一份随手可改的源码而是只接受 push 和 pull。sudo -u git mkdir -p /srv/git/example.git sudo -u git git init --bare --sharedgroup /srv/git/example.git--sharedgroup 的作用是让同组用户有写权限后面如果多个账号管理同一个仓库或者 Gitea 要接管这个目录这个参数能省不少麻烦。初始化之后仓库目录里会有 HEAD、objects、refs 等文件确认一下结构正常ls -la /srv/git/example.git3.2 SSH 公钥认证配置客户端生成密钥对ssh-keygen -t ed25519 -C your_emailexample.com -f ~/.ssh/id_ed25519现在推荐 ed25519安全性高且密钥短RSA 4096 还能用但没必要新生成。生成后把公钥.pub 文件内容加到服务器的授权列表里sudo -u git mkdir -p /home/git/.ssh sudo -u git chmod 700 /home/git/.ssh sudo -u git touch /home/git/.ssh/authorized_keys sudo -u git chmod 600 /home/git/.ssh/authorized_keys echo ssh-ed25519 AAAA... your_emailexample.com | sudo -u git tee -a /home/git/.ssh/authorized_keys文件权限这步不是形式主义。authorized_keys 如果权限太宽松sshd 会直接忽略它然后你面对的会是诡异但最常见的 SSH 认证失败这个坑我后面专门展开。连接测试ssh -T gityour-server正常的话因为 git 用户 shell 是 git-shell你会看到类似 “Welcome to Git” 的提示。如果看到 Permission denied (publickey)直接跳到第 5 章的排查流程。3.3 克隆、推送与拉取的完整链路仓库初始化好了客户端操作和用 GitHub 几乎没有区别git clone gityour-server:/srv/git/example.git cd example echo # example README.md git add README.md git commit -m init git push origin main这里有个细节如果服务器端初始化的默认分支还是 master而本地 Git 默认分支是 mainpush 时会提示分支不匹配。最简单的做法是初始化仓库时直接指定sudo -u git git init --bare --initial-branchmain /srv/git/example.git初次 push 如果遇到 cant find upstream branch 之类的提示那是本地分支没有关联远程分支执行git push -u origin main之后 git push / git pull 就能平趟了。这套流程下来一个完全够用的个人私有仓库就立起来了。3.4 bare 仓库的高级玩法Hook 自动部署Bare 仓库没有工作区但它有一个被低估的技能Git Hooks。我在实际中用得最多的场景是“push 后自动部署”。比如个人网站或者博客代码推到服务器后希望服务器自动把最新代码 checkout 到 /var/www/html然后跑构建脚本。在 /srv/git/example.git/hooks 下创建 post-receive#!/bin/bash TARGET/var/www/html git --work-tree$TARGET --git-dir/srv/git/example.git checkout -f cd $TARGET make build systemctl reload nginx注意给执行权限sudo -u git chmod x /srv/git/example.git/hooks/post-receive每次 push 后服务器就自动执行部署。这里有个实际坑post-receive 脚本的执行用户是 push 的 SSH 用户如果部署目标目录归属 nginx 用户就会权限不足需要把 git 用户加进目标目录的组或者调整目录属组具体看你的部署目录权限设计。4. 方案二Gitea轻量 Web 管理下的正规军体验如果说 bare 仓库是“能用”那 Gitea 就是“好用”。它把用户注册、仓库管理、Pull Request、Webhook、CI 集成全装进一个二进制文件里。装完之后团队协作体验非常接近 GitHub。4.1 安装 Gitea 的三种方式对比方式一是二进制安装去 Gitea 官网下载对应平台的可执行文件放到 /usr/local/bin配置一下 service 文件。最纯粹升级时替换文件即可。方式二是 Docker 部署适合服务器本来就有 Docker 环境的情况一条命令拉起数据目录挂载到宿主机升级方便但多一层抽象排查问题需要懂一点 Docker 网络和卷的知识。方式三是发行版包或 Gitea 官方源部分发行版软件源里有 Gitea但版本可能落后。我自己推荐二进制安装。理由很简单——Gitea 本身就一个可执行文件加一个配置目录升级就是下载新文件替换旧文件然后重启服务没有任何理由引入额外复杂度。Docker 只是把你的管理复杂度换了一种形式。4.2 初始化配置app.ini 里的关键选项二进制安装后首次访问会进入安装页面有几个配置点要仔细数据库选 SQLite。Gitea 的 SQLite 支持足够成熟几百个仓库毫无压力。除非团队规模大到几十人并发提交否则没必要上 MySQL/PostgreSQL那只是给自己找运维活。仓库根目录建议单独建一个目录比如 /srv/git/gitea-repositories和前面 bare 仓库分开避免管理混乱。SSH 端口默认 22。如果服务器上已有其他 SSH 服务可以让 Gitea 复用系统 SSH或者配置在 2222。复用系统 SSH 的好处是用户只需一套密钥配置不需要额外记忆端口。安装完成后核心配置在 /etc/gitea/app.ini。几个我后来必须改的项[server] DOMAIN git.example.com ROOT_URL https://git.example.com/ SSH_DOMAIN git.example.com [service] DISABLE_REGISTRATION true REQUIRE_SIGNIN_VIEW true [repository] DEFAULT_PRIVATE privateDISABLE_REGISTRATION true 是必须的公网部署时不关注册等于敞开大门。DEFAULT_PRIVATE private 确保新建仓库默认私有避免手滑把代码公开。4.3 团队协作组织、权限与分支保护Gitea 的权限模型和 GitHub 基本一致用户是每个人一个账号组织把成员分组统一管理权限仓库可以配置多个协作者权限分读、写、管理三档。小团队建议按组织管理项目建在组织下成员按角色分配权限。不要在个人账号下管理团队项目——人走了代码也跟着走了。这个教训我在真实团队里见过太多次。分支保护是另一个不可跳过的地方进入仓库 Settings → Branches选择要保护的分支比如 main勾选启用分支保护要求 PR 通过后才能合并、禁止直接 push。这套机制防的不是恶意破坏而是手滑和冲动。配合 PR 评审流程小团队的代码质量就有了最基本的底线。另外提一句 IDEA 用户关心的操作在 IntelliJ IDEA 里新建项目并拉取远程仓库时菜单选择 Get from VCS填仓库 URL会自动识别 Git 并克隆。如果配置了 SSH 密钥记得在 Preferences → Version Control → Git 里检查 SSH executable 配置选 “Native” 模式IDEA 才会正确使用系统 SSH 密钥否则可能出现能命令行 clone 但 IDEA 拉取失败的情况。4.4 备份、升级与运行维护经验Gitea 备份极其简单因为数据等于仓库目录加 SQLite 文件加配置文件。我的备份脚本核心就三行mkdir -p /backup/gitea/$(date %F) cp -r /srv/git/gitea-repositories /backup/gitea/$(date %F)/ sqlite3 /var/lib/gitea/data/gitea.db .backup /backup/gitea/$(date %F)/gitea.db cp /etc/gitea/app.ini /backup/gitea/$(date %F)/用 sqlite3 的 .backup 命令而不是直接 copy db 文件是因为直接复制正在使用的 SQLite 文件可能导致备份损坏。这个小细节值得记住。备份文件最好再 rsync 到另一台机器或对象存储服务器被勒索病毒搞掉的时候本地备份和没有备份没有本质区别。升级流程下载新版二进制停服务替换启动检查版本号和仓库列表。Gitea 的向后兼容做得相当好小版本升级基本无感但升级前永远备份这是原则。5. SSH 认证失败全排查实录SSH 认证失败是 Git 自建服务里出现频率最高、最气人的问题。明明公钥配好了还是给你一个 Permission denied (publickey)。我把实际遇到过的场景整理成一份排查单按这个顺序查基本十分钟内定位。5.1 先用 debug 模式定位客户端看详细日志ssh -vT gityour-server-v 参数会输出认证全过程重点看最后几行。如果能看到 Authentications that can continue: publickey说明服务器允许公钥登录问题在密钥匹配环节。如果看到 Connection closed by remote host说明 sshd 直接拒绝了多半是权限或配置问题。这一步能帮你少走一半弯路。5.2 服务端三大高频原因最经典的就是 .ssh 目录和文件权限不对。正确权限如下路径正确权限/home/git 目录755/home/git/.ssh 目录700/home/git/.ssh/authorized_keys600修复命令sudo -u git chmod 700 /home/git/.ssh sudo -u git chmod 600 /home/git/.ssh/authorized_keys sudo chown -R git:git /home/git第二个高频原因是 sshd 配置里关闭了公钥认证。检查 /etc/ssh/sshd_configgrep -E PubkeyAuthentication|AuthorizedKeysFile /etc/ssh/sshd_config确认 PubkeyAuthentication yesAuthorizedKeysFile 指向 .ssh/authorized_keys。改完记得重启 sshd。第三个原因是 SELinux 或 AppArmor 拦截。CentOS 上 SELinux 默认 enforcing 时新创建的用户 SSH 认证可能被拒绝。临时验证sudo setenforce 0如果这样认证就通了说明是 SELinux 策略问题。长期解决办法不是关 SELinux而是设置正确的上下文sudo restorecon -Rv /home/git5.3 客户端四类常见操作失误第一类本机有多个密钥没有指定用哪个。Git 默认尝试 id_rsa、id_ed25519 等默认文件名如果密钥文件叫其他名字需要配置 ~/.ssh/configHost git.example.com User git IdentityFile ~/.ssh/id_ed25519_work第二类key 格式复制错误。authorized_keys 里要的是公钥.pub 文件内容不是私钥。有人会把私钥内容粘进去sshd 要么报格式错要么直接忽略。第三类authorized_keys 里格式不对每行应该一个 key不要有多余空格或空行。第四类known_hosts 冲突。服务器 IP 变更或重装系统后客户端 known_hosts 里旧的指纹会让连接直接报 REMOTE HOST IDENTIFICATION HAS CHANGED处理方式ssh-keygen -R your-server然后重新连接确认新指纹后写入。5.4 SSH 认证问题速查表报错或现象最可能原因处理方式Permission denied (publickey)公钥未匹配或未指定ssh-add 加载 key检查 authorized_keys服务器没提示直接断连sshd 配置或目录权限问题检查 PubkeyAuthentication、目录权限Host key verification failedknown_hosts 过期ssh-keygen -R 清理后重连Bad owner or permissions本地 .ssh 或 key 权限不对chmod 700 ~/.sshchmod 600 ~/.ssh/id_ed25519Connection timed out防火墙或安全组检查 22 端口放行情况6. 分支合并的实战配置好服务器只是开始仓库服务器搭好日常操作里的分支和合并才是团队协作真正花时间的地方。小团队刚迁移到自建服务时对分支模型混乱、直接 push 主干、合并冲突处理不当这些问题我见得太多。6.1 分支的创建、切换与命名# 创建并切换 git checkout -b feature/user-login # 新版命令 git switch -c feature/user-login命名建议用类型前缀feature/、bugfix/、hotfix/、release/。Git 本身不关心分支名字但人关心。PR 列表一眼扫过去能看出是功能、修 bug 还是发版这是廉价却有效的协作收益。6.2 merge、rebase、squash 到底用哪个这是 Git 协作里最容易争论的话题。我的建议很直接按场景选方式适用场景历史结果merge默认选择保留完整历史脉络有分叉有 merge commitrebase本地整理、保持单线历史线性历史但改写提交号squash merge功能分支合并到主干历史要干净多个提交合并为一个个人经验主干分支用 squash merge这样 main 的历史是干净的提交列表每个功能一个 commit开发过程中的中间提交不污染主干。功能分支内部协作用普通 merge保留工作痕迹。rebase 更适合在本地提交还没推出去的时候用线上分支不要轻易 rebase重写已公开历史会让队友想打人。6.3 冲突解决实录冲突本身不可怕可怕的是不知道自己在干嘛。举一个实际例子git merge feature/user-login输出 CONFLICT (content): Merge conflict in src/UserService.java。打开文件会看到 HEAD private String name old; private String name new; feature/user-login处理原则先读两边的代码理解各自意图再决定保留哪个或者合并两者。不要机械地删除标记那是灾难的开始。解决完成后git add src/UserService.java git commit小技巧冲突多的时候先 git status 列出所有冲突文件逐个处理处理完一个 add 一个最后统一 commit。大多数 IDE 的可视化冲突解决工具很好用IntelliJ IDEA 的冲突界面能清晰展示左侧、右侧和合并结果比手改标记直观得多。6.4 小团队的分支策略推荐不建议一上来就上 Git Flow 那套完整模型tag、hotfix、release 分支对很多小团队是过度设计。我更推荐简化版main 分支始终可发布只能通过 squash merge 进入。feature 分支从 main 拉出命名 feature/xxx开发完提 PR/MR。bugfix 分支从 main 拉出修完合并回 main。这套做法的核心是保护 main。自建服务器上如果不设置分支保护小团队人手一 push 直接往 main 上怼仓库历史和线上发布都会乱掉。Gitea 里把分支保护配置好加上一条 PR 至少一人 review 的约定基本就是这个体量团队的最优解了。7. 一点个人体会轻量方案的边界在哪里整理完这套方案后我最大的感受是很多人对自建 Git 服务器的恐惧其实来自 GitLab 那套重型方案留下的心理阴影。其实在个人和小团队这个量级轻量方案的体验和维护成本都好得出乎意料。我自己现在生产环境用的是 Gitea个人知识库和博客仓库用的是 bare repo 加 post-receive 自动部署两条线各司其职几乎没有需要半夜爬起来处理的事故。最后再分享一个我踩过几次坑之后养成的习惯不管用哪个方案每周固定做一次备份备份完顺手从另一台机器 clone 一次验证备份可用。这个验证动作比备份本身更重要因为不可恢复的备份等于没有备份。至于后续扩展轻量方案也不是死胡同——bare 仓库可以平滑迁入 GiteaGitea 也有官方工具迁往其他平台你先跑起来后面的事都好说。
返回列表