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
0%