From 31cb5de4df3b8ff9b61f55e2c23a3ad6a2d303bf Mon Sep 17 00:00:00 2001 From: Matthieu Borgognon Date: Fri, 7 Feb 2025 16:46:06 +0100 Subject: [PATCH 1/2] fix(recover mysql): script + accept any separator in container name + make linter happy --- recovery-docker-mysql.sh | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/recovery-docker-mysql.sh b/recovery-docker-mysql.sh index 754c42f..23ac20b 100644 --- a/recovery-docker-mysql.sh +++ b/recovery-docker-mysql.sh @@ -5,13 +5,13 @@ BACKUPDIR="/backup/mysql" # Display a numbered list of containers from the backup directory echo "Available containers:" -CONTAINERS=($(for file in "$BACKUPDIR"/*.sql.gz; do IFS='-' read -r CONTAINER _ <<< "$(basename "$file" .sql.gz)"; echo "$CONTAINER"; done | sort -u)) +mapfile -t CONTAINERS < <(docker ps --format '{{.Names}}:{{.Image}}' | grep 'mysql\|mariadb' | cut -d":" -f1) for ((i=0; i<${#CONTAINERS[@]}; i++)); do echo "$((i+1)). ${CONTAINERS[$i]}" done # Prompt the user to select a container -read -p "Enter the number of the container: " CONTAINER_NUMBER +read -r -p "Enter the number of the container: " CONTAINER_NUMBER if [[ ! $CONTAINER_NUMBER =~ ^[0-9]+$ || $CONTAINER_NUMBER -lt 1 || $CONTAINER_NUMBER -gt ${#CONTAINERS[@]} ]]; then echo "Invalid input. Please enter a valid container number." exit 1 @@ -22,13 +22,18 @@ CONTAINER=${CONTAINERS[$((CONTAINER_NUMBER-1))]} # Display a numbered list of databases for the selected container echo "Available databases in container $CONTAINER:" -DATABASES=($(for file in "$BACKUPDIR"/*.sql.gz; do IFS='-' read -r FILE_CONTAINER FILE_DATABASE _ <<< "$(basename "$file" .sql.gz)"; if [ "$FILE_CONTAINER" == "$CONTAINER" ]; then echo "$FILE_DATABASE"; fi> +mapfile -t DATABASES < <(for file in "$BACKUPDIR"/"$CONTAINER"*sql.gz; do + filename=$(basename "$file" .sql.gz) + db_part=${filename#"$CONTAINER"[^[:alnum:]]} # Remove container prefix and any separator + db_name=${db_part:0:-13} # Remove timestamp and its separator + echo "$db_name" +done | sort -u) for ((i=0; i<${#DATABASES[@]}; i++)); do echo "$((i+1)). ${DATABASES[$i]}" done # Prompt the user to select a database -read -p "Enter the number of the database: " DATABASE_NUMBER +read -r -p "Enter the number of the database: " DATABASE_NUMBER if [[ ! $DATABASE_NUMBER =~ ^[0-9]+$ || $DATABASE_NUMBER -lt 1 || $DATABASE_NUMBER -gt ${#DATABASES[@]} ]]; then echo "Invalid input. Please enter a valid database number." exit 1 @@ -39,7 +44,12 @@ DATABASENAME=${DATABASES[$((DATABASE_NUMBER-1))]} # Display a numbered list of timestamps for the selected container and database echo "Available timestamps for container $CONTAINER and database $DATABASENAME:" -TIMESTAMPS=($(for file in "$BACKUPDIR"/*.sql.gz; do IFS='-' read -r FILE_CONTAINER FILE_DATABASE FILE_TIMESTAMP _ <<< "$(basename "$file" .sql.gz)"; if [ "$FILE_CONTAINER" == "$CONTAINER" ] && [ "$FILE_DAT> +mapfile -t TIMESTAMPS < <(for file in "$BACKUPDIR"/"$CONTAINER"*"$DATABASENAME"*sql.gz; do + filename=$(basename "$file" .sql.gz) + # Extract timestamp (12 digits) from the end of filename + timestamp=${filename:(-12)} + echo "$timestamp" +done | sort -u) for ((i=0; i<${#TIMESTAMPS[@]}; i++)); do # Manually split and format the timestamp as YYYY-MM-DD HH:mm YEAR=${TIMESTAMPS[$i]:0:4} @@ -52,7 +62,7 @@ for ((i=0; i<${#TIMESTAMPS[@]}; i++)); do done # Prompt the user to select a timestamp -read -p "Enter the number of the timestamp: " TIMESTAMP_NUMBER +read -r -p "Enter the number of the timestamp: " TIMESTAMP_NUMBER if [[ ! $TIMESTAMP_NUMBER =~ ^[0-9]+$ || $TIMESTAMP_NUMBER -lt 1 || $TIMESTAMP_NUMBER -gt ${#TIMESTAMPS[@]} ]]; then echo "Invalid input. Please enter a valid timestamp number." exit 1 @@ -65,18 +75,18 @@ TIMESTAMP=${TIMESTAMPS[$((TIMESTAMP_NUMBER-1))]} FILENAME="$CONTAINER-$DATABASENAME-$TIMESTAMP.sql.gz" # Set the MySQL password in the MYSQL_PWD variable -MYSQL_PWD=$(docker exec $CONTAINER env | grep MYSQL_ROOT_PASSWORD | cut -d"=" -f2) +MYSQL_PWD=$(docker exec "$CONTAINER" env | grep MYSQL_ROOT_PASSWORD | cut -d"=" -f2) # Display confirmation prompt -read -p "Confirm recovery of DB $DATABASENAME in Container $CONTAINER from $TIMESTAMP_FORMATTED? (yes/y): " CONFIRMATION -if [[ ! $CONFIRMATION =~ ^[Yy][Ee][Ss]|[Yy]$ ]]; then +read -r -p "Confirm recovery of DB $DATABASENAME in Container $CONTAINER from $TIMESTAMP_FORMATTED? (yes/y): " CONFIRMATION +if [[ ! $CONFIRMATION =~ ^([Yy][Ee][Ss]|[Yy])$ ]]; then echo "Recovery canceled by user." exit 1 fi # Execute the desired command and capture the output -OUTPUT=$(zcat "$BACKUPDIR/$FILENAME" | docker exec -i $CONTAINER /usr/bin/mysql -u root --password=$MYSQL_PWD $DATABASENAME 2>&1) +OUTPUT=$(zcat "$BACKUPDIR/$FILENAME" | docker exec -i "$CONTAINER" /usr/bin/mysql -u root --password="$MYSQL_PWD" "$DATABASENAME" 2>&1) # Display the output echo "Output of MySQL command:" -echo "$OUTPUT" +echo "$OUTPUT" \ No newline at end of file From 8e8e28cbc35c9cd9cc097c765cea4f624e6cf5b8 Mon Sep 17 00:00:00 2001 From: Matthieu Borgognon Date: Wed, 4 Jun 2025 13:59:11 +0200 Subject: [PATCH 2/2] refactor(recover mysql): use while loop instead of mapfile to be POSIX compliant --- recovery-docker-mysql.sh | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/recovery-docker-mysql.sh b/recovery-docker-mysql.sh index 23ac20b..abadd5a 100644 --- a/recovery-docker-mysql.sh +++ b/recovery-docker-mysql.sh @@ -5,7 +5,10 @@ BACKUPDIR="/backup/mysql" # Display a numbered list of containers from the backup directory echo "Available containers:" -mapfile -t CONTAINERS < <(docker ps --format '{{.Names}}:{{.Image}}' | grep 'mysql\|mariadb' | cut -d":" -f1) +CONTAINERS=() +while IFS= read -r line; do + CONTAINERS+=("$line") +done < <(docker ps --format '{{.Names}}:{{.Image}}' | grep 'mysql\|mariadb' | cut -d":" -f1) for ((i=0; i<${#CONTAINERS[@]}; i++)); do echo "$((i+1)). ${CONTAINERS[$i]}" done @@ -22,7 +25,10 @@ CONTAINER=${CONTAINERS[$((CONTAINER_NUMBER-1))]} # Display a numbered list of databases for the selected container echo "Available databases in container $CONTAINER:" -mapfile -t DATABASES < <(for file in "$BACKUPDIR"/"$CONTAINER"*sql.gz; do +DATABASES=() +while IFS= read -r line; do + DATABASES+=("$line") +done < <(for file in "$BACKUPDIR"/"$CONTAINER"*sql.gz; do filename=$(basename "$file" .sql.gz) db_part=${filename#"$CONTAINER"[^[:alnum:]]} # Remove container prefix and any separator db_name=${db_part:0:-13} # Remove timestamp and its separator @@ -44,7 +50,10 @@ DATABASENAME=${DATABASES[$((DATABASE_NUMBER-1))]} # Display a numbered list of timestamps for the selected container and database echo "Available timestamps for container $CONTAINER and database $DATABASENAME:" -mapfile -t TIMESTAMPS < <(for file in "$BACKUPDIR"/"$CONTAINER"*"$DATABASENAME"*sql.gz; do +TIMESTAMPS=() +while IFS= read -r line; do + TIMESTAMPS+=("$line") +done < <(for file in "$BACKUPDIR"/"$CONTAINER"*"$DATABASENAME"*sql.gz; do filename=$(basename "$file" .sql.gz) # Extract timestamp (12 digits) from the end of filename timestamp=${filename:(-12)} @@ -89,4 +98,4 @@ OUTPUT=$(zcat "$BACKUPDIR/$FILENAME" | docker exec -i "$CONTAINER" /usr/bin/mysq # Display the output echo "Output of MySQL command:" -echo "$OUTPUT" \ No newline at end of file +echo "$OUTPUT"