1

I am looking for an open source program (preferably available on GitHub) for Windows, with graphical interface. I only need to backup files and folders by copying them to a local backup folder, on one of the drives installed on the system. I don't need FTP, SSH, cloud server support, none of that. Just simply schedule backups and copy files and folders when asked to.

I am using Windows 10 64 bit

1 Answer 1

1

With your pretty basic requirements, you might want to have a look at RSYNC for Windows: https://itefix.net/cwrsync/client

There's good support for including/excluding files and directories, there's a lot of knowledge in the internet about how RSYNC works, you can easily create a script on your own calling things with different complex commandline arguments, it can be scheduled using the Windows Task Planner, with custom scripts you might even create VSS-based snapshots etc.

#!/usr/bin/env bash

##
# Backup the same named host using RSYNC running locally to the NAS.
# <p>
# Consider this a PoC for creating a file-based backup, most likely in addition to other available
# backup. Some of those might store files in additional containers like VHDX, some like Active
# Backup for Business use deduplication and custom repos and all of those are difficult to repair
# case of errors. Having a file-based backup additionally, updated once a while, might be another
# safety-net.
# </p>
# <p>
# If the script runs elevated, currently simply notified using an additional argument, it takes
# care of creating a snapshot, syncing from there and cleaning it up again afterwards. This prevents
# problem with denied access to files exclusively used by some apps.
# </p>
# @param[in,opt] {@code 1} if the process is elevated.
#

set -o errexit
set -o errtrace
#set -o functrace
set -o pipefail
#set -o xtrace

declare -r RUNS_ELEVATED="${1:-0}"
declare -r BAK_DST='/volume1/bak_sch.tst.ts-nb_rsync/c'
declare    BAK_SRC='/cygdrive/c'

declare -r DIR_SCRIPT="$(readlink -f -n "$(dirname "$(cygpath -u ${0})")")"
declare -r FNAME="$(basename "${0}" '.sh')"
declare -r FILE_LOG="${DIR_SCRIPT}/${FNAME}.log"
declare -r FILE_LOG_CYG="/cygdrive/${FILE_LOG#/}"

# Some snapshot related global data like paths, which simply don't change by convention. Snaps need
# to be created and deleted by Windows tools using Windows paths.
#
# The snap is created in this directory currently, because I'm unable to directly mount it as e.g.
# a drive-letter, only something like "subst" might work. But RSYNC should be able to handle even
# long paths properly and support long Unicode paths for Windows, so it seems to make sense to keep
# everything of interest grouped together.
declare -r SNAP_DIR_LIN="${DIR_SCRIPT}/${FNAME}.snap"
declare -r SNAP_DIR_WIN="$(cygpath -w "${SNAP_DIR_LIN}")"
declare    SNAP_ID=

# Order is important, RSYNC is not compatible with SSH from Git for Windows. SSH bundled by cwRsync
# needs to be configured specially as well, because it expects "known_hosts" in non-default folder.
# Have a look et /etc/ssh_config" and e.g. comment the existing path option.
# dup() in/out/err failed
declare -r PATH="/c/Program Files/cwRsync/bin:${PATH}"

# Git Bash must not convert paths to something RSYNC can't make use of.
export MSYS_NO_PATHCONV=1

##
# Create a snapshot if the script runs elevated.
#
# @return the ID fo the snapshot for later deletion.
#
snap_create_if() {
    if [ "${RUNS_ELEVATED}" != '1' ]; then
        return
    fi

    local -a args=(
        '-NonInteractive'
        '-NoLogo'
        '-NoProfile'
        '-OutputFormat' 'Text'
    )

    local -r details="$(powershell.exe "${args[@]}" << 'EOT' 2> '/dev/null'
$basics  = (Get-WmiObject -List Win32_ShadowCopy).Create("C:\\", "ClientAccessible")
$details =  Get-WmiObject Win32_ShadowCopy | Where-Object { $_.ID -eq $basics.ShadowID }
echo $details
EOT
)"

    # Don't understand the ouput of PS too much, contains Windows line endings, tabs etc. But even after normalizing
    # I'm unable to capture some values including their "column names", only the "column values".
    local -r parsable="$(echo "${details}"  | tr '\r\n\t\v' ' ')"
    local -r snap_dev="$(echo "${parsable}" | sed -E 's!.+ : (.+HarddiskVolumeShadowCopy[0-9]+) .+!\1!')"
    local -r snap_id="$( echo "${parsable}" | sed -E 's!.+.ID="(\{.+\})" .+!\1!')"

    # It's important to end the shadow device with a "\" or access won't work, even if the link is created!
    cmd.exe /C "mklink /D "${SNAP_DIR_WIN}" "${snap_dev}\\"" 1> '/dev/null'

    printf '%s' "${snap_id}"
}

