# 代码资产自托管:群晖 NAS 搭建 Gitea、GitHub 镜像备份与本地双 Remote 实战
## 正文
*English version / 英文版: [Git Sovereignty: Setting Up Gitea on Synology NAS, Mirroring GitHub, and Mastering Dual Remotes](https://www.icsteve.com/1551.html)*
在现代软件工程与芯片设计领域,Git 是不可或缺的协作底座。然而,许多工程师常常将 Git(分布式版本控制系统)与 GitHub(微软旗下的商业托管平台)混为一谈。
![]()
2005 年 Linus Torvalds 设计 Git 时,其核心哲学是彻底的去中心化:每个本地克隆都是一个功能完备的仓库,拥有完整的历史记录与提交链。Git 协议中根本不存在所谓的“中心服务器”。但在实际开发中,我们对单一云平台的过度依赖,正在形成新的单点故障:
- 账号合规与不可抗力风险:自动化封控、DMCA 版权争议或账户异动,可能导致仓库瞬间无法访问。
- 上游服务中断与外网波动:云端服务宕机或断网时,本地开发、脚本调试及测试流程不应停摆。
- 敏感代码与私有资产安全性:EDA 自动化脚本、物理设计约束、私有量化工具及个人笔记,理应存放在防火墙之内的私有介质上。
解决方案并不是放弃 GitHub——它依然是开源协作与开发者声誉的核心枢纽。最稳健的工程架构是“双轨 Git 体系”:对外使用 GitHub 协作与展示,对内依托群晖 NAS 部署轻量级 Gitea,搭建私有代码枢纽,并配合自动化镜像同步与多 Remote 本地推送。
架构拓扑:本地与云端协同模型
+-------------------------------------------+
| 云端托管:GitHub (开源与协作中心) |
+-------------------------------------------+
^ |
| | [Gitea 定时拉取镜像]
[git push origin] | v (自动同步 Commit/Tag)
+-----------------------------+
| 本地群晖 NAS: Gitea 容器 |
| (局域网内网枢纽 / 3000 端口)|
+-----------------------------+
^
| [git push gitea]
|
+--------------------------------------+
| 开发者本地工作站 (Mac / PC) |
| - origin: github.com/user/repo |
| - gitea: nas-ip:2222/user/repo |
+--------------------------------------+
该架构具备三大优势:
- 局域网全速访问:内网 2.5G/10G 带宽读写,无视外网网络环境波动。
- 全自动免维护备份:Gitea 周期性从 GitHub 拉取全量分支与标签,历史记录自动同步。
- 灵活的分支分流策略:敏感或试验性分支推送至 NAS,稳定版代码合流推送至 GitHub。
第一部分:群晖 NAS 部署轻量级 Gitea (Container Manager)
为什么选择 Gitea?
在自建 Git 方案(GitLab、Gitea、Forgejo、Gogs)中,Gitea 是个人与团队私有化部署的首选。它由 Go 语言编写,内存占用极低(通常仅 80MB~120MB),空闲 CPU 占用几乎为零,非常适合运行在群晖 x86 或 ARM 架构的 NAS 上。
![]()
步骤 1:创建目录结构
打开群晖 DSM 的 File Station,在 docker 共享文件夹下创建如下路径:
/volume1/docker/gitea/
└── data/ <-- 存放仓库数据、SSH 秘钥及 SQLite 数据库
确认当前 DSM 管理员或 docker 用户组对该目录具有读写权限。
步骤 2:通过 Container Manager 配置 docker-compose.yml
打开群晖 DSM 中的 Container Manager:
- 点击左侧 项目 (Project) -> 点击 新增。
- 项目名称填写
gitea,路径选择/docker/gitea。 - 来源勾选 创建 docker-compose.yml,粘贴以下配置文件:
version: "3"
networks:
gitea_net:
driver: bridge
services:
server:
image: gitea/gitea:latest
container_name: gitea
restart: always
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=sqlite3
- GITEA__database__PATH=/data/gitea/gitea.db
- GITEA__server__DOMAIN=192.168.1.100 # 请替换为你的群晖内网 IP
- GITEA__server__SSH_DOMAIN=192.168.1.100 # 请替换为你的群晖内网 IP
- GITEA__server__SSH_PORT=2222 # 容器外部映射的 SSH 端口,避开群晖默认 22
- GITEA__server__SSH_LISTEN_PORT=22
- GITEA__server__ROOT_URL=http://192.168.1.100:3000/
- GITEA__server__LFS_START_SERVER=true
- TZ=Asia/Shanghai
networks:
- gitea_net
volumes:
- /volume1/docker/gitea/data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "3000:3000" # Web 管理后台端口
- "2222:22" # Git SSH 端口(映射至容器内 22)
为何使用 2222 端口? 群晖 DSM 默认占用系统 22 端口用于管理终端。将容器 SSH 端口映射为
2222,既可保留系统终端,又可保证 Git 协议顺畅工作。
点击下一步直至完成,Container Manager 将自动拉取镜像并启动容器。
步骤 3:初次启动向导
- 浏览器访问
http://<群晖IP>:3000。 - 确认配置参数(SQLite3 数据库文件路径
/data/gitea/gitea.db,SSH 端口2222,基础 URLhttp://<群晖IP>:3000/)。 - 展开底部的 管理员账号设置,填入你的管理员用户名、密码及邮箱。
- 点击 立即安装,数秒后即可进入管理界面。
第二部分:在 Gitea 中设置 GitHub 仓库自动镜像备份
Gitea 内置了企业级的 镜像迁移(Mirror Migration) 功能。与简单的静态备份不同,镜像仓库会持续追踪上游提交,保持分支、Tag 与提交链的实时一致。
方法 A:Web 界面配置自动拉取镜像(推荐)
- 登录 Gitea 后台,点击右上角
+号,选择 迁移外部仓库。 - 在迁移来源中选择 GitHub。
- 填写迁移参数:
- 迁移 URL:
https://github.com/<用户名>/<仓库名>.git - 访问凭证:若为私有仓库,勾选“需要授权”,填入具有
repo权限的 GitHub Personal Access Token (PAT)。 - 镜像选项(核心):务必勾选 该仓库将作为镜像(This repository will be a mirror)。
- 同步周期:根据需求设置自动同步间隔(如
4h、8h或24h)。
- 迁移 URL:
- 点击 迁移仓库。
此后,Gitea 后台会自动执行周期性同步。即使你在云端推送代码,群晖本地也会全自动保有最新的镜像副本。
方法 B:命令行裸镜像推送(适合首次全量导入)
若需一次性快速导入现有大型仓库:
# 1. 抓取完整的 bare 镜像
git clone --mirror https://github.com/username/my-project.git
# 2. 进入裸仓库
cd my-project.git
# 3. 添加本地 Gitea 地址为推送目标
git remote set-url --push origin ssh://git@192.168.1.100:2222/username/my-project.git
# 4. 一键镜像推送所有分支与 Tag
git push --mirror
第三部分:本地工作机配置多 Remote(双轨推送实战)
在本地开发机(Mac、Linux 或 Windows)上,无需在 GitHub 与群晖之间二选一。通过 Git 原生的多 Remote 机制,我们可以游刃有余地控制推送路径。
策略一:独立双 Remote,精准分流(推荐)
1. 检查当前本地仓库 Remote
git remote -v
通常会显示默认的 GitHub 远程端点:
origin git@github.com:steve/my-project.git (fetch)
origin git@github.com:steve/my-project.git (push)
2. 添加本地群晖 Gitea 作为第二个 Remote
git remote add gitea ssh://git@192.168.1.100:2222/steve/my-project.git
再次查看远程端点:
gitea ssh://git@192.168.1.100:2222/steve/my-project.git (fetch)
gitea ssh://git@192.168.1.100:2222/steve/my-project.git (push)
origin git@github.com:steve/my-project.git (fetch)
origin git@github.com:steve/my-project.git (push)
3. 按需分流推送
- 未定稿代码或私有调试分支仅推送到群晖 NAS:
git push gitea feature/timing-closure-opt - 正式发布版本推送到 GitHub:
git push origin main - 全量同步本地全部历史与标签至群晖:
git push gitea --all && git push gitea --tags
策略二:一条命令同步推送到两个 Remote
若希望每次执行 git push 时,代码能同时提交到 GitHub 与群晖 NAS,可以利用 Git 的多 pushurl 特性:
# 1. 显式保留 GitHub 作为主推送目标
git remote set-url --add --push origin git@github.com:steve/my-project.git
# 2. 将本地 Gitea 地址追加为 origin 的第二个推送目标
git remote set-url --add --push origin ssh://git@192.168.1.100:2222/steve/my-project.git
检查配置结果:
origin git@github.com:steve/my-project.git (fetch)
origin git@github.com:steve/my-project.git (push)
origin ssh://git@192.168.1.100:2222/steve/my-project.git (push)
此后只需执行:
git push origin main
Git 便会按序依次推送到 GitHub 和群晖 Gitea,实现双活冗余备份。
第四部分:配置 SSH 免密访问
为了避免在端口 2222 上反复输入密码,可在本地开发机的 ~/.ssh/config 中加入如下别名配置:
Host synology-gitea
HostName 192.168.1.100
Port 2222
User git
IdentityFile ~/.ssh/id_ed25519
配置后,添加 Remote 的命令即可简化为:
git remote add gitea synology-gitea:steve/my-project.git
同时,将本地公钥(cat ~/.ssh/id_ed25519.pub)添加到 Gitea 后台的 用户设置 -> SSH / GPG 密钥 中。
第五部分:灾难重建实战 — Gitea 备份与完整恢复指南
任何自托管系统的安全性,最终都由其灾难恢复流程(Disaster Recovery)决定。容器镜像与软件包本身是可随手抛弃的消耗品;真正不可替代的是你的 Git 仓库数据、数据库(SQLite/PostgreSQL)、用户身份令牌以及服务端配置文件中的核心加密密钥(SECRET_KEY 与 INTERNAL_TOKEN)。
Gitea 原生提供了一站式原子备份命令:gitea dump。它能在无需停止服务的情况下,将数据库、代码仓库裸文件、LFS 大文件对象与主配置文件整合成一个标准的 .zip 归档包。
1. 执行全量原子备份
场景 A:群晖套件中心部署 (SynoCommunity 原生套件)
如果你是通过群晖套件中心(SynoCommunity)安装的原生 Gitea,系统会分配专用的 sc-gitea 运行账号。请在群晖 SSH 终端中直接运行以下备份命令:
sudo su -s /bin/sh -c "/var/packages/gitea/target/bin/gitea dump -c /var/packages/gitea/var/conf.ini" sc-gitea
命令逐项拆解:
sudo su -s /bin/sh -c "..." sc-gitea:切换至 Gitea 的底层专属运行账号sc-gitea执行操作,避免因 root 权限产生文件属主错乱,确保备份进程具备完整读取权限;/var/packages/gitea/target/bin/gitea dump:调用群晖套件路径下的 Gitea 原生 CLI 备份程序;-c /var/packages/gitea/var/conf.ini:精准指定群晖套件的当前生效配置文件路径。
命令执行完毕后,会在当前工作目录生成带有时间戳的归档文件(如 gitea-dump-1727600000.zip)。
场景 B:Container Manager (Docker) 容器化部署
如果是本文第一部分推荐的 Docker 容器方式,只需在宿主机终端向容器发送执行指令:
docker exec -u 1000 -it gitea /app/gitea/gitea dump -c /data/gitea/conf/app.ini --file /data/gitea-dump-backup.zip
生成的压缩包会实时保存在群晖宿主机挂载的 /volume1/docker/gitea/data/gitea-dump-backup.zip,可配合群晖 Hyper Backup 定期转储至冷存储。
2. 从零重建与完整恢复步骤 (Restore & Rebuild)
当群晖硬盘损坏、阵列重组或迁移至新 NAS 时,只需遵循以下五步即可无损重建:
步骤 1:启动全新的 Gitea 干净实例
在全新群晖系统中通过 Container Manager(使用本文提供的 docker-compose.yml)或套件中心重新安装 Gitea。启动后立即停止容器/服务,防止恢复过程中数据库产生写入锁:
# Docker 容器:
docker stop gitea
# 套件中心:
sudo synopkg stop gitea
步骤 2:解压备份归档包
将 gitea-dump-*.zip 上传至群晖,解压到临时目录:
unzip gitea-dump-*.zip -d /tmp/gitea-restore/
解压后包含四个核心模块:
repos/:所有 Git 裸仓库目录;gitea-db.sql:全量数据库导出脚本;data/:附件、用户头像与 Git LFS 存储对象;app.ini:主配置文件(包含核心加密 Secret,保持此文件可确保用户密码与 Token 无缝生效)。
步骤 3:还原数据资产与数据库
以 Docker 部署路径为例:
- 恢复仓库裸文件:
cp -R /tmp/gitea-restore/repos/* /volume1/docker/gitea/data/git/repositories/ - 恢复系统配置:
cp /tmp/gitea-restore/app.ini /volume1/docker/gitea/data/gitea/conf/app.ini - 还原 SQLite 数据库:
sqlite3 /volume1/docker/gitea/data/gitea/gitea.db < /tmp/gitea-restore/gitea-db.sql - 还原 LFS 与静态资源:
cp -R /tmp/gitea-restore/data/* /volume1/docker/gitea/data/gitea/
步骤 4:重置文件属主权限(核心避坑点)
恢复复制后文件属主可能变为当前操作用户,必须修复权限,否则 Gitea 无法通过 SSH/HTTP 进行写入:
# Docker 部署(对应 docker-compose 中的 UID 1000):
sudo chown -R 1000:1000 /volume1/docker/gitea/data
# 套件中心部署:
sudo chown -R sc-gitea:sc-gitea /var/packages/gitea/var/
步骤 5:启动服务并执行钩子同步
启动服务:
docker start gitea
登录管理员后台,进入 管理后台 -> 应用监控统计 -> 操作管理 (Operations),点击执行两项关键维护:
- 重新同步所有仓库的 pre-receive、update 和 post-receive 钩子;
- 重新生成
.ssh/authorized_keys文件(恢复所有用户的 SSH 公钥访问)。
至此,全站仓库、提交历史、镜像配置与权限凭据均已 100% 完整复活。
第五部分:灾难重建实战 — Gitea 备份与完整恢复指南
容器与可执行程序随时可重新安装,但 Git 仓库数据、数据库及核心密钥不可替代。Gitea 原生提供 gitea dump 实现免中断原子备份。
1. 执行全量原子备份
场景 A:群晖套件中心部署 (SynoCommunity 原生套件)
sudo su -s /bin/sh -c "/var/packages/gitea/target/bin/gitea dump -c /var/packages/gitea/var/conf.ini" sc-gitea
以 sc-gitea 服务账号执行,保证文件归属与权限正常,并在当前路径生成包含完整数据库、仓库裸文件与主配置的 gitea-dump-*.zip。
场景 B:Container Manager (Docker) 容器化部署
docker exec -u 1000 -it gitea /app/gitea/gitea dump -c /data/gitea/conf/app.ini --file /data/gitea-dump-backup.zip
2. 从零重建 5 步恢复流程
- 启动干净实例并立即关停:重新安装 Gitea 生成基础结构后立即停止服务(
docker stop gitea或synopkg stop gitea); - 解压归档包:
unzip gitea-dump-*.zip -d /tmp/gitea-restore/; - 数据覆盖还原:将
repos/*复制回仓库路径,覆盖恢复app.ini密钥,执行sqlite3 .../gitea.db < gitea-db.sql导入数据库; - 重置文件属主权限:Docker 执行
sudo chown -R 1000:1000 /volume1/docker/gitea/data,套件执行sudo chown -R sc-gitea:sc-gitea /var/packages/gitea/var/; - 启动并重建钩子:启动服务,在管理后台点击“重新同步所有仓库的 Hook 钩子”并“重新生成 authorized_keys”。
方案对比与总结
| 评估维度 | 纯云端托管 (GitHub) | 双轨架构 (GitHub + 群晖 Gitea) |
|---|---|---|
| 网络依赖性 | 强依赖外网与云服务状态 | 内网 2.5G/10G 秒级读写,断网亦能完整作业 |
| 数据主权 | 受制于第三方平台服务条款 | 存放在私有 RAID 阵列上,掌控权完整归属个人 |
| 灾备机制 | 依赖手动打包或外部爬虫 | Gitea 自动拉取镜像 + 双 Remote 实时推送 |
| 分支管理粒度 | 仓库级别公有/私有 | 灵活精细:敏感草稿留内网,成熟成果发云端 |
在个人 NAS 上搭建 Gitea 并与本地开发流深度结合,回归了 Git 最初“完全分布式”的设计初衷。让核心代码资产在私有介质与云端生态之间无缝流动,既拥有了云端的协作生态,也握紧了本地的数据主权。