Automatic two-way Proton Drive sync on Linux (bisync + systemd), tuned against 429 rate limits

Proton doesn't ship a Linux sync client, and the usual advice is either a FUSE
mount or a periodic one-way sync. I wanted something closer to the Dropbox
behaviour across two machines, so I ended up with bisync driven by a systemd
user timer. Sharing the setup and, more importantly, the numbers, since I
couldn't find real measurements anywhere.

Setup

Local folder ~/Proton drive against a protondrive remote, one systemd user
timer per machine, two machines pointing at the same remote folder.

Service unit (the part that matters):

ExecStart=/usr/bin/rclone bisync "%h/Proton drive" proton:kali \
    --resilient \
    --recover \
    --conflict-resolve newer \
    --modify-window 1s \
    --protondrive-replace-existing-draft=true \
    --tpslimit 4 \
    --checkers 3 \
    --log-file=%h/.local/share/rclone-proton/bisync.log \
    --log-level INFO

TimeoutStartSec=0 in the unit is not optional: the default 90 s timeout kills
a sync that legitimately runs for 20+ minutes, and the systemd error message
tells you nothing about why.

Timer uses OnUnitActiveSec plus RandomizedDelaySec=5min, so two machines
drift apart over time instead of hitting the API at the same instant.

Why not a FUSE mount

Tried it first. The cache drifts out of sync and writes from a second machine
get messy, which makes sense given the API exposes no real-time change events.
Periodic bisync turned out far more predictable.

429 rate limits

Out of the box I kept hitting 429 Too Many Requests on api/auth/v4 — note
that this is the authentication endpoint, not transfers. Every bisync run
opens a fresh session, so a short timer interval multiplies logins. What fixed
it for me was --tpslimit 4 and --checkers 3, plus a longer timer interval.

A shared-exit-IP VPN seems to make it worse, though I still saw 429s with the
VPN off, so it is an aggravating factor rather than the cause.

Timings (this is the part I was missing when I started)

Roughly 23,000 files, about 6 GB:

Situation Duration
No changes at all ~8 min (June) → ~23 min (September)
One file added ~13 min

Same file count both times, so the slowdown is on the API side, not my data.
Throughput went from roughly 2,700 files/min to 1,000.

The key point: duration scales with file count, not data volume. bisync
computes no hashes by default ("Checksum": false, modtime + size only), so
almost all the wall time is the Building Path1 and Path2 listings stage,
capped by --tpslimit and --checkers. Going from 6 GB to 500 GB at the same
file count would cost nothing extra. Doubling the number of small files would
double the time.

Practical consequence: the timer interval must stay above the real sync
duration, otherwise runs overlap.

The stale lock file

This one cost me a while, so it may save someone else the trouble. After an
interrupted run, every subsequent run failed after ~4 seconds with:

Failed to bisync: prior lock file found

with an expiry date in the year 2226. Without --max-lock, the lock
effectively never expires, so a single interrupted run blocks every later one
indefinitely, and the systemd journal only reports "exit code 1".

Diagnosis and fix:

pgrep -a rclone     # confirm nothing is actually running
rclone deletefile "<path from the log>.lck"

Adding --max-lock 2h makes the lock self-expire while still being renewed
during a genuine long run. I'd recommend it from the start.

Full setup with install script, systemd units and troubleshooting notes:

Happy to hear if anyone has found a way to push --tpslimit higher without
tripping the limits.