##
# Delete a possibly created snapshot if an ID is available.
#
snap_delete_if() {
    if [ -z "${SNAP_ID}" ]; then
        return
    fi

    vssadmin 'Delete' 'Shadows' "/Shadow=${SNAP_ID}" '/Quiet'
    cmd.exe /C "rmdir "${SNAP_DIR_WIN}""
}

##
# Sync the data of interest.
#
rsync_main() {
    # Some filters should be applied on the receiving side only, which requires them to be "global"
    # or e.g. placed in rule files transferred to the receiving side to be taken into account there.
    # The latter seems unnecessary difficult, BUT "global" DOES NOT include providing rules using
    # STDIN here. So these special rules really need to be part of the commandline, because that is
    # forwarded to the receiving side. OTOH, STIND most likely is simply handled like rule files and
    # aren't considered to be "global" nor are they transferred to the receiver magically somehow.
    local -a filters=(
        # The backup might be indexed by Synology to easier find files, but DSM might create private
        # dirs and files not known in the backup source. So without special care, RSYNC would delete
        # them like it were files deleted by the user in the source, which would break the whole
        # indexing and searching process.
        '--filter=P /**/@eaDir/'
    )

    local -a args=(
        '--compress-level=6'
        '--delete'
        '--delete-during'
        '--delete-excluded'
        "${filters[@]}"
        '--hard-links'
        '--include-from=-'
        '--inplace'
        '--links'
        "--log-file=${FILE_LOG_CYG}"
        '--progress'
        '--recursive'
        '--rsync-path=/bin/rsync'
        '--times'
        '--verbose'
    )

    rsync "${args[@]}" "${BAK_SRC}/" "sch.tst.ds218:${BAK_DST}/" << 'EOT'
+ /Program Files/cwRsync/
+ /Program Files/diskshadow/
+ /Program Files/HxD/
+ /Program Files (x86)/
+ /Program Files (x86)/moneyplex/
+ /Program Files (x86)/ShadowExplorer/
+ /Windows/Logs/
+ /Windows/System32/
+ /Windows/System32/drivers/
+ /Windows/System32/drivers/etc/

- /$Recycle.Bin/
- /$WinREAgent/
- /Recovery/
- /Program Files/*
- /Program Files (x86)/*
- /ProgramData/Adobe/
- /System Volume Information/
- /Windows/*
- /Windows/System32/*
- /Windows/System32/drivers/*
- /hiberfil.sys
- /pagefile.sys
- /swapfile.sys
EOT
}

##
# Sync the log file.
# <p>
# If a snapshot is created, the log file isn't updated at runtime, so the current approach is to
# sync that in an additional step. This way we can put it into the root of the backup destination
# as well, where it's easier to get recognized.
# </p>
#
rsync_log() {
    local -a args=(
        '--compress-level=6'
        '--inplace'
        '--progress'
        '--rsync-path=/bin/rsync'
        '--times'
        '--verbose'
    )

    rsync "${args[@]}" "${FILE_LOG_CYG}" "sch.tst.ds218:${BAK_DST}/.."
}

rm "${FILE_LOG}" || true

SNAP_ID="$(snap_create_if)"
if [ -n "${SNAP_ID}" ]; then
    BAK_SRC="/cygdrive/${SNAP_DIR_LIN#/}"
fi

rsync_main || true
snap_delete_if
rsync_log

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.