ARTICLE DETAIL

资讯详情

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

自建私有代码仓库全流程:Gitea部署、运维与备份实战

自建私有代码仓库全流程:Gitea部署、运维与备份实战 先交代一个背景我自己的服务器上跑了一整套代码托管和协作环境名字就叫t3code。熟悉这套东西的人应该看得出来名字的含义是“third-generation code hub”第三代代码仓库环境。听起来有点中二但这个名字其实是从我这些年摸爬滚打的经历里长出来的——从最早用公共仓库存代码、到后面自己租云主机搭环境、再到把所有东西收拢回本地机房t3code 就是第三代的产物也是我自己用着最顺手的工具链。这篇文章把 t3code 从需求分析、架构设计、实际部署到日常运维、踩坑排障的完整过程都捋一遍。如果你也有以下任何一个需求这篇文章值得看完想在自己服务器上搭一个私有代码仓库、希望摆脱公共平台的诸多限制、需要一个能处理多仓库多团队的轻量级方案、或者就是单纯对“自托管基础设施”这件事感兴趣。我会尽量讲得具体涉及到的配置、命令和思路都能直接参考复现而不是给一堆看不懂的架构图。1. 为什么我会自建 t3code被公共平台逼出来的选择很多朋友听到自建代码仓库第一反应是“有必要吗用现成的不好吗”说实话大部分人确实没必要。但一旦你开始管理多个项目、多支团队、多套权限公共平台的限制就会一点点暴露出来。1.1 核心需求拆解从“存代码”到“管代码”我最早用公共仓库纯粹是为了备份和同步代码。但项目多起来之后需求很快从“存”变成了“管”。具体来说我的核心需求可以拆成四层第一层隐私与所有权。有些项目涉及客户数据、算法策略、内部工具不能放到公共仓库里。虽然公共平台也有私有仓库但价格随着用户数和仓库数线性上涨算下来并不划算。第二层团队协作的灵活性。我需要给不同的协作者分配不同的权限有的人只读有的人能提交有的人能管理仓库设置。公共平台的权限模型是“组织-团队-成员”三级结构好用但有点重对于三五人的小团队来说每次加人都要走一套流程太繁琐。第三层与现有工作流的深度整合。我需要代码仓库能直接对接我的 CI/CD 流水线、服务器部署钩子、自动备份任务。公共平台的 webhook 功能很强大但受限于平台网络自建环境可以和自己的服务器做内网直连速度和稳定性都更可控。第四层数据随时可控。公共平台偶尔会有服务波动、政策调整虽然概率很低但对于一个把生产代码放在上面的团队来说那种“数据不在自己手里”的不安感是绕不过去的。自建 t3code 之后代码的物理载体在自己的硬盘上备份策略自己定安全感完全不同。1.2 方案选型为什么是 t3code 而不是其他自托管方案市面上自托管代码仓库的方案不少老牌的有 GitLab轻量的有 Gitea、Gogs还有基于纯 Git 的物理仓库。我当时在几个方案之间纠结了很久最后选择自建一条 Gitea 为核心的技术链也就是 t3code原因有三第一资源占用非常友好。GitLab 的完整版功能确实强但那是给几十人团队准备的。我的需求是跑一台 4 核 8G 的小机器GitLab 光启动就要占掉一半内存还要配 PostgreSQL、Redis、Sidekiq 等一堆组件。Gitea 的默认后端是 SQLite单个二进制文件就能跑起来内存占用常年保持在 200MB 左右完全不是一个量级的负担。第二部署和维护成本低。Gitea 支持单文件部署、Docker Compose 部署、包管理器安装等多种方式升级就是换二进制重启服务没有任何复杂的依赖迁移。这点对个人维护者来说太重要了——时间成本也是成本能把维护工作压缩到“半小时搞定”的方案才有长期跑下去的可能。第三功能覆盖恰好卡在我的需求点上。Git 仓库托管、Web 编辑器、Issue 跟踪、Pull Request、Webhook、组织管理这些核心功能 Gitea 都已经具备足够支撑中小团队的日常开发。不需要的功能也不会堆在界面上碍事界面清爽度比 GitLab 高出一截。2. t3code 的整体架构与技术选型确定以 Gitea 为代码仓库核心之后我围绕它规划了一套完整的运行环境。这一节把架构拆开讲包括各个组件的职责、为什么这么搭配以及整体网络布局。2.1 配套设施仓库之外还需要什么代码仓库本身只是起点。真正支撑日常开发流量的是一整套配套的周边设施反向代理与 HTTPS 终端t3code 对外只暴露 80/443 端口所有内部服务都通过反向代理转发。代理层同时负责 TLS 证书的自动申请和续期让仓库服务始终以 HTTPS 方式提供访问。数据库层Gitea 默认用 SQLite单机环境下性能和稳定性都够用。但如果后续要上多实例或更复杂的权限模型迁移到 PostgreSQL 也是顺理成章的事——Gitea 对 PostgreSQL 的支持非常完善配置文件里改两行就能切换。对象存储与备份层Git 仓库是纯文本和二进制文件的混合体体积会随着项目迭代增长。我用的备份方案是把整个仓库目录和数据库批量打包异地增量同步到独立存储保证即使服务器宕机代码也能在半小时内恢复。监控与告警t3code 不会自己告诉你它生病了。我搭了一个轻量的健康检查机制定期检测关键端口和进程状态有问题直接推送到即时通讯工具。这套机制在后来的排障中帮了大忙后面会详细说。2.2 网络布局与端口规划服务器的网络规划遵循一个简单的原则对外尽量少暴露端口对内组件通过内网互通。具体到 t3code 这套环境我做了如下端口规划服务绑定地址端口说明反向代理0.0.0.080/443对外提供 HTTP/HTTPS 服务Gitea Web127.0.0.13000仅内网访问由代理转发Gitea SSH0.0.0.06222自定义 SSH 端口避免默认 22 被扫描数据库127.0.0.13306数据库不对外仅本机访问备份服务127.0.0.18080内部备份接口不对公网开放很多人在部署时容易忽略一个细节Gitea 的 Web 端口不建议直接暴露公网原因是多一层反向代理可以做访问控制、限流和日志记录。如果直接把 3000 端口暴露出去一旦应用本身有漏洞或者密码被爆破攻击者可以绕过代理直连应用安全层面缺失了最关键的一道防线。2.3 目录结构与数据流t3code 的目录结构也经过了几轮迭代最终固定为下面的形式/opt/t3code/ ├── app/ # 应用主目录 │ ├── gitea/ # Gitea 数据目录 │ │ ├── repositories/ # Git 仓库存储区 │ │ ├── lfs/ # LFS 大文件存储 │ │ └── logs/ # 应用日志 │ ├── conf/ # 配置文件 │ └── bin/ # 可执行文件 ├── backup/ # 备份暂存区 ├── certs/ # TLS 证书存放区 ├── scripts/ # 运维脚本 └── logs/ # 系统级日志数据流大致是用户 HTTPS 请求 → 反向代理终止 TLS → 转发到 Gitea 3000 端口 → Gitea 读写 repositories 和 SQLite 数据库 → 备份脚本定期打包整个目录到 backup 区再 rsync 到异地。这套结构的核心优势是职责清晰、备份边界明确——只要把/opt/t3code/app和数据库文件老老实实备份整个服务就能原样复活。3. 部署实操从零把 t3code 跑起来这一节是全文最核心的实操部分。我会按真实操作顺序把从一台干净服务器到 t3code 完全可用的每一步都写清楚包括配置文件、启动命令、验证方法以及我当时踩过的坑。3.1 初始环境准备与依赖安装我用的是一台 Debian 12 系统的 4 核 8G 云主机。系统装好之后先做基础配置# 更新系统源安装基础工具 apt update apt upgrade -y apt install -y curl wget git sudo ufw fail2ban tar rsync # 创建专用运行用户避免用 root 跑应用 useradd -m -s /bin/bash gitea这里有一个值得强调的点千万不要用 root 用户运行 Git 服务。Git 仓库里存的是不可信输入一旦 web 应用有漏洞导致命令执行以 root 权限运行的后果是灾难性的。单独建一个低权限用户授权最小化能把风险控制在小范围内。依赖安装方面需要注意 Git 的版本。Gitea 对 Git 的版本有最低要求Debian 12 自带的 Git 2.39 完全够用。如果系统自带版本太旧建议用源码编译或者从官方源装新版避免功能缺失导致部分操作异常。3.2 下载 Gitea 二进制并做基础配置Gitea 的发布渠道很稳定直接从官网拉对应架构的二进制文件即可# 切到 gitea 用户下载最新的稳定版 su - gitea cd /opt/t3code/bin wget -O gitea https://dl.gitea.com/gitea/1.22.0/gitea-1.22.0-linux-amd64 chmod x gitea下载完成后先手动跑一次看看能否正常初始化./gitea web --config /opt/t3code/conf/app.ini如果看到类似“Listen: http://0.0.0.0:3000”的日志输出说明基础运行没问题。此时按下 CtrlC 停掉接下来写正式的配置文件。/opt/t3code/conf/app.ini的关键配置如下[server] PROTOCOL http DOMAIN git.example.com HTTP_PORT 3000 ROOT_URL https://git.example.com/ SSH_DOMAIN git.example.com SSH_PORT 6222 LFS_START_SERVER true [database] DB_TYPE sqlite3 PATH /opt/t3code/app/gitea/gitea.db [repository] ROOT /opt/t3code/app/gitea/repositories DEFAULT_PRIVATE private ALLOW_ADOPTION_OF_UNADOPTED_REPOSITORIES false [service] DISABLE_REGISTRATION true REQUIRE_SIGN_IN_VIEW true [mailer] ENABLED false [security] INSTALL_LOCK true SECRET_KEY 你的随机密钥配置里我解释几个常被忽略的选项DEFAULT_PRIVATE private新建仓库默认私有防止开发者误操作把代码传成公开。在团队场景下宁可严格要求一点也不要让代码意外泄露。DISABLE_REGISTRATION true关闭公开注册。t3code 只有你自己和邀请的人能用避免被陌生账号扫描或恶意注册占资源。INSTALL_LOCK true锁定安装向导。配置文件手写完成后锁掉安装页面能防止配置被网页端误改。3.3 用 systemd 托管 Gitea 进程手动启动只能用于验证。正式环境我用 systemd 把 Gitea 托管为系统服务开机自启、崩溃自动拉起。服务文件在/etc/systemd/system/gitea.service[Unit] DescriptionGitea (t3code) Afternetwork.target [Service] Usergitea Groupgitea WorkingDirectory/opt/t3code/app/gitea ExecStart/opt/t3code/bin/gitea web --config /opt/t3code/conf/app.ini Restartalways RestartSec5s LimitNOFILE65536 [Install] WantedBymulti-user.target然后执行systemctl daemon-reload systemctl enable --now gitea systemctl status gitea看到active (running)之后就说明服务托管成功。LimitNOFILE65536这个参数不是乱写的——Git 操作会产生大量文件描述符默认的 1024 上限在高并发 clone 或 push 时会直接撑爆连接导致“too many open files”错误。我一开始没配压测时翻过车后来补上这个参数才稳定。3.4 反向代理与 HTTPS 证书配置反向代理我选了 Nginx配置简单、大量踩坑资料可查。这里放一份精简但完整的站点配置server { listen 80; server_name git.example.com; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name git.example.com; ssl_certificate /opt/t3code/certs/fullchain.pem; ssl_certificate_key /opt/t3code/certs/privkey.pem; client_max_body_size 512m; location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 300s; } }证书部分用 acme.sh 脚本自动申请和续期。这一步如果是第一次配置最容易出现问题的是 NGINX 转发后提示“无法获取当前用户”“CSRF token 不匹配”之类的报错。主要原因就是缺了上面配置里的X-Forwarded-Proto头——Gitea 没识别到用户是通过 HTTPS 访问的仍然按 HTTP 生成跳转链接和 Cookie 安全策略。加上之后问题当场消失。3.5 创建管理员账号并完成初始化验证配置完成后用浏览器访问https://git.example.com因为INSTALL_LOCK true不会出现安装向导而是直接进入登录页面。此时需要通过命令行创建第一个管理员账号su - gitea cd /opt/t3code/bin ./gitea admin create-user \ --username admin \ --password 强密码 \ --email adminexample.com \ --admin \ --config /opt/t3code/conf/app.ini登录后建议顺手完成三件事修改资料里的 SSH 公钥添加本地开发机的公钥测试 SSH 方式 clone 是否正常。创建一个组织把所有项目纳入组织管理权限分配在组织层面统一做。创建一个测试仓库完整走一遍 init/add/commit/push/clone 流程验证 HTTP 和 SSH 两条通道都通。4. 日常运维备份、升级与权限管理跑起来只算完成了第一步长期可维护才是 t3code 真正有价值的体现。这一节集中讲我每天、每周、每月在做什么以及为什么这么做。4.1 备份策略让“恢复”永远有路可走自托管最大的隐忧是数据安全所以我从第一天就定了备份策略并固化成脚本定时执行。核心思路是代码库与数据库分开备份异地上传本地留存每周一次恢复演练。备份脚本核心逻辑大致如下#!/bin/bash # /opt/t3code/scripts/backup.sh BACKUP_DIR/opt/t3code/backup DATE$(date %Y%m%d%H%M) # 1. 备份 Gitea 配置和数据库 tar czf $BACKUP_DIR/gitea-conf-$DATE.tar.gz /opt/t3code/conf # 2. 备份 Git 仓库数据排除 LFS 之外的大文件单独处理 tar czf $BACKUP_DIR/gitea-repos-$DATE.tar.gz \ --exclude*.pack \ /opt/t3code/app/gitea/repositories # 3. 备份 LFS 文件 tar czf $BACKUP_DIR/gitea-lfs-$DATE.tar.gz \ /opt/t3code/app/gitea/lfs # 4. 异地同步 rsync -avz --delete $BACKUP_DIR/ backup-server:/data/t3code-backup/ $BACKUP_DIR/rsync.log # 5. 清理 7 天前的本地备份 find $BACKUP_DIR -name *.tar.gz -mtime 7 -delete备份策略有几个值得注意的点--exclude*.pack是为了跳过 Git 的包文件因为 pack 文件可以通过后面的仓库重建。但注意如果仓库特别大或者历史特别长建议还是全部备份省得恢复时还要重新构建——我权衡之后把 pack 排除换成了全量备份反正磁盘空间足够简单粗暴最可靠。异地 rsync 用--delete参数要在前面写清楚这是因为备份服务器上只保留最新状态旧备份本地已经清掉了远端也要同步清避免远端磁盘被历史残留塞满。每周做一次恢复演练别等到真出问题时才发现备份文件是坏的。我在/tmp里解包旧备份拉起一个临时实例让同事 push 一个测试仓库验证读写。这招救我一次后面排障篇会讲。4.2 升级流程与版本锁定Gitea 的迭代速度比较快每隔一段时间会发布新版本包含安全修复和功能更新。升级这件事看似简单但直接替换二进制导致数据库版本不兼容的案例我见多了。我的升级流程是标准化的登录 t3code 后台先把服务切到维护模式用逻辑上的“冻结”来保证升级过程中没有写操作进来。执行一次手动备份确保无论如何都有回滚点。停掉 Gitea 服务systemctl stop gitea。备份老版本二进制cp gitea gitea.bak。下载新版本二进制替换启动服务。访问首页确认仓库列表、Issue、PR 等核心数据都在然后关闭维护模式。升级中最容易踩的坑是跨大版本升级。比如从 1.18 直接跳到 1.21数据库迁移脚本可能设计为逐版本迁移直接跳版本可能触发隐藏 bug。我的建议是跨大版本时先升到目标版本的前一个大版本跑一遍确认没问题再升目标版本。虽然多花一点时间但稳妥性极大提升。4.3 权限模型与多团队协作t3code 中我实践了一套比较顺手的权限模型核心是“组织隔离、团队分配、仓库权限三级”。给每个业务方向建一个组织比如前端组、后端组、数据组每个组织下有对应的仓库组织的成员通过团队来绑定每个团队有固定的仓库权限。具体分配原则很简单只读权限给所有成员保证大家能查看代码、参与讨论。写权限给该仓库的核心维护者让他们可以 push 分支、管理 PR。管理员权限只给负责人和需要管理 CI 配置的人。这套模型比“直接把所有人都加成仓库管理员”要精细得多。实际使用中权限最多的人反而是最容易误操作的人。让大部分成员保持在“能提交、不能管理”的层级对团队协作顺畅度有明显改善。4.4 监控告警让 t3code 自己报告健康状况服务挂在后台久了进程状态、磁盘占用、证书有效期这些东西要持续盯着。我做的监控体系很简单进程存活检测每 5 分钟探测一次 HTTPS 端口和 SSH 端口连续失败 3 次就告警。磁盘水位检测写个脚本看/opt/t3code挂载点的使用率超过 80% 就通知。证书有效期检测用 acme.sh 的自动续期钩子续期失败就发告警。备份完成检测备份脚本执行完成后比对远端文件和本地文件大小不一致就告警。监控方式可以很轻没必要一上来就上一套完整的监控平台。一条 cron 加几行脚本能在出问题时第一时间通知你就已经完成了 80% 的工作。5. 排障实录t3code 上线后我踩过的五个坑这一节的内容是长期运行后才积累下来的。很多问题在部署文档里根本不会提但实际运行中出现的概率并不低希望对你有帮助。5.1 坑一git clone 大仓库时报“RPC failed; HTTP 500”现象是克隆一个大仓库有大量二进制历史时HTTP 通道会中断服务端日志显示大量 500 错误。排查过程花了我不少时间最终定位是 Nginx 和后端 Gitea 的超时时间不够。原因大仓库打包时耗时长Nginx 默认的proxy_read_timeout 60s不够用另外 Gitea 侧的LFS_MAX_FILE_SIZE限制也可能触发 500。解决方式proxy_read_timeout 300s; proxy_send_timeout 300s;同时在app.ini中提高限制[lfs] LFS_MAX_FILE_SIZE 1024 LFS_MAX_FILES_PER_BATCH 100调整之后重新 clone 就不再报了。5.2 坑二HTTPS 跳转后无法登录提示 Cookie 失效这个问题在前面提到过但这里补充一下完整的排查思路。现象是访问https://git.example.com能打开页面但登录时提示“您的会话已过期”。排查步骤打开浏览器开发者工具看登录请求的响应头。检查 Set-Cookie 中的 Secure 属性是否被错误地去掉。检查 Gitea 日志中是否有 CSRF 校验错误。最终原因就是 Nginx 配置里少了X-Forwarded-Proto。加了之后问题立刻消失。经验是任何经过反向代理的服务转发原始协议头都是必须的否则应用层无法判断用户到底用的是 HTTP 还是 HTTPS进而影响 Cookie 和其他安全策略。这个问题在几乎所有自托管 web 应用里都会遇到值得先排查。5.3 坑三push 时报“403 Unauthorized”但密码没错这个问题一度让我怀疑是权限配置错了反复检查用户角色和团队权限都正常。后来注意到服务端日志里有 “token authentication failed” 的字样才想起来可能是 SSH key 没配对。在实际使用中HTTP 密码和 SSH 公钥是两套认证体系。你在网页上创建的账号密码只能用于 HTTP(S) 通道SSH 通道用的是本机生成的密钥对需要把公钥添加到 t3code 后台的 SSH Keys 配置里。如果客户端默认走的是 SSH 协议那密码再正确也过不了认证。解决方法很简单生成本机 SSH 密钥将公钥复制到后台ssh-keygen -t ed25519 -C your_emailexample.com cat ~/.ssh/id_ed25519.pub然后在 后台 设置 → SSH Keys 中添加即可。5.4 坑四数据库文件无限膨胀服务越来越慢运行几个月后我发现 Gitea 进程占用的内存变大响应速度下降。查看 SQLite 数据库文件已经膨胀得超出预期。原因有两个方向一是仓库和 Issue 的活跃度提高数据库数据量增长是正常的二是 SQLite 在频繁删除和更新后不会自动回收空间文件只增不减造成碎片化。解决思路每周执行一次VACUUM回收空闲页sqlite3 /opt/t3code/app/gitea/gitea.db VACUUM;有条件的话将数据库从 SQLite 迁移到 PostgreSQL。Gitea 官方文档有迁移说明过程不算复杂但需要停机维护。我后来迁移到了 PostgreSQL性能提升明显尤其是 Issue 多、PR 多的活跃仓库响应速度改善很大。5.5 坑五仓库目录权限错乱导致推送被拒绝有一次服务器重启之后Gitea 服务能正常起来但所有仓库的 push 都返回 500。查看系统日志发现仓库目录的所有权和权限在异常关机后发生了变化有的目录变成了 root 所有Gitea 进程用低权限用户运行自然写不进去。修复命令chown -R gitea:gitea /opt/t3code/app/gitea chmod -R 755 /opt/t3code/app/gitea/repositories修复后再测 push恢复正常。这个问题的教训是运行服务的用户必须能写整个仓库目录任何手动操作、备份恢复、切换用户都要确认一遍权限一致。我在备份恢复演练时碰到过一次从此把权限校验写进了恢复流程。6. 数据安全与异地容灾扩展思路t3code 用到现在我对“数据安全”的理解已经从单纯“做好备份”升级成了“备份容灾演练”三层体系。这一节讲讲安全架构和可以继续扩展的方向。6.1 备份加固双副本、加密与异地基础备份策略前面已经说了但可以再加两个加固点加密和双副本。加密是对异地备份文件做 GPG 对称加密防止备份服务器被入侵后代码泄露。双副本是在另一个物理位置再放一份冷备即使其中一个备份点整体损坏另一个依然能兜底。加密脚本大致是这样gpg --batch --yes --passphrase 你的加密密码 \ -c $BACKUP_DIR/gitea-repos-$DATE.tar.gz加密后的备份文件即使被拖走没有密码也解不开算是对异地存储风险的必要补充。6.2 容灾演练怎样验证“恢复”真的可行备份再多没演练过就等于没有。我这里的容灾演练方案是这样的在另一台闲置机器上重新装一套同样版本 Gitea。从备份服务拉最新的加密备份到本地解密、解包。按全新部署流程启动临时实例。客户端 clone 一个测试仓库检查历史和分支是否完整。这个流程每季度做一次。第一次演练出过问题解密脚本因为 GPG 版本不一致报了错后来把公钥和私钥导出归档好再也没翻车。容灾演练的意义就在于真到灾难发生时所有操作都变得条件反射一样熟练不会手忙脚乱。6.3 从单机到多机t3code 的横向演进如果团队规模扩大t3code 的架构可以横向演进。我这里规划了几条路线虽然还没有全部落地但方向是清晰的数据库独立把 PostgreSQL 从本机迁到独立数据库服务器消除单机内存和磁盘瓶颈。存储独立把 repositories 和 LFS 放到 NAS 或对象存储上Gitea 支持仓库路径外置这样做之后备份策略可以统一到存储层。镜像与容灾在另一个区域跑一套从库环境主库数据实时复制到从库主库故障时切换域名解析即可实现分钟级恢复。接入统一认证如果公司已经有一套 LDAP 或 OAuth 系统Gitea 支持对接这样团队成员不需要单独维护一套账号密码安全性和便利性都提升。这些演进方向不是一蹴而就的建议按需逐步推进。先把单机环境做到极致稳定再考虑多机扩展避免一上来就铺太大导致维护不过来。7. 个人实操心得自托管这条路值不值得走t3code 从规划到现在跑了大半年我个人的体验是自托管这条路适合愿意投入时间学习运维的人但回报也极其实在。每次需要团队协作时不用再把代码传到第三方平台数据永远在自己掌控之中每次排查问题、写脚本、优化性能时积累的经验也已经完全回报了投入的时间成本。如果你还在犹豫要不要自建我的建议是先想想自己的核心需求是“省事”还是“掌控”。如果是省事那公共平台确实是更优解如果是掌控自建带来的可控性、隐私保护和灵活度是公共平台给不了的。最后分享一个小技巧t3code 这类自建系统最好的起步方式不是一上来就追求功能齐全而是先从“能跑”开始再加“能备份”再加“能告警”。小步快跑比一步到位容易太多了。先把一套最小可用的环境跑通你自然会知道下一步该加什么。这套环境到现在依然在稳定运行中间出过大大小小的问题但都在可控范围内解决了。后续我打算把 CI/CD 流水线也整合进来让代码提交后自动完成构建和测试把 t3code 从“代码仓库”升级成“完整研发中台”。到时候如果再踩出新坑再来和你分享。
返回列表