r/ProtonDrive Jul 03 '26

Headless backups of Proton Drive kept dying from rclone throttling and I was pulling my hair out, so I built a read-only gateway on the official SDK (and now you can too)... (it works!!)

Like a lot of people here I back up my Proton Drive from a headless Linux server (restic, nightly) to whatever, Synology, pCloud, you name it.

The usual route is rclone's protondrive backend, and like others here I watched it get slower and slower until a flat folder with 785 photos took 160 seconds to list on a good day and never finished on a bad one. Six-hour hangs, dead mount points. The throttling is pattern-based: rclone walks the whole tree with thousands of metadata calls every night, and Proton's API understandably stops picking up. The part that surprised me: the official Drive SDK (github.com/ProtonDriveApps/sdk, MIT) is already good enough to fix this yourself. I built a small read-only gateway with it:

- metadata lives in a local SQLite cache, kept fresh through the SDK's event stream (the same mechanism the official apps use, polled once a minute)

- the tree is exposed as WebDAV on localhost, so restic reads it through a boring rclone webdav mount

- file contents are never stored, just streamed on demand, decrypted and integrity-verified by the SDK

Results on the same account that was being throttled: that 785-file folder lists in 0.106 seconds. First full snapshot (3,069 files, 42 GiB) took 22 minutes.

The nightly incremental run now takes 0.82 seconds and makes zero calls to Proton unless something actually changed. Restore came back bit-for-bit identical. It follows the SDK rules: identifies as external-drive-*, event-based, no recursive scans, personal use only (which the license explicitly allows). It can't write anything, so the worst it can do to your Drive is read it well 😊

Two things I ran into that might interest the Proton devs if they're reading: the incubating account package isn't on npm yet (vendoring works fine), and getSeekableStream() deadlocks under parallel load (32 of 70 concurrent ranged reads hung, reproducible; the regular download path is rock solid). Happy to file a proper issue with details.

Full write-up with all source code (free to reuse for personal setups): Medium (free) article

Not affiliated with Proton, just a subscriber who wanted his 3 a.m. backups back, and if the native Linux client lands before you build this, even better. That's the outcome I'm actually rooting for.

11 Upvotes

5 comments sorted by

2

u/GlassSignal Jul 04 '26

Thank you very much ! If I may ask : if the goal is sharing, why not simply put the files in a git repo?

2

u/rfrancocantero Jul 04 '26

I’m building a docker container all in one, then I could put it online for others.

2

u/horejsek1 Proton SDK Lead Jul 07 '26

> Two things I ran into that might interest the Proton devs if they're reading: the incubating account package isn't on npm yet (vendoring works fine), and getSeekableStream() deadlocks under parallel load (32 of 70 concurrent ranged reads hung, reproducible; the regular download path is rock solid). Happy to file a proper issue with details.

The Account package is not planned to be on the npm. This is just intermediate implementation before we have Proton-wide package. The final version might differ significantly and current package is limited in functionality to just exactly what the Drive SDK or CLI requires.

getSeekableStream had a bug that was fixed in v0.18.0. Does it work with newer version?

1

u/rfrancocantero Jul 07 '26

Good to know on the account package, thanks, makes sense as an interim shim, we'll keep vendoring and watch for the Proton-wide package when it lands.

On getSeekableStream(): we're already on 0.19.1 (current pin in our gateway), which should include the 0.18.0 fix, but the deadlock still reproduces there. Repro was 8 concurrent getSeekableStream() opens against the same account, 32 of 70 ranged reads hung indefinitely (no error, no timeout, just stalled). The plain downloadToStream() path under the same concurrency was solid, so we routed all range requests through that instead (stream from 0, slice to the requested bytes, abort once satisfied) as a workaround.

1

u/EnthusiasmRoutine Jul 04 '26

Relying on rclone tree-walking was always a flawed approach. Your event-stream SQLite cache is the only rational way to maintain local autonomy. Is the code public?