Compare commits
2
Commits
5b15ba3125
...
0e07c9ec97
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e07c9ec97 | ||
|
|
e79d26435b |
No files matched your search
@@ -0,0 +1,352 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Script Name: AppBackup-restic.sh
|
||||||
|
# Description:
|
||||||
|
# Creates a backup of a web application and optionally its MySQL database.
|
||||||
|
# The script archives the application home directory and uploads the backup
|
||||||
|
# either to a remote SFTP server or to a Backblaze B2 repository using Restic,
|
||||||
|
# depending on the host on which it is executed.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# AppBackup-restic.sh --appname <name> --appuser <user> [--apppassword <password>]
|
||||||
|
#
|
||||||
|
# Author: Charlie Silverman
|
||||||
|
# Version: 1.6
|
||||||
|
#
|
||||||
|
# Revision History:
|
||||||
|
# 1.0 - Initial version.
|
||||||
|
# 1.1 - Added logging to file.
|
||||||
|
# 1.2 - Added strict error handling, input validation, and FQDN support.
|
||||||
|
# 1.3 - Added trap-based error logging and improved operational visibility.
|
||||||
|
# 1.4 - Added application-specific log files, interrupt handling,
|
||||||
|
# and enhanced logging.
|
||||||
|
# 1.5 - Added command-line help.
|
||||||
|
# 1.6 - Replaced positional parameters with named command-line options
|
||||||
|
# and added support for short/long option formats.
|
||||||
|
#
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o nounset
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# Help Function
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
show_help() {
|
||||||
|
|
||||||
|
cat << EOF
|
||||||
|
|
||||||
|
AppBackup-restic.sh v1.6
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
Creates a backup of a web application's files and optionally its
|
||||||
|
MySQL database. The backup is uploaded either via SFTP or Restic,
|
||||||
|
depending on the host.
|
||||||
|
|
||||||
|
USAGE
|
||||||
|
|
||||||
|
AppBackup-restic.sh --appname <name> --appuser <user> [--apppassword <password>]
|
||||||
|
|
||||||
|
OPTIONS
|
||||||
|
|
||||||
|
-a, --appname <name>
|
||||||
|
Name of the application and MySQL database.
|
||||||
|
Required.
|
||||||
|
|
||||||
|
-u, --appuser <user>
|
||||||
|
Linux account that owns the application files.
|
||||||
|
Required.
|
||||||
|
|
||||||
|
-p, --apppassword <password>
|
||||||
|
MySQL password and SFTP password (if applicable).
|
||||||
|
Optional.
|
||||||
|
|
||||||
|
-h, --help
|
||||||
|
Display this help and exit.
|
||||||
|
|
||||||
|
EXAMPLES
|
||||||
|
|
||||||
|
AppBackup-restic.sh \
|
||||||
|
--appname KISNetManager \
|
||||||
|
--appuser kisuser \
|
||||||
|
--apppassword MySecurePassword
|
||||||
|
|
||||||
|
AppBackup-restic.sh \
|
||||||
|
-a CustomerPortal \
|
||||||
|
-u portaluser
|
||||||
|
|
||||||
|
LOG FILE
|
||||||
|
|
||||||
|
/tmp/AppBackup-<ApplicationName>.log
|
||||||
|
|
||||||
|
AUTHOR
|
||||||
|
|
||||||
|
Charlie Silverman
|
||||||
|
|
||||||
|
VERSION
|
||||||
|
|
||||||
|
1.6
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# Process Command Line Arguments
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
AppName=""
|
||||||
|
AppUser=""
|
||||||
|
AppPassword=""
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
|
||||||
|
-a|--appname)
|
||||||
|
AppName="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
|
||||||
|
--appname=*)
|
||||||
|
AppName="${1#*=}"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
|
||||||
|
-u|--appuser)
|
||||||
|
AppUser="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
|
||||||
|
--appuser=*)
|
||||||
|
AppUser="${1#*=}"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
|
||||||
|
-p|--apppassword)
|
||||||
|
AppPassword="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
|
||||||
|
--apppassword=*)
|
||||||
|
AppPassword="${1#*=}"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
|
||||||
|
-h|--help)
|
||||||
|
show_help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "ERROR: Unknown option '$1'"
|
||||||
|
echo
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
|
||||||
|
esac
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# Validate Required Parameters
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
if [[ -z "$AppName" || -z "$AppUser" ]]; then
|
||||||
|
echo "ERROR: --appname and --appuser are required."
|
||||||
|
echo
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# Runtime Variables
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
DATE=$(date "+%Y%m%d_%H%M%S")
|
||||||
|
HOSTNAME_FQDN="$(hostname -f)"
|
||||||
|
|
||||||
|
SAFE_APPNAME=$(echo "$AppName" | tr -cs '[:alnum:]_-' '_')
|
||||||
|
|
||||||
|
LOGFILE="/tmp/AppBackup-${SAFE_APPNAME}.log"
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# Logging Function
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
log() {
|
||||||
|
echo "$(date '+%Y-%m-%d %H:%M:%S') [$$] $1" | tee -a "$LOGFILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# Error Handling
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
error_handler() {
|
||||||
|
|
||||||
|
local exit_code=$?
|
||||||
|
local line_number=$1
|
||||||
|
|
||||||
|
log "ERROR: Backup failed on line ${line_number} with exit code ${exit_code}"
|
||||||
|
|
||||||
|
exit "${exit_code}"
|
||||||
|
}
|
||||||
|
|
||||||
|
interrupt_handler() {
|
||||||
|
|
||||||
|
log "ERROR: Backup interrupted by signal"
|
||||||
|
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
trap 'error_handler $LINENO' ERR
|
||||||
|
trap interrupt_handler SIGINT SIGTERM
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# Startup Logging
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
log "================================================================="
|
||||||
|
log "Starting backup job"
|
||||||
|
log "Application: $AppName"
|
||||||
|
log "Application User: $AppUser"
|
||||||
|
log "Host: $HOSTNAME_FQDN"
|
||||||
|
log "Log File: $LOGFILE"
|
||||||
|
log "================================================================="
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# Restic Repository Configuration
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export RESTIC_REPOSITORY="s3:https://s3.us-west-004.backblazeb2.com/ackitsbucket/docker"
|
||||||
|
export RESTIC_PASSWORD_FILE="/root/.restic-password"
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# Host Specific Configuration
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
HostPrefix=""
|
||||||
|
|
||||||
|
if [[ "$HOSTNAME_FQDN" == "ackitserver.ackcentuate.com" ]]; then
|
||||||
|
|
||||||
|
if [[ "$AppName" == "KISNetManager" ]]; then
|
||||||
|
HostPrefix="dev-"
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
ArchiveFile="${AppName}-${DATE}.tar.bz2"
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# Prepare Backup Workspace
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
log "Changing to backup directory"
|
||||||
|
|
||||||
|
cd /home/Backups
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# Database Backup
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
if [[ -n "$AppPassword" ]]; then
|
||||||
|
|
||||||
|
log "Creating MySQL database backup"
|
||||||
|
|
||||||
|
mkdir -p "/home/${AppUser}/SQLBackup"
|
||||||
|
|
||||||
|
mysqldump \
|
||||||
|
-u "$AppUser" \
|
||||||
|
--password="$AppPassword" \
|
||||||
|
"$AppName" \
|
||||||
|
> "/home/${AppUser}/SQLBackup/${AppName}.sqldump"
|
||||||
|
|
||||||
|
log "MySQL dump completed successfully"
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
log "Database password not supplied. Skipping MySQL backup."
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# Remove Previous Backup Archives
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
log "Removing previous backup archives"
|
||||||
|
|
||||||
|
rm -f /home/Backups/"${AppName}"*.tar.bz*
|
||||||
|
rm -f /home/Backups/"${HostPrefix}${AppName}"*.tar.bz*
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# Create Archive
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
log "Creating archive ${HostPrefix}${ArchiveFile}"
|
||||||
|
|
||||||
|
tar cjf \
|
||||||
|
"/home/Backups/${HostPrefix}${ArchiveFile}" \
|
||||||
|
"/home/${AppUser}/"
|
||||||
|
|
||||||
|
log "Archive created successfully"
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# Upload Backup
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
if [[ "$HOSTNAME_FQDN" != "ackitserver.ackcentuate.com" ]]; then
|
||||||
|
|
||||||
|
if [[ -z "$AppPassword" ]]; then
|
||||||
|
log "ERROR: SFTP upload requires --apppassword"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Uploading archive via SFTP"
|
||||||
|
|
||||||
|
lftp "sftp://${AppUser}:${AppPassword}@webbuilder.ackcentuate.com" -e \
|
||||||
|
"cd /home/Backups; put /home/Backups/${HostPrefix}${ArchiveFile}; bye"
|
||||||
|
|
||||||
|
log "SFTP upload completed successfully"
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
log "Loading Restic environment"
|
||||||
|
|
||||||
|
source /root/.restic-env
|
||||||
|
|
||||||
|
log "Starting Restic backup"
|
||||||
|
|
||||||
|
restic backup "/home/${AppUser}/" \
|
||||||
|
--tag apache \
|
||||||
|
--tag website \
|
||||||
|
--tag "$AppName" \
|
||||||
|
--tag "$HOSTNAME_FQDN" \
|
||||||
|
>> "$LOGFILE" 2>&1
|
||||||
|
|
||||||
|
log "Restic backup completed successfully"
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# Cleanup
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
if [[ -n "$AppPassword" ]]; then
|
||||||
|
|
||||||
|
log "Removing temporary SQL backup files"
|
||||||
|
|
||||||
|
rm -rf "/home/${AppUser}/SQLBackup"
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# Completion
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
log "Backup completed successfully"
|
||||||
|
log "Application: $AppName"
|
||||||
|
log "Archive: ${HostPrefix}${ArchiveFile}"
|
||||||
|
log "Host: $HOSTNAME_FQDN"
|
||||||
|
log "================================================================="
|
||||||
+17
-10
@@ -3,8 +3,8 @@
|
|||||||
### Docker Stack Backup Script
|
### Docker Stack Backup Script
|
||||||
### Created by Charlie Silverman - ackitsme3@gmail.com
|
### Created by Charlie Silverman - ackitsme3@gmail.com
|
||||||
###############################################################################
|
###############################################################################
|
||||||
### Version: 1.1.3
|
### Version: 2.0.1
|
||||||
### Updated: 2026-08-05
|
### Updated: 2026-10-18
|
||||||
### Purpose: Backs up all docker stacks in a given directory to Restic bucket
|
### Purpose: Backs up all docker stacks in a given directory to Restic bucket
|
||||||
###############################################################################
|
###############################################################################
|
||||||
### Requirements:
|
### Requirements:
|
||||||
@@ -25,6 +25,10 @@
|
|||||||
# 1.1.1 - Added snapshot size to log
|
# 1.1.1 - Added snapshot size to log
|
||||||
# 1.1.2 - Added aggregate backup size to end of log
|
# 1.1.2 - Added aggregate backup size to end of log
|
||||||
# 1.1.3 - Added GiB to total backup size information
|
# 1.1.3 - Added GiB to total backup size information
|
||||||
|
# 2.0.0 - Modified to save all files to a folder tree for backup rather than
|
||||||
|
# creating a single monolithic archive. This will allow restic to
|
||||||
|
# properly manage snapshots
|
||||||
|
# 2.0.1 - Changed exception lookup to case-insensitive
|
||||||
|
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
@@ -68,12 +72,16 @@ log() {
|
|||||||
should_skip() {
|
should_skip() {
|
||||||
local project="$1"
|
local project="$1"
|
||||||
|
|
||||||
|
shopt -s nocasematch
|
||||||
|
|
||||||
for pattern in "${SKIP_PROJECTS[@]}"; do
|
for pattern in "${SKIP_PROJECTS[@]}"; do
|
||||||
if [[ "$project" == $pattern ]]; then
|
if [[ "$project" == $pattern ]]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
shopt -u nocasematch
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +156,7 @@ for compose_file in "${COMPOSE_FILES[@]}"; do
|
|||||||
log "Compose file: ${compose_file}"
|
log "Compose file: ${compose_file}"
|
||||||
log "=================================================="
|
log "=================================================="
|
||||||
|
|
||||||
STAGING_DIR="/tmp/docker-backup-${project_name}-${TIMESTAMP}"
|
STAGING_DIR="/home/Backups/docker-backup-${project_name}"
|
||||||
|
|
||||||
mkdir -p \
|
mkdir -p \
|
||||||
"${STAGING_DIR}/images" \
|
"${STAGING_DIR}/images" \
|
||||||
@@ -198,11 +206,11 @@ for compose_file in "${COMPOSE_FILES[@]}"; do
|
|||||||
###################################################
|
###################################################
|
||||||
|
|
||||||
log "Backing up compose files..."
|
log "Backing up compose files..."
|
||||||
|
mkdir -p "${STAGING_DIR}/compose"
|
||||||
|
|
||||||
tar -czf \
|
cp -a \
|
||||||
"${STAGING_DIR}/${project_name}-compose.tar.gz" \
|
"${compose_dir}" \
|
||||||
-C "$(dirname "$compose_dir")" \
|
"${STAGING_DIR}/compose/"
|
||||||
"$(basename "$compose_dir")"
|
|
||||||
|
|
||||||
###################################################
|
###################################################
|
||||||
# Backup Images
|
# Backup Images
|
||||||
@@ -221,8 +229,7 @@ for compose_file in "${COMPOSE_FILES[@]}"; do
|
|||||||
log "Saving image: $image"
|
log "Saving image: $image"
|
||||||
|
|
||||||
docker save "$image" \
|
docker save "$image" \
|
||||||
| gzip \
|
> "${STAGING_DIR}/images/${safe_name}.tar"
|
||||||
> "${STAGING_DIR}/images/${safe_name}.tar.gz"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
done < "${STAGING_DIR}/metadata/images.list"
|
done < "${STAGING_DIR}/metadata/images.list"
|
||||||
@@ -243,7 +250,7 @@ for compose_file in "${COMPOSE_FILES[@]}"; do
|
|||||||
-v "${volume}:/source:ro" \
|
-v "${volume}:/source:ro" \
|
||||||
-v "${STAGING_DIR}/volumes:/backup" \
|
-v "${STAGING_DIR}/volumes:/backup" \
|
||||||
alpine \
|
alpine \
|
||||||
tar -czf "/backup/${volume}.tar.gz" -C /source .
|
sh -c "cd /source && tar cf - . | tar xf - -C /backup"
|
||||||
|
|
||||||
done < "${STAGING_DIR}/metadata/volumes.list"
|
done < "${STAGING_DIR}/metadata/volumes.list"
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user