部署配方 · 开发与 Git
机架上的 Gitea:不需要订阅的 git
这家里其它一切都依赖某个地方的仓库:Caddyfile、compose 文件、仪表盘 JSON、以及解释「为什么缓冲池是 512MB」的那些笔记。把它们放在免费的托管服务上可以用,直到那家服务改了条款。树莓派上的 Gitea 占用 320MB 内存和一个 SQLite 文件,它让这台机架自己描述自己。
| 容器镜像 | gitea/gitea:1.23.4-rootless |
|---|---|
| 宿主端口 | :3002/tcp, :2222/tcp |
| 数据目录 | /srv/homelab/gitea/data |
| 内存预留 | 320 MB |
| CPU 上限 | 0.50 vCPU (cpus: "0.50") |
| 更新节奏 | quarterly, and always read the release notes: Gitea ships database migrations that run on first boot and there is no downgrade |
| ARM 兼容 | arm64 native, and the rootless tag avoids running git with capabilities it does not need |
| 宿主 | 容器 | proto | 暴露 | 用途 |
|---|---|---|---|---|
| :3002 | 3000 | tcp | 仅局域网 | web UI, HTTP git remote and the API |
| :2222 | 2222 | tcp | 仅局域网 | SSH remote for git, which is what makes push work from a laptop |
部署步骤
按 rootless 镜像的 uid 建好两个目录
rootless 镜像以 uid 1000 运行,没有权限自己创建配置目录。两个数据卷都要在首次启动前改好属主。
run sudo mkdir -p /srv/homelab/gitea/{data,config} sudo chown -R 1000:1000 /srv/homelab/gitea首次启动前生成密钥
缺少 SECRET_KEY 时 Gitea 会在首次启动时自己写一个,而这个密钥用于签名会话。从环境变量提供它,重建之后所有人不会被登出。
run echo "GITEA__security__SECRET_KEY=$(openssl rand -base64 32)" >> /srv/homelab/.env echo "GITEA__security__INTERNAL_TOKEN=$(openssl rand -base64 48)" >> /srv/homelab/.env启动后立刻创建管理员账号
第一个注册的账号会成为管理员。在关闭注册之前把它注册好,并且走局域网而不是走代理。
run cd /srv/homelab/gitea && docker compose up -d docker logs --tail 30 gitea # 打开 http://192.168.10.20:3002 注册管理员关闭注册,并配好 SSH 客户端条目
环境变量已经关掉了注册,但要在界面上再确认一次——首次启动写出的配置文件可能带着过期的值。
run docker exec gitea gitea admin user list # 预期:只有你的管理员账号,且登录页没有注册入口推一个仓库,证明 SSH 远端能用
先用一个临时仓库测试,再把真实配置迁进来。HTTPS 能用而 SSH 不能用的远端,问题在密钥或端口上,而晚发现这件事更麻烦。
run cd /tmp && git clone [email protected]:ops/sandbox.git && cd sandbox git commit --allow-empty -m 'first commit over ssh' && git push # 推送必须成功且不询问密码
这个体量下 SQLite 就是对的数据库
Gitea 的文档建议生产环境用 MySQL 或 Postgres,对团队来说这是对的。对一个家庭、二十个仓库、没有并发推送的场景,SQLite 意味着少一个容器、少一个密码,以及一个「复制文件」级别的备份。
它开始不够用的时候是并发写入:两个人同时在同一个大仓库上推送,偶尔会看到锁错误。真到那一步,迁到 Postgres 是有文档的迁移,而不是重搭。
SSH 端口的这个选择是刻意的
Gitea 的 SSH 远端监听 2222 而不是 22,理由和宿主机自己的 SSH 服务无关:它让「在路由器上转发一个端口、结果意外暴露了仓库服务」变得不可能,因为这个端口号不是任何人会去转发的那一个。
客户端这边就是 ~/.ssh/config 里一行,之后 git clone 和用任何其它远端一样。
# ~/.ssh/config
Host git.lan
HostName 192.168.10.20
Port 2222
User git
IdentityFile ~/.ssh/id_ed25519
# 之后:git clone [email protected]:ops/rack-config.git仓库里到底放什么
四个仓库,每一个的存在都是因为某个文件需要一份历史:rack-config(compose 文件和 Caddyfile)、rack-notes(运行手册:什么会坏、试过什么、为什么缓冲池是 512MB)、rack-backup(脚本与恢复演练)、rack-dashboards(Grafana JSON)。
最容易被跳过、却最重要的那个是备份仓库:一个从没被人复核过的备份脚本,就是一个四个月前就悄悄停止工作的备份脚本。这件事不需要 GitHub Actions——这个规模下,每周在 Grafana 里看一眼任务输出就够了。
Actions、Webhook,以及「在派上跑 CI」的诱惑
Gitea Actions 能用,它跑一个 runner 容器,可以在同一块板子上执行你的 CI。这很诱人,在这里却基本是个错误:一次把树莓派 CPU 占满四分钟的构建,会让机架上所有其它服务一起变慢,而这个家里维护的仓库都是配置文件,本来就不需要编译。
唯一值得做的自动化,是仓库收到推送到 main 时往通知渠道发一条 webhook。五行配置,它能兜住「半夜改了东西然后忘了」这种情况。
# 仓库 -> 设置 -> Webhook -> 新增
# 推送事件、仅 main 分支,POST 到通知端点compose 文件
整份文件放在 /srv/homelab/gitea/compose.yaml。标签固定版本号,不用 latest——树莓派上回滚比升级麻烦得多。
services:
gitea:
image: gitea/gitea:1.23.4-rootless
container_name: gitea
restart: unless-stopped
ports:
- "3002:3000/tcp"
- "2222:2222/tcp"
environment:
TZ: Asia/Shanghai
GITEA__database__DB_TYPE: sqlite3
GITEA__server__DOMAIN: git.lan
GITEA__server__ROOT_URL: https://git.lan/
GITEA__server__SSH_PORT: "2222"
GITEA__service__DISABLE_REGISTRATION: "true"
GITEA__service__REQUIRE_SIGNIN_VIEW: "true"
GITEA__repository__DEFAULT_BRANCH: main
volumes:
- /srv/homelab/gitea/data:/var/lib/gitea
- /srv/homelab/gitea/config:/etc/gitea
networks: [rack]
networks:
rack:
external: true加固清单
- 关闭注册,账号只能由管理员创建
- git 的 SSH 端口是 2222 而不是 22,这样被封锁或被误转发的 22 端口永远不会意外打到仓库服务上
- 管理员账号强制开启双因素;在用的 API 令牌除备份任务那一个外全部只读
- 启用了 LFS 但对象存储保持在本地;大二进制文件在贡献说明里被劝阻,而不是悄悄把固态盘填满
备份方案
gitea dump 会生成一个包含数据库、仓库、配置和附件的完整归档。每周跑一次,每次升级前再跑一次,归档拷到 NAS。仓库在每台推送过的笔记本上本来就有一份,除了 issue 之外这也算一份真实的第二副本。
上线后验证
- 从笔记本 git push 走 SSH 成功,且不提示输入密码
- gitea dump 生成的归档里同时包含数据库和仓库
- 管理员账号已开启双因素,登录页上没有注册链接
- 渲染一个带相对图片链接的 Markdown 文件正常,说明 raw 路径和 ROOT_URL 是一致的
踩过的坑
- GITEA__server__ROOT_URL 必须是客户端真正使用的地址,含协议。在代理后面填错,克隆地址、头像和 webhook 内容里全是容器主机名。
- rootless 镜像绑不了 22 端口,这也是 SSH 远端放在 2222 的另一个理由。把 SSH_PORT 设成 22 会得到一个能启动但拒绝所有连接的服务。
- 升级会在启动时跑数据库迁移,而且没有降级路径。每次升版本之前先 dump 一次,十五秒的事,却决定了一个下午是难受还是只需恢复。
- 加到 Gitea 里的 SSH 公钥和加到宿主机上的不是一回事。以 [email protected] 推送用的是容器的密钥库;把公钥塞进 ~/.ssh/authorized_keys 只会安静地什么都不发生。
硬件与选型问答
- 为什么不用免费的托管 git 服务?
- 公开代码就用它。这里改变的是:这些仓库就是机架的配置,而放在别人账号体系后面的配置,在对方账号出问题时是恢复不了的。存放配置的那台派,和配置所描述的备份,不是同一台机器——这就是重点。
- 自建 git 服务需要多少磁盘?
- 应用本身约 40MB,二十个小仓库含历史约 250MB,再加上你允许的 LFS 对象。在固态盘上这是舍入误差;在 SD 卡上,它是把整个 /srv 目录挪到固态盘的又一个理由。
- 能让 Gitea 给机架自己的配置跑 CI 吗?
- 可以,而且值得为唯一一个任务这么做:在配置被应用之前校验 Caddyfile 和 compose 文件。这个任务两秒就跑完,也不需要把 runner 钉在派上——caddy validate 和 docker compose config 可以在收到 webhook 的同一秒内跑完。