Repository: borgbackup/borg

Issue:
rclone Integration
Hi,
I'm one of developers of [rclone](https://github.com/rclone/rclone) a command line tool written in golang to interact with various cloud storage providers. I'm wondering if the team here is interested in some sort of integration with rclone. 
We have a number of users that users that use borg with a cloud storage provider mounted via rclone with varying levels of success. Even though rclone's vfs has gotten much mature over time there is still significant overhead associated with it so a more direct would be beneficial. Currently rclone already has integration with restic that is achieved by a custom http2 api we are running on stdio. If only had a quick look at borg's remote repository protocol and not yet sure if it would be viable to implement something based on this on rclone's side.

Given the rather obvious absence of any cloud storage integration on borg's side, which may have been a conscious decision, I'd like to know if this something you would be in principle interested in before looking into this any further.
buengese

Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/borgbackup/borg/pull/8405?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=borgbackup) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 80.93%. Comparing base [(`ec9d412`)](https://app.codecov.io/gh/borgbackup/borg/commit/ec9d4127565cb8a7e7d9456edb1ed0e2c232da79?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=borgbackup) to head [(`473b63c`)](https://app.codecov.io/gh/borgbackup/borg/commit/473b63c03b5f7088cf1e2e788cb63cfb8e10034a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=borgbackup).
> Report is 5 commits behind head on master.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #8405      +/-   ##
==========================================
- Coverage   81.43%   80.93%   -0.51%     
==========================================
  Files          70       70              
  Lines       12733    12712      -21     
  Branches     2310     2008     -302     
==========================================
- Hits        10369    10288      -81     
- Misses       1707     1754      +47     
- Partials      657      670      +13     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/borgbackup/borg/pull/8405?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=borgbackup).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=borgbackup).


Patch:
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index 918f059c..47c493fc 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -411,6 +411,7 @@ Due to using the `borgstore` project, borg now also supports other kinds of
 
 - sftp: the borg client will directly talk to an sftp server.
   This does not require borg being installed on the sftp server.
+- rclone: the borg client will talk via rclone to cloud storage.
 - Others may come in the future, adding backends to `borgstore` is rather simple.
 
 Restoring a backup
diff --git a/docs/usage/general/file-systems.rst.inc b/docs/usage/general/file-systems.rst.inc
index d53eb96b..342c1ee5 100644
--- a/docs/usage/general/file-systems.rst.inc
+++ b/docs/usage/general/file-systems.rst.inc
@@ -24,7 +24,7 @@ Pros:
   and re-write segment files to free space.
 - In future, easier to adapt to other kinds of storage:
   borgstore's backends are quite simple to implement.
-  A ``sftp:`` backend already exists, cloud storage might be easy to add.
+  ``sftp:`` and ``rclone:`` backends already exists, others might be easy to add.
 - Parallel repository access with less locking is easier to implement.
 
 Cons:
diff --git a/docs/usage/general/repository-urls.rst.inc b/docs/usage/general/repository-urls.rst.inc
index db2a1e19..058047d6 100644
--- a/docs/usage/general/repository-urls.rst.inc
+++ b/docs/usage/general/repository-urls.rst.inc
@@ -14,7 +14,7 @@ Note: you may also prepend a ``file://`` to a filesystem path to get URL style.
 
 **Remote repositories** accessed via ssh user@host:
 
-``ssh://user@host:port/path/to/repo`` - absolute path`
+``ssh://user@host:port/path/to/repo`` - absolute path
 
 ``ssh://user@host:port/./path/to/repo`` - path relative to current directory
 
@@ -22,10 +22,15 @@ Note: you may also prepend a ``file://`` to a filesystem path to get URL style.
 
 **Remote repositories** accessed via sftp:
 
-``sftp://user@host:port/path/to/repo`` - absolute path`
+``sftp://user@host:port/path/to/repo`` - absolute path
 
 For ssh and sftp URLs, the ``user@`` and ``:port`` parts are optional.
 
