From 541da5bf8abbc37d1ecaea5f65beac0532366841 Mon Sep 17 00:00:00 2001 From: "Hahn Axel (hahn)" <axel.hahn@unibe.ch> Date: Thu, 1 Feb 2024 17:02:18 +0100 Subject: [PATCH] first lines of rest_pruner script --- docs/30_Scripts.md | 94 +++++++++++++++++++++ inc_config.sh.dist | 3 + rest_pruner.sh | 202 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 299 insertions(+) create mode 100755 rest_pruner.sh diff --git a/docs/30_Scripts.md b/docs/30_Scripts.md index da5497b..52ca90b 100644 --- a/docs/30_Scripts.md +++ b/docs/30_Scripts.md @@ -123,3 +123,97 @@ In the backup status the deactivated target is listed with D = Disabled and is g BTW: the opposite way is possible to: `./storage_helper.sh setactive` + +# rest_pruner.sh + +**!! This script is in version 0.1 - and work in progress !!** + +The pruner script is for restic rest server with append only option. It can prune all repositories on server side. + +## inc_config.sh + +The inc_config.sh mus contain 3 variables for pruning + +```bash + # for prune on restic rest server + prune_basedir=/netshare/restic-backup + prune_params="--group-by paths,tags --prune --keep-within 180d --max-unused unlimited --max-repack-size 100M --cleanup-cache" + prune_skipdays="7" +``` + +## rest_pruner.cfg + +To access the different local repositories we need the RESTIC_PASSWORD for each repository. +The config file `rest_pruner.cfg` contains lines in the syntax + +`<USER>:<RESTIC_PASSWORD>` + +If a directory matches `${prune_basedir}/<USER>` then it will be pruned. + +You need a mechanism to create this file eg. by Ansible. + +For securiy reasons this file must be owned by root:root and must have the permissions 0400. + +```txt +ls -l rest_pruner.cfg +-r--------. 1 root root 159 Feb 1 13:35 rest_pruner.cfg +``` + +## Syntax + +```txt + +========== RESTIC REST PRUNER v0.1 ========== + + +Pruner for restic rest server with append only option. +This script prunes all repositories on server side. + +The config file [rest_pruner.cfg] contains <USER>:<RESTIC_PASSWORD> +If a directory matches /netshare/restic-backup/<USER> then it will be pruned. + +SYNTAX: + rest_pruner.sh [OPTIONS] [FILTER] + +OPTIONS: + -h, --help show help and exit. + +PARAMETERS: + FILTER regex to filter directory list in + /netshare/restic-backup/* + +EXAMPLES: + rest_pruner.sh + Start pruning of all matching repositories + rest_pruner.sh mail + Prune servers that match "mail", + eg. my-mailhub.example.com + +``` + +## How does it work + +It detects some requirements: + +* was it started by root? (The help is shown without being root) +* permissions of rest_pruner.cfg +* variables from inc_config.sh + +It loops over the starting dir `${prune_basedir}` and reads all its subdirectories. +If a subdir matches a configuration entry (text before first ":" `<USER>:<RESTIC_PASSWORD>`) then the prune process will be started. It detects the username of the owner of the directory and executes + +`su - $_user - /bin/bash -c "<restic forget ...>"` + +Exitcodes: + +* 0 if the run was without errors. +* >0 if a prune run of a reposaitory failed. +* 1..3 if a requirement failed to start + +The output of the prune command is written into + +`_last_prune/<USER>.log` + +If a job failed it is renamed to + +`_last_prune/<USER>.log.error` diff --git a/inc_config.sh.dist b/inc_config.sh.dist index df10f4e..18621c0 100644 --- a/inc_config.sh.dist +++ b/inc_config.sh.dist @@ -27,5 +27,8 @@ sLogdir="${sSelfdir}/log" sLogfile="$sLogdir/connections.log" + # for prune on restic rest server + prune_params="--group-by paths,tags --prune --keep-within 180d --max-unused unlimited --max-repack-size 100M --cleanup-cache" + prune_skipdays="7" # ---------------------------------------------------------------------- diff --git a/rest_pruner.sh b/rest_pruner.sh new file mode 100755 index 0000000..37cae6a --- /dev/null +++ b/rest_pruner.sh @@ -0,0 +1,202 @@ +#!/bin/bash +# ====================================================================== +# +# RESTIC REST PRUNER +# +# ---------------------------------------------------------------------- +# 2024-02-01 v0.1 <axel.hahn@unibe.ch> first lines +# ====================================================================== + + +cd "$( dirname $0 )" || exit +_version=0.1 +logdir=_last_prune + +prune_basedir= +prune_params= +bOptDebug=0 + +typeset -i iCountDirs=0 +typeset -i iCountMatch=0 +typeset -i iCountPrune=0 + + +typeset -i rcAll=0 + +. "inc_config.sh" || exit 1 +cfgfile=rest_pruner.cfg || exit 1 + + + +# ---------------------------------------------------------------------- +# FUNCTIONS +# ---------------------------------------------------------------------- + +# Show help text +function _showHelp(){ + local _self; _self=$( basename $0 ) + cat <<EOH + +Pruner for restic rest server with append only option. +This script prunes all repositories on server side. + +The config file [$cfgfile] contains <USER>:<RESTIC_PASSWORD> +If a directory matches ${prune_basedir}/<USER> then it will be pruned. + +SYNTAX: + $_self [OPTIONS] [FILTER] + +OPTIONS: + -h, --help show help and exit. + +PARAMETERS: + FILTER regex to filter directory list in + ${prune_basedir}/* + +EXAMPLES: + $_self + Start pruning of all matching repositories + $_self mail + Prune servers that match "mail", + eg. my-mailhub.example.com + +EOH +} + +# start prune of a given repository +# global string cfgfile name of the config file +# global integer rcAll sum of all prune exit status +# +# param string directory to prune +function _prune(){ + local _dir="$1" + + local mybase; mybase=$( basename "${_dir}" ) + local mypw; mypw=$( grep "^${mybase}:" "${cfgfile}" | cut -f2 -d ':') + local logfile="${logdir}/${mybase}.log" + + iCountDirs+=1 + + if [ -n "$mypw" ]; then + local _user=$( stat -c "%U" "$_dir" ) + echo "----- $_dir" + + iCountMatch+=1 + bDoRun=1 + + if ps -ef | grep "restic forget.*${_dir}" | grep -v "grep" | grep . ; then + echo "SKIP: a process is still running..." + bDoRun=0 + else + rm -f "${logfile}.running" + echo "TODO: check age of last prune run." + fi + + if [ "$bDoRun" -eq "1" ]; then + echo "Starting prune as user $_user ..." + su - $_user - /bin/bash -c " + echo START $( date ) $_dir + export RESTIC_PASSWORD=$mypw + restic forget -r $_dir $prune_params 2>&1 + " | tee "${logfile}.running" + rc=${PIPESTATUS[0]} + rcAll+=$rc + echo END $( date ) exitcode $rc | tee -a "${logfile}.running" + if [ "$rc" -eq "0" ]; then + mv "${logfile}.running" "${logfile}" + else + mv "${logfile}.running" "${logfile}.error" + fi + fi + fi +} + +# ---------------------------------------------------------------------- +# MAIN +# ---------------------------------------------------------------------- + +echo +echo "========== RESTIC REST PRUNER v${_version} ==========" +echo + +# ----- check parameters + +while [[ "$#" -gt 0 ]]; do case $1 in + -h|--help) _showHelp; exit 0;; + -d|--debug) bOptDebug=1; shift;; + -p|--path) if ! grep ":{$2}:" <<< ":{$PATH}:" >/dev/null; then + PATH="$2:$PATH"; + fi + shift; shift;; + -t|--target) export DOCKER_HOST="$2"; shift; shift;; + *) echo "ERROR: Unknown parameter: $1"; _showHelp; exit 2; +esac; done + +# ----- verify needed settings + +if [ ! -f "$cfgfile" ]; then + echo "ERROR: The configuration [$cfgfile] does not exist (yet)." + exit 3 +fi + +if [ "$USER" != "root" ]; then + echo "ERROR: This script must be started as root." + exit 1 +fi + +cfgpermissions=$( stat -c '%A:%U:%G' "$cfgfile" ) +if [ "$cfgpermissions" != "-r--------:root:root" ]; then + echo "WARNING: The configuration file must be owned by root:root with permission 0400." + ls -l "$cfgfile" + echo "Try to fix it..." + chown root:root "$cfgfile" + chmod 0400 "$cfgfile" + ls -l "$cfgfile" + cfgpermissions=$( stat -c '%A:%U:%G' "$cfgfile" ) + if [ "$cfgpermissions" != "-r--------:root:root" ]; then + echo "Fix failed. Aborting." + exit 3 + fi + echo "OK" +fi + +if [ -z "${prune_basedir}" ]; then + echo "ERROR: variable [prune_basedir] was not set in [inc_config.sh]" + exit 3 +fi + +if [ ! -d "${prune_basedir}" ]; then + echo "ERROR: variable [prune_basedir] in [inc_config.sh] points to a directory" + echo "that does not exist: ${prune_basedir}" + exit 3 +fi + +if [ -z "${prune_params}" ]; then + echo "ERROR: variable [prune_params] was not set in [inc_config.sh]" + exit 3 +fi + +if ! grep "\-\-keep\-" <<< "${prune_params}" >/dev/null; then + echo "ERROR: variable [prune_params] does not contain a --keep-* prameter." + exit 3 +fi + +if ! which restic >/dev/null; then + echo "ERROR: The restic client was not found." + exit 3 +fi + +# ----- Go + +test -d "${logdir}" || mkdir -p "${logdir}" +filter=${1:-.} +for mydir in $( find ${prune_basedir} -maxdepth 1 -type d | grep -E "$filter") +do + _prune "${mydir}" +done + + +echo "done - exitcode $rcAll" +exit $rcAll + +# ---------------------------------------------------------------------- -- GitLab