home.social

#synologydrive — Public Fediverse posts

Live and recent posts from across the Fediverse tagged #synologydrive, aggregated by home.social.

fetched live
  1. Oh for crying out loud! Again fails to sync which in turn results in conflicting files. GAH!

  2. You know you're doing self-hosting right when it takes more than 30 minutes of no internet before you notice anything is wrong. Music is playing, files are uploading, the home automation is fully functional...

  3. Oh great, the sync issues where says it has synced but really isn't seems to be back. Blugh.

  4. Ugh I've had it with . It just doesn't sync stuff. On one computer I delete a file or folder, it's gone. I check my other computer, nothing changes. I check the server, files are still there. Both clients say they're all done syncing.

    This gets really old when a KeePass database starts getting out of sync on two systems. Grrr... Any alternatives on Synology

  5. On the off chance that you think #SynologyDrive is enterprise-grade file synchronization software, let me disabuse you of that notion by telling you about two incredibly stupid things it does, or perhaps more accurately, doesn't do.
    [This is in addition to having multiple data-loss bugs which I've posted about previously.]
    #Synology #TechIsShitDispatch (1/5)

  6. #Syncthing has been working exactly as I need it to for the two weeks since I switched to it from #SynologyDrive because of Drive's multiple data loss bugs. I therefore just set up a $12/year donation to Syncthing paid out every 4 years via Liberapay. Because if those of us who can, don't support the #FOSS software we depend on, it won't keep existing!

  7. Właśnie się zorientowałem, że #Synology wycofało aplikację #SynologyDrive z #macOS… Z tego co widzę nie ma żadnej oficjalnej alternatywy. Jak zatem teraz mam się łączyć z moim nosem na MacBooku? Ktoś coś podpowie?

  8. #Synology's repeated insistence on introducing data-loss bugs into their #SynologyDrive client for #Linux, followed by not fixing those bugs when they are reported (ref: blog.kamens.us/2021/03/17/syno, blog.kamens.us/2025/04/22/anot, blog.kamens.us/2025/10/20/yet-), has finally annoyed me enough that I've ditched Synology Drive and switched to #syncthing. It took me very little time to set up, and every question I had was answered by the documentation. This is how software should be.
    1/2
    #FOSS

  9. #TechIsShitDispatch
    #Synology just came out with their first #SynologyDrive update since I reported a new data-loss bug with no workaround to them, and they the new release doesn't fix the bug.
    Ref: blog.kamens.us/2025/10/20/yet-
    And let's not forget about the data-loss bug I reported to them five years ago which they still haven't fixed.
    Ref: blog.kamens.us/2021/03/17/syno
    1/

  10. Yet another Synology Drive Client for Linux data-loss bug

    [IMPORTANT UPDATE: As of January 14, 2026, this data-loss bug in Synology Drive still is fixed in version 4.0.2-17886, which on Linux is inexplicably numbered as version 8.0.2-17886. This is the second release since I reported the issue to Synology. The release notes do not make it clear that the issue is fixed, but there is something there that seems vaguely relevant, and I’ve tested this version and the bug is no longer present.]

    Oh, look, Synology just put out a major new upgrade of their Synology Drive Client app, from 7.5.2 to 8.0.0, and what a surprise, there’s another data-loss bug in it, at least on Linux! [previously, previously]

    The bug is quite easy to reproduce:

    1. Set up Synology Drive Client 8.0.0 on two Linux machines, machA and machB.
    2. On machA, create a directory in your Drive.
    3. Observe that the new directory is synchronized to your Drive on machB within a few seconds.
    4. On machB, create a file inside the new directory.
    5. Observe that the file you created inside the new directory is not synchronized to your NAS or back to machA.

    The file you just created won’t be synchronized until/unless you quit and restart Drive Client on machB. If, in the meantime, you create a file of the same name on machA, you will have a conflict situation to deal with, which you may not notice. If you were relying on your NAS as the backup of the files in your Drive, then you may very well lose data, e.g., if something happens to machB or if you accidentally delete the file before restarting Drive Client.

    Synology Drive Client on Linux uses the kernel inotify feature to get notified about files that are added to or deleted from your Drive, and clearly there is a bug in the new version that is causing it to forget to add inotify watches to directories created via synchronization from the NAS.

    There is no workaround I know of except to downgrade to version 7.5.2 and not upgrade until they fix the bug.

    I have reported the bug to Synology. Who knows if/when they will fix it.

    #Synology #SynologyDrive
  11. Here's today's #TechIsShitDispatch, detailing the tech problems I've had to deal with just since yesterday's thread. Today we've got a Linux kernel bug, an update on all the Synology Drive issues I've been having, and an old Synology Drive bug that still isn't fixed rearing its ugly head thanks to a bug in an open-source Python package.
    #Linux #kernel #Synology #SynologyDrive #PyInotify
    🧵1/10

  12. Today's #TechIsShitDispatch is an update about two different problems, which I've mentioned here previously, that I've been trying to solve: #Thunderbird recently started pausing sometimes for several seconds while delivering outbound email messages; and #SynologyDrive keeps getting confused and failing to sync the files it's supposed to be syncing.
    (Also, in the process of further troubleshooting the Thunderbird issue, I discovered three additional Thunderbird bugs.)
    #Synology
    🧵1/12

  13. Time for today's #TechIsShitDispatch, documenting the shitty tech I've had to deal with since yesterday. Today we've got a threefer: Synology Drive had one job and just couldn't do it; a medical billing website had one job and just couldn't do it; and yet another service treating VPN users as second-class citizens.
    #Synology #SynologyDrive #VPN #ArchiveToday
    🧵1/12

  14. Niveau de geekerie: j'installe #KDE depuis le wifi du train.

    Pour référence :
    #SynologyDrive n'arrive pas à afficher son icone dans le systray de #Gnome.
    Sous #Cosmic ça marche, mais #Steam affiche une fenêtre noire
    Est-ce que KDE fera un sans-faute?

    Faut vraiment avoir que ça à faire, je vous jure.

    Ah, aussi: si je boote sans brancher mes moniteurs externes, ils ne sont pas détectés quand je les branche à chaud. Il y a une astuce?

    #Fedora #Linux #Asus

  15. Another Synology Drive data loss bug

    Notwithstanding their recent boneheaded announcement (reported on by Ars Technica) about restricting which drives can be used in their NASes, #Synology gets most things right, but every once in a while their apps just… lose data, and it’s not clear that they care.

    I’ve written before about a Synology Drive Client bug on Linux they’ve known about for years and haven’t bothered to fix. And then there’s the time one of their NASes had a gradually manifesting hardware bug that they could have notified customers about and proactively done a recall, but instead they just let customer NASes fail at which point they were forced to shell out money for a new one.

    Today I’m hear to tell you about another data-loss bug in Synology Drive, and the workaround I’ve been forced to implement to avoid having it bite me (again).

    Simply put, sometimes Synology Drive Client stops pulling files down from the server. When this happens it claims that everything is fine and it’s synchronizing successfully and it will happily upload to the server any files you modify locally, but any files modified on other computers and synchronized by them to the server don’t get pulled down to the computer that is in this broken state.

    Let me say this again: it claims everything is working properly but it isn’t. That’s generally considered Really Bad.

    You can get the client to start synchronizing again by restarting the client, but (a) it’s not clear to me that files which weren’t synchronized in the interim get synchronized when you restart, and (b) there are various data-loss and data-conflict scenarios which occur when you modify files on multiple computers when one or more of them aren’t synchronizing properly.

    I don’t know the root cause of this, so I don’t know of any way to prevent the problem from happening. Therefore, instead I am now running a script every minute on all of my computers that sends and receives “pings” to/from the other computers in the group via temporary directories and files created within my Synology Drive directory. The script emails me when it doesn’t receive a “response” to a ping it sent to one of the other computers in the group. This means I’ll get some spurious emails when one of my computers is asleep or not on the network, but these are a small price to pay compared to the price of losing data because Synology Drive is failing again.

    I haven’t reported this issue to Synology Drive because it’s intermittent and I have no idea how to reproduce it so I’m certain they’ll blow me off.

    Here’s the script, for those of you who are curious.

    #!/bin/bashset -eshopt -s nullglobPINGDIR=~jik/CloudStation/tmp/syno-pingsME=$(hostname --short)DEBUG=falseINTERVAL=60while [ -n "$1" ]; do    case "$1" in        -d|--debug) DEBUG=true; shift ;;        -i|--interval) shift; INTERVAL="$1"; shift ;;        -*) echo "Unrecognized option: $1" 1>&2; exit 1 ;;        *) break ;;    esacdone           if [ -z "$1" ]; then    echo "No remote host(s) specified" 1>&2    exit 1fidebug() {    if ! $DEBUG; then        return    fi    echo "$@"}file_age() {    local path="$1"; shift    now=$(date +%s)    if then=$(stat -c %Y "$path" 2>/dev/null); then        echo $((now-then))    else        echo missing    fi}wait_for() {    local delay="$1"; shift    local path="$1"; shift    age=$(file_age "$path")    if [ $age = missing ]; then        echo missing    elif ((age < delay)); then        echo waiting    else        echo finished    fi}      settling() {    local path="$1"; shift    case $(wait_for $((INTERVAL/2)) "$path") in        missing) echo missing ;;        waiting) echo yes ;;        finished) echo no ;;    esac}late() {    local path="$1"; shift    case $(wait_for $((INTERVAL*2)) "$path") in        missing) echo missing ;;        waiting) echo no ;;        finished) echo yes ;;    esac        }dohost() {    local them="$1"; shift    debug Working on pings from $ME to $them    # Note if we were previously broken.    set -- $PINGDIR/ping.$ME-$them.*/broken    if [ -n "$1" ]; then        was_broken=true    else        was_broken=false    fi    debug was_broken=$was_broken    # Clear any pings that have been answered    for ping in $PINGDIR/ping.$ME-$them.*/ack; do        dir=$(dirname $ping)        if [ $(settling $dir) = yes ]; then            debug Ignoring recently acknowledged ping $dir            continue        fi        debug Clearing acknowledged ping $dir        rm -rf $dir    done    # Check for old pings that have not been answered yet.    is_broken=false    for ping in $PINGDIR/ping.$ME-$them.*/syn; do        dir=$(dirname $ping)        if [ -f $dir/broken ]; then            debug $dir remains broken            continue        fi        if [ $(late $dir) = no ]; then            debug Ignoring recently generated ping $dir            continue        fi        is_broken=true        echo $(date) > $dir/broken        debug $dir is newly broken    done    if $was_broken && ! $is_broken; then        echo Pings from $ME to $them have recovered    elif ! $was_broken && $is_broken; then        echo Pings from $ME to $them are failing, one of us is not syncing 1>&2    fi    # Create a new ping.    newpingdir=$PINGDIR/ping.$ME-$them.$(date +%s)    mkdir $newpingdir    echo $(date) > $newpingdir/syn    debug Created $newpingdir/syn}# Respond to pings sent to me.for ping in $PINGDIR/ping.*-$ME.*/syn; do    dir=$(dirname $ping)    if [ -f $dir/ack ]; then        debug Ignoring already acknowledged ping $dir        continue    fi    result=$(settling $dir)    if [ $result = missing ]; then        # Other end deleted it        debug Ignoring $dir after it disappeared        continue    elif [ $result = yes ]; then        debug Ignoring recently received ping $dir        continue    fi    debug Responding to $dir    echo $(date) > $dir/ackdone    for them; do    case "$them" in        *\ *) echo no spaces allowed in host names 1>&2; exit 1 ;;    esac    dohost $themdone

    #Synology #SynologyDrive

  16. Desde que comence esta mudanza habían pasado mas de 220 dias sin hacer el respaldo de mi #MacbookPro en #TimeMachine .. pero no, no soy tan descuidado, pues con mi #Synology tengo activado el respaldo continuo en #SynologyDrive. Una de las ventajas de esta comoda tecnología.

  17. Ich frage mal in die Schwarmoffensive des Fediserv, ob jemand eine #Synology nutzt und, anstatt das mitgelieferte #SynologyDrive zur Synchronisation auf #Linux, ein #rsync ?

    Vielen Dank

  18. @darkufo

    #Firefox gang here, though I've used it forever, so no switch here
    #Mastodon as well
    #Lemmy too
    #Bitwarden from LastPass
    #ProtonDrive from GoogleDrive
    #SynologyDrive from GoogleDrive
    #SynologyOffice from GoogleDocs
    #Protonmail from Gmail (actually finished the migration, it's refreshing)

    For a year or so I've also replaced Android with #SailfishOS but just last week I've come back to Android.

  19. Today's nerd rabbit hole: I set up #Syncthing on my #SteamDeck with the goal to sync files to my #Synology and then to other devices via #SynologyDrive.
    Unfortunately it looks like Syncthing via Docker doesn't trigger the right inotify events in the host OS when it writes, so Synology Drive doesn’t pick up the changes it makes.

    My crappy workaround: schedule a cron job every 5 minutes to copy the syncthing files in to Synology Drive. it ... works!

  20. Synology Drive Client for Linux has a data-loss bug Synology refuses to fix; here’s a workaround

    [As of January 14, 2026, this data-loss bug in Synology Drive still is not fixed in version 4.0.2-17886, which on Linux is inexplicably numbered as version 8.0.2-17886. It has been over five years since I reported the issue to Synology. The workaround described below still works.]

    I use GnuCash to track my finances. I run GnuCash on three different computers: two Linux and one Mac. For a long time I was using a shell-script wrapper to sync my GnuCash data file between the computers when launching GnuCash, but I recently decided to store the file on my Synology NAS and synchronize it between computers using Synology Drive Client.

    Unfortunately, I quickly noticed a significant problem: when I edited my GnuCash data on Mac, it was successfully synchronized onto the NAS as soon as I saved it, but when I edited on Linux, it wasn’t. Then, the next time I edited and saved on Mac, Linux decided there was a conflict between the edited version it had and the updated version sent over from the Mac, so it uploaded its conflicting version onto the NAS, and suddenly I was faced with two different, divergent versions of my GnuCash data file. I then had to merge these by hand, figuring out all the changes in both files from their common ancestor and merging them into one file to avoid losing data. Even worse, if I edited on Linux 1, then edited on Linux 2, then edited on the Mac, I was ending up with three conflicting versions of the data file, with three different sets of changes. Oy!

    The root cause of this is actually quite straightforward: on Linux, when a hard link is created within a Drive Client folder, the client does not notice the hard link or upload the file to the NAS. When GnuCash saves a modified data file on Linux, it first saves the file under a temporary file name, then deletes the older version of the file with its “real” file name, then creates a hard link from that name to the temporary file, then deletes the temporary file.

    The macOS version of Drive Client does not have this bug. The Linux version of the Dropbox Client does not have this bug.

    I reported this problem to Synology Support. Even after I explained to them exactly what the problem is and even explained to them how to reproduce it easily, they refused to acknowledge that the behavior is incorrect or commit to fixing it.

    To work around this issue, I wrote a Python script which scrapes the list of sync directories from the Drive Client SQLite database, sets up watchers for files created within those directories, and every time it detects that a file has been created, it updates the timestamp on the file, which tricks Drive Client into noticing the file and synchronizing it to the NAS.

    The script is below.

    For what it’s worth, in January 2023, I had a conversation with a helpful and competent Synology support engineer, in which I believe I have successfully convinced Synology that there is a bug here that they should fix, and they claim they’ve put it into the queue to be fixed as resources permit. So maybe we’ll get a fix at some point, but as of September 2025 we haven’t yet.

    #!/usr/bin/env python3
    
    import argparse
    import inotify.adapters
    import logging
    import logging.handlers
    import os
    import requests
    import signal
    import sqlite3
    import stat
    import sys
    import threading
    import time
    
    sys_db_path = os.path.expanduser('~/.SynologyDrive/data/db/sys.sqlite')
    # The file should be modified at least this often (seconds) or something is
    # wrong and we shouldn't update the canary.
    sys_db_max_idle = 120
    
    logger = None
    resetting = False
    last_crash = None
    watchers = {}
    watcher_tests = {}
    
    
    class TaskWatcher(object):
        def __init__(self, task):
            self.task = task
            path = task
            if path.endswith(os.path.sep):
                path = os.path.dirname(path)
            logger.info(f'Starting watcher {id(self):x} for {path}')
            self.path = path
            self.synology_dir = os.path.join(self.path,
                                             '.SynologyWorkingDirectory')
            self.obsolete = False
            self.thread = threading.Thread(target=self.watch, daemon=True)
            self.thread.start()
    
        def clean_tree(self):
            base = os.path.join(self.path, '.SynologyWorkingDirectory')
            prefix = base + os.path.sep
            # This is naughty because we are accessing private attributes inside
            # the Inotify object. Hopefully they won't change the internal
            # structure of the code!
            for watch in list(self.inotify.inotify._Inotify__watches.keys()):
                if watch == base or watch.startswith(prefix):
                    self.inotify.inotify.remove_watch(watch)
                    logger.debug(f'Removed watch {watch}')
    
        def watch(self):
            global watcher_tests
    
            self.inotify = inotify.adapters.InotifyTree(self.path)
            self.clean_tree()
    
            while not self.obsolete:
                for event in self.inotify.event_gen(
                        yield_nones=False, timeout_s=1):
                    self.clean_tree()
                    (_, type_names, path, filename) = event
                    logger.debug(f'event: type_names={type_names}, path={path}, '
                                 f'filename={filename}')
                    if path == self.synology_dir:
                        continue
                    if 'IN_CREATE' not in type_names:
                        continue
                    full_path = os.path.join(path, filename)
                    try:
                        stat_obj = os.stat(full_path, follow_symlinks=False)
                    except Exception:
                        continue
                    if not stat.S_ISREG(stat_obj.st_mode):
                        continue
                    if self.task in watcher_tests and \
                       watcher_tests[self.task][0] == filename:
                        logger.info(f'Got event for test file {full_path}')
                        os.unlink(full_path)
                        watcher_tests.pop(self.task)
                        continue
                    logger.info('Touching {}'.format(full_path))
                    try:
                        os.utime(full_path, times=(stat_obj.st_mtime,
                                                   stat_obj.st_mtime))
                    except Exception as e:
                        logger.info('Failed to touch {} ({}), continuing'.format(
                            full_path, e))
            logger.info(f'Exiting obsolete watcher {id(self):x} for {self.path}')
    
        def wait(self):
            self.thread.join()
    
        def is_alive(self):
            return self.thread.is_alive()
    
    
    def find_tasks():
        conn = sqlite3.connect(sys_db_path)
        cursor = conn.cursor()
        cursor.execute('SELECT sync_folder from session_table')
        return list(r[0] for r in cursor)
    
    
    def start_watchers():
        global resetting, watchers
    
        db_warned = False
        while True:
            if not db_warned:
                logger.info(f'{"Resetting" if resetting else "Scanning"} tasks.')
            try:
                tasks = find_tasks()
            except Exception as e:
                if not db_warned:
                    logger.error(f'Failed to open {sys_db_path} ({e}), '
                                 f'sleeping and retrying until success')
                    db_warned = True
                time.sleep(5)
                continue
            if db_warned:
                logger.info(f'Successfully opened {sys_db_path}')
                db_warned = False
            new_watchers = {}
            for task in tasks:
                if not resetting and task in watchers:
                    new_watchers[task] = watchers.pop(task)
                else:
                    new_watchers[task] = TaskWatcher(task)
            for task, watcher in watchers.items():
                logger.info(f'Telling watcher {id(watcher):x} for {task} to exit')
                watcher.obsolete = True
            resetting = False
            watchers = new_watchers
            return
    
    
    def resurrect_watchers():
        global resetting, watchers, last_crash
    
        crash_time = None
    
        for p, w in watchers.items():
            if not w.is_alive():
                logger.error(f'Watcher {id(w):x} for {p} crashed')
                crash_time = time.time()
        if crash_time:
            if last_crash and crash_time - last_crash < 5:
                raise Exception('Watchers are crashing too quickly, aborting')
            last_crash = crash_time
            logger.error('Resetting all watchers because of crashed threads')
            resetting = True
            start_watchers()
    
    
    def watch_tasks():
        global resetting, watcher_tests
    
        start_watchers()
        start_time = 0
        db_warned = False
        while True:
            # I use 62 seconds here because in my experience Synology Drive updates
            # the file every 60 seconds, perhaps 61 at the outside, so waiting 62
            # seconds should be long enough for it to do it. If not, no harm done,
            # there's no harm in creating a new watcher for it.
            if time.time() - start_time >= 62:
                # If we didn't do a rescan during the previous pass through the
                # loop, then perhaps something is wrong with the inotify watcher?
                # Let's throw our old one away and start over just to be safe.
                # One way this could happen: if the user deletes and recreates
                # their ~/.SynologyDrive directory!
                if not db_warned:
                    logger.info(f'Creating watcher for {sys_db_path}')
                i = inotify.adapters.Inotify()
                try:
                    i.add_watch(sys_db_path)
                except Exception as e:
                    if not db_warned:
                        logger.info(f'Failed to watch {sys_db_path} ({e}), '
                                    f'will delay and keep retrying')
                        db_warned = True
                    time.sleep(5)
                    continue
                start_time = time.time()
                if db_warned:
                    logger.info(f'Successfully watched {sys_db_path}')
                    db_warned = False
    
            if resetting:
                start_watchers()
            else:
                resurrect_watchers()
    
            for task, test in [(task, test) for task, test in watcher_tests.items()
                               if time.time() - test[1] > 2]:
                watcher_tests.pop(task)
                full_path = os.path.join(task, test[0])
                logger.error(f'No event for test file {full_path} after 2 seconds')
                os.unlink(full_path)
    
            for event in i.event_gen(yield_nones=False, timeout_s=1):
                (_, type_names, path, filename) = event
                if 'IN_MODIFY' not in type_names:
                    continue
                start_watchers()
                start_time = time.time()
    
    
    def maintain_canary(url, stable_interval):
        unstable_interval = 1
        last_problem = ''
        interval = 0
        while True:
            time.sleep(interval)
            interval = stable_interval
            stat_obj = os.stat(sys_db_path)
            delta = int(time.time() - stat_obj.st_mtime)
            if delta > sys_db_max_idle:
                interval = unstable_interval
                if last_problem != 'idle':
                    last_problem = 'idle'
                    logger.error(f'{sys_db_path} unmodified in {delta}s; '
                                 f'not triggering canary')
                continue
            elif last_problem == 'idle':
                last_problem = ''
                logger.info(f'{sys_db_path} modifications resumed; '
                            f'triggering canary')
            try:
                response = requests.get(url, timeout=5)
                response.raise_for_status()
                logger.debug(f'Successfully fetched {url}')
                last_problem = ''
            except Exception as e:
                new_problem = False
                # It's gross to test for this using a string operation like this,
                # but the root cause of the failure is buried so deep in a stack of
                # nested exceptions that doing it this way is less gross than any
                # of the alternatives.
                if 'Temporary failure in name resolution' in str(e) or \
                   'Name or service not known' in str(e):
                    if last_problem != 'dns':
                        last_problem = 'dns'
                        new_problem = True
                        logger.error(f'DNS failure fetching {url}')
                else:
                    if last_problem != 'fetch':
                        last_problem = 'fetch'
                        new_problem = True
                        logger.exception(f'Failed to fetch {url}')
                interval = unstable_interval
                if new_problem:
                    logger.error('Sleeping briefly and retrying until success')
    
    
    def parse_args():
        parser = argparse.ArgumentParser(
            description='Work around Synology Drive data loss bug')
        parser.add_argument('--canary-url', action='store', help='URL to fetch '
                            'periodically as proof of life')
        parser.add_argument('--canary-interval', type=int, action='store',
                            default=300, help='How frequently (seconds) to  fetch '
                            'canary URL (default 300)')
        return parser.parse_args()
    
    
    def toggle_debug(signum, frame):
        debugging = logger.level == logging.DEBUG
        logger.info(f'Changing log level to {"INFO" if debugging else "DEBUG"} '
                    f'in response to signal')
        logger.setLevel(logging.INFO if debugging else logging.DEBUG)
    
    
    def reset_watchers(signum, frame):
        global resetting
        logger.info('Queueing watcher reset in response to signal')
        resetting = True
    
    
    def test_watchers(signum, frame):
        global watcher_tests
    
        filename1 = f'testfile1.{os.getpid()}'
        filename2 = f'testfile2.{os.getpid()}'
        for task in watchers.keys():
            watcher_tests[task] = (filename2, time.time())
            path1 = os.path.join(task, filename1)
            path2 = os.path.join(task, filename2)
            with open(path1, 'w') as f:
                print("foo", file=f)
            os.link(path1, path2)
            os.unlink(path1)
            logger.info(f'Waiting for event for test file {path2}')
    
    
    def main():
        global logger
        logger = logging.getLogger(os.path.basename(sys.argv[0]))
        logger.setLevel(logging.INFO)
        handler = logging.handlers.SysLogHandler(address='/dev/log')
        logger.addHandler(handler)
        signal.signal(signal.SIGUSR1, toggle_debug)
        signal.signal(signal.SIGUSR2, reset_watchers)
        signal.signal(signal.SIGPWR, test_watchers)
    
        args = parse_args()
        if args.canary_url:
            canary_thread = threading.Thread(
                target=maintain_canary, daemon=True,
                args=(args.canary_url, args.canary_interval))
            canary_thread.start()
        watch_tasks()
    
    
    if __name__ == '__main__':
        main()
    
    

    Note that the script depends on some non-standard modules you’ll have to install from your OS package manager or PyPI.

    Here’s the trivial systemd unit file I use to run the script on my Linux computers (obviously, you’ll have to change the path for to wherever you put the script) as a systemd user service when I log in (if you don’t understand what that means, perhaps you shouldn’t be trying to run this script with systemd 😉 ):

    [Unit]
    Description=Force hard-linked files to sync to Synology Drive
    
    [Service]
    Type=exec
    ExecStart=/home/jik/bin/synology-inotify.py
    
    [Install]
    WantedBy=default.target

    Perhaps this will be useful to someone other than me! If so, post a comment or email me and let me know.

    #Synology #SynologyDrive