Gitea 备份
Gitea 备份
当前网站的文章、博客代码和其他的项目代码都由自己在云端部署的 Gitea 服务管理。如果某一天阿里云「跪」了,那么我所有数据也就全体「升天」。因此 Gitea 服务的备份工作显得格外重要。
Github 很多时候访问不了,有时候想要拉取一些代码,就特别麻烦。需要安装 Git 都还好说,但代码下载不下来就难受了。Gitee 和 GitCode 需要迎合市场需求,掺杂了不需要的东西也不好用(做代理不错)。
所以就目前来看,比较好的方案还是私有化部署 Gitea。GitLab 太大,云服务器配置低容不下,况且它其中的功能对于个人以及小型企业来说非必须的。
Gitea 维护模式
Gitea 本身没有维护模式这一说法。 Gitea 服务是由 Nginx 反向代理出去的,除了提供 SSL 支持外,还需要代理博客网页的请求,二者通过域名来区分。为了保证 Gitea 的数据库与本地数据的完整性,因此备份时需要停止服务。而这样 Nginx 接收到来自客户的 Gitea 服务请求时就直接 502 了,这是不希望看到的。

维护页的创建
维护页最简单的就是一个 HTML 网页了。AI 技术的发展太快了,这种网页甚至不需要自己去写。
使用 create-vue 脚手架,创建好 maintenance-html 工程。然后用 Trae 打开工程,输入提示词,网页就做好了。
其实工程都不需要自己创建,但是为了保证工程的一致性,就手动创建了。由 Trae 创建的话,还需要提示词来约束。


