Repository: borgbackup/borg

Issue:
add --append-only to 'borg init'
It's a small nit, but I'm automating some borg repo creations and it would be great if append-only mode could be specified at init time instead of having to automate editing the config file.

---

:moneybag: [there is a bounty for this](https://www.bountysource.com/issues/35866629-add-append-only-to-borg-init)



Discussion:
did you see that you can use `borg serve --append-only ...` now in 1.0.5?

see the updated append-only docs:
http://borgbackup.readthedocs.io/en/stable/usage.html?highlight=append-only#append-only-mode

So, I guess you'ld rather want to do that, right?

I did, but how does one do that in a case where repos are accessed via network attached disk (NFS in my case) rather than via ssh?

Ah, true, doesn't help for that.


Resolving patch:
diff --git a/borg/archiver.py b/borg/archiver.py
--- a/borg/archiver.py
+++ b/borg/archiver.py
@@ -64,13 +64,16 @@ def decorator(method):
         @functools.wraps(method)
         def wrapper(self, args, **kwargs):
             location = args.location  # note: 'location' must be always present in args
+            append_only = getattr(args, 'append_only', False)
             if argument(args, fake):
                 return method(self, args, repository=None, **kwargs)
             elif location.proto == 'ssh':
-                repository = RemoteRepository(location, create=create, lock_wait=self.lock_wait, lock=lock, args=args)
+                repository = RemoteRepository(location, create=create, lock_wait=self.lock_wait, lock=lock,
+                                              append_only=append_only, args=args)
             else:
                 repository = Repository(location.path, create=create, exclusive=argument(args, exclusive),
-                                        lock_wait=self.lock_wait, lock=lock)
+                                        lock_wait=self.lock_wait, lock=lock,
+                                        append_only=append_only)
             with repository:
                 if manifest or cache:
                     kwargs['manifest'], kwargs['key'] = Manifest.load(repository)
@@ -947,6 +950,8 @@ def build_parser(self, args=None, prog=None):
         subparser.add_argument('-e', '--encryption', dest='encryption',
                                choices=('none', 'keyfile', 'repokey'), default='repokey',
                                help='select encryption key mode (default: "%(default)s")')
+        subparser.add_argument('-a', '--append-only', dest='append_only', action='store_true',
+                               help='create an append-only mode repository')
 
         check_epilog = textwrap.dedent("""
         The check command verifies the consistency of a repository and the corresponding archives.
diff --git a/borg/remote.py b/borg/remote.py
--- a/borg/remote.py
+++ b/borg/remote.py
@@ -113,7 +113,7 @@ def serve(self):
     def negotiate(self, versions):
         return RPC_PROTOCOL_VERSION
 
-    def open(self, path, create=False, lock_wait=None, lock=True):
+    def open(self, path, create=False, lock_wait=None, lock=True, append_only=False):
         path = os.fsdecode(path)
         if path.startswith('/~'):
             path = path[1:]
@@ -124,7 +124,7 @@ def open(self, path, create=False, lock_wait=None, lock=True):
                     break
             else:
                 raise PathNotAllowed(path)
-        self.repository = Repository(path, create, lock_wait=lock_wait, lock=lock, append_only=self.append_only)
+        self.repository = Repository(path, create, lock_wait=lock_wait, lock=lock, append_only=self.append_only or append_only)
         self.repository.__enter__()  # clean exit handled by serve() method
         return self.repository.id
 
@@ -133,10 +133,14 @@ class RemoteRepository:
     extra_test_args = []
 
     class RPCError(Exception):
-        def __init__(self, name):
+        def __init__(self, name, remote_type):
             self.name = name
+            self.remote_type = remote_type
 
-    def __init__(self, location, create=False, lock_wait=None, lock=True, args=None):
+    class NoAppendOnlyOnServer(Error):
+        """Server does not support --append-only."""
+
+    def __init__(self, location, create=False, lock_wait=None, lock=True, append_only=False, args=None):
         self.location = self._location = location
         self.preload_ids = []
         self.msgid = 0
@@ -172,7 +176,17 @@ def __init__(self, location, create=False, lock_wait=None, lock=True, args=None)
         if version != RPC_PROTOCOL_VERSION:
             raise Exception('Server insisted on using unsupported protocol version %d' % version)
         try:
-            self.id = self.call('open', self.location.path, create, lock_wait, lock)
+            # Because of protocol versions, only send append_only if necessary
+            if append_only:
+                try:
+                    self.id = self.call('open', self.location.path, create, lock_wait, lock, append_only)
+                except self.RPCError as err:
+                    if err.remote_type == 'TypeError':
+                        raise self.NoAppendOnlyOnServer() from err
+                    else:
+                        raise
+            else:
+                self.id = self.call('open', self.location.path, create, lock_wait, lock)
         except Exception:
             self.close()
             raise
@@ -264,7 +278,7 @@ def handle_error(error, res):
             elif error == b'InvalidRPCMethod':
                 raise InvalidRPCMethod(*res)
             else:
-                raise self.RPCError(res.decode('utf-8'))
+                raise self.RPCError(res.decode('utf-8'), error.decode('utf-8'))
 
         calls = list(calls)
         waiting_for = []
diff --git a/borg/repository.py b/borg/repository.py
--- a/borg/repository.py
+++ b/borg/repository.py
@@ -108,7 +108,7 @@ def create(self, path):
         config.set('repository', 'version', '1')
         config.set('repository', 'segments_per_dir', str(self.DEFAULT_SEGMENTS_PER_DIR))
         config.set('repository', 'max_segment_size', str(self.DEFAULT_MAX_SEGMENT_SIZE))
-        config.set('repository', 'append_only', '0')
+        config.set('repository', 'append_only', str(int(self.append_only)))
         config.set('repository', 'id', hexlify(os.urandom(32)).decode('ascii'))
         self.save_config(path, config)
 


Test patch: