Gitlab Google Drive Backup
Prerequisite: Install Rclone And Setup With Google Drive
https://learn.darmist.com/learn/git/install-rclone-and-setup-with-google-drive
Create bash script:
sudo nano /var/scripts/gitlab_gdrive_backup.sh
#!/usr/bin/env bash
# gitlab_gdrive_backup.sh
# Purpose:
# - Take ONE GitLab full backup per run (efficient; GitLab backups are full by nature)
# - Upload to Google Drive using rclone with daily / weekly / monthly tiers
# - Retention on Google Drive:
# * Daily: keep last 7 days
# * Weekly: keep last 4 weeks
# * Monthly: keep last 12 months
# - Delete local backup files after successful upload (low VPS storage)
#
# Recommended schedule (cron):
# - Run daily at 02:30 (it will also do weekly/monthly when applicable)
#
# IMPORTANT:
# - Configure rclone remote first (e.g. remote name: gdrive)
# - Run as root (or user with permissions to run gitlab-backup and read /etc/gitlab)
#
# Safety features:
# - Strict mode + lock file to prevent overlap
# - Verifies backup file exists and upload succeeded before deleting local copy
# - Remote cleanup limited to the exact tier folders
# Strategy:
# - Daily backup → upload → delete local
# - Weekly & Monthly are PROMOTED inside Google Drive (no re-backup)
# - Retention handled ONLY on Google Drive
#
# Retention:
# - Daily → last 7 days
# - Weekly → last 4 weeks
# - Monthly → last 12 months
#
# ============================================================
set -Eeuo pipefail
IFS=$'\n\t'
# ---------------- CONFIG ----------------
RCLONE_REMOTE="gdrive"
REMOTE_ROOT="gitlab-backups/git.darmist.com"
GITLAB_BACKUP_DIR="/var/opt/gitlab/backups"
WORK_DIR="/var/backups/gitlab_work"
BACKUP_ETC_GITLAB="yes"
ETC_GITLAB_DIR="/etc/gitlab"
WEEKLY_DOW="7" # Sunday
MONTHLY_DOM="1" # 1st of month
KEEP_DAILY_DAYS="7"
KEEP_WEEKLY_DAYS="$((4*7))"
KEEP_MONTHLY_DAYS="$((365))"
RCLONE_FLAGS=(
--retries 8
--retries-sleep 10s
--checkers 8
--transfers 4
--fast-list
--stats 30s
--stats-one-line
)
LOG_FILE="/var/log/gitlab-gdrive-backup.log"
LOCK_FILE="/var/lock/gitlab-gdrive-backup.lock"
# ---------------- INTERNALS ----------------
TODAY="$(date +%F)"
TODAY_YYYYMMDD="$(date +%Y%m%d)"
DOW="$(date +%u)"
DOM="$(date +%d | sed 's/^0//')"
REMOTE_DAILY="${RCLONE_REMOTE}:${REMOTE_ROOT}/daily"
REMOTE_WEEKLY="${RCLONE_REMOTE}:${REMOTE_ROOT}/weekly"
REMOTE_MONTHLY="${RCLONE_REMOTE}:${REMOTE_ROOT}/monthly"
# ---------------- LOGGING ----------------
log() {
printf "[%s] %s\n" "$(date '+%F %T')" "$*" | tee -a "$LOG_FILE" >&2
}
die() {
log "ERROR: $*"
exit 1
}
# ---------------- SAFETY ----------------
exec 200>"$LOCK_FILE"
flock -n 200 || die "Backup already running"
mkdir -p "$WORK_DIR"
# ---------------- FUNCTIONS ----------------
latest_gitlab_backup() {
ls -1t "$GITLAB_BACKUP_DIR"/*_gitlab_backup.tar 2>/dev/null | head -n 1
}
create_gitlab_backup() {
log "Creating GitLab backup"
gitlab-backup create >/dev/null
}
create_etc_gitlab_backup() {
[[ "$BACKUP_ETC_GITLAB" == "yes" ]] || return 0
local out="$WORK_DIR/etc-gitlab_${TODAY_YYYYMMDD}.tar.gz"
log "Creating /etc/gitlab backup: $out"
tar -czf "$out" -C / etc/gitlab >/dev/null
echo "$out"
}
upload_file() {
local file="$1"
local remote="$2"
[[ -f "$file" ]] || die "Upload source does not exist: $file"
rclone mkdir "$remote" >/dev/null 2>&1 || true
log "Uploading $(basename "$file") → $remote"
rclone copy "$file" "$remote" "${RCLONE_FLAGS[@]}" >/dev/null
}
verify_and_delete_local() {
local file="$1"
local remote="$2"
local base
base="$(basename "$file")"
if rclone ls "$remote" | awk '{print $2}' | grep -Fxq "$base"; then
rm -f "$file"
log "Deleted local file: $file"
else
die "Remote verification failed for $base"
fi
}
promote_remote() {
local src="$1"
local dest="$2"
rclone mkdir "$dest" >/dev/null 2>&1 || true
log "Promoting $(basename "$src") → $dest"
rclone copyto "$src" "$dest/$(basename "$src")" "${RCLONE_FLAGS[@]}" >/dev/null
}
cleanup_remote() {
local remote="$1"
local age="$2"
log "Retention cleanup on $remote (older than ${age}d)"
rclone delete "$remote" \
--min-age "${age}d" \
--include "*_gitlab_backup.tar" \
--include "etc-gitlab_*.tar.gz" \
>/dev/null || true
rclone rmdirs "$remote" >/dev/null || true
}
# ---------------- MAIN ----------------
log "===== BACKUP START ($TODAY) ====="
create_gitlab_backup
GITLAB_TAR="$(latest_gitlab_backup)"
[[ -n "$GITLAB_TAR" ]] || die "No GitLab backup file found"
ETC_TAR=""
if [[ "$BACKUP_ETC_GITLAB" == "yes" ]]; then
ETC_TAR="$(create_etc_gitlab_backup)"
fi
upload_file "$GITLAB_TAR" "$REMOTE_DAILY"
verify_and_delete_local "$GITLAB_TAR" "$REMOTE_DAILY"
if [[ -n "$ETC_TAR" ]]; then
upload_file "$ETC_TAR" "$REMOTE_DAILY"
verify_and_delete_local "$ETC_TAR" "$REMOTE_DAILY"
fi
REMOTE_DAILY_FILE="$REMOTE_DAILY/$(basename "$GITLAB_TAR")"
if [[ "$DOW" == "$WEEKLY_DOW" ]]; then
promote_remote "$REMOTE_DAILY_FILE" "$REMOTE_WEEKLY"
[[ -n "$ETC_TAR" ]] && promote_remote "$REMOTE_DAILY/$(basename "$ETC_TAR")" "$REMOTE_WEEKLY"
fi
if [[ "$DOM" == "$MONTHLY_DOM" ]]; then
promote_remote "$REMOTE_DAILY_FILE" "$REMOTE_MONTHLY"
[[ -n "$ETC_TAR" ]] && promote_remote "$REMOTE_DAILY/$(basename "$ETC_TAR")" "$REMOTE_MONTHLY"
fi
cleanup_remote "$REMOTE_DAILY" "$KEEP_DAILY_DAYS"
cleanup_remote "$REMOTE_WEEKLY" "$KEEP_WEEKLY_DAYS"
cleanup_remote "$REMOTE_MONTHLY" "$KEEP_MONTHLY_DAYS"
log "===== BACKUP COMPLETE ($TODAY) ====="
Cron example (runs daily; weekly/monthly happen automatically based on date):
# Run every day at 02:30
30 2 * * * /bin/bash /var/scripts/gitlab_gdrive_backup.sh >/dev/null 2>&1