维护页的激活与停止
第一种方式,在 Gitea 服务启/停前后,运行一个同端口的 HTTP 服务,用来响应来自 Nginx 的代理请求。这样不需对 Nginx 做任何操作,缺点在于二者转换存在一定的延迟,可能会出现 502 。
第二种方式,通过切换 Nginx 配置文件来解决的。这样能够无缝切换到维护模式,然后再从维护模式切换回来。但是执行备份脚本的角色就需要提供 Nginx 控制权限和文件修改权限。另外,后续 Gitea 重新部署的话也同样需要这样一份文件。
第三种方式,当上游代理(Gitea 服务)不可达时,由 Nginx 响应维护页。维护页需要以单个 HTML 文件的形式存在,多文件配置起来比较麻烦(未测试成功)。部署 Gitea 的时候也需要针对维护页作配置。
如果仅仅只是为了达成目的,就不需要考虑这么多了。更多的还是希望规划好每个步骤,并对每一步制定相应的约束。这样下来整体协调统一,机器自动执行时保证不会出问题才是关键。
数据备份
根据官方文档介绍,gitea (二进制)拥有数据备份功能。不过仅支持备份,不能恢复。
Gitea Dump 备份数据
添加一份维护页的代理配置 gitea-mainten.conf,将 gitea.conf 改名为 gitea.conf.disable。
server {
listen 80;
server_name gitea.mtfh.cc;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name gitea.mtfh.cc;
ssl_certificate /etc/nginx/certificates/gitea.mtfh.cc.pem;
ssl_certificate_key /etc/nginx/certificates/gitea.mtfh.cc.key;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
location / {
root /var/www/maintenance
}
}重新加载 Nginx :
systemctl reload nginx.service停止 Gitea 服务:
systemctl stop gitea.service最后使用命令 gitea 命令备份数据:
./gitea dump --config app.ini
脚本备份数据
根据 AI 提供的备份脚本,简单修改后,大致内容如下:
#!/bin/bash
# gitea-backup.sh
# Gitea 备份/恢复全流程自动化脚本(7z 压缩 + Nginx 维护页 + sudo + 自动清理旧备份)
set -euo pipefail
# 省略的配置内容
# ================= 工具函数 =================
run_sudo() {
if [[ -n "$SUDO_PASS" ]]; then
echo "$SUDO_PASS" | sudo -S "$@"
else
sudo "$@"
fi
}
switch_to_maintenance() {
echo "===> 开启维护页"
run_sudo mv "$NGINX_CONF_DIR/$NGINX_NORMAL_CONF" "$NGINX_CONF_DIR/$NGINX_NORMAL_CONF.disabled"
run_sudo cp "$NGINX_CONF_DIR/$NGINX_MAINT_CONF" "$NGINX_CONF_DIR/$NGINX_NORMAL_CONF"
run_sudo nginx -s reload
}
switch_to_normal() {
echo "===> 恢复正常代理"
run_sudo mv "$NGINX_CONF_DIR/$NGINX_NORMAL_CONF" "$NGINX_CONF_DIR/$NGINX_MAINT_CONF"
run_sudo mv "$NGINX_CONF_DIR/$NGINX_NORMAL_CONF.disabled" "$NGINX_CONF_DIR/$NGINX_NORMAL_CONF"
run_sudo nginx -s reload
}
stop_gitea() {
echo "===> 停止 Gitea 服务"
run_sudo systemctl stop gitea
}
start_gitea() {
echo "===> 启动 Gitea 服务"
run_sudo systemctl start gitea
}
# 省略掉部分辅助函数
# ================= 主流程 =================
if [[ $# -lt 1 ]]; then
echo "Usage: $0 backup|restore [backup_file.7z]"
exit 1
fi
ACTION="$1"
if [[ "$ACTION" == "backup" ]]; then
DATE=$(date +'%Y-%m-%d_%H-%M-%S')
TMP_DIR="$BACKUP_DIR/tmp-$DATE"
mkdir -p "$TMP_DIR"
switch_to_maintenance
stop_gitea
echo "===> 备份 Gitea 数据目录"
rsync -a --delete "$GITEA_DATA/" "$TMP_DIR/data/"
backup_service
echo "===> 备份数据库"
backup_db "$TMP_DIR"
start_gitea
switch_to_normal
echo "===> 压缩为 7z"
7z a -mx=9 "$BACKUP_DIR/gitea-backup-$DATE.7z" "$TMP_DIR"
rm -rf "$TMP_DIR"
cleanup_old_backups
echo "===> 备份完成: $BACKUP_DIR/gitea-backup-$DATE.7z"
elif [[ "$ACTION" == "restore" ]]; then
# 节省篇幅,省略掉恢复步骤
# 后来也没用上
else
echo "未知操作: $ACTION"
exit 1
fi完整内容看这里。经测试,执行后拿到的备份数据,能够成功还原数据。
脚本虽然能成功执行,但其中有大量的 sudo 影子。不止是需要管控 Gitea 服务,还需要控制 Nginx 服务,同时还会修改 Nginx 配置文件。一个简单的备份脚本,哪能允许干这么多事儿!
git 用户本质是为 Git 提供 SSH 协议的,根本就不允许有 sudo 权限。可现在倒好,sudo 有了,登录密码有了,攻击者拿到密钥和密码,可以为所欲为了!
镜像 Gitea 服务
线上服务是正常,所以此处采取镜像的方式进行数据恢复测试。
将备份后的数据拷贝到另一台设备,部署 Gitea 服务,然后恢复数据。不管是 Gitea Dump 备份,还是脚本备份,恢复流程一致。
数据库准备
安装数据库,原来用什么数据库,还原的时候一样。
线上用的 MySql,镜像的时候装的 Mariadb。后面假设线上用的 Mariadb
sudo dnf install -y mariadb-server mariadb
# 执行数据库初始化
mysql_secure_installation
# 连接数据库
mysql -uroot -p创建数据库和关联用户
CREATE DATABASE giteadb CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_bin';
CREATE USER 'gitea'@'%' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON gitea.* TO 'gitea'@'%' WITH GRANT OPTION;
FLUSH PRIVILEGES;初次安装的历史比较久远,忘记创建数据库是需要对数据库字符集和排序规则进行设定,导致数据恢复之后,安全检查老提示字符集问题。
Screenshot 2025-09-01 at 19.03.57
gitea 安装
创建 git 用户,用于执行 gitea 程序和存放运行数据,同时也便于支持 SSH 协议。
# 创建 git 用户,并分配家目录
useradd -m git
# 创建 Gitea 数据目录
su - git
mkdir gitea获取线上同版本 gitea 程序,放到家目录或 /usr/local/bin 。由于脚本打包,将可执行文件一同打包了进去,所以我就不需要单独下载了。
systemd 配置,需要根据可执行文件的位置作出相应的变化。
Gitea 的 systemd 配置示例如下:
[Unit]
Description=Gitea (Git with a cup of tea)
After=syslog.target
After=network.target
[Service]
RestartSec=2s
Type=simple
User=git
Group=git
WorkingDirectory=/home/git/gitea
ExecStart=/home/git/gitea/gitea web --config /home/git/gitea/app.ini
Restart=always
Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/home/git/gitea
[Install]
WantedBy=multi-user.targetNginx 代理配置如下:
server {
listen 80;
server_name gitea.home.local;
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;
}
}本地测试环境,省事的话可以不走代理。用代理的话主要为了 HTTPS 代理和虚拟主机功能。
还原数据
解压备份数据:
# Gitea Dump 备份的数据
unzip gitea-dump-1756097579.zip
# 脚本备份的数据
#7za x gitea-backup-2025-08-28_10-30-01.7z
7z x gitea-backup-2025-08-28_10-30-01.7z使用解压出的 SQL 文件,恢复数据库:
# Gitea Dump 备份的数据
mysql --default-character-set=utf8mb4 -ugitea -p gitea < gitea-db.sql
# 脚本备份的数据
sed -i 's/utf8mb4_0900_as_cs/utf8mb4_bin/g' db.sql
mysql --default-character-set=utf8mb4 -ugitea -p gitea < db.sql恢复数据时需要重点关注的配置:
WORK_PATH = /home/git/gitea
[repository]
ROOT = gitea-repositories
[server]
SSH_DOMAIN = gitea.home.local
DOMAIN = gitea.home.local
HTTP_ADDR = 127.0.0.1
HTTP_PORT = 3000
ROOT_URL = http://gitea.home.local
[lfs]
PATH = lfs
[log]
ROOT_PATH = log仓库地址位置不对的话,仓库列表有该仓库,但仓库里代码没了;
ROOT_URL配置不正确的话就会出现,登录不了的情况(认证成功,但跳转不到个人主页)。
最后检查解压出的备份数据位置是否和配置文件中的一致;检查数据库能否登录并访问数据表;检查 systemd 配置文件是否配置正确;检查代理配置是否正确(未使用代理忽略)。
启动服务:
systemctl daemon-reload
systemctl start gitea.service- 打开网页,进行登入测试
- 进入
管理后台->维护->自我检查- 查看可能存在的问题;
- 进入
管理后台->维护->管理面板- 执行
健康检查所有仓库 - 执行
重新同步所有仓库的 pre-receive、update 和 post-receive 钩子
- 执行
- ...(待补充)
使用 Gitea Dump 数据恢复之后,代码仓库不完整。曾认为是 Gitea Dump 备份代码仓库时数据备份不完全,但其实是我配置文件没改。因此不管是 Gitea Dump 还是脚本均能够还原数据。
改进脚本并部署测试
如果顺着 AI 提供的框架拓展的话,也许上个月就不会有结果了。仅仅只是为了自动化备份的话——删除其他逻辑,仅保留备份的顺序逻辑和辅助代码,就会变得简单多了。
为了以最小权限来执行备份脚本,维护页的激活方式改为了「第三种方式」。同时脚本不再对 Nginx 内容做任何操作。我只需要给 git 用户授权 systemctl start gitea.service 和 systemctl stop gitea.service 两条命令的权限即可。由于数据库的备份需要 PROCESS 权限,因此不得不去创建一个管理员权限,用来备份数据库。
关于 systemd 、Nginx 和备份等相关配置暂且只能由笔记记录下来,部署时手动恢复了。
改进后的脚本流程图: 
脚本内容如下:
#!/bin/bash
# gitea-backup.sh
# Gitea 备份流程自动化脚本
set -euo pipefail
# ================= 配置区 =================
BACKUP_DIR="${PWD}/backup" # 备份存放目录
GITEA_DATA="$HOME/gitea" # Gitea 数据目录
DB_TYPE="mysql" # mysql / postgres / sqlite3
DB_NAME="gitea"
DB_USER="happilys"
DB_PASS=${DB_PASS:-}
KEEP_DAYS=7 # 自动清理超过 KEEP_DAYS 的旧备份
# ================= 工具函数 =================
backup_db() {
local dst="$1"
case $DB_TYPE in
mysql)
mysqldump -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" > "$dst/db.sql"
sed -i 's/utf8mb4_0900_as_cs/utf8mb4_bin/g' "$dst/db.sql"
;;
postgres)
PGPASSWORD="$DB_PASS" pg_dump -U "$DB_USER" "$DB_NAME" > "$dst/db.sql"
;;
sqlite3)
cp "$GITEA_DATA/gitea.db" "$dst/db.sqlite"
;;
*)
echo "未知数据库类型:$DB_TYPE"
exit 1
;;
esac
}
cleanup_old_backups() {
echo "===> 清理 $KEEP_DAYS 天前的备份"
find "$BACKUP_DIR" -maxdepth 1 -type f -name "gitea-backup-*.7z" -mtime +$KEEP_DAYS -print -exec rm -f {} \;
}
# ================= 主流程 =================
DATE=$(date +'%Y-%m-%d_%H-%M-%S')
TMP_DIR="$BACKUP_DIR/gitea-backup-$DATE"
mkdir -p "$TMP_DIR"
sudo systemctl stop gitea.service
echo "===> Gitea 服务已停止"
echo "===> 备份 Gitea 数据目录"
rsync -a --delete "$GITEA_DATA/" "$TMP_DIR/data/"
echo "===> 备份数据库"
backup_db "$TMP_DIR"
sudo systemctl start gitea.service
echo "===> Gitea 服务已启动"
echo "===> 压缩为 7z"
7z a -mx=9 "$BACKUP_DIR/gitea-backup-$DATE.7z" "$TMP_DIR"
rm -rf "$TMP_DIR"
cleanup_old_backups
echo "===> 备份完成: $BACKUP_DIR/gitea-backup-$DATE.7z"!!! 不得将上述代码应用于您的生产环境,否则造成的数据损失,后果自负!
rclone 上传备份数据至云端
使用安装脚本安装 rclone 客户端,执行 rclone config 根据提示配置好网络存储后就可以使用命令同步或上传数据到云端了。网络存储方式 rclone 支持的列表很多,比如亚马逊 S3、七牛 Kodo,还有阿里云 OSS 等等,选择一个合适的即可。
使用脚本安装 rclone :
sudo -v ; curl https://rclone.org/install.sh | sudo bash初次脚本安装还挺流畅,写这篇文章的时候,本地试了下——卡着不动了。实在不行,要不咱们上点「魔法」?
没「魔法」的话,想办法下载可执行文件拷到主机,给到可执行权限也行。
rclone 配置参考这个内容,阿里云 OSS 同理。上传数据到阿里云 OSS :
rclone copy backup/gitea-backup-2025-08-27_18-14-16.7z aliyun:mtfh-code-backup --v --log-file=~/.config/rclone/rclone.log使用 rclone 向七牛 Kodo 上传备份数据时,尝试几次始终出现
RequestTimeTooSkewed错误;换成阿里云 OSS 才上传成功。request-time-too-skewed-error
上传没问题后,就可以将这段命令添加进脚本,自动同步备份数据了。修改后,内容大致如下:
# 省略的内容
echo "===> 压缩为 7z"
7z a -mx=9 "$BACKUP_DIR/gitea-backup-$DATE.7z" "$TMP_DIR"
rm -rf "$TMP_DIR"
cleanup_old_backups
echo "===> 上传文件"
rclone copy "$BACKUP_DIR/gitea-backup-$DATE.7z" aliyun:mtfh-code-backup -vv --log-file=~/.config/rclone/rclone.log
echo "===> 备份完成: $BACKUP_DIR/gitea-backup-$DATE.7z"crontab 创建定时任务
脚本有了,云端同步的方式也有了,接下来就是让脚本定期执行了。使用 crontab 辅助工具,定时任务也就搞定了,唯一需要注意的就是环境变量。
由于当前代码仓库用户仅有我一个人,数据更新不是很频繁,因此采取一周备份一次就够用了。
crontab 配置如下:
HOME=/home/git
DB_PASS=<password>
00 3 * * 6 $HOME/backup/gitea-backup.sh >> $HOME/backup/gitea-backup.log 2>&1在此之前我是这么配置的
00 3 * * 6 DB_PASS=<password> $HOME/backup/gitea-backup.sh >> $HOME/backup/gitea-backup.log 2>&1检查日志
less /var/log/cron,发现只执行了变量定义,没有执行脚本。
待续
目前博客与 Gitea 为一体,恢复了 Gitea 博客也便能够恢复(还需要手动干预)。然而 Nginx 配置与定时任务缺少代码关联,只能根据配置笔记来手动恢复。需要寻找方法将这些内容关联起来,达成一条命令一杯茶的功夫,所有内容自动恢复。
距离上面那一步还比较遥远 😂,近在眼前的——想好如何完成自动化部署任务吧!


