给Gitea备份上保险:从0到1搭好加密备份链路
【免费下载链接】giteaGit with a cup of tea! Painless self-hosted all-in-one software development service, including Git hosting, code review, team collaboration, package registry and CI/CD项目地址: https://gitcode.com/GitHub_Trending/gi/gitea
凌晨三点,监控推来一条告警:备份目录被误挂到了可被匿名访问的路径下。你打开那个gitea-dump-xxx.zip,里面是数据库全量导出、带着 SMTP 密码的 app.ini、所有私有仓库的完整文件——等于把机房钥匙连同图纸一起放进了公共快递柜。这类事故的根源不是运气差,而是备份链路只做了"打包",没做"保护"。
本文以 Gitea 的dump命令为基础,讲透 Gitea 备份加密的落地路径:从备份内容分级,到目录权限、GPG/OpenSSL 加密、定时任务与留痕,最后给出一套可执行的恢复演练清单。
读完你能做到:
- 说清 Gitea 备份包里哪些内容最敏感、为什么明文存放等于裸奔
- 用最小权限用户 + 700 目录把备份存放地的"门"锁上
- 用 GPG 或 OpenSSL 两条路线给备份文件加"防撬网",并知道怎么选
- 用定时任务 + 审计日志让备份自动跑、出事可追溯
- 按清单完成一次真实恢复演练,确认备份真的能救你
本文导航
| 章节 | 内容 | 预计耗时 |
|---|---|---|
| 先看懂:备份包里到底有什么 | 内容分级与敏感点 | 2 分钟 |
| 落地:三步搭好加密备份流水线 | 权限、加密、定时化 | 8 分钟 |
| 容易翻车的5个地方 | 排查对照表 | 3 分钟 |
| 把恢复当日常 | 演练清单与流程图 | 3 分钟 |
| 一页纸速查表 | 命令/配置/权限速查 | 按需查阅 |
先看懂:备份包里到底有什么
结论先行:gitea dump生成的包不是"几个文件的压缩包",而是一份全量数据镜像,明文存放它等同于把机房整体搬进公共快递柜。
翻看 cmd/dump.go 的实现可以确认,一个默认备份包依次装入这些内容:
| 包内条目 | 内容 | 敏感层级 |
|---|---|---|
repos/ | 所有 Git 仓库完整对象(含私有仓库全部提交) | 高:泄露即代码泄露 |
gitea-db.sql | 数据库全量导出,含用户密码哈希、会话、Webhook 地址、OAuth 凭据 | 高:可直接撞库、横向渗透 |
app.ini | 服务器配置,含数据库口令、SMTP 密码、Secret Key | 高:拿到即可冒充管理员 |
data/ | LFS 对象、附件、包仓库文件 | 中:业务资产 |
log/ | 历史日志,含部分请求与操作痕迹 | 低:辅助情报 |
结构上可以这样理解一个备份包:
gitea-dump-20260823.zip ├── gitea-db.sql ← 数据库全量(最敏感) ├── app.ini ← 全部配置(含密钥) ├── repos/ ← 所有仓库 ├── data/ ← LFS / 附件 / 包 └── log/ ← 日志代码层面还有一个容易忽视的事实:生成完成后,Gitea 会把文件权限收紧为0600(见 cmd/dump.go 末尾的os.Chmod),但这只防"同机其他用户",防不了目录被误暴露、磁盘被物理接触、传输被中间人截获。权限是锁,加密是防撬网,两个都得有。
注意:
app.ini在包内是无条件包含的,不存在"跳过配置"的参数。只要包泄露,密钥就泄露,这是必须加密的第一理由。
落地:三步搭好加密备份流水线
第1步:给备份目录上最小权限的锁
先做减法:备份不该由功能用户(gitea)直接持有,目录只允许备份专用用户进入。下面 4 行创建专用用户并把备份目录锁成 700,防的是"任何本机账户都能翻备份目录":
useradd -r -s /usr/sbin/nologin gitea-backup mkdir -p /var/backups/gitea chown -R gitea-backup:gitea /var/backups/gitea chmod 700 /var/backups/gitea执行完的验证命令:ls -ld /var/backups/gitea,应看到drwx------且属组为gitea-backup。
风险:专用用户没有登录 shell,这是刻意的。任何"方便起见给它开个 ssh"的念头都会让这一层白做。
第2步:选一条加密传输与存储的路线
打包和加密之间用管道衔接,明文备份在磁盘上只存在几秒。gitea dump -f - --type=tar会把裸 tar 流写到标准输出(-f -即 stdout),随后交给加密工具。先确认系统装有gpg或openssl:gpg --version || openssl version。
两条路线各有适用场景,先看决策表再动手:
| 维度 | 路线A:GPG 对称加密 | 路线B:OpenSSL 密钥派生 |
|---|---|---|
| 依赖 | gpg(多数发行版预装) | openssl(几乎必然有) |
| 密钥形态 | 密码短语文件,支持--batch自动化 | 口令文件,-pbkdf2抗暴破 |
| 密钥轮换 | 重新对称加密旧备份即可 | 同左,且无密钥对管理负担 |
| 适合谁 | 已用 GPG 体系管密钥的团队 | 环境精简、只信 OpenSSL 的场景 |
选路线A的话,下面这行把 dump 流直接加密落盘,防的是"备份文件本身被拷走":
sudo -u gitea-backup sh -c '/usr/local/bin/gitea dump -f - --type=tar | \ gpg --symmetric --cipher-algo AES256 --batch \ --passphrase-file /var/backups/gitea/.passphrase \ -o /var/backups/gitea/gitea-$(date +%F).tar.gpg'选路线B则换成这条,防的同样是被拷走,区别在于用 OpenSSL 的 KDF 派生密钥:
sudo -u gitea-backup sh -c '/usr/local/bin/gitea dump -f - --type=tar | \ openssl enc -aes-256-cbc -pbkdf2 -iter 200000 \ -pass file:/var/backups/gitea/.key \ -out /var/backups/gitea/gitea-$(date +%F).tar.enc'执行完的验证命令:gpg --list-packets /var/backups/gitea/gitea-*.tar.gpg | head -n 2(路线B用openssl enc -d -aes-256-cbc -pbkdf2 -iter 200000 -pass file:/var/backups/gitea/.key -in <文件> | head -c 100 | tar t),能列出包内容说明加密链路完整。
坑:
.passphrase/.key必须chmod 600且移出备份目录。口令和备份放同一目录,等于把备用钥匙和房产证装进同一个袋子。
第3步:让备份定时跑、把过程留痕
手动跑一次不算完成,只有跑进计划任务、且日志能查到每次成败,才算流水线。下面这行把备份注册为每天 03:10 的 cron 任务,stdout 与错误都落进日志:
(crontab -u gitea-backup -l 2>/dev/null; \ echo '10 3 * * * /usr/local/bin/gitea-backup.sh >> /var/log/gitea-backup.log 2>&1') \ | crontab -u gitea-backup -同时把 Gitea 自身的日志从 console 切到文件,这样备份窗口内 dump 过程产生的信息也进日志而不是飘走。改 custom/conf/app.example.ini 中的[log]段为:
[log] MODE = file LEVEL = Info执行完的验证命令:crontab -u gitea-backup -l应列出该行;tail -n 20 /var/log/gitea-backup.log能看到最近一次成功输出Finish dumping to stdout。
注意:
gitea-backup.sh里写死第2步的完整命令。cron 环境极简,任何依赖交互输入的写法都会静默失败,务必先手动su gitea-backup跑通脚本再交给 cron。
容易翻车的5个地方
| 现象 | 原因 | 修复 |
|---|---|---|
| 密钥/口令存哪没共识,团队各存一份 | 口令文件散落在备份机各处 | 口令文件单独存于异盘或密码管理器,备份机上只留 600 权限副本;轮换时先换口令再重加密 |
解密时报BAD DECIPHERMENT或 GPG 直接拒绝 | 口令文件版本对不上,或备份在磁盘满时被截断写成半个文件 | 解密前先校验包尾完整性(tar 包最后应有 10240 字节零填充);从上一个完整包重来 |
cron 日志里一片Permission denied | cron 用户与目录属组不匹配,或sudo未配免密 | 统一用sudo -u gitea-backup执行;目录属组保持gitea-backup,不给人情属组 |
| 换机器恢复失败,restore 报找不到数据 | 新机器目录结构、属主不一致,数据库类型还不同 | 先解包到临时目录核对内部结构(tar tf);用gitea restore-repo恢复仓库时指定--repo_dir与实际路径一致 |
| 备份到一半失败,报空间不足 | dump 的临时目录(-t参数,默认/tmp)与备份盘是同一块盘,双份写爆 | --tempdir指到另一块盘;备份盘按"备份包大小×2"预留空间并纳入监控 |
风险:跨机器恢复是最容易"平时没测、真出事才发现"的环节。上面第4行对应的完整流程,直接走下一节的演练清单。
把恢复当日常
备份的价值只在恢复成功的那一刻兑现。建议把演练固定为每季度一次(或每次升级 Gitea 大版本后加做一次),按下面的清单执行:
- 取最近一个加密包,在非生产机上执行解密,确认包可完整解开
- 解包后
gitea-db.sql非空、repos/下仓库数与生产一致(ls repos | wc -l对照) - 挑一个核心仓库恢复后验证提交历史:
git log --oneline | wc -l与备份前记录一致 - 核对恢复后的 app.ini 中密钥可正常登录(用测试账号,别碰生产会话)
- 演练结果写进运维记录:日期、人、耗时、是否通过
注意:演练用的密钥口令如果和日常用同一份,演练本身就是一次口令泄露面审计——顺手检查有没有人把它拷到过别的地方。
一页纸速查表
| 类别 | 条目 | 速查 |
|---|---|---|
| 命令 | 打包到 stdout | gitea dump -f - --type=tar |
| 命令 | GPG 加密落盘 | gpg --symmetric --cipher-algo AES256 --batch --passphrase-file .passphrase -o 目标.tar.gpg |
| 命令 | OpenSSL 加密落盘 | openssl enc -aes-256-cbc -pbkdf2 -iter 200000 -pass file:.key -out 目标.tar.enc |
| 命令 | 恢复仓库 | gitea restore-repo --repo_dir <解包目录> --owner_name o --repo_name r |
| 配置 | 日志落盘 | [log]下MODE = file、LEVEL = Info |
| 权限 | 备份目录 | 700,属主gitea-backup |
| 权限 | 口令文件 | 600,且不在备份目录内 |
| 权限 | dump 产物 | Gitea 自动0600(见 cmd/dump.go) |
| 习惯 | 定时任务 | cron 内先sudo -u gitea-backup手动跑通再上线 |
| 习惯 | 演练 | 每季度一次,核对"解密完整、行数对得上、git log 完整"三点 |
链路搭完,最后只问一句:上周那份备份,除了你,还有谁能解开它?
【免费下载链接】giteaGit with a cup of tea! Painless self-hosted all-in-one software development service, including Git hosting, code review, team collaboration, package registry and CI/CD项目地址: https://gitcode.com/GitHub_Trending/gi/gitea
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考