From cce5c54177d16af9349182adc2c71e843cdae830 Mon Sep 17 00:00:00 2001 From: Per Magnus Petersen Date: Thu, 19 Feb 2026 21:33:18 +0100 Subject: [PATCH] Add automated daily Gitea backup Alpine sidecar container runs gitea dump via docker exec at 2 AM daily, writing backups to /data/backup/gitea/ with 7-day retention. Co-Authored-By: Claude Opus 4.6 --- backup.sh | 57 ++++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 24 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100755 backup.sh diff --git a/backup.sh b/backup.sh new file mode 100755 index 0000000..4e93a98 --- /dev/null +++ b/backup.sh @@ -0,0 +1,57 @@ +#!/bin/sh +set -e + +BACKUP_DIR="/backup" +RETAIN_DAYS="${BACKUP_RETAIN_DAYS:-7}" +TIMESTAMP="$(date +%Y-%m-%d_%H%M)" +BACKUP_FILE="gitea-dump-${TIMESTAMP}.zip" + +echo "========================================" +echo "Gitea backup started at $(date)" +echo "========================================" + +# Pre-flight check +if ! docker inspect gitea >/dev/null 2>&1; then + echo "ERROR: gitea container not found or not running." + exit 1 +fi + +# Ensure backup directory exists inside gitea container +docker exec -u git gitea sh -c "mkdir -p /tmp/backup" + +# Run gitea dump +echo "Running gitea dump..." +docker exec -u git -w /tmp gitea \ + /app/gitea/gitea dump \ + -c /data/gitea/conf/app.ini \ + --file "/tmp/backup/${BACKUP_FILE}" \ + --skip-log \ + --type zip + +# Verify backup was created +if [ ! -f "${BACKUP_DIR}/${BACKUP_FILE}" ]; then + echo "ERROR: Backup file not found at ${BACKUP_DIR}/${BACKUP_FILE}" + exit 1 +fi + +FILESIZE=$(stat -c%s "${BACKUP_DIR}/${BACKUP_FILE}" 2>/dev/null || stat -f%z "${BACKUP_DIR}/${BACKUP_FILE}" 2>/dev/null) +echo "Backup created: ${BACKUP_FILE} (${FILESIZE} bytes)" + +# Retention: delete backups older than N days +echo "Cleaning up backups older than ${RETAIN_DAYS} days..." +DELETED=$(find "${BACKUP_DIR}" -name "gitea-dump-*.zip" -mtime "+${RETAIN_DAYS}" -print -delete) +if [ -n "${DELETED}" ]; then + echo "Deleted old backups:" + echo "${DELETED}" +else + echo "No old backups to delete." +fi + +echo "" +echo "Current backups:" +ls -lh "${BACKUP_DIR}"/gitea-dump-*.zip 2>/dev/null || echo "(none)" + +echo "" +echo "========================================" +echo "Gitea backup completed at $(date)" +echo "========================================" diff --git a/docker-compose.yml b/docker-compose.yml index 15860db..bf0ed60 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,9 +15,33 @@ services: - gitea_data:/data - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro + - /data/backup/gitea:/tmp/backup ports: - "3008:3000" - "2222:22" + gitea-backup: + image: alpine:3.21 + container_name: gitea-backup + depends_on: + - gitea + restart: unless-stopped + environment: + - BACKUP_RETAIN_DAYS=7 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - /data/backup/gitea:/backup + - ./backup.sh:/usr/local/bin/backup.sh:ro + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + entrypoint: /bin/sh + command: + - -c + - | + apk add --no-cache docker-cli + echo "0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1" > /etc/crontabs/root + touch /var/log/backup.log + crond -f -l 2 + volumes: gitea_data: