Skip to content

Instantly share code, notes, and snippets.

@cau1k
Last active December 17, 2025 18:18
Show Gist options
  • Select an option

  • Save cau1k/ac2db1cf91087dc7eba33ce402c5ebec to your computer and use it in GitHub Desktop.

Select an option

Save cau1k/ac2db1cf91087dc7eba33ce402c5ebec to your computer and use it in GitHub Desktop.
#!/bin/bash
# forgejo backup script - READ ONLY, NON-DESTRUCTIVE
# run directly on the forgejo server
# thanks, opus!
set -euo pipefail
# auto-detect forgejo container (matches forgejo or gitea in image name)
CONTAINER=$(docker ps --format '{{.Names}}\t{{.Image}}' | grep -E 'forgejo|gitea' | head -1 | cut -f1)
if [ -z "${CONTAINER}" ]; then
echo "ERROR: No Forgejo/Gitea container found running!"
echo "Running containers:"
docker ps --format '{{.Names}}\t{{.Image}}'
exit 1
fi
# auto-detect data volume from container mounts
DATA_VOLUME=$(docker inspect "${CONTAINER}" --format '{{range .Mounts}}{{if eq .Destination "/data"}}{{.Source}}{{end}}{{end}}')
if [ -z "${DATA_VOLUME}" ]; then
echo "ERROR: Could not find /data volume mount for container ${CONTAINER}"
exit 1
fi
# auto-detect UID:GID from data files
RUN_UID=$(stat -c '%u:%g' "${DATA_VOLUME}/gitea/gitea.db" 2>/dev/null || stat -c '%u:%g' "${DATA_VOLUME}/gitea" 2>/dev/null || echo "1000:1000")
BACKUP_DIR="$HOME/forgejo-backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="forgejo-backup-${TIMESTAMP}"
BACKUP_PATH="${BACKUP_DIR}/${BACKUP_NAME}"
echo "=== Forgejo Backup Script (NON-DESTRUCTIVE) ==="
echo "Timestamp: ${TIMESTAMP}"
echo "Container: ${CONTAINER}"
echo "Data volume: ${DATA_VOLUME}"
echo "Run UID:GID: ${RUN_UID}"
echo ""
# create backup directory
mkdir -p "${BACKUP_PATH}"
echo "[1/6] Created backup directory: ${BACKUP_PATH}"
# verify container exists
echo "[2/6] Verifying container is running..."
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER}$"; then
echo "ERROR: Container ${CONTAINER} not found or not running!"
exit 1
fi
echo " ✓ Container is running"
# get forgejo version
echo "[3/6] Recording Forgejo version..."
FORGEJO_VERSION=$(docker exec ${CONTAINER} forgejo --version 2>/dev/null || docker exec ${CONTAINER} gitea --version 2>/dev/null)
echo "${FORGEJO_VERSION}" > "${BACKUP_PATH}/VERSION.txt"
echo " ✓ Version: ${FORGEJO_VERSION}"
# use forgejo's built-in dump (creates a zip with db + repos + config)
echo "[4/6] Running forgejo dump inside container..."
docker exec -u ${RUN_UID} ${CONTAINER} forgejo dump -c /data/gitea/conf/app.ini -f /tmp/forgejo-dump.zip 2>/dev/null || \
docker exec -u ${RUN_UID} ${CONTAINER} gitea dump -c /data/gitea/conf/app.ini -f /tmp/forgejo-dump.zip
docker cp ${CONTAINER}:/tmp/forgejo-dump.zip "${BACKUP_PATH}/forgejo-dump.zip"
docker exec -u ${RUN_UID} ${CONTAINER} rm -f /tmp/forgejo-dump.zip
echo " ✓ Dump created and copied"
# also backup the raw data directory (belt and suspenders)
echo "[5/6] Creating raw volume backup (tar archive)..."
tar -czf "${BACKUP_PATH}/forgejo-data-raw.tar.gz" -C "${DATA_VOLUME}" .
echo " ✓ Raw data backup complete"
# generate and verify checksums
echo "[6/6] Generating and verifying checksums..."
cd "${BACKUP_PATH}"
sha256sum forgejo-dump.zip > forgejo-dump.zip.sha256
sha256sum forgejo-data-raw.tar.gz > forgejo-data-raw.tar.gz.sha256
sha256sum VERSION.txt > VERSION.txt.sha256
echo " ✓ Checksums generated"
echo ""
echo "=== Verifying backup integrity ==="
sha256sum -c forgejo-dump.zip.sha256
sha256sum -c forgejo-data-raw.tar.gz.sha256
sha256sum -c VERSION.txt.sha256
# summary
echo ""
echo "=== BACKUP COMPLETE ==="
echo "Location: ${BACKUP_PATH}"
echo ""
echo "Contents:"
ls -lh "${BACKUP_PATH}"
echo ""
echo "Checksums:"
cat "${BACKUP_PATH}"/*.sha256
echo ""
echo "To verify later, run:"
echo " cd ${BACKUP_PATH} && sha256sum -c *.sha256"
echo ""
echo "To restore, see: https://forgejo.org/docs/latest/admin/backup-and-restore/"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment