#!/bin/bash # Cara pakai: # # curl -s https://tools-scr.apps360.id/backup-files/backup-dir-v2.sh | \ # bash -s -- \ # --env prod \ # --org Bigio \ # --app kerisjateng-web \ # --service Web \ # --base-dir /home/app \ # --backup-dir uploads \ # --retention 7 \ # --exclude "uploads/cache/*" \ # --exclude "uploads/tmp/*" set -euo pipefail trap 'send_webhook "fail"' ERR SCRIPT_VERSION="3.0.0" echo "Running backup script version $SCRIPT_VERSION" ############################################# # DEFAULT VALUES ############################################# DRY_RUN=false LOG_FILE="/tmp/backup-s3.log" EXCLUDES=() WEBHOOK_URL="" WEBHOOK_RETRY=3 HOSTNAME=$(hostname) START_TIME=$(date +%s) COMPRESSED_SIZE="unknown" ############################################# # PARAMETER PARSER ############################################# while [[ $# -gt 0 ]]; do case "$1" in --env) ENV="$2"; shift 2 ;; --webhook) WEBHOOK_URL="$2"; shift 2 ;; --org) ORG_NAME="$2"; shift 2 ;; --app) APP_NAME="$2"; shift 2 ;; --service) APP_SERVICE_NAME="$2"; shift 2 ;; --base-dir) BASE_DIR="$2"; shift 2 ;; --backup-dir) BAC_DIR="$2"; shift 2 ;; --retention) RETENTION="$2"; shift 2 ;; --exclude) EXCLUDES+=("$2"); shift 2 ;; --dry-run) DRY_RUN=true; shift ;; --log-file) LOG_FILE="$2"; shift 2 ;; *) echo "Unknown parameter: $1"; exit 1 ;; esac done ############################################# # VALIDATION ############################################# required_vars=(ENV ORG_NAME APP_NAME APP_SERVICE_NAME BASE_DIR BAC_DIR RETENTION) for v in "${required_vars[@]}"; do if [[ -z "${!v:-}" ]]; then echo "❌ Missing required parameter: $v" exit 1 fi done if [[ "$ENV" != "dev" && "$ENV" != "prod" ]]; then echo "❌ ENV must be dev or prod" exit 1 fi if ! [[ "$RETENTION" =~ ^[0-9]+$ ]]; then echo "❌ RETENTION must be numeric" exit 1 fi if [[ ! -d "$BASE_DIR" ]]; then echo "❌ BASE_DIR not found: $BASE_DIR" exit 1 fi if [[ ! -d "$BASE_DIR/$BAC_DIR" ]]; then echo "❌ BAC_DIR not found: $BASE_DIR/$BAC_DIR" exit 1 fi ############################################# # LOCK FILE ############################################# LOCK_FILE="/tmp/backup-${APP_NAME}-${APP_SERVICE_NAME}.lock" exec 200>"$LOCK_FILE" if ! flock -n 200; then echo "Another backup process already running" exit 1 fi ############################################# # LOGGING ############################################# exec > >(tee -a "$LOG_FILE") 2>&1 echo echo "========================================" echo "Backup started: $(date)" echo "========================================" echo "ENV : $ENV" echo "ORG : $ORG_NAME" echo "APP : $APP_NAME" echo "SERVICE : $APP_SERVICE_NAME" echo "BASE DIR : $BASE_DIR" echo "BACKUP DIR : $BAC_DIR" echo "RETENTION : $RETENTION days" echo "DRY RUN : $DRY_RUN" if [[ ${#EXCLUDES[@]} -gt 0 ]]; then echo "EXCLUDES:" for e in "${EXCLUDES[@]}"; do echo " - $e" done fi ############################################# # CONFIG ############################################# TIMESTAMP=$(date +"%Y%m%d-%H%M%S") ARCHIVE_NAME="${APP_NAME}_${APP_SERVICE_NAME}-${TIMESTAMP}.tar.gz" if [[ "$ENV" == "dev" ]]; then BUCKET_NAME="bigio-dev-app-archives" else BUCKET_NAME="bigio-prod-app-archives" fi S3_BASE="s3://${BUCKET_NAME}/${ORG_NAME}/${APP_NAME}/${APP_SERVICE_NAME}" S3_PATH="${S3_BASE}/${ARCHIVE_NAME}" echo "S3 PATH : $S3_PATH" cd "$BASE_DIR" ############################################# # CHECK EMPTY DIR ############################################# if ! find "$BAC_DIR" -mindepth 1 -print -quit | grep -q .; then echo "Directory empty, skipping backup" exit 0 fi ############################################# # SIZE REPORT ############################################# DIR_SIZE=$(du -sh "$BAC_DIR" | cut -f1) echo "Backup size : $DIR_SIZE" ############################################# # PREPARE EXCLUDE ############################################# TAR_EXCLUDES=() for e in "${EXCLUDES[@]}"; do TAR_EXCLUDES+=(--exclude="$e") done ############################################# # SEND to WEBHOOK ############################################# send_webhook() { STATUS="$1" [[ -z "$WEBHOOK_URL" ]] && return END_TIME=$(date +%s) DURATION=$((END_TIME - START_TIME)) PAYLOAD=$(cat </dev/null; then echo "Webhook sent" return fi echo "Webhook retry ($i/$WEBHOOK_RETRY)" sleep 2 done echo "Webhook failed" } ############################################# # UPLOAD BACKUP ############################################# echo "Tar exclude args: ${TAR_EXCLUDES[*]}" upload() { if [[ "$DRY_RUN" == "true" ]]; then echo "[DRY RUN] upload $BAC_DIR → $S3_PATH" return fi echo echo "Uploading backup..." CPU=$(nproc) for i in {1..3}; do if tar --ignore-failed-read --warning=no-failed-read "${TAR_EXCLUDES[@]}" -cf - "$BAC_DIR" \ | pigz -p "$CPU" \ | s3cmd put - "$S3_PATH"; then echo "Upload success" # get compressed size from S3 COMPRESSED_BYTES=$(s3cmd ls "$S3_PATH" | awk '{print $3}') COMPRESSED_SIZE=$(numfmt --to=iec-i --suffix=B "$COMPRESSED_BYTES") return fi echo "Retry upload ($i/3)" sleep 5 done echo "❌ Upload failed" send_webhook "fail" exit 1 } upload ############################################# # RETENTION CLEANUP ############################################# echo echo "Running retention cleanup..." CUTOFF_TS=$(date -d "$RETENTION days ago" +%s) SCAN_COUNT=0 DELETE_COUNT=0 shopt -s extglob while read -r DATE TIME SIZE FILE; do ((++SCAN_COUNT)) [[ "$FILE" != *.@(tar.gz|tgz|zip|tar|gz|bz2|7z|xz) ]] && continue FILE_TS=$(date -d "$DATE $TIME" +%s) if (( FILE_TS < CUTOFF_TS )); then ((++DELETE_COUNT)) if [[ "$DRY_RUN" == "true" ]]; then echo "[DRY RUN] delete $FILE" else echo "Deleting $FILE" s3cmd del "$FILE" fi fi done < <(s3cmd ls "${S3_BASE}/") ############################################# # SUMMARY ############################################# echo echo "========================================" echo "Backup completed: $(date)" echo "========================================" echo "Archive : $ARCHIVE_NAME" echo "Scanned : $SCAN_COUNT" echo "Deleted : $DELETE_COUNT" echo "Log file : $LOG_FILE" echo echo "✅ Backup finished" send_webhook "success"