#!/bin/bash

version="0.3.1"
reldate="2026/09/10"

scriptname=$(basename ${0})

if (( EUID != 0 )); then
    echo "${scriptname} must be run as root"
    exit 1
fi

SERVICEDIR=/etc/systemd/system
UDEVDIR=/etc/udev/rules.d

CFGDIR=/usr/share/nanolx
SERVICE="${CFGDIR}/${scriptname}.service"
TIMER="${CFGDIR}/${scriptname}.timer"
USB_SERVICE="${CFGDIR}/${scriptname}-usb.service"
USB_RULES="${CFGDIR}/${scriptname}-usb.rules"

manage_backups () {
    action="${1}"
    delete="${2}"

    mapfile -t backups < <(systemctl list-unit-files --type=timer "${scriptname}*" | \
        gawk '/${scriptname}/{print $1}')
    mapfile -t -O "${#backups[@]}" backups < <(systemctl list-unit-files --type=service "${scriptname}-*" | \
        gawk '/${scriptname}/{print $1}')
    if [[ -n "${backups[@]}" ]]; then
        PS3="Select Entry (enter number): "
        select choice in "${backups[@]}" "Cancel"; do
            if [ "${choice}" = "Cancel" ]; then
                break
            elif [ -n "${choice}" ]; then
                case "${choice}" in
                    ${scriptname}.* )
                        systemctl "${action}" --now "${choice}"
                        if ${delete}; then
                            rm -v "${SERVICEDIR}/${choice%.timer}.{timer,service}"
                            systemctl daemon-reload
                        fi
                    ;;
                    ${scriptname}-* )
                        # compat
                        if [ -f "${UDEVDIR}/99${choice/.service/.rules}" ]; then
                            mv "${UDEVDIR}/99${choice/.service/.rules}" \
                                "${UDEVDIR}/99-${choice/.service/.rules}"
                        fi
                        local udev_file="${UDEVDIR}/99-${choice/.service/.rules}"
                        case ${action} in
                            enable)  sed -e 's/^#//g' -i "${udev_file}" ;;
                            disable) sed -e 's/^\([^#]\)/#\1/g' -i "${udev_file}" ;;
                        esac
                        if ${delete}; then
                            rm -f "${SERVICEDIR}/${choice}"
                            rm -f "${udev_file}"
                            udevadm control --reload-rules
                            systemctl daemon-reload
                        fi
                    ;;
                esac
                break
            else
                echo "Invalid choice"
            fi
        done
    else
        echo "No timers created by ${scriptname}"
    fi
}

create_backup_timer () {
    # ensure trailing /
    srcdir="${1%/}/"
    destdir="${2%/}/"
    period="${3:-daily}"

    if [ ! -d "${srcdir}" ]; then
        echo "Source directory: ${srcdir} does not exist!"
        exit 1
    fi

    # if systemd doesn't recognize the value of period, fallback to daily aswell
    if ! systemd-analyze calendar "${period}" &>/dev/null; then
        echo "period \"${period}\" not supported by systemd, falling back to \"daily\"."
        period="daily"
    fi

    # cleanup srcdir so it's usable as file name for service
    # eg.: /home/testuser -> .home.testuser
    # use [:alnum:] for all alphanumeric chars, not just latin
    # and to be double safe enforce C.UTF-8 locale
    name=$(echo "${srcdir%/}" | LC_ALL=C.UTF-8 sed 's,/,.,g;s/[^[:alnum:]._-]//g')
    service="${scriptname}${name}.service"
    timer="${scriptname}${name}.timer"

    if [ ! -d "$(dirname ${destdir})" ]; then
        echo "Toplevel directory: $(dirname ${destdir}) for backup does not exist!"
        exit 1
    fi

    sed -e "s#@SOURCE@#${srcdir}#;s#@DEST@#${destdir}#" "${SERVICE}" > \
        "${SERVICEDIR}/${service}"

    sed -e "s#@SOURCE@#${srcdir}#;s#@PERIOD@#${period}#" "${TIMER}" > \
        "${SERVICEDIR}/${timer}"

    systemctl daemon-reload
    systemctl enable --now "${timer}"
}

create_backup_usb () {
    # ensure trailing /
    srcdir="${1%/}/"
    uuid="${2}"

    if [ ! -d "${srcdir}" ]; then
        echo "Source directory: ${srcdir} does not exist!"
        exit 1
    fi

    if [[ ! "${uuid}" =~ ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$ ]] && \
       [[ ! "${uuid}" =~ ^[0-9a-fA-F]{16}$ ]] && \
       [[ ! "${uuid}" =~ ^[0-9a-fA-F]{4}-[0-9a-fA-F]{4}$ ]]; then
        echo "The UUID ${uuid} is incorrect!"
        exit 1
    fi

    service="${scriptname}-${uuid}.service"
    rules="99-${scriptname}-${uuid}.rules"

    sed -e "s#@SOURCE@#${srcdir}#;s#@UUID@#${uuid}#" "${USB_SERVICE}" > \
        "${SERVICEDIR}/${service}"

    sed -e "s#@SERVICE@#${service}#;s#@UUID@#${uuid}#" "${USB_RULES}" > \
        "${UDEVDIR}/${rules}"

    udevadm control --reload-rules
    systemctl daemon-reload
}

case "${1}" in
    time* )
        create_backup_timer "${2}" "${3}" "${4}"
    ;;

    usb )
        create_backup_usb "${2}" "${3}"
    ;;

    list )
        systemctl list-timers --all "${scriptname}*"
        systemctl list-unit-files --type=timer "${scriptname}*"
        systemctl list-unit-files --type=service "${scriptname}-*"
    ;;

    list-short )
        systemctl list-unit-files --type=timer "${scriptname}*" | \
            gawk '/nanolx-backup/{print $1}'
        systemctl list-unit-files --type=service "${scriptname}-*" | \
            gawk '/nanolx-backup/{print $1}'
    ;;

    status )
        TIMERS=$(systemctl list-unit-files --type=timer "${scriptname}*" | \
            gawk '/${scriptname}/{print $1}')
        STATIC=$(systemctl list-unit-files --type=service "${scriptname}-*" | \
            gawk '/${scriptname}/{print $1}')
        if [[ -n "${TIMERS[@]}" || -n "${STATIC[@]}" ]]; then
            STRING="TIMER ACTIVE ENABLED"
            for backup in "${TIMERS[@]}" "${STATIC[@]}"; do
                # remove ${scriptname} prefix and .timer/.service suffix
                _backup="${backup%.timer}"
                _backup="${backup%.service}"
                _backup="${_backup#${scriptname}}"
                _backup="${_backup#-}"
                case ${backup} in
                    ${scriptname}-* )
                        _active="always"
                        # compat
                        if [ -f "/etc/udev/rules.d/99${scriptname}-${_backup}.rules" ]; then
                            mv "/etc/udev/rules.d/99${scriptname}-${_backup}.rules" \
                                "/etc/udev/rules.d/99-${scriptname}-${_backup}.rules"
                        fi
                        if grep -q -m1 "^#.*ACTION" "/etc/udev/rules.d/99-${scriptname}-${_backup}.rules"; then
                                _enabled="disabled"
                        else    _enabled="enabled"
                        fi
                    ;;
                    * )
                        _active="$(systemctl is-active "${backup}")"
                        _enabled="$(systemctl is-enabled "${backup}")"
                    ;;
                esac
                STRING+="\n${_backup} ${_active} ${_enabled}"
            done
            echo -e "${STRING}" | column -t
        else
            echo "No timers created by ${scriptname}"
        fi
    ;;

    disable )
        manage_backups disable false
    ;;

    enable )
        manage_backups enable false
    ;;

    delete )
        manage_backups disable true
    ;;

    version)
        echo "${scriptname} ${version} (${reldate})"
    ;;

    help | * )
        echo "${scriptname} v${version} (${reldate})
© 2026 Christopher Roy Bratusek <nano@jpberlin.de>

licensed under the GNU General Public License v3 (or newer)

usage   ${scriptname} [option], where [option] is one of:

    timed source destination period
        period is optional, any value known to systemd (fallback is 'daily')

        typically: hourly, daily, weekly, monthly, quarterly or yearly

        others: Mon,Wed,Fri       (backup on theese days)
                \"Mon..Fri 22:00\"  (backup monday to friday on 22:00 (10:00 PM))
                    since this period contains a space, quote it
                use 'systemd-analyze calendar [period]' to validate

    usb source uuid
        Create a udev rule and systemd service, that will trigger an rsync backup
        as soon as the specific device/partition becomes available.

        Only the source must exist, the UUID is checked for technical validity,
        not for actual correctness, so the device must not be plugged in to use
        ${scriptname} to create the necessary triggers.

    list
        list timers/USB backups created by ${scriptname}

    list-short
        list timers/USB backups created by ${scriptname} (names only)

    status
        show status of timers/USB backups created by ${scriptname}

    enable
        choose from ${scriptname} timers and enable them

    disable
        choose from ${scriptname} timers and disable them

    delete
        choose from ${scriptname} timers, disable and delete them

    version
        print version
"
    ;;
esac