+**Remote repositories** accessed via rclone:
+
+``rclone://remote:path``
+
+
 If you frequently need the same repo URL, it is a good idea to set the
 ``BORG_REPO`` environment variable to set a default for the repo URL:
 
diff --git a/pyproject.toml b/pyproject.toml
index 2f6f1469..fcadcc01 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -30,7 +30,7 @@ classifiers = [
 ]
 license = {text="BSD"}
 dependencies = [
-  "borgstore ~= 0.0.1",
+  "borgstore ~= 0.0.4",
   "msgpack >=1.0.3, <=1.1.0",
   "packaging",
   "platformdirs >=3.0.0, <5.0.0; sys_platform == 'darwin'",  # for macOS: breaking changes in 3.0.0,
diff --git a/src/borg/archiver/_common.py b/src/borg/archiver/_common.py
index f8195b62..3b9453b1 100644
--- a/src/borg/archiver/_common.py
+++ b/src/borg/archiver/_common.py
@@ -46,7 +46,7 @@ def get_repository(
             args=args,
         )
 
-    elif location.proto in ("sftp", "file") and not v1_or_v2:  # stuff directly supported by borgstore
+    elif location.proto in ("sftp", "file", "rclone") and not v1_or_v2:  # stuff directly supported by borgstore
         repository = Repository(
             location,
             create=create,
diff --git a/src/borg/helpers/parseformat.py b/src/borg/helpers/parseformat.py
index ee30d0eb..171e1462 100644
--- a/src/borg/helpers/parseformat.py
+++ b/src/borg/helpers/parseformat.py
@@ -467,6 +467,14 @@ class Location:
         re.VERBOSE,
     )  # path
 
+    rclone_re = re.compile(
+        r"""
+        (?P<proto>rclone)://                                    # rclone://
+        (?P<path>(.*))
+        """,
+        re.VERBOSE,
+    )  # path
+
     socket_re = re.compile(
         r"""
         (?P<proto>socket)://                                    # socket://
@@ -546,6 +554,11 @@ def normpath_special(p):
             self.port = m.group("port") and int(m.group("port")) or None
             self.path = normpath_special(m.group("path"))
             return True
+        m = self.rclone_re.match(text)
+        if m:
+            self.proto = m.group("proto")
+            self.path = m.group("path")
+            return True
         m = self.file_re.match(text)
         if m:
             self.proto = m.group("proto")
@@ -575,7 +588,7 @@ def __str__(self):
 
     def to_key_filename(self):
         name = re.sub(r"[^\w]", "_", self.path).strip("_")
-        if self.proto not in ("file", "socket"):
+        if self.proto not in ("file", "socket", "rclone"):
             name = re.sub(r"[^\w]", "_", self.host) + "__" + name
         if len(name) > 100:
             # Limit file names to some reasonable length. Most file systems


Test patch:
diff --git a/src/borg/testsuite/helpers.py b/src/borg/testsuite/helpers.py
index 4f29ce74..e7ce9eea 100644
--- a/src/borg/testsuite/helpers.py
+++ b/src/borg/testsuite/helpers.py
@@ -187,6 +187,14 @@ def test_ssh(self, monkeypatch, keys_dir):
             "host='2a02:0001:0002:0003:0004:0005:0006:0007', port=1234, path='/some/path')"
         )
 
+    def test_rclone(self, monkeypatch, keys_dir):
+        monkeypatch.delenv("BORG_REPO", raising=False)
+        assert (
+            repr(Location("rclone://remote:path"))
+            == "Location(proto='rclone', user=None, host=None, port=None, path='remote:path')"
+        )
+        assert Location("rclone://remote:path").to_key_filename() == keys_dir + "remote_path"
+
     def test_sftp(self, monkeypatch, keys_dir):
         monkeypatch.delenv("BORG_REPO", raising=False)
         assert (