From bb796024e397c8b16c35c7b64daec384e049385e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20Zolt=C3=A1n?= Date: Sun, 12 Apr 2026 19:15:39 +0200 Subject: [PATCH] Gitbackup export facility and minor improvements. --- .metadata | Bin 22101 -> 22487 bytes .../tools/backup.d/storage_gitbackup.sh | 13 +- .../tools/backup.d/xport_gitbackup.sh | 135 ++++++++++++++++++ .../tools/backup.d/storage_gitbackup.sh | 13 +- .../tools/backup.d/xport_gitbackup.sh | 135 ++++++++++++++++++ .../tools/backup.d/storage_gitbackup.sh | 17 ++- .../tools/backup.d/xport_gitbackup.sh | 135 ++++++++++++++++++ .../tools/backup.d/storage_gitbackup.sh | 10 +- .../tools/backup.d/xport_gitbackup.sh | 135 ++++++++++++++++++ 9 files changed, 572 insertions(+), 21 deletions(-) create mode 100644 .recipes/mediawiki_mariadb/tools/backup.d/xport_gitbackup.sh create mode 100644 .recipes/nextcloud_mariadb/tools/backup.d/xport_gitbackup.sh create mode 100644 .recipes/redmine_mariadb/tools/backup.d/xport_gitbackup.sh create mode 100644 .recipes/wordpress_mariadb/tools/backup.d/xport_gitbackup.sh diff --git a/.metadata b/.metadata index e1ad2632af13dcc453dcd1d472b4eb5ff22cd3c7..36e3c485c3dfceef57906dc75a42e572de3116c7 100644 GIT binary patch delta 400 zcmcb*hVlA(#tHX$_ME$&$p8dv*R402>?p22QJ-~kZYU?4enmlkQOV?4>{3En=OIc& zuDd9JlrCwUEXbrd`IW2wW(UR+HXfNX5cyL<{v4AJu(xtw+XoT&DL!Yiqq5HA_pJPz zyE)FtPcD#=nVb>Ihiv#`sQumFcg1@28+Tq120XB5ApxYZZ xZX{n!{%9q!S;4oIjSCtqNqZh{o*(>#kq0>(Cl`j8PyWEqHaWmkc=G+wG5}6-hc*BJ delta 194 zcmcbbC@H@=fpIGv%iDI{jLCsUB9nDFDtR>bOvz*bg8bY~CX*Wl zv?m9!@lWnG7E_^~^vMrR^)`oT#WJ&8er=cuxc49UM # License: GNU/GPL v3+ (https://www.gnu.org/licenses/gpl-3.0.en.html) +# 2026-04-12 v0.4 +# mod: The PAR_BACKUPDIR environment variable changed to PAR_SOURCEDIR. +# mod: Introduced the SOURCEPATH environment variable to avoid hardcoding. # 2025-05-21 v0.3.1 # fix: Wrong variable name (BASE_DIR instead of SERVICE_BASE) in a check. # 2025-05-20 v0.3 @@ -26,7 +29,7 @@ BOTEMAIL=${BOTEMAIL:-"backupbot@example.com"} # Git repo owner's email (fake) BOTNAME=${BOTNAME:-"Backup Bot"} # Git repo owner's name (fake) SERVICE_BASE=${PAR_BASEDIR:-""} # Corresponding service's base GITDIR=${PAR_GITDIR:-""} # Folder containing .git -SOURCEDIR=${PAR_BACKUPDIR:-""} # Folder to backup into git +SOURCEDIR=${PAR_SOURCEDIR:-""} # Folder to backup into git # Basic environment settings. # @@ -48,6 +51,7 @@ MSG_WRONGGIT="Fatal: unusable backup (git) folder" # Other initialisations. # GITPATH="storage/backups/webcontent" +SOURCEPATH="storage/volumes/mediawiki_images" YMLFILE="docker-compose.yml" # Checks the dependencies. @@ -74,7 +78,7 @@ while [ -h "$SOURCE" ]; do # relative to the path where the symlink file was located [[ $SOURCE != /* ]] && SOURCE="$SCRPATH/$SOURCE" done; SCRPATH="$( cd -P "$("$DIRNAME" "$SOURCE" )" && echo "$PWD" )" #" - + # Let's find which version of docker-compose is installed. if [ $($DOCKER compose version >/dev/null 2>&1; echo $?) -eq 0 ]; then # We'll use v2 if it is available. @@ -104,14 +108,12 @@ TEST_DIR="$("$DIRNAME" "$TEST_DIR")" if [ -z "$SERVICE_BASE" -o ! -r "$SERVICE_BASE/$YMLFILE" ]; then echo "$MSG_MISSINGYML" >&2; exit 1 fi -# Sets the absolute paths. -BACKUPDIR="${PAR_BACKUPDIR:-$SERVICE_BASE/$BACKUPDIR}" # Locates the source folder. # # Maybe given as a command line parameter. [[ -n "$1" ]] && SOURCEDIR="$1" && shift -[[ -z "$SOURCEDIR" ]] && SOURCEDIR="$SERVICE_BASE/storage/volumes/wordpress_html" +[[ -z "$SOURCEDIR" ]] && SOURCEDIR="$SERVICE_BASE/$SOURCEPATH" # Gives up here if doesn't found. if [ -z "$SOURCEDIR" -o ! -d "$("$READLINK" -e "$SOURCEDIR")" ]; then echo "$MSG_MISSINGSOURCE $SOURCEDIR"; exit 1 @@ -150,6 +152,7 @@ if [ ! -d "$GITDIR/.git" ]; then fi # Stages all the files and non-empty folders. "$GIT" --git-dir="$GITDIR/.git" --work-tree="$SOURCEDIR" add . >/dev/null + # Stores the file system metadata as well, if the tool has been installed. if [ ! -z "$(which metastore)" -a -x "$(which metastore)" ]; then # This commamd silently creates the metastore file if it doesnt' exist yet. diff --git a/.recipes/mediawiki_mariadb/tools/backup.d/xport_gitbackup.sh b/.recipes/mediawiki_mariadb/tools/backup.d/xport_gitbackup.sh new file mode 100644 index 0000000..e3f1723 --- /dev/null +++ b/.recipes/mediawiki_mariadb/tools/backup.d/xport_gitbackup.sh @@ -0,0 +1,135 @@ +#!/bin/bash +# +# An optional additional backup operation designed to export a +# Time Machine-style, git-based backup for our customers of their data +# we manage. The script compresses the local git backup repository into +# a tarball that can be downloaded from the web. +# +# This script has special naming conventions: +# - If the script file name (without extension) ends in "_w", the script will +# only run on Sundays. +# - If the script file name (without extension) ends in "_m", the script will +# only run on the first day of the month. +# +# Author: Kovács Zoltán +# License: GNU/GPL v3+ (https://www.gnu.org/licenses/gpl-3.0.en.html) +# 2026-04-12 v0.1 Initial release + +# Accepted environment variables and their defaults. +# +PAR_BASEDIR=${PAR_BASEDIR:-""} # Service's base folder +PAR_EXPORTDIR=${PAR_EXPORTDIR:-""} # Absolute path to export dir +PAR_GITDIR=${PAR_GITDIR:-""} # Absolute path to tgz dumps + +# Other initialisations. +# +EXPORTPATH="storage/backups/export" # Default path to export dir +GITPATH="storage/backups/webcontent" # Default path to git repository +SERVICENAME="mediawiki" # The composed MediaWiki service +USER=${USER:-LOGNAME} # Fix for cron enviroment only +YMLFILE="docker-compose.yml" + +# Messages. +# +MSG_DOCKERGRPNEED="You must be a member of the docker group." +MSG_DOESNOTRUN="This service doesn't run." +MSG_MISSINGDEP="Fatal: missing dependency" +MSG_MISSINGYML="Fatal: didn't find the docker-compose.yml file" +MSG_NOLOCATE="Cannot locate the service container." +MSG_NONWRITE="The target directory isn't writable" + +# Checks the dependencies. +# +TR=$(which tr 2>/dev/null) +if [ -z "$TR" ]; then echo "$MSG_MISSINGDEP tr."; exit 1 ; fi +for item in basename cp cut date dirname docker grep hostname id readlink tar +do + if [ -n "$(which $item)" ] + then export $(echo $item | "$TR" '[:lower:]' '[:upper:]' | "$TR" '-' '_')=$(which $item) + else echo "$MSG_MISSINGDEP $item." >&2; exit 1; fi +done +# All dependencies are available via "$THECOMMAND" (upper case) call. +# +# Let's find which version of docker-compose is installed. +if [ $($DOCKER compose version 2>&1 >/dev/null; echo $?) -eq 0 ]; then + # We'll use v2 if it is available. + DOCKER_COMPOSE="$DOCKER" + commandstring="compose" +else + # Otherwise falling back to v1. + DOCKER_COMPOSE="$(which docker-compose)" + commandstring="" +fi +# One of the two is mandatory. +if [ -z "$DOCKER_COMPOSE" ];then echo "$MSG_MISSINGDEP docker-compose" >&2; exit 1; fi +# Below docker-compose should be called as "$DOCKER_COMPOSE" $commandstring sequence. + +# Checks the timing conventions. +# +# Last two characters of the filename. +TIMING="$("$BASENAME" "$0")"; TIMING="${TIMING%.*}"; TIMING="${TIMING:0-2}" +# If this "_w" continues only on Sundays. +[ "$TIMING" = "_w" ] && [[ $("$DATE" +%u) -ne 0 ]] && exit 0 +# If this "_m" continues only on the 1st day of the month. +[ "$TIMING" = "_m" ] && [[ $("$DATE" +%d) -ne 1 ]] && exit 0 +# There is no time limit, go on. + +# Where I'm? +# https://gist.github.com/TheMengzor/968e5ea87e99d9c41782 +# +SOURCE="$0" +while [ -h "$SOURCE" ]; do + # resolve $SOURCE until the file is no longer a symlink + SCRPATH="$( cd -P "$("$DIRNAME" "$SOURCE" )" && pwd )" #" + SOURCE="$("$READLINK" "$SOURCE")" + # if $SOURCE was a relative symlink, we need to resolve it + # relative to the path where the symlink file was located + [[ $SOURCE != /* ]] && SOURCE="$SCRPATH/$SOURCE" +done; SCRPATH="$( cd -P "$("$DIRNAME" "$SOURCE" )" && pwd )" #" + +# Need to be root or a Docker manager user. +# +[[ "$USER" != 'root' ]] \ +&& [[ -z "$(echo "$("$ID" -Gn "$USER") " | "$GREP" ' docker ')" ]] \ +&& echo "$MSG_DOCKERGRPNEED" >&2 && exit 1 #" + +# Searches the base folder, containing a docker-compose.yml file. +# +# Called from the base folder (./)? +BASE_DIR="$PAR_BASEDIR" +TEST_DIR="$SCRPATH" +[[ -z "$BASE_DIR" ]] && [[ -r "$TEST_DIR/$YMLFILE" ]] && BASE_DIR="$TEST_DIR" +# Called from ./tools? +TEST_DIR="$("$DIRNAME" "$TEST_DIR")" +[[ -z "$BASE_DIR" ]] && [[ -r "$TEST_DIR/$YMLFILE" ]] && BASE_DIR="$TEST_DIR" +# Called from ./tools/*.d? +TEST_DIR="$("$DIRNAME" "$TEST_DIR")" +[[ -z "$BASE_DIR" ]] && [[ -r "$TEST_DIR/$YMLFILE" ]] && BASE_DIR="$TEST_DIR" +# On failure gives it up here. +if [ -z "$BASE_DIR" -o ! -r "$BASE_DIR/$YMLFILE" ]; then + echo "$MSG_MISSINGYML" >&2; exit 1 +fi +# Sets the absolute paths. +EXPORTDIR="${PAR_EXPORTDIR:-$BASE_DIR/$EXPORTPATH}" +GITDIR="${PAR_GITDIR:-$BASE_DIR/$GITPATH}" +# Exits silently if EXPORTDIR or GITDIR isn't present. +[[ ! -e "$EXPORTDIR" ]] && exit 0 +[[ ! -e "$GITDIR" ]] && exit 0 +# EXPORTDIR must be writable. +[[ ! -w "$EXPORTDIR" ]] \ +&& echo "$MSG_NONWRITE: $EXPORTDIR" >&2 && exit 1 + +# Converts the service name to an actual running container's name. +# +MYCONTAINER="$("$DOCKER" inspect -f '{{.Name}}' $(cd "$BASE_DIR"; "$DOCKER_COMPOSE" $commandstring ps -q "$SERVICENAME") | "$CUT" -c2-)" +# Gives up here if failed. +if [ -z "$MYCONTAINER" ]; then echo "$MSG_NOLOCATE" >&2; exit 1; fi + +# Tries the backup. +# +pushd "$GITDIR" > /dev/null + EXPORTNAME=$MYCONTAINER-git.$("$DATE" '+%Y%m%d_%H%M%S').$("$HOSTNAME") + "$TAR" czf "$EXPORTDIR/$EXPORTNAME.tgz" . 2>"$EXPORTDIR/$EXPORTNAME.log" +popd > /dev/null + +# That's all, Folks! :) diff --git a/.recipes/nextcloud_mariadb/tools/backup.d/storage_gitbackup.sh b/.recipes/nextcloud_mariadb/tools/backup.d/storage_gitbackup.sh index f7f141f..3135469 100644 --- a/.recipes/nextcloud_mariadb/tools/backup.d/storage_gitbackup.sh +++ b/.recipes/nextcloud_mariadb/tools/backup.d/storage_gitbackup.sh @@ -11,6 +11,9 @@ # # Author: Kovács Zoltán # License: GNU/GPL v3+ (https://www.gnu.org/licenses/gpl-3.0.en.html) +# 2026-04-12 v0.2 +# mod: The PAR_BACKUPDIR environment variable changed to PAR_SOURCEDIR. +# mod: Introduced the SOURCEPATH environment variable to avoid hardcoding. # 2025-11-21 v0.1 Initial release # Accepted environment variables and their defaults. @@ -19,7 +22,7 @@ BOTEMAIL=${BOTEMAIL:-"backupbot@example.com"} # Git repo owner's email (fake) BOTNAME=${BOTNAME:-"Backup Bot"} # Git repo owner's name (fake) SERVICE_BASE=${PAR_BASEDIR:-""} # Corresponding service's base GITDIR=${PAR_GITDIR:-""} # Folder containing .git -BACKUPDIR=${PAR_BACKUPDIR:-""} # Folder to backup into git +SOURCEDIR=${PAR_SOURCEDIR:-""} # Folder to backup into git # Basic environment settings. # @@ -40,8 +43,8 @@ MSG_WRONGGIT="Fatal: unusable backup (git) folder" # Other initialisations. # -BACKUPPATH="storage/volumes/nextcloud_data" GITPATH="storage/backups/webcontent" +SOURCEPATH="storage/volumes/nextcloud_data" YMLFILE="docker-compose.yml" # Checks the dependencies. @@ -99,12 +102,11 @@ if [ -z "$SERVICE_BASE" -o ! -r "$SERVICE_BASE/$YMLFILE" ]; then echo "$MSG_MISSINGYML" >&2; exit 1 fi -# Locates the folder to backup. +# Locates the source folder. # # Maybe given as a command line parameter. [[ -n "$1" ]] && SOURCEDIR="$1" && shift -# Or as an environment variable, or fallbacks to the default. -[[ -z "$SOURCEDIR" ]] && SOURCEDIR="${BACKUPDIR:-$SERVICE_BASE/$BACKUPPATH}" +[[ -z "$SOURCEDIR" ]] && SOURCEDIR="$SERVICE_BASE/$SOURCEPATH" # Gives up here if doesn't found. if [ -z "$SOURCEDIR" -o ! -d "$("$READLINK" -e "$SOURCEDIR")" ]; then echo "$MSG_MISSINGSOURCE $SOURCEDIR"; exit 1 @@ -114,7 +116,6 @@ fi # # Maybe given as a command line parameter. [[ -n "$1" ]] && GITDIR="$1" && shift -# Or fallbacks to the default. [[ -z "$GITDIR" ]] && GITDIR="$SERVICE_BASE/$GITPATH" # Gives up here if doesn't found. if [ -z "$GITDIR" -o ! -d "$("$READLINK" -e "$GITDIR")" ]; then diff --git a/.recipes/nextcloud_mariadb/tools/backup.d/xport_gitbackup.sh b/.recipes/nextcloud_mariadb/tools/backup.d/xport_gitbackup.sh new file mode 100644 index 0000000..5e04ccf --- /dev/null +++ b/.recipes/nextcloud_mariadb/tools/backup.d/xport_gitbackup.sh @@ -0,0 +1,135 @@ +#!/bin/bash +# +# An optional additional backup operation designed to export a +# Time Machine-style, git-based backup for our customers of their data +# we manage. The script compresses the local git backup repository into +# a tarball that can be downloaded from the web. +# +# This script has special naming conventions: +# - If the script file name (without extension) ends in "_w", the script will +# only run on Sundays. +# - If the script file name (without extension) ends in "_m", the script will +# only run on the first day of the month. +# +# Author: Kovács Zoltán +# License: GNU/GPL v3+ (https://www.gnu.org/licenses/gpl-3.0.en.html) +# 2026-04-12 v0.1 Initial release + +# Accepted environment variables and their defaults. +# +PAR_BASEDIR=${PAR_BASEDIR:-""} # Service's base folder +PAR_EXPORTDIR=${PAR_EXPORTDIR:-""} # Absolute path to export dir +PAR_GITDIR=${PAR_GITDIR:-""} # Absolute path to tgz dumps + +# Other initialisations. +# +EXPORTPATH="storage/backups/export" # Default path to export dir +GITPATH="storage/backups/webcontent" # Default path to git repository +SERVICENAME="nextcloud" # The composed Nextcloud service +USER=${USER:-LOGNAME} # Fix for cron enviroment only +YMLFILE="docker-compose.yml" + +# Messages. +# +MSG_DOCKERGRPNEED="You must be a member of the docker group." +MSG_DOESNOTRUN="This service doesn't run." +MSG_MISSINGDEP="Fatal: missing dependency" +MSG_MISSINGYML="Fatal: didn't find the docker-compose.yml file" +MSG_NOLOCATE="Cannot locate the service container." +MSG_NONWRITE="The target directory isn't writable" + +# Checks the dependencies. +# +TR=$(which tr 2>/dev/null) +if [ -z "$TR" ]; then echo "$MSG_MISSINGDEP tr."; exit 1 ; fi +for item in basename cp cut date dirname docker grep hostname id readlink tar +do + if [ -n "$(which $item)" ] + then export $(echo $item | "$TR" '[:lower:]' '[:upper:]' | "$TR" '-' '_')=$(which $item) + else echo "$MSG_MISSINGDEP $item." >&2; exit 1; fi +done +# All dependencies are available via "$THECOMMAND" (upper case) call. +# +# Let's find which version of docker-compose is installed. +if [ $($DOCKER compose version 2>&1 >/dev/null; echo $?) -eq 0 ]; then + # We'll use v2 if it is available. + DOCKER_COMPOSE="$DOCKER" + commandstring="compose" +else + # Otherwise falling back to v1. + DOCKER_COMPOSE="$(which docker-compose)" + commandstring="" +fi +# One of the two is mandatory. +if [ -z "$DOCKER_COMPOSE" ];then echo "$MSG_MISSINGDEP docker-compose" >&2; exit 1; fi +# Below docker-compose should be called as "$DOCKER_COMPOSE" $commandstring sequence. + +# Checks the timing conventions. +# +# Last two characters of the filename. +TIMING="$("$BASENAME" "$0")"; TIMING="${TIMING%.*}"; TIMING="${TIMING:0-2}" +# If this "_w" continues only on Sundays. +[ "$TIMING" = "_w" ] && [[ $("$DATE" +%u) -ne 0 ]] && exit 0 +# If this "_m" continues only on the 1st day of the month. +[ "$TIMING" = "_m" ] && [[ $("$DATE" +%d) -ne 1 ]] && exit 0 +# There is no time limit, go on. + +# Where I'm? +# https://gist.github.com/TheMengzor/968e5ea87e99d9c41782 +# +SOURCE="$0" +while [ -h "$SOURCE" ]; do + # resolve $SOURCE until the file is no longer a symlink + SCRPATH="$( cd -P "$("$DIRNAME" "$SOURCE" )" && pwd )" #" + SOURCE="$("$READLINK" "$SOURCE")" + # if $SOURCE was a relative symlink, we need to resolve it + # relative to the path where the symlink file was located + [[ $SOURCE != /* ]] && SOURCE="$SCRPATH/$SOURCE" +done; SCRPATH="$( cd -P "$("$DIRNAME" "$SOURCE" )" && pwd )" #" + +# Need to be root or a Docker manager user. +# +[[ "$USER" != 'root' ]] \ +&& [[ -z "$(echo "$("$ID" -Gn "$USER") " | "$GREP" ' docker ')" ]] \ +&& echo "$MSG_DOCKERGRPNEED" >&2 && exit 1 #" + +# Searches the base folder, containing a docker-compose.yml file. +# +# Called from the base folder (./)? +BASE_DIR="$PAR_BASEDIR" +TEST_DIR="$SCRPATH" +[[ -z "$BASE_DIR" ]] && [[ -r "$TEST_DIR/$YMLFILE" ]] && BASE_DIR="$TEST_DIR" +# Called from ./tools? +TEST_DIR="$("$DIRNAME" "$TEST_DIR")" +[[ -z "$BASE_DIR" ]] && [[ -r "$TEST_DIR/$YMLFILE" ]] && BASE_DIR="$TEST_DIR" +# Called from ./tools/*.d? +TEST_DIR="$("$DIRNAME" "$TEST_DIR")" +[[ -z "$BASE_DIR" ]] && [[ -r "$TEST_DIR/$YMLFILE" ]] && BASE_DIR="$TEST_DIR" +# On failure gives it up here. +if [ -z "$BASE_DIR" -o ! -r "$BASE_DIR/$YMLFILE" ]; then + echo "$MSG_MISSINGYML" >&2; exit 1 +fi +# Sets the absolute paths. +EXPORTDIR="${PAR_EXPORTDIR:-$BASE_DIR/$EXPORTPATH}" +GITDIR="${PAR_GITDIR:-$BASE_DIR/$GITPATH}" +# Exits silently if EXPORTDIR or GITDIR isn't present. +[[ ! -e "$EXPORTDIR" ]] && exit 0 +[[ ! -e "$GITDIR" ]] && exit 0 +# EXPORTDIR must be writable. +[[ ! -w "$EXPORTDIR" ]] \ +&& echo "$MSG_NONWRITE: $EXPORTDIR" >&2 && exit 1 + +# Converts the service name to an actual running container's name. +# +MYCONTAINER="$("$DOCKER" inspect -f '{{.Name}}' $(cd "$BASE_DIR"; "$DOCKER_COMPOSE" $commandstring ps -q "$SERVICENAME") | "$CUT" -c2-)" +# Gives up here if failed. +if [ -z "$MYCONTAINER" ]; then echo "$MSG_NOLOCATE" >&2; exit 1; fi + +# Tries the backup. +# +pushd "$GITDIR" > /dev/null + EXPORTNAME=$MYCONTAINER-git.$("$DATE" '+%Y%m%d_%H%M%S').$("$HOSTNAME") + "$TAR" czf "$EXPORTDIR/$EXPORTNAME.tgz" . 2>"$EXPORTDIR/$EXPORTNAME.log" +popd > /dev/null + +# That's all, Folks! :) diff --git a/.recipes/redmine_mariadb/tools/backup.d/storage_gitbackup.sh b/.recipes/redmine_mariadb/tools/backup.d/storage_gitbackup.sh index c874eb1..31eadb4 100644 --- a/.recipes/redmine_mariadb/tools/backup.d/storage_gitbackup.sh +++ b/.recipes/redmine_mariadb/tools/backup.d/storage_gitbackup.sh @@ -11,6 +11,11 @@ # # Author: Kovács Zoltán # License: GNU/GPL v3+ (https://www.gnu.org/licenses/gpl-3.0.en.html) +# 2026-04-12 v0.5 +# mod: The PAR_BACKUPDIR environment variable changed to PAR_SOURCEDIR. +# mod: Introduced the SOURCEPATH environment variable to avoid hardcoding. +# 2025-05-20 v0.4 +# fix: Wrong output redirection order (>/dev/null 2>&1 was reversed). # 2025-01-18 v0.3 # fix: a typo (BASE_DIR instead of SERVICE_BASE). # 2024-09-10 v0.2 @@ -24,7 +29,7 @@ BOTEMAIL=${BOTEMAIL:-"backupbot@example.com"} # Git repo owner's email (fake) BOTNAME=${BOTNAME:-"Backup Bot"} # Git repo owner's name (fake) SERVICE_BASE=${PAR_BASEDIR:-""} # Corresponding service's base GITDIR=${PAR_GITDIR:-""} # Folder containing .git -SOURCEDIR=${PAR_BACKUPDIR:-""} # Folder to backup into git +SOURCEDIR=${PAR_SOURCEDIR:-""} # Folder to backup into git # Basic environment settings. # @@ -46,6 +51,7 @@ MSG_WRONGGIT="Fatal: unusable backup (git) folder" # Other initialisations. # GITPATH="storage/backups/attachments" +SOURCEPATH="storage/volumes/redmine_files" YMLFILE="docker-compose.yml" # Checks the dependencies. @@ -72,9 +78,9 @@ while [ -h "$SOURCE" ]; do # relative to the path where the symlink file was located [[ $SOURCE != /* ]] && SOURCE="$SCRPATH/$SOURCE" done; SCRPATH="$( cd -P "$("$DIRNAME" "$SOURCE" )" && echo "$PWD" )" #" - + # Let's find which version of docker-compose is installed. -if [ $($DOCKER compose version 2>&1 >/dev/null; echo $?) -eq 0 ]; then +if [ $($DOCKER compose version >/dev/null 2>&1; echo $?) -eq 0 ]; then # We'll use v2 if it is available. DOCKER_COMPOSE="$DOCKER" commandstring="compose" @@ -102,14 +108,12 @@ TEST_DIR="$("$DIRNAME" "$TEST_DIR")" if [ -z "$SERVICE_BASE" -o ! -r "$SERVICE_BASE/$YMLFILE" ]; then echo "$MSG_MISSINGYML" >&2; exit 1 fi -# Sets the absolute paths. -BACKUPDIR="${PAR_BACKUPDIR:-$SERVICE_BASE/$BACKUPDIR}" # Locates the source folder. # # Maybe given as a command line parameter. [[ -n "$1" ]] && SOURCEDIR="$1" && shift -[[ -z "$SOURCEDIR" ]] && SOURCEDIR="$SERVICE_BASE/storage/volumes/redmine_files" +[[ -z "$SOURCEDIR" ]] && SOURCEDIR="$SERVICE_BASE/$SOURCEPATH" # Gives up here if doesn't found. if [ -z "$SOURCEDIR" -o ! -d "$("$READLINK" -e "$SOURCEDIR")" ]; then echo "$MSG_MISSINGSOURCE $SOURCEDIR"; exit 1 @@ -148,6 +152,7 @@ if [ ! -d "$GITDIR/.git" ]; then fi # Stages all the files and non-empty folders. "$GIT" --git-dir="$GITDIR/.git" --work-tree="$SOURCEDIR" add . >/dev/null + # Stores the file system metadata as well, if the tool has been installed. if [ ! -z "$(which metastore)" -a -x "$(which metastore)" ]; then # This commamd silently creates the metastore file if it doesnt' exist yet. diff --git a/.recipes/redmine_mariadb/tools/backup.d/xport_gitbackup.sh b/.recipes/redmine_mariadb/tools/backup.d/xport_gitbackup.sh new file mode 100644 index 0000000..eefbb53 --- /dev/null +++ b/.recipes/redmine_mariadb/tools/backup.d/xport_gitbackup.sh @@ -0,0 +1,135 @@ +#!/bin/bash +# +# An optional additional backup operation designed to export a +# Time Machine-style, git-based backup for our customers of their data +# we manage. The script compresses the local git backup repository into +# a tarball that can be downloaded from the web. +# +# This script has special naming conventions: +# - If the script file name (without extension) ends in "_w", the script will +# only run on Sundays. +# - If the script file name (without extension) ends in "_m", the script will +# only run on the first day of the month. +# +# Author: Kovács Zoltán +# License: GNU/GPL v3+ (https://www.gnu.org/licenses/gpl-3.0.en.html) +# 2026-03-03 v0.1 Initial release + +# Accepted environment variables and their defaults. +# +PAR_BASEDIR=${PAR_BASEDIR:-""} # Service's base folder +PAR_EXPORTDIR=${PAR_EXPORTDIR:-""} # Absolute path to export dir +PAR_GITDIR=${PAR_GITDIR:-""} # Absolute path to tgz dumps + +# Other initialisations. +# +EXPORTPATH="storage/backups/export" # Default path to export dir +GITPATH="storage/backups/webcontent" # Default path to git repository +SERVICENAME="redmine" # The composed Redmine service +USER=${USER:-LOGNAME} # Fix for cron enviroment only +YMLFILE="docker-compose.yml" + +# Messages. +# +MSG_DOCKERGRPNEED="You must be a member of the docker group." +MSG_DOESNOTRUN="This service doesn't run." +MSG_MISSINGDEP="Fatal: missing dependency" +MSG_MISSINGYML="Fatal: didn't find the docker-compose.yml file" +MSG_NOLOCATE="Cannot locate the service container." +MSG_NONWRITE="The target directory isn't writable" + +# Checks the dependencies. +# +TR=$(which tr 2>/dev/null) +if [ -z "$TR" ]; then echo "$MSG_MISSINGDEP tr."; exit 1 ; fi +for item in basename cp cut date dirname docker grep hostname id readlink tar +do + if [ -n "$(which $item)" ] + then export $(echo $item | "$TR" '[:lower:]' '[:upper:]' | "$TR" '-' '_')=$(which $item) + else echo "$MSG_MISSINGDEP $item." >&2; exit 1; fi +done +# All dependencies are available via "$THECOMMAND" (upper case) call. +# +# Let's find which version of docker-compose is installed. +if [ $($DOCKER compose version 2>&1 >/dev/null; echo $?) -eq 0 ]; then + # We'll use v2 if it is available. + DOCKER_COMPOSE="$DOCKER" + commandstring="compose" +else + # Otherwise falling back to v1. + DOCKER_COMPOSE="$(which docker-compose)" + commandstring="" +fi +# One of the two is mandatory. +if [ -z "$DOCKER_COMPOSE" ];then echo "$MSG_MISSINGDEP docker-compose" >&2; exit 1; fi +# Below docker-compose should be called as "$DOCKER_COMPOSE" $commandstring sequence. + +# Checks the timing conventions. +# +# Last two characters of the filename. +TIMING="$("$BASENAME" "$0")"; TIMING="${TIMING%.*}"; TIMING="${TIMING:0-2}" +# If this "_w" continues only on Sundays. +[ "$TIMING" = "_w" ] && [[ $("$DATE" +%u) -ne 0 ]] && exit 0 +# If this "_m" continues only on the 1st day of the month. +[ "$TIMING" = "_m" ] && [[ $("$DATE" +%d) -ne 1 ]] && exit 0 +# There is no time limit, go on. + +# Where I'm? +# https://gist.github.com/TheMengzor/968e5ea87e99d9c41782 +# +SOURCE="$0" +while [ -h "$SOURCE" ]; do + # resolve $SOURCE until the file is no longer a symlink + SCRPATH="$( cd -P "$("$DIRNAME" "$SOURCE" )" && pwd )" #" + SOURCE="$("$READLINK" "$SOURCE")" + # if $SOURCE was a relative symlink, we need to resolve it + # relative to the path where the symlink file was located + [[ $SOURCE != /* ]] && SOURCE="$SCRPATH/$SOURCE" +done; SCRPATH="$( cd -P "$("$DIRNAME" "$SOURCE" )" && pwd )" #" + +# Need to be root or a Docker manager user. +# +[[ "$USER" != 'root' ]] \ +&& [[ -z "$(echo "$("$ID" -Gn "$USER") " | "$GREP" ' docker ')" ]] \ +&& echo "$MSG_DOCKERGRPNEED" >&2 && exit 1 #" + +# Searches the base folder, containing a docker-compose.yml file. +# +# Called from the base folder (./)? +BASE_DIR="$PAR_BASEDIR" +TEST_DIR="$SCRPATH" +[[ -z "$BASE_DIR" ]] && [[ -r "$TEST_DIR/$YMLFILE" ]] && BASE_DIR="$TEST_DIR" +# Called from ./tools? +TEST_DIR="$("$DIRNAME" "$TEST_DIR")" +[[ -z "$BASE_DIR" ]] && [[ -r "$TEST_DIR/$YMLFILE" ]] && BASE_DIR="$TEST_DIR" +# Called from ./tools/*.d? +TEST_DIR="$("$DIRNAME" "$TEST_DIR")" +[[ -z "$BASE_DIR" ]] && [[ -r "$TEST_DIR/$YMLFILE" ]] && BASE_DIR="$TEST_DIR" +# On failure gives it up here. +if [ -z "$BASE_DIR" -o ! -r "$BASE_DIR/$YMLFILE" ]; then + echo "$MSG_MISSINGYML" >&2; exit 1 +fi +# Sets the absolute paths. +EXPORTDIR="${PAR_EXPORTDIR:-$BASE_DIR/$EXPORTPATH}" +GITDIR="${PAR_GITDIR:-$BASE_DIR/$GITPATH}" +# Exits silently if EXPORTDIR or GITDIR isn't present. +[[ ! -e "$EXPORTDIR" ]] && exit 0 +[[ ! -e "$GITDIR" ]] && exit 0 +# EXPORTDIR must be writable. +[[ ! -w "$EXPORTDIR" ]] \ +&& echo "$MSG_NONWRITE: $EXPORTDIR" >&2 && exit 1 + +# Converts the service name to an actual running container's name. +# +MYCONTAINER="$("$DOCKER" inspect -f '{{.Name}}' $(cd "$BASE_DIR"; "$DOCKER_COMPOSE" $commandstring ps -q "$SERVICENAME") | "$CUT" -c2-)" +# Gives up here if failed. +if [ -z "$MYCONTAINER" ]; then echo "$MSG_NOLOCATE" >&2; exit 1; fi + +# Tries the backup. +# +pushd "$GITDIR" > /dev/null + EXPORTNAME=$MYCONTAINER-git.$("$DATE" '+%Y%m%d_%H%M%S').$("$HOSTNAME") + "$TAR" czf "$EXPORTDIR/$EXPORTNAME.tgz" . 2>"$EXPORTDIR/$EXPORTNAME.log" +popd > /dev/null + +# That's all, Folks! :) diff --git a/.recipes/wordpress_mariadb/tools/backup.d/storage_gitbackup.sh b/.recipes/wordpress_mariadb/tools/backup.d/storage_gitbackup.sh index d599041..169cbb2 100644 --- a/.recipes/wordpress_mariadb/tools/backup.d/storage_gitbackup.sh +++ b/.recipes/wordpress_mariadb/tools/backup.d/storage_gitbackup.sh @@ -11,6 +11,9 @@ # # Author: Kovács Zoltán # License: GNU/GPL v3+ (https://www.gnu.org/licenses/gpl-3.0.en.html) +# 2026-04-12 v0.4 +# mod: The PAR_BACKUPDIR environment variable changed to PAR_SOURCEDIR. +# mod: Introduced the SOURCEPATH environment variable to avoid hardcoding. # 2025-05-21 v0.3.1 # fix: Wrong variable name (BASE_DIR instead of SERVICE_BASE) in a check. # 2025-05-20 v0.3 @@ -27,7 +30,7 @@ BOTEMAIL=${BOTEMAIL:-"backupbot@example.com"} # Git repo owner's email (fake) BOTNAME=${BOTNAME:-"Backup Bot"} # Git repo owner's name (fake) SERVICE_BASE=${PAR_BASEDIR:-""} # Corresponding service's base GITDIR=${PAR_GITDIR:-""} # Folder containing .git -SOURCEDIR=${PAR_BACKUPDIR:-""} # Folder to backup into git +SOURCEDIR=${PAR_SOURCEDIR:-""} # Folder to backup into git # Basic environment settings. # @@ -49,6 +52,7 @@ MSG_WRONGGIT="Fatal: unusable backup (git) folder" # Other initialisations. # GITPATH="storage/backups/webcontent" +SOURCEPATH="storage/volumes/wordpress_html" YMLFILE="docker-compose.yml" # Checks the dependencies. @@ -105,14 +109,12 @@ TEST_DIR="$("$DIRNAME" "$TEST_DIR")" if [ -z "$SERVICE_BASE" -o ! -r "$SERVICE_BASE/$YMLFILE" ]; then echo "$MSG_MISSINGYML" >&2; exit 1 fi -# Sets the absolute paths. -BACKUPDIR="${PAR_BACKUPDIR:-$SERVICE_BASE/$BACKUPDIR}" # Locates the source folder. # # Maybe given as a command line parameter. [[ -n "$1" ]] && SOURCEDIR="$1" && shift -[[ -z "$SOURCEDIR" ]] && SOURCEDIR="$SERVICE_BASE/storage/volumes/wordpress_html" +[[ -z "$SOURCEDIR" ]] && SOURCEDIR="$SERVICE_BASE/$SOURCEPATH" # Gives up here if doesn't found. if [ -z "$SOURCEDIR" -o ! -d "$("$READLINK" -e "$SOURCEDIR")" ]; then echo "$MSG_MISSINGSOURCE $SOURCEDIR"; exit 1 diff --git a/.recipes/wordpress_mariadb/tools/backup.d/xport_gitbackup.sh b/.recipes/wordpress_mariadb/tools/backup.d/xport_gitbackup.sh new file mode 100644 index 0000000..f38f241 --- /dev/null +++ b/.recipes/wordpress_mariadb/tools/backup.d/xport_gitbackup.sh @@ -0,0 +1,135 @@ +#!/bin/bash +# +# An optional additional backup operation designed to export a +# Time Machine-style, git-based backup for our customers of their data +# we manage. The script compresses the local git backup repository into +# a tarball that can be downloaded from the web. +# +# This script has special naming conventions: +# - If the script file name (without extension) ends in "_w", the script will +# only run on Sundays. +# - If the script file name (without extension) ends in "_m", the script will +# only run on the first day of the month. +# +# Author: Kovács Zoltán +# License: GNU/GPL v3+ (https://www.gnu.org/licenses/gpl-3.0.en.html) +# 2026-04-12 v0.1 Initial release + +# Accepted environment variables and their defaults. +# +PAR_BASEDIR=${PAR_BASEDIR:-""} # Service's base folder +PAR_EXPORTDIR=${PAR_EXPORTDIR:-""} # Absolute path to export dir +PAR_GITDIR=${PAR_GITDIR:-""} # Absolute path to tgz dumps + +# Other initialisations. +# +EXPORTPATH="storage/backups/export" # Default path to export dir +GITPATH="storage/backups/webcontent" # Default path to git repository +SERVICENAME="wordpress" # The composed WordPress service +USER=${USER:-LOGNAME} # Fix for cron enviroment only +YMLFILE="docker-compose.yml" + +# Messages. +# +MSG_DOCKERGRPNEED="You must be a member of the docker group." +MSG_DOESNOTRUN="This service doesn't run." +MSG_MISSINGDEP="Fatal: missing dependency" +MSG_MISSINGYML="Fatal: didn't find the docker-compose.yml file" +MSG_NOLOCATE="Cannot locate the service container." +MSG_NONWRITE="The target directory isn't writable" + +# Checks the dependencies. +# +TR=$(which tr 2>/dev/null) +if [ -z "$TR" ]; then echo "$MSG_MISSINGDEP tr."; exit 1 ; fi +for item in basename cp cut date dirname docker grep hostname id readlink tar +do + if [ -n "$(which $item)" ] + then export $(echo $item | "$TR" '[:lower:]' '[:upper:]' | "$TR" '-' '_')=$(which $item) + else echo "$MSG_MISSINGDEP $item." >&2; exit 1; fi +done +# All dependencies are available via "$THECOMMAND" (upper case) call. +# +# Let's find which version of docker-compose is installed. +if [ $($DOCKER compose version 2>&1 >/dev/null; echo $?) -eq 0 ]; then + # We'll use v2 if it is available. + DOCKER_COMPOSE="$DOCKER" + commandstring="compose" +else + # Otherwise falling back to v1. + DOCKER_COMPOSE="$(which docker-compose)" + commandstring="" +fi +# One of the two is mandatory. +if [ -z "$DOCKER_COMPOSE" ];then echo "$MSG_MISSINGDEP docker-compose" >&2; exit 1; fi +# Below docker-compose should be called as "$DOCKER_COMPOSE" $commandstring sequence. + +# Checks the timing conventions. +# +# Last two characters of the filename. +TIMING="$("$BASENAME" "$0")"; TIMING="${TIMING%.*}"; TIMING="${TIMING:0-2}" +# If this "_w" continues only on Sundays. +[ "$TIMING" = "_w" ] && [[ $("$DATE" +%u) -ne 0 ]] && exit 0 +# If this "_m" continues only on the 1st day of the month. +[ "$TIMING" = "_m" ] && [[ $("$DATE" +%d) -ne 1 ]] && exit 0 +# There is no time limit, go on. + +# Where I'm? +# https://gist.github.com/TheMengzor/968e5ea87e99d9c41782 +# +SOURCE="$0" +while [ -h "$SOURCE" ]; do + # resolve $SOURCE until the file is no longer a symlink + SCRPATH="$( cd -P "$("$DIRNAME" "$SOURCE" )" && pwd )" #" + SOURCE="$("$READLINK" "$SOURCE")" + # if $SOURCE was a relative symlink, we need to resolve it + # relative to the path where the symlink file was located + [[ $SOURCE != /* ]] && SOURCE="$SCRPATH/$SOURCE" +done; SCRPATH="$( cd -P "$("$DIRNAME" "$SOURCE" )" && pwd )" #" + +# Need to be root or a Docker manager user. +# +[[ "$USER" != 'root' ]] \ +&& [[ -z "$(echo "$("$ID" -Gn "$USER") " | "$GREP" ' docker ')" ]] \ +&& echo "$MSG_DOCKERGRPNEED" >&2 && exit 1 #" + +# Searches the base folder, containing a docker-compose.yml file. +# +# Called from the base folder (./)? +BASE_DIR="$PAR_BASEDIR" +TEST_DIR="$SCRPATH" +[[ -z "$BASE_DIR" ]] && [[ -r "$TEST_DIR/$YMLFILE" ]] && BASE_DIR="$TEST_DIR" +# Called from ./tools? +TEST_DIR="$("$DIRNAME" "$TEST_DIR")" +[[ -z "$BASE_DIR" ]] && [[ -r "$TEST_DIR/$YMLFILE" ]] && BASE_DIR="$TEST_DIR" +# Called from ./tools/*.d? +TEST_DIR="$("$DIRNAME" "$TEST_DIR")" +[[ -z "$BASE_DIR" ]] && [[ -r "$TEST_DIR/$YMLFILE" ]] && BASE_DIR="$TEST_DIR" +# On failure gives it up here. +if [ -z "$BASE_DIR" -o ! -r "$BASE_DIR/$YMLFILE" ]; then + echo "$MSG_MISSINGYML" >&2; exit 1 +fi +# Sets the absolute paths. +EXPORTDIR="${PAR_EXPORTDIR:-$BASE_DIR/$EXPORTPATH}" +GITDIR="${PAR_GITDIR:-$BASE_DIR/$GITPATH}" +# Exits silently if EXPORTDIR or GITDIR isn't present. +[[ ! -e "$EXPORTDIR" ]] && exit 0 +[[ ! -e "$GITDIR" ]] && exit 0 +# EXPORTDIR must be writable. +[[ ! -w "$EXPORTDIR" ]] \ +&& echo "$MSG_NONWRITE: $EXPORTDIR" >&2 && exit 1 + +# Converts the service name to an actual running container's name. +# +MYCONTAINER="$("$DOCKER" inspect -f '{{.Name}}' $(cd "$BASE_DIR"; "$DOCKER_COMPOSE" $commandstring ps -q "$SERVICENAME") | "$CUT" -c2-)" +# Gives up here if failed. +if [ -z "$MYCONTAINER" ]; then echo "$MSG_NOLOCATE" >&2; exit 1; fi + +# Tries the backup. +# +pushd "$GITDIR" > /dev/null + EXPORTNAME=$MYCONTAINER-git.$("$DATE" '+%Y%m%d_%H%M%S').$("$HOSTNAME") + "$TAR" czf "$EXPORTDIR/$EXPORTNAME.tgz" . 2>"$EXPORTDIR/$EXPORTNAME.log" +popd > /dev/null + +# That's all, Folks! :)