Skip to content

backup /data/ directory #165

backup /data/ directory

backup /data/ directory #165

Workflow file for this run

name: backup /data/ directory
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
jobs:
backup-data:
runs-on: ubuntu-latest
permissions:
contents: read
env:
REMOTE: deploy@45.76.134.105
TARGET: /var/www/fridge.dev
REMOTE_BACKUP_DIR: /home/deploy
GDRIVE_REMOTE: gdrive
GDRIVE_FOLDER_ID: ${{ secrets.GDRIVE_BACKUP_FOLDER_ID }}
steps:
- name: install ssh, rclone, and jq
run: sudo apt-get update && sudo apt-get install -y openssh-client rclone jq
- name: set up ssh
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
RCLONE_CONFIG_CONTENT: ${{ secrets.RCLONE_CONFIG }}
run: |
mkdir -p ~/.ssh ~/.config/rclone
printf '%s\n' "$DEPLOY_KEY" | tr -d '\r' > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H 45.76.134.105 >> ~/.ssh/known_hosts
printf '%s' "$RCLONE_CONFIG_CONTENT" > ~/.config/rclone/rclone.conf
chmod 600 ~/.config/rclone/rclone.conf
- name: create remote backup archive
id: remote-backup
run: |
set -euo pipefail
remote_archive_path="$(
ssh "$REMOTE" TARGET="$TARGET" REMOTE_BACKUP_DIR="$REMOTE_BACKUP_DIR" bash -s <<'EOF'
set -euo pipefail
[ "$TARGET" = "/var/www/fridge.dev" ] || { echo "refusing to back up unexpected target: $TARGET" >&2; exit 1; }
[ -d "$TARGET" ] || { echo "site directory not found: $TARGET" >&2; exit 1; }
cd "$TARGET"
command -v zip >/dev/null 2>&1 || { echo "zip is not installed on the server" >&2; exit 1; }
[ -d data ] || { echo "data directory not found at $TARGET/data" >&2; exit 1; }
[ -w "$REMOTE_BACKUP_DIR" ] || { echo "remote backup directory is not writable: $REMOTE_BACKUP_DIR" >&2; exit 1; }
# PHP-FPM owns runtime data as http:http. Repair group-read/traverse
# bits before checking so files left at 0600 by older writers do
# not block deploy, which is a member of the http group.
sudo -n -u http find data \
-path 'data/etc/banlists/index' -prune -o \
-exec chmod g+rX {} + 2>/dev/null || true
unreadable_paths="$(
find data \
-path 'data/etc/banlists/index' -prune -o \
\( -type f ! -readable -o -type d \( ! -readable -o ! -executable \) \) -print 2>&1 \
| head -50 || true
)"
if [ -n "$unreadable_paths" ]; then
echo "data contains paths the deploy user cannot archive:" >&2
echo "$unreadable_paths" >&2
echo "fix ownership/permissions on $TARGET/data, then rerun this workflow" >&2
exit 1
fi
echo "Removing stale remote backup archives from $REMOTE_BACKUP_DIR" >&2
find "$REMOTE_BACKUP_DIR" -maxdepth 1 -type f \
-regextype posix-extended \
-regex ".*/[0-9]{2}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}\\.zip" \
-delete
archive_name="$(date +"%d-%m-%y_%H-%M-%S").zip"
archive_path="${REMOTE_BACKUP_DIR%/}/$archive_name"
zip_log="$(mktemp)"
trap 'rm -f "$zip_log"' EXIT
echo "Creating backup archive at $archive_path" >&2
df -h "$REMOTE_BACKUP_DIR" >&2
rm -f "$archive_path"
if zip -r "$archive_path" data -x 'data/etc/banlists/index/*' >"$zip_log" 2>&1; then
:
else
status=$?
echo "zip failed with exit code $status" >&2
cat "$zip_log" >&2
rm -f "$archive_path"
exit "$status"
fi
ls -lh "$archive_path" >&2
printf "%s" "$archive_path"
EOF
)"
archive_name="$(basename "$remote_archive_path")"
echo "remote_archive_path=$remote_archive_path" >> "$GITHUB_OUTPUT"
echo "archive_name=$archive_name" >> "$GITHUB_OUTPUT"
- name: download backup archive
run: |
set -euo pipefail
scp "$REMOTE:${{ steps.remote-backup.outputs.remote_archive_path }}" "${{ steps.remote-backup.outputs.archive_name }}"
- name: upload backup to Google Drive
run: |
set -euo pipefail
rclone copy \
"${{ steps.remote-backup.outputs.archive_name }}" \
"${GDRIVE_REMOTE}:" \
--drive-root-folder-id "${GDRIVE_FOLDER_ID}"
- name: remove remote backup archive
if: always()
run: |
set -euo pipefail
remote_archive_path="${{ steps.remote-backup.outputs.remote_archive_path }}"
[ -n "$remote_archive_path" ] || exit 0
ssh "$REMOTE" "rm -f '$remote_archive_path'"
- name: keep only the 10 newest backups
run: |
set -euo pipefail
old_files="$(
rclone lsjson "${GDRIVE_REMOTE}:" \
--drive-root-folder-id "${GDRIVE_FOLDER_ID}" \
--files-only \
| jq -r 'sort_by(.ModTime) | reverse | .[10:][]?.Path'
)"
if [ -z "$old_files" ]; then
echo "No old backups to delete."
exit 0
fi
while IFS= read -r file_path; do
[ -n "$file_path" ] || continue
rclone deletefile "${GDRIVE_REMOTE}:${file_path}" --drive-root-folder-id "${GDRIVE_FOLDER_ID}"
done <<< "$old_files"
- name: remove local backup archive
if: always()
run: |
archive_name="${{ steps.remote-backup.outputs.archive_name }}"
[ -n "$archive_name" ] || exit 0
rm -f "$archive_name"