对于开发者和团队来说,GitLab 中的代码和 issue 就是核心资产。很多人在使用 Docker 部署 GitLab 时,往往只配置了数据卷映射,却忽略了灾备问题。
今天分享一套基于 Shell 脚本的完整 GitLab 备份方案。这套方案无需依赖宝塔面板,不仅能自动打包数据库和代码仓库,还能备份至关重要的核心配置文件,并通过 7z 强加密后直推至支持 WebDAV 的云盘(如 123云盘、阿里云盘等),最后配合 Cron 实现每天凌晨自动执行。
一、 为什么不直接用官方的 backup 命令?
官方的 gitlab-backup create 命令确实好用,但它存在两个致命盲区:
- 不备份配置文件:它只会备份数据(代码、附件、数据库等)。如果你丢失了
/etc/gitlab/gitlab-secrets.json文件,恢复备份后,所有开启双因素认证(2FA)的用户将无法登录,CI/CD 变量也会全部失效! - 缺乏远端流转和加密能力:备份包默认只停留在本地服务器,且未经过加密。一旦服务器遭遇勒索病毒或硬件损坏,本地备份将形同虚设。
二、 本套方案的核心亮点
- 全维度备份:自动打包官方 tar 数据包,并同时提取
gitlab.rb和gitlab-secrets.json。 - 机器绑定,固定密码:脚本会读取服务器的硬件 ID 计算出固定的 32 位安全密码进行 7z 深度加密(隐藏文件名)。同一台机器每次运行密码相同,不同机器密码完全隔离,既安全又防遗忘。
- 纯净日志输出:剔除了所有特殊符号,确保在任何老旧终端或日志采集中不会出现乱码。
- 自动清理机制:自带本地临时目录防残留清理功能,并可按需开启云端过期备份清理(例如自动删除 365 天前的旧文件)。
三、 准备工作
在开始之前,我们需要确保宿主机上安装了 p7zip,用于提供高强度的 AES-256 加密。
Ubuntu / Debian 系统:
sudo apt update && sudo apt install p7zip-full -y
CentOS / RHEL 系统:
sudo yum install epel-release -y
sudo yum install p7zip p7zip-plugins -y
四、 核心备份脚本
在服务器的 /root 目录下新建文件 gitlab_backup.sh,将以下代码粘贴进去。请务必修改必填配置区域中的容器名称、映射路径和云盘信息。
#!/bin/bash
# ================= 必填配置区域 =================
# 1. 你的 GitLab 容器名称 (默认为 gitlab)
GITLAB_CONTAINER_NAME="gitlab"
# 2. GitLab 宿主机映射路径 (请根据你的 docker-compose.yml 修改)
GITLAB_BACKUP_DIR="/srv/gitlab/data/backups" # 数据目录下的 backups 文件夹
GITLAB_CONFIG_DIR="/srv/gitlab/config" # 配置目录
# 3. 云盘 WebDAV 配置
WEBDAV_URL="https://webdav.123pan.cn/webdav"
WEBDAV_USER="你的WebDAV账号"
WEBDAV_PASS="你的WebDAV密码"
# 4. 机器专属的二级目录名称
WEBDAV_ENDPOINT="gitlab_server"
# 5. 自动清理配置
ENABLE_REMOTE_CLEAN="false" # 开启远端清理 (true/false)
REMOTE_CLEAN_DAYS="365" # 保留多少天的备份
# ================================================
# 默认路径配置
TEMP_DIR="/tmp/gitlab_backup_encrypt"
DATE_STR=$(date +"%Y%m%d_%H%M")
# --- 1. 本地异常残留清理 ---
if [ -d "$TEMP_DIR" ]; then
echo "[INFO] 正在检测并清理本地可能残留的临时文件..."
rm -rf "${TEMP_DIR:?}"/*
fi
mkdir -p "$TEMP_DIR"
# --- 2. 密码生成机制 ---
if [ -f /etc/machine-id ]; then
MACHINE_UUID=$(cat /etc/machine-id)
elif [ -f /sys/class/dmi/id/product_uuid ]; then
MACHINE_UUID=$(cat /sys/class/dmi/id/product_uuid)
else
MACHINE_UUID=$(hostname)
fi
ZIP_PASS=$(echo -n "${MACHINE_UUID}_GitLabSalt_2026" | sha256sum | awk '{print $1}' | cut -c 1-32)
echo "=================================================="
echo "[START] 开始执行: GitLab 数据+配置 深度加密直传"
echo "=================================================="
echo "[重要提示] 本机专属备份加密密码如下,请务必妥善保存:"
echo "[解压密码] ${ZIP_PASS}"
echo "=================================================="
# --- 3. 计算远端清理的阈值日期 ---
if [ "$ENABLE_REMOTE_CLEAN" == "true" ]; then
THRESHOLD_DATE=$(date -d "-${REMOTE_CLEAN_DAYS} days" +%Y%m%d 2>/dev/null)
if [ -z "$THRESHOLD_DATE" ]; then
THRESHOLD_DATE=$(date -v-${REMOTE_CLEAN_DAYS}d +%Y%m%d 2>/dev/null)
fi
fi
# --- 函数:静默创建远程 WebDAV 目录 ---
make_webdav_dir() {
local DIR_URL=$1
curl -s -X MKCOL -u "${WEBDAV_USER}:${WEBDAV_PASS}" "${DIR_URL}" > /dev/null 2>&1
}
# --- 函数:清理远端过期文件 ---
clean_old_webdav_files() {
local DIR_URL=$1
if [ "$ENABLE_REMOTE_CLEAN" != "true" ] || [ -z "$THRESHOLD_DATE" ]; then return; fi
local HREFS=$(curl -s -X PROPFIND -u "${WEBDAV_USER}:${WEBDAV_PASS}" "${DIR_URL}/" -H "Depth: 1" 2>/dev/null | awk -F'[><]' '/:href>|href>/{for(i=1;i<=NF;i++) if($i~"href$") print $(i+1)}')
for HREF in $HREFS; do
local FILE_NAME=$(basename "$HREF")
if [ "$FILE_NAME" == "$(basename "$DIR_URL")" ] || [ -z "$FILE_NAME" ]; then continue; fi
local FILE_DATE=$(echo "$FILE_NAME" | grep -Eo '[0-9]{8}_[0-9]{4}' | cut -d'_' -f1)
if [ -n "$FILE_DATE" ] && [ "$FILE_DATE" -lt "$THRESHOLD_DATE" ]; then
echo " -> [DELETE] 发现过期云端文件 (${FILE_DATE}),正在销毁: ${FILE_NAME}"
local DOMAIN=$(echo "$WEBDAV_URL" | awk -F/ '{print $1"//"$3}')
local DELETE_URL="${DOMAIN}${HREF}"
if [[ "$HREF" == http* ]]; then DELETE_URL="$HREF"; fi
curl -s -X DELETE -u "${WEBDAV_USER}:${WEBDAV_PASS}" "${DELETE_URL}" > /dev/null 2>&1
fi
done
}
# --- 函数:上传并自动触发清理 ---
upload_and_clean() {
local LOCAL_FILE=$1
local TARGET_REMOTE_DIR=$2
local FILE_NAME=$(basename "$LOCAL_FILE")
local REMOTE_URL="${TARGET_REMOTE_DIR}/${FILE_NAME}"
echo " -> [UPLOAD] 正在上传至云端: ${TARGET_REMOTE_DIR} ..."
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -u "${WEBDAV_USER}:${WEBDAV_PASS}" -T "${LOCAL_FILE}" "${REMOTE_URL}")
if [ "$HTTP_CODE" -eq 200 ] || [ "$HTTP_CODE" -eq 201 ] || [ "$HTTP_CODE" -eq 204 ]; then
echo " -> [SUCCESS] 上传成功!销毁本地缓存..."
rm -f "$LOCAL_FILE"
clean_old_webdav_files "${TARGET_REMOTE_DIR}"
else
echo " -> [ERROR] 上传失败!(状态码: $HTTP_CODE) 已保留本地缓存防丢。"
fi
}
echo "[INFO] [0/3] 正在云端构建基础分级目录..."
REMOTE_ROOT="${WEBDAV_URL}/${WEBDAV_ENDPOINT}"
REMOTE_DATA_ROOT="${REMOTE_ROOT}/Data"
REMOTE_CONF_ROOT="${REMOTE_ROOT}/Config"
make_webdav_dir "${REMOTE_ROOT}"
make_webdav_dir "${REMOTE_DATA_ROOT}"
make_webdav_dir "${REMOTE_CONF_ROOT}"
# 1. 触发 GitLab 内部数据备份
echo "--------------------------------------------------"
echo "[INFO] [1/3] 正在通过 Docker 触发 GitLab 数据备份..."
docker exec -t "$GITLAB_CONTAINER_NAME" gitlab-backup create > /dev/null 2>&1
LATEST_TAR=$(ls -t "${GITLAB_BACKUP_DIR}"/*_gitlab_backup.tar 2>/dev/null | head -n 1)
if [ -z "$LATEST_TAR" ]; then
echo "[ERROR] 未在 ${GITLAB_BACKUP_DIR} 找到备份包!"
exit 1
fi
# 2. 加密并上传 GitLab 数据备份
echo "--------------------------------------------------"
echo "[INFO] [2/3] 正在加密并上传 GitLab 数据包..."
DATA_ZIP="$TEMP_DIR/GitLab_Data_${DATE_STR}.7z"
7z a -p"${ZIP_PASS}" -mhe=on "${DATA_ZIP}" "${LATEST_TAR}" > /dev/null 2>&1
upload_and_clean "$DATA_ZIP" "${REMOTE_DATA_ROOT}"
# 3. 加密并上传 GitLab 核心配置文件
echo "--------------------------------------------------"
echo "[INFO] [3/3] 正在加密并上传 GitLab 核心配置文件..."
if [ -d "$GITLAB_CONFIG_DIR" ]; then
CONF_ZIP="$TEMP_DIR/GitLab_Config_${DATE_STR}.7z"
7z a -p"${ZIP_PASS}" -mhe=on "${CONF_ZIP}" "${GITLAB_CONFIG_DIR}/gitlab.rb" "${GITLAB_CONFIG_DIR}/gitlab-secrets.json" > /dev/null 2>&1
upload_and_clean "$CONF_ZIP" "${REMOTE_CONF_ROOT}"
else
echo " -> [ERROR] 未找到配置目录 ${GITLAB_CONFIG_DIR},跳过!"
fi
echo "=================================================="
echo "[FINISH] GitLab 数据及配置备份全部完成!"
echo "[提醒] 再次提醒,本服务器解压密码为: ${ZIP_PASS}"
echo "=================================================="
保存后,赋予执行权限:
sudo chmod +x /root/gitlab_backup.sh
五、 定时任务踩坑指南(解决无权限报错)
既然需要自动备份,就不可避免要使用 cron。很多人手动运行脚本成功,但放入定时任务后却因为 Docker 执行权限问题导致备份失败。
核心避坑点:永远不要在 crontab 中写 sudo!后台非交互式执行时等不到你输入密码。
正确的做法是直接编辑系统全局定时任务,指定使用 root 用户运行。
执行:
sudo vi /etc/crontab
在文件最下方添加以下内容(注意时间后面的 root 字样):
30 4 * * * root /bin/bash /root/gitlab_backup.sh >> /var/log/gitlab_backup.log 2>&1
保存退出。这样配置后,系统将在每天凌晨 4:30 以最高权限执行备份,并将纯净的日志输出保存到 /var/log/gitlab_backup.log。以后如果忘记了 32 位解压密码,直接去日志里 cat 一下就能找回。
版权属于:soarli
本文链接:https://blog.soarli.top/archives/856.html
转载时须注明出处及本声明。