mirror of
https://github.com/graysky2/anything-sync-daemon.git
synced 2026-10-20 10:18:26 +02:00
This will avoid symlinking issues and race conditions which leave the directory in inconsistent state. The change was inspired by the implementation of @ThibaultLemaire with some minor tweaks to keep the backup functionality intact. Directories with hardlinks aren't supported anymore to keep the backup functionality intact Co-authored-by: Thibault Lemaire <thibault.lemaire@zoho.eu>
678 lines
24 KiB
Bash
678 lines
24 KiB
Bash
#!/bin/bash
|
|
|
|
#
|
|
# Anything-sync-daemon by graysky <graysky AT archlinux DOT us>
|
|
# Inspired by some code originally written by Colin Verot
|
|
#
|
|
|
|
# For debug, add DEBUG=1 asd
|
|
debug() {
|
|
if [[ -v $DEBUG && $DEBUG -eq 1 ]]
|
|
then
|
|
echo -e "$@"
|
|
fi
|
|
}
|
|
|
|
set -e
|
|
Error() {
|
|
echo "Error occurred at $1"
|
|
exit 1
|
|
}
|
|
trap 'Error $LINENO' ERR
|
|
|
|
debug "checking flock"
|
|
command -v flock >/dev/null 2>&1 || {
|
|
echo "I require flock but it's not installed. Aborting." >&2
|
|
exit 1; }
|
|
debug "flock found"
|
|
|
|
BLD="\e[01m"
|
|
RED="\e[01;31m"
|
|
GRN="\e[01;32m"
|
|
BLU="\e[01;34m"
|
|
NRM="\e[00m"
|
|
VERS="@VERSION@"
|
|
|
|
ASDCONF=${ASDCONF:-"/etc/asd.conf"}
|
|
DAEMON_FILE=${DAEMON_FILE:-"/run/asd"}
|
|
CRASH_RECOVERY_SUFFIX="crashrecovery"
|
|
ASDNAME="asd"
|
|
if [[ "$ASDCONF" != "/etc/asd.conf" ]]; then
|
|
suffix=$(realpath "$ASDCONF" | sha256sum | cut -b -16)
|
|
ASDNAME="$ASDNAME-$suffix"
|
|
if [[ "$DAEMON_FILE" == "/run/asd" ]]; then
|
|
DAEMON_FILE="/run/$ASDNAME"
|
|
fi
|
|
fi
|
|
LOCKFILE="/run/$ASDNAME-lock"
|
|
# shellcheck disable=SC2015
|
|
[[ ${FLOCKER} != "$0" ]] && echo -e "${RED}Waiting for lock...${NRM}" && exec env FLOCKER="$0" flock --verbose -e "$LOCKFILE" "$0" "$*" ||
|
|
|
|
debug "${RED}ASDNAME:${NRM} $ASDNAME"
|
|
debug "${BLU}Configuration file:${NRM} $ASDCONF"
|
|
debug "${GRN}Daemon file:${NRM} $DAEMON_FILE"
|
|
debug "${RED}Lockfile:${NRM} $LOCKFILE\n"
|
|
|
|
# Setup check /etc/asd.conf
|
|
debug "Checking for existence of $ASDCONF"
|
|
if [[ -f "$ASDCONF" ]]; then
|
|
debug "Checking if asd is already running"
|
|
if [[ ! -f "$DAEMON_FILE" ]]; then
|
|
debug "Daemon file not found: asd is not running"
|
|
# do nothing if asd is currently running, otherwise
|
|
# make sure only comments and variables/arrays are defined to prevent
|
|
# problems
|
|
if grep -Eqv "^$|^\s*['\")#]|^[^ ]*=[^;]*" "$ASDCONF"; then
|
|
# found text that isn't a blank line, comment, or variable present so exit
|
|
echo -e " ${RED}ERROR:${NRM}${BLD} Syntax error(s) detected in ${BLU}$ASDCONF${NRM}"
|
|
echo -e "${NRM}${BLD}Line number: offending comment${NRM}"
|
|
grep -Evn "^$|^\s*['\")#]|^[^ ]*=[^;]*" "$ASDCONF"
|
|
exit 1
|
|
fi
|
|
else
|
|
debug "Daemon file found: asd is running"
|
|
fi
|
|
# shellcheck source=./asd.conf
|
|
. "$ASDCONF"
|
|
else
|
|
echo -e " ${BLD}Cannot find $ASDCONF so bailing.${NRM}"
|
|
echo -e " ${BLD}Reinstall package to use anything-sync-daemon.${NRM}"
|
|
exit 1
|
|
fi
|
|
|
|
# if asd is active, source the snapshot of /etc/asd.conf preferentially
|
|
if [[ -f "${DAEMON_FILE}.conf" ]]; then
|
|
debug "\nRestoring old asd.conf configurations"
|
|
unset WHATTOSYNC USE_OVERLAYFS USE_BACKUPS VOLATILE
|
|
ASDCONF="${DAEMON_FILE}.conf"
|
|
# shellcheck source=./asd.conf
|
|
. "$ASDCONF"
|
|
fi
|
|
|
|
debug "" # just for an additional enter
|
|
|
|
# define default number of crash-recovery snapshots to save if the user did not
|
|
# and check that it is an integer if user did define it
|
|
if [[ -z "$BACKUP_LIMIT" ]]; then
|
|
BACKUP_LIMIT=5
|
|
else
|
|
if [[ "$BACKUP_LIMIT" =~ ^[0-9]+$ ]]; then
|
|
# correctly setup
|
|
/bin/true
|
|
else
|
|
echo -e " ${RED}ERROR:${NRM}${BLD} Bad value for BACKUP_LIMIT detected!${NRM}"
|
|
exit 1
|
|
fi
|
|
fi
|
|
debug "Backup limit: $BACKUP_LIMIT"
|
|
|
|
# saving current extended pattern matching setting
|
|
# command returns non zero exit code if the option is unset hence
|
|
# pipe with true
|
|
previous_extglob_setting=$(shopt -p extglob || true)
|
|
|
|
# ensuring pattern matching is enabled
|
|
shopt -s extglob
|
|
|
|
# removing any trailing slash(es) from the list of directories to sync
|
|
WHATTOSYNC=("${WHATTOSYNC[@]%%+(/)}")
|
|
VOLATILE="${VOLATILE%%+(/)}"
|
|
|
|
# setting everything back
|
|
$previous_extglob_setting
|
|
unset previous_extglob_setting
|
|
|
|
[[ -z "$VOLATILE" ]] && VOLATILE=/tmp
|
|
debug "Volatile dir: $VOLATILE"
|
|
|
|
# bail if $VOLATILE isn't tmpfs
|
|
df -T "$VOLATILE" | grep -m 1 -q '\( tmpfs \|^/dev/zram\)' || {
|
|
echo "$VOLATILE is not tmpfs/zram so running asd is pointless. Aborting." >&2
|
|
exit 1; }
|
|
|
|
[[ -z "$ENABLE_HARDLINK_SAFETY_CHECK" ]] && ENABLE_HARDLINK_SAFETY_CHECK=1
|
|
debug "Hardlink safety check: $ENABLE_HARDLINK_SAFETY_CHECK"
|
|
|
|
# simple function to determine user intent rather than using a null value
|
|
case "${USE_OVERLAYFS,,}" in
|
|
y|yes|true|t|on|1|enabled|enable|use)
|
|
OLFS=1
|
|
;;
|
|
*)
|
|
OLFS=0
|
|
;;
|
|
esac
|
|
|
|
# since the default for this one is a yes, need to force a null value to yes
|
|
[[ -z "${USE_BACKUPS,,}" ]] && USE_BACKUPS="yes"
|
|
debug "Backups enabled: $USE_BACKUPS"
|
|
|
|
case "${USE_BACKUPS,,}" in
|
|
y|yes|true|t|on|1|enabled|enable|use)
|
|
CRRE=1
|
|
;;
|
|
*)
|
|
CRRE=0
|
|
;;
|
|
esac
|
|
debug "Crash recovery enabled: $CRRE"
|
|
|
|
# determine is we are using overlayfs (v22 and below) or overlay (v23 and above)
|
|
# overlay FS v23 and later requires both an upper and a work directory, both on
|
|
# the same filesystem, but not part of the same subtree.
|
|
#
|
|
# ubuntu 15.04 has both overlay and overlayfs so prefer version 23
|
|
if [[ $OLFS -eq 1 ]]; then
|
|
# first test if either module is manually loaded manually or hardcoded
|
|
[[ $(grep -ciE "overlayfs$" /proc/filesystems) -eq 1 ]] && OLFSVER=22
|
|
[[ $(grep -ciE "overlay$" /proc/filesystems) -eq 1 ]] && OLFSVER=23
|
|
if [[ -z $OLFSVER ]]; then
|
|
# since mount should call modprobe on invocation, check to see if either
|
|
# module is in the tree using modinfo
|
|
modinfo overlayfs &>/dev/null && OLFSVER=22
|
|
modinfo overlay &>/dev/null && OLFSVER=23
|
|
if [[ -z $OLFSVER ]]; then
|
|
echo "overlayfs is not supported by your system, falling back to normal"
|
|
OLFS=0
|
|
fi
|
|
fi
|
|
fi
|
|
debug "overlayfs enabled: $OLFS"
|
|
debug "overlayfs version: $OLFSVER"
|
|
|
|
# get distro name
|
|
# first try os-release
|
|
if [[ -f /etc/os-release ]]; then
|
|
# shellcheck source=/dev/null
|
|
source /etc/os-release
|
|
if [[ -n "$PRETTY_NAME" ]]; then
|
|
distro="$PRETTY_NAME"
|
|
elif [[ -n "$NAME" ]]; then
|
|
distro="$NAME"
|
|
fi
|
|
else
|
|
# if not os-release try issue
|
|
if [[ -n $(sed 's| \\.*$||' /etc/issue | head -n 1) ]]; then
|
|
distro="$(sed 's| \\.*$||' /etc/issue | head -n 1)"
|
|
else
|
|
# fuck it
|
|
distro=
|
|
fi
|
|
fi
|
|
|
|
header() {
|
|
[[ -z "$distro" ]] && echo -e "${BLD}Anything-sync-daemon v$VERS${NRM}" ||
|
|
echo -e "${BLD}Anything-sync-daemon v$VERS${NRM}${BLD} on $distro${NRM}"
|
|
echo
|
|
}
|
|
|
|
dep_check() {
|
|
# Function is used to ensure all dependencies are installed
|
|
debug "\n${BLU}Checking dependencies${NRM}"
|
|
debug "checking rsync"
|
|
command -v rsync >/dev/null 2>&1 || {
|
|
echo "I require rsync but it's not installed. Aborting." >&2
|
|
exit 1; }
|
|
debug "checking awk"
|
|
command -v awk >/dev/null 2>&1 || {
|
|
echo "I require awk but it's not installed. Aborting." >&2; exit 1; }
|
|
debug "checking pv"
|
|
command -v pv >/dev/null 2>&1 || {
|
|
echo "I require pv but it's not installed. Aborting." >&2; exit 1; }
|
|
debug "checking tar"
|
|
command -v tar >/dev/null 2>&1 || {
|
|
echo "I require tar but it's not installed. Aborting." >&2; exit 1; }
|
|
debug "checking zstd"
|
|
command -v zstd >/dev/null 2>&1 || {
|
|
echo "I require zstd but it's not installed. Aborting." >&2; exit 1; }
|
|
if [[ $OLFS -eq 1 ]]; then
|
|
[[ $OLFSVER -ge 22 ]] || {
|
|
echo -e " ${BLD}Your kernel requires either the ${BLU}overlay${NRM}${BLD} or ${BLU}overlayfs${NRM}${BLD} module to use${NRM}"
|
|
echo -e " ${BLD}to use asd's in overlay mode. Cannot find either in your kernel so compile it in and${NRM}"
|
|
echo -e " ${BLD}try again or remove the option from ${BLU}$ASDCONF${NRM}${BLD}. ${RED}Aborting!${NRM}" >&2; exit 1;}
|
|
fi
|
|
}
|
|
|
|
config_check() {
|
|
debug "\n${GRN}Checking configs${NRM}"
|
|
# nothing to do if these are empty
|
|
if [[ -z "${WHATTOSYNC[0]}" ]]; then
|
|
echo -e " ${BLD}Must define at least one directory in ${NRM}${BLU}$ASDCONF${NRM}"
|
|
exit 1
|
|
fi
|
|
|
|
# make sure the user defined real dirs
|
|
for DIR in "${WHATTOSYNC[@]}"; do
|
|
debug "DIR: $DIR"
|
|
[[ ${DIR##*/} == .* ]] && BACKUP="${DIR%/*}/${DIR##*/}-backup_asd" ||
|
|
BACKUP="${DIR%/*}/.${DIR##*/}-backup_asd"
|
|
[[ ${DIR##*/} == .* ]] && BACK_OLD="${DIR%/*}/${DIR##*/}-backup_asd-old" ||
|
|
BACK_OLD="${DIR%/*}/.${DIR##*/}-backup_asd-old"
|
|
debug "BACKUP: $BACKUP"
|
|
debug "BACK_OLD: $BACK_OLD"
|
|
if [[ ! -d "$DIR" ]]; then
|
|
if [[ ! -d "$BACK_OLD" ]]; then
|
|
echo -e "${BLD}Bad entry in your WHATTOSYNC array detected:${NRM}"
|
|
echo -e " ${BLD}${RED}$DIR${NRM}"
|
|
echo -e "${BLD}Edit ${BLU}$ASDCONF${NRM}${BLD} correcting the mistake and try again.${NRM}"
|
|
exit 1
|
|
fi
|
|
else
|
|
# sanity check for hardlinks
|
|
if [[ ! -d "$BACK_OLD" && $ENABLE_HARDLINK_SAFETY_CHECK -ne 0 && -n $(find "$DIR" -type f -links +1) ]]; then
|
|
echo -e "$DIR:\n${RED} Presence of hardlinks found, asd might break them:${NRM}"
|
|
exit 1
|
|
else
|
|
debug "No hardlinks found"
|
|
fi
|
|
fi
|
|
done
|
|
debug "Configs seem to be fine"
|
|
}
|
|
|
|
root_check() {
|
|
# we call this to ensure that only the root user is calling the
|
|
# function why care? both the sync and unsync functions require
|
|
# root access to $DAEMON_FILE Running as unprivileged user will
|
|
# fuck up the sync process resulting in unhappy users
|
|
|
|
debug "\n${RED}Checking root permissions${NRM}"
|
|
if [[ $EUID -ne 0 ]]; then
|
|
echo -e " ${BLD}This function must be called as root!${NRM}" 1>&2
|
|
exit 1
|
|
fi
|
|
debug "Have root permissions"
|
|
}
|
|
|
|
ungraceful_state_check() {
|
|
# if the machine was ungracefully shutdown then the backup will be
|
|
# on the filesystem and the link to tmpfs will be on the filesystem
|
|
# but the contents will be empty we need to simply remove the link
|
|
# and rotate the backup into place
|
|
debug "\n${BLU}checking ungraceful state${NRM}"
|
|
local DIR USER BACKUP TMP
|
|
for DIR in "${WHATTOSYNC[@]}"; do
|
|
# did user define a real dir
|
|
# this is the hdd bound backup in case of power failure
|
|
[[ ${DIR##*/} == .* ]] && BACKUP="${DIR%/*}/${DIR##*/}-backup_asd" ||
|
|
BACKUP="${DIR%/*}/.${DIR##*/}-backup_asd"
|
|
[[ ${DIR##*/} == .* ]] && BACK_OLD="${DIR%/*}/${DIR##*/}-backup_asd-old" ||
|
|
BACK_OLD="${DIR%/*}/.${DIR##*/}-backup_asd-old"
|
|
if [[ -d "$BACKUP" ]]; then
|
|
USER=$(stat -c %U "$BACKUP")
|
|
else
|
|
USER=$(stat -c %U "$DIR")
|
|
fi
|
|
TMP="$VOLATILE/$ASDNAME-$USER$DIR"
|
|
UPPER="$VOLATILE/$ASDNAME-$USER$DIR-rw"
|
|
WORK="$VOLATILE/.$ASDNAME-$USER$DIR"
|
|
debug "DIR: $DIR\nBACKUP: $BACKUP\nBACK_OLD: $BACK_OLD\nUSER: $USER\nTMP: $TMP"
|
|
|
|
if [[ -e "$TMP"/.flagged || ! -d "$BACKUP" ]]; then
|
|
debug "No ungraceful state detected"
|
|
# all is well so continue
|
|
continue
|
|
else
|
|
echo "Ungraceful state detected for $DIR so fixing"
|
|
NOW=$(date +%Y%m%d_%H%M%S)
|
|
|
|
debug "unmounting $DIR"
|
|
mountpoint -q "$DIR" && umount -R -l "$DIR"
|
|
debug "unmounting $BACKUP"
|
|
mountpoint -q "$BACKUP" && umount -R -l "$BACKUP" && rm -rf "$BACKUP" && debug "removed $BACKUP dir"
|
|
|
|
mountpoint -q "$TMP" && umount "$TMP" && rm -rf "$TMP" "$UPPER" "$WORK" && debug "unmounted overlay dirs"
|
|
|
|
if [[ -d "$BACK_OLD" ]]; then
|
|
if [[ $CRRE -eq 1 ]]; then
|
|
debug "copying $BACK_OLD to $BACKUP-$CRASH_RECOVERY_SUFFIX-$NOW.tar.zstd"
|
|
tar cf - -C "${BACK_OLD%/*}" "${BACK_OLD##*/}" | pv -s "$(du -sb "$BACK_OLD" | awk '{print $1}')" | zstd > "$BACKUP-$CRASH_RECOVERY_SUFFIX-$NOW.tar.zstd"
|
|
fi
|
|
rm -rf "$BACK_OLD" && debug "deleting the $BACK_OLD directory"
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
|
|
cleanup() {
|
|
debug "\n${BLU}Cleaning up crashrecoveries${NRM}"
|
|
local DIR USER GROUP BACKUP TMP
|
|
for DIR in "${WHATTOSYNC[@]}"; do
|
|
# this is the hdd bound backup in case of power failure
|
|
[[ ${DIR##*/} == .* ]] && BACKUP="${DIR%/*}/${DIR##*/}-backup_asd" ||
|
|
BACKUP="${DIR%/*}/.${DIR##*/}-backup_asd"
|
|
USER=$(stat -c %U "$DIR")
|
|
GROUP=$(id -g "$USER")
|
|
TMP="$VOLATILE/$ASDNAME-$USER$DIR"
|
|
UPPER="$VOLATILE/$ASDNAME-$USER$DIR-rw"
|
|
WORK="$VOLATILE/.$ASDNAME-$USER$DIR"
|
|
debug "DIR: $DIR\nUSER: $USER\nGROUP: $GROUP\nTMP: $TMP\nUPPER: $UPPER\nWORK: $WORK"
|
|
|
|
mapfile -t CRASHArr < <(find "${BACKUP%/*}" -maxdepth 1 -name "${BACKUP##*/}-$CRASH_RECOVERY_SUFFIX-*" 2>/dev/null|sort -r)
|
|
|
|
if [[ ${#CRASHArr[@]} -gt 0 ]]; then
|
|
echo -e "${BLD}Deleting ${#CRASHArr[@]} crashrecovery dir(s) for sync target ${BLU}$DIR${NRM}"
|
|
for backup in "${CRASHArr[@]}"; do
|
|
echo -e "${BLD}${RED} $backup${NRM}"
|
|
debug "removing $backup"
|
|
rm -rf "$backup"
|
|
done
|
|
unset CRASHArr
|
|
else
|
|
echo -e "${BLD}Found no crashrecovery dirs for: ${BLU}$DIR${NRM}${BLD}${NRM}"
|
|
fi
|
|
echo
|
|
done
|
|
}
|
|
|
|
enforce() {
|
|
debug "\n${BLU}Enforcing number of backups${NRM}"
|
|
local DIR BACKUP
|
|
for DIR in "${WHATTOSYNC[@]}"; do
|
|
# this is the hdd bound backup in case of power failure
|
|
[[ ${DIR##*/} == .* ]] && BACKUP="${DIR%/*}/${DIR##*/}-backup_asd" ||
|
|
BACKUP="${DIR%/*}/.${DIR##*/}-backup_asd"
|
|
debug "DIR: $DIR\nBACKUP: $BACKUP"
|
|
mapfile -t CRASHArr < <(find "${BACKUP%/*}" -maxdepth 1 -name "${BACKUP##*/}-$CRASH_RECOVERY_SUFFIX-*" 2>/dev/null|sort -r)
|
|
|
|
if [[ ${#CRASHArr[@]} -gt $BACKUP_LIMIT ]]; then
|
|
debug "The backups are greater than $BACKUP_LIMIT"
|
|
for remove in "${CRASHArr[@]:$BACKUP_LIMIT}"; do
|
|
debug "removing $remove"
|
|
rm -rf "$remove"
|
|
done
|
|
else
|
|
debug "The backups are less than $BACKUP_LIMIT, nothing to do"
|
|
fi
|
|
unset CRASHArr
|
|
done
|
|
}
|
|
|
|
do_sync() {
|
|
debug "\n${GRN}Syncing files${NRM}"
|
|
|
|
# make a snapshot of /etc/asd.conf and redefine its location to tmpfs while
|
|
# asd is running to keep any edits made to the live /etc/asd.conf from
|
|
# potentially orphaning the tmpfs copies thus preserving the data
|
|
[[ ! -f "${DAEMON_FILE}.conf" ]] && cp "$ASDCONF" "${DAEMON_FILE}.conf" && debug "copied $ASDCONF to ${DAEMON_FILE}.conf"
|
|
|
|
# sync to tmpfs and back again
|
|
local DIR USER GROUP BACKUP TMP
|
|
for DIR in "${WHATTOSYNC[@]}"; do
|
|
# this is the hdd bound backup in case of power failure
|
|
[[ ${DIR##*/} == .* ]] && BACKUP="${DIR%/*}/${DIR##*/}-backup_asd" ||
|
|
BACKUP="${DIR%/*}/.${DIR##*/}-backup_asd"
|
|
[[ ${DIR##*/} == .* ]] && BACK_OLD="${DIR%/*}/${DIR##*/}-backup_asd-old" ||
|
|
BACK_OLD="${DIR%/*}/.${DIR##*/}-backup_asd-old"
|
|
USER=$(stat -c %U "$DIR")
|
|
GROUP=$(id -g "$USER")
|
|
TMP="$VOLATILE/$ASDNAME-$USER$DIR"
|
|
UPPER="$VOLATILE/$ASDNAME-$USER$DIR-rw"
|
|
WORK="$VOLATILE/.$ASDNAME-$USER$DIR"
|
|
debug "\nDIR: $DIR\nUSER: $USER\nGROUP: $GROUP\nTMP: $TMP\nUPPER: $UPPER\nWORK: $WORK\n"
|
|
|
|
# make tmpfs container
|
|
if [[ -d "$DIR" ]]; then
|
|
# retain permissions on sync target
|
|
PREFIXP=$(stat -c %a "$DIR")
|
|
[[ -r "$TMP" ]] || install -dm"$PREFIXP" --owner="$USER" --group="$GROUP" "$TMP"
|
|
[[ -r "$BACKUP" ]] || install -dm"$PREFIXP" --owner="$USER" --group="$GROUP" "$BACKUP"
|
|
|
|
if [[ $OLFS -eq 1 ]]; then
|
|
debug "ensuring overlay directories"
|
|
if [[ $OLFSVER -eq 23 ]]; then
|
|
[[ -r "$UPPER" ]] || install -dm"$PREFIXP" --owner="$USER" --group="$GROUP" "$UPPER"
|
|
[[ -r "$WORK" ]] || install -dm"$PREFIXP" --owner="$USER" --group="$GROUP" "$WORK"
|
|
elif [[ $OLFSVER -eq 22 ]]; then
|
|
[[ -r "$UPPER" ]] || install -dm"$PREFIXP" --owner="$USER" --group="$GROUP" "$UPPER"
|
|
fi
|
|
fi
|
|
|
|
# sync the tmpfs targets to the disc
|
|
if [[ -e "$TMP"/.flagged ]]; then
|
|
debug "Syncing $TMP and $BACKUP"
|
|
# don't do inplace sync
|
|
set -x
|
|
rsync -aX --delete-after --exclude .flagged "$TMP/" "$BACKUP/" --info=progress2
|
|
set +x
|
|
else
|
|
# backup target and link to tmpfs container
|
|
debug "Bind mounting $DIR -> $BACKUP"
|
|
mount --rbind --make-private -o noatime "$DIR" "$BACKUP"
|
|
if [[ $CRRE -eq 1 ]];then
|
|
debug "Creating new linked backup directory $BACK_OLD"
|
|
tempfile=$(mktemp)
|
|
|
|
set -x
|
|
# this copies all the files
|
|
find "$BACKUP" -type l -printf '%P\n' > "$tempfile"
|
|
rm -rf "$BACK_OLD" && rsync -aX --no-links --link-dest="$DIR" "$DIR/" "$BACK_OLD/" --info=progress2
|
|
|
|
# this is used to handle the symlinks
|
|
rsync -aXl --files-from="$tempfile" "$DIR/" "$BACK_OLD/" --info=progress2
|
|
|
|
set +x
|
|
rm "$tempfile"
|
|
fi
|
|
|
|
# initial sync
|
|
if [[ $OLFS -eq 1 ]]; then
|
|
debug "Mounting overlay directory"
|
|
if [[ $OLFSVER -eq 23 ]]; then
|
|
mount -t overlay overlaid -olowerdir="$BACKUP",upperdir="$UPPER",workdir="$WORK" "$TMP"
|
|
elif [[ $OLFSVER -eq 22 ]]; then
|
|
mount -t overlayfs overlaid -olowerdir="$BACKUP",upperdir="$UPPER" "$TMP"
|
|
fi
|
|
else
|
|
debug "Doing initial sync with $BACKUP and $TMP"
|
|
set -x
|
|
rsync -aXl --append "$BACKUP/" "$TMP/" --info=progress2
|
|
set +x
|
|
fi
|
|
debug "bind mounting $TMP -> $DIR"
|
|
mount --rbind --make-private -o noatime "$TMP" "$DIR"
|
|
|
|
touch "$TMP"/.flagged
|
|
fi
|
|
fi
|
|
done
|
|
echo -e "${BLD}Sync successful${NRM}"
|
|
|
|
debug "creating $DAEMON_FILE"
|
|
touch "$DAEMON_FILE"
|
|
}
|
|
|
|
do_unsync() {
|
|
debug "\n${RED}Unsyncing files${NRM}"
|
|
|
|
local DIR USER BACKUP TMP
|
|
for DIR in "${WHATTOSYNC[@]}"; do
|
|
# this is the hdd bound backup in case of power failure
|
|
[[ ${DIR##*/} == .* ]] && BACKUP="${DIR%/*}/${DIR##*/}-backup_asd" ||
|
|
BACKUP="${DIR%/*}/.${DIR##*/}-backup_asd"
|
|
[[ ${DIR##*/} == .* ]] && BACK_OLD="${DIR%/*}/${DIR##*/}-backup_asd-old" ||
|
|
BACK_OLD="${DIR%/*}/.${DIR##*/}-backup_asd-old"
|
|
USER=$(stat -c %U "$DIR")
|
|
GROUP=$(id -g "$USER")
|
|
TMP="$VOLATILE/$ASDNAME-$USER$DIR"
|
|
UPPER="$VOLATILE/$ASDNAME-$USER$DIR-rw"
|
|
WORK="$VOLATILE/.$ASDNAME-$USER$DIR"
|
|
debug "DIR: $DIR\nUSER: $USER\nGROUP: $GROUP\nTMP: $TMP\nUPPER: $UPPER\nWORK: $WORK\n"
|
|
|
|
# remove link and move data from tmpfs to disk
|
|
if mountpoint -q "$DIR"; then
|
|
# this assumes that the backup is always
|
|
# updated so be sure to invoke a sync before an unsync
|
|
debug "unmounting $DIR"
|
|
umount -R -f -l "$DIR"
|
|
debug "unmounting $BACKUP"
|
|
umount -R -f -l "$BACKUP" && rm -rf "$BACKUP" && debug "removing $BACKUP"
|
|
|
|
if [[ $OLFS -eq 1 ]] && mountpoint -q "$TMP"; then
|
|
umount -l "$TMP" && debug "unmount $TMP"
|
|
rm -rf "$TMP" "$UPPER" "$WORK" && debug "removing overlayfs folders"
|
|
else
|
|
[[ -d "$TMP" ]] && rm -rf "$TMP" && debug "removing $TMP"
|
|
fi
|
|
[[ $CRRE -eq 1 ]] && rm -rf "$BACK_OLD" && debug "removing $BACK_OLD"
|
|
fi
|
|
done
|
|
|
|
# delete daemon file in the end, so that unsync can be run again
|
|
# incase of some failure midway
|
|
# since unsync also requires sync before, if any of the DIRS are unsynced during last run,
|
|
# they will be synced again before unsyncing not leading to any breakage
|
|
|
|
debug "Removing $DAEMON_FILE and ${DAEMON_FILE}.conf"
|
|
rm -f "$DAEMON_FILE" "${DAEMON_FILE}.conf"
|
|
|
|
echo -e "${BLD}Unsync successful${NRM}"
|
|
}
|
|
|
|
parse() {
|
|
if [[ -f /usr/lib/systemd/system/asd.service ]]; then
|
|
# running systemd
|
|
asd_state=$(systemctl show -p ActiveState --value asd)
|
|
resync_state=$(systemctl show -p ActiveState --value asd-resync.timer)
|
|
[[ "$asd_state" = "active" ]] && asd_color="${GRN}" || asd_color="${RED}"
|
|
[[ "$resync_state" = "active" ]] && resync_color="${GRN}" || resync_color="${RED}"
|
|
echo -e " ${BLD}Systemd service is currently ${asd_color}$asd_state${NRM}${BLD}.${NRM}"
|
|
echo -e " ${BLD}Systemd resync service is currently ${resync_color}$resync_state${NRM}${BLD}.${NRM}"
|
|
else
|
|
# using other init system + cron job for resync
|
|
[[ -x /etc/cron.hourly/asd-update ]] && resync_state="present" || resync_state="not present"
|
|
[[ "$resync_state" = "present" ]] && resync_color="${GRN}" || resync_color="${RED}"
|
|
echo -e " ${BLD}Daemon pid file is $([[ -f $DAEMON_FILE ]] &&
|
|
echo -e "${GRN}"present"${NRM}""${BLD}" || echo -e "${RED}"not present"${NRM}""${BLD}").${NRM}"
|
|
echo -e " ${BLD}Resync cronjob is ${resync_color}${resync_state}${NRM}${BLD}.${NRM}"
|
|
fi
|
|
[[ $OLFS -eq 1 ]] &&
|
|
echo -e "${BLD} Overlayfs v$OLFSVER is currently ${GRN}active${NRM}${BLD}.${NRM}" ||
|
|
echo -e "${BLD} Overlayfs technology is currently ${RED}inactive${NRM}${BLD}.${NRM}"
|
|
echo
|
|
echo -e "${BLD}Asd will manage the following per ${BLU}${ASDCONF}${NRM}${BLD} settings:${NRM}"
|
|
echo
|
|
local DIR USER GROUP BACKUP TMP
|
|
for DIR in "${WHATTOSYNC[@]}"; do
|
|
# this is the hdd bound backup in case of power failure
|
|
[[ ${DIR##*/} == .* ]] && BACKUP="${DIR%/*}/${DIR##*/}-backup_asd" ||
|
|
BACKUP="${DIR%/*}/.${DIR##*/}-backup_asd"
|
|
USER=$(stat -c %U "$DIR")
|
|
GROUP=$(id -g "$USER")
|
|
TMP="$VOLATILE/$ASDNAME-$USER$DIR"
|
|
UPPER="$VOLATILE/$ASDNAME-$USER$DIR-rw"
|
|
WORK="$VOLATILE/.$ASDNAME-$USER$DIR"
|
|
|
|
# sync target dir size
|
|
if [[ -d "$DIR" ]]; then
|
|
echo -en " ${BLD}owner/group id:"
|
|
echo -e "$(tput cr)""$(tput cuf 20)" "$USER/$GROUP${NRM}"
|
|
echo -en " ${BLD}target to manage:"
|
|
echo -e "$(tput cr)""$(tput cuf 20)" "${GRN}$DIR${NRM}"
|
|
echo -en " ${BLD}sync target:"
|
|
echo -e "$(tput cr)""$(tput cuf 20)" "${BLU}$BACKUP${NRM}"
|
|
echo -en " ${BLD}tmpfs target:"
|
|
echo -e "$(tput cr)""$(tput cuf 20)" "${RED}$TMP${NRM}"
|
|
echo -en " ${BLD}dir size:"
|
|
psize=$(du -Dh --max-depth=0 "$DIR" 2>/dev/null | awk '{ print $1 }')
|
|
echo -e "$(tput cr)$(tput cuf 20) $psize${NRM}"
|
|
if [[ $OLFS -eq 1 ]]; then
|
|
rwsize=$(du -Dh --max-depth=0 "$UPPER" 2>/dev/null | awk '{ print $1 }')
|
|
echo -en " ${BLD}overlayfs size:"
|
|
echo -e "$(tput cr)$(tput cuf 20) $rwsize${NRM}"
|
|
fi
|
|
echo -en " ${BLD}recovery dirs:"
|
|
mapfile -t CRASHArr < <(find "${BACKUP%/*}" -maxdepth 1 -name "${BACKUP##*/}-$CRASH_RECOVERY_SUFFIX-*" 2>/dev/null|sort -r)
|
|
if [[ "${#CRASHArr[@]}" -eq 0 ]]; then
|
|
echo -e "$(tput cr)$(tput cuf 20) none${NRM}"
|
|
else
|
|
echo -e "$(tput cr)$(tput cuf 20) ${RED}${#CRASHArr[@]}${NRM}${BLD} <- delete with the c option${NRM}"
|
|
for backup in "${CRASHArr[@]}"; do
|
|
psize=$(du -Dh --max-depth=0 "$backup" 2>/dev/null | awk '{ print $1 }')
|
|
echo -en " ${BLD} dir path/size:"
|
|
echo -e "$(tput cr)$(tput cuf 20) ${BLU}$backup ${NRM}${BLD}($psize)${NRM}"
|
|
done
|
|
fi
|
|
unset CRASHArr
|
|
echo
|
|
fi
|
|
done
|
|
}
|
|
|
|
case "$1" in
|
|
p|P|Parse|parse|Preview|preview|debug)
|
|
header
|
|
dep_check
|
|
config_check
|
|
parse
|
|
;;
|
|
c|C|clean|Clean)
|
|
header
|
|
dep_check
|
|
config_check
|
|
cleanup
|
|
;;
|
|
sync)
|
|
if [[ ! -f "$DAEMON_FILE" ]]; then
|
|
root_check
|
|
dep_check
|
|
config_check
|
|
ungraceful_state_check
|
|
do_sync
|
|
enforce
|
|
else
|
|
echo -e "${GRN}ASD is already running${NRM}"
|
|
fi
|
|
;;
|
|
resync)
|
|
if [[ -f "$DAEMON_FILE" ]]; then
|
|
root_check
|
|
ungraceful_state_check
|
|
do_sync
|
|
else
|
|
echo -e "${RED}ASD is not running${NRM}"
|
|
fi
|
|
;;
|
|
unsync)
|
|
# make sure the daemon ran to setup the links
|
|
if [[ -f "$DAEMON_FILE" ]]; then
|
|
root_check
|
|
ungraceful_state_check
|
|
do_sync
|
|
do_unsync
|
|
else
|
|
echo -e "${RED}ASD is not running${NRM}"
|
|
fi
|
|
;;
|
|
*)
|
|
echo -e "${BLD}Anything-sync-daemon v$VERS${NRM}"
|
|
echo
|
|
echo -e " ${BLD}$0 ${NRM}${GRN}[option]${NRM}"
|
|
echo -e " ${BLD} ${NRM}${GRN}preview${NRM}${BLD} Parse config file (${NRM}${BLU}${ASDCONF}${NRM}${BLD}) to see what will be managed.${NRM}"
|
|
echo -e " ${BLD} ${NRM}${GRN}clean${NRM}${BLD} Clean (delete without prompting) ALL crashrecovery dirs.${NRM}"
|
|
echo -e " ${BLD} ${NRM}${GRN}resync${NRM}${BLD} Synchronize the tmpfs and media bound copy. Must be run as root user.${NRM}"
|
|
echo -e " ${BLD} ${NRM}${RED}sync${NRM}${BLD} Force a manual sync. Must be run as root user and NOT recommended.${NRM}"
|
|
echo -e " ${BLD} ${NRM}${RED}unsync${NRM}${BLD} Force a manual unsync. Must be run as root user and NOT recommended.${NRM}"
|
|
echo
|
|
echo -e " ${BLD}It is ${RED}HIGHLY DISCOURAGED${NRM}${BLD} to directly call $0 to sync or to unsync.${NRM}"
|
|
if [[ -f /usr/lib/systemd/system/asd.service ]]; then
|
|
echo -e " ${BLD}Instead, use systemd to start/stop anything-sync-daemon.${NRM}"
|
|
echo
|
|
echo -e " ${BLD}systemctl ${NRM}${GRN}[option]${NRM}${BLD} asd asd-resync${NRM}"
|
|
echo -e " ${BLD} ${NRM}${GRN}start${NRM}${BLD} Turn on daemon; make symlinks and actively manage targets in tmpfs.${NRM}"
|
|
echo -e " ${BLD} ${NRM}${GRN}stop${NRM}${BLD} Turn off daemon; remove symlinks and rotate tmpfs data back to disc.${NRM}"
|
|
echo -e " ${BLD} ${NRM}${GRN}enable${NRM}${BLD} Autostart daemon when system comes up.${NRM}"
|
|
echo -e " ${BLD} ${NRM}${GRN}disable${NRM}${BLD} Remove daemon from the list of autostart daemons.${NRM}"
|
|
elif [[ -f /etc/init.d/asd ]]; then
|
|
echo -e " ${BLD}Instead, use the init system to start/stop anything-sync-daemon.${NRM}"
|
|
echo
|
|
echo -e " ${BLD}sudo service asd ${NRM}${GRN}[option]${NRM}${BLD} or /etc/init.d/asd ${NRM}${GRN}[option]${NRM}"
|
|
echo -e " ${BLD} ${NRM}${GRN}start${NRM}${BLD} Turn on daemon; make symlinks and actively manage targets in tmpfs.${NRM}"
|
|
echo -e " ${BLD} ${NRM}${GRN}stop${NRM}${BLD} Turn off daemon; remove symlinks and rotate tmpfs data back to disc.${NRM}"
|
|
fi
|
|
;;
|
|
esac
|
|
exit 0
|
|
|
|
# vim:set ts=2 sw=2 et:
|