2
0
Эх сурвалжийг харах

remove remainders of append-only support

Some features like append-only repositories rely on a server-side component
that enforces them (because that shall only be controllable server-side,
not client-side).

So, that can only work, if such a server-side component exists, which is the
case for borg 1.x ssh: repositories (but not for borg 1.x non-ssh: repositories).

For borg2, we currently have:
- fs repos
- sftp: repos
- rclone: repos (enabling many different cloud providers)
- s3/b3: repos
- ssh: repos using client/server rpc code similar as in borg 1.x

So, only for the last method we have a borg server-side process that could enforce some features, but not for any of the other repo types.

For append-only the current idea is that this should not be done within borg,
but solved by a missing repo object delete permission enforced by the storage.

borg create could then use credentials that miss permission to delete,
while borg compact would use credentials that include permission to delete.
Thomas Waldmann 1 сар өмнө
parent
commit
9e6d90754e

+ 0 - 10
docs/deployment/hosting-repositories.rst

@@ -55,16 +55,6 @@ multiple times to permit access to more than one repository.
 The repository may not exist yet; it can be initialized by the user,
 which allows for encryption.
 
-**Specificities: Append-only repositories**
-
-Running ``borg init`` via a ``borg serve --append-only`` server will **not**
-create a repository that is configured to be append-only by its repository
-config.
-
-But, ``--append-only`` arguments in ``authorized_keys`` will override the
-repository config, therefore append-only mode can be enabled on a key by key
-basis.
-
 Refer to the `sshd(8) <https://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man8/sshd.8>`_
 man page for more details on SSH options.
 See also :ref:`borg_serve`

+ 4 - 6
docs/deployment/pull-backup.rst

@@ -247,7 +247,7 @@ to *borg-client* has to have read and write permissions on ``/run/borg``::
 On *borg-server*, we have to start the command ``borg serve`` and make its
 standard input and output available to a unix socket::
 
-   borg-server:~$ socat UNIX-LISTEN:/run/borg/reponame.sock,fork EXEC:"borg serve --append-only --restrict-to-path /path/to/repo"
+   borg-server:~$ socat UNIX-LISTEN:/run/borg/reponame.sock,fork EXEC:"borg serve --restrict-to-path /path/to/repo"
 
 Socat will wait until a connection is opened. Then socat will execute the
 command given, redirecting Standard Input and Output to the unix socket. The
@@ -350,7 +350,7 @@ dedicated ssh key:
 
   borgs@borg-server$ install -m 700 -d ~/.ssh/
   borgs@borg-server$ ssh-keygen -N '' -t rsa  -f ~/.ssh/borg-client_key
-  borgs@borg-server$ { echo -n 'command="borg serve --append-only --restrict-to-repo ~/repo",restrict '; cat ~/.ssh/borg-client_key.pub; } >> ~/.ssh/authorized_keys
+  borgs@borg-server$ { echo -n 'command="borg serve --restrict-to-repo ~/repo",restrict '; cat ~/.ssh/borg-client_key.pub; } >> ~/.ssh/authorized_keys
   borgs@borg-server$ chmod 600 ~/.ssh/authorized_keys
 
 ``install -m 700 -d ~/.ssh/``
@@ -365,12 +365,10 @@ dedicated ssh key:
   Another more complex approach is using a unique ssh key for each pull operation.
   This is more secure as it guarantees that the key will not be used for other purposes.
 
-``{ echo -n 'command="borg serve --append-only --restrict-to-repo ~/repo",restrict '; cat ~/.ssh/borg-client_key.pub; } >> ~/.ssh/authorized_keys``
+``{ echo -n 'command="borg serve --restrict-to-repo ~/repo",restrict '; cat ~/.ssh/borg-client_key.pub; } >> ~/.ssh/authorized_keys``
 
   Add borg-client's ssh public key to ~/.ssh/authorized_keys with forced command and restricted mode.
-  The borg client is restricted to use one repo at the specified path and to append-only operation.
-  Commands like *delete*, *prune* and *compact* have to be executed another way, for example directly on *borg-server*
-  side or from a privileged, less restricted client (using another authorized_keys entry).
+  The borg client is restricted to use one repo at the specified path.
 
 ``chmod 600 ~/.ssh/authorized_keys``
 

+ 2 - 10
docs/faq.rst

@@ -146,13 +146,6 @@ How can I restore huge file(s) over an unstable connection?
 Try using ``borg mount`` and ``rsync`` (or a similar tool that supports
 resuming a partial file copy from what's already copied).
 
-How can I switch append-only mode on and off?
----------------------------------------------
-
-You could do that (via borg config REPO append_only 0/1), but using different
-ssh keys and different entries in ``authorized_keys`` is much easier and also
-maybe has less potential of things going wrong somehow.
-
 My machine goes to sleep causing `Broken pipe`
 ----------------------------------------------
 
@@ -371,8 +364,8 @@ Another option is to not consider inode numbers in the files cache by passing
 Why are backups slow on a Linux server that is a member of a Windows domain?
 ----------------------------------------------------------------------------
 
-If a Linux server is a member of a Windows domain, username to userid resolution might be 
-performed via ``winbind`` without caching, which can slow down backups significantly. 
+If a Linux server is a member of a Windows domain, username to userid resolution might be
+performed via ``winbind`` without caching, which can slow down backups significantly.
 You can use e.g. ``nscd`` to add caching and improve the speed.
 
 Security
@@ -553,7 +546,6 @@ C to delete all backups residing on S.
 
 These are your options to protect against that:
 
-- Do not allow to delete data permanently from the repo, see :ref:`append_only_mode`.
 - Use a pull-mode setup using ``ssh -R``, see :ref:`pull_backup` for more information.
 - Mount C's filesystem on another machine and then create a backup of it.
 - Do not give C filesystem-level access to S.

+ 0 - 131
docs/usage/notes.rst

@@ -177,10 +177,6 @@ Separate compaction
 Borg does not auto-compact the segment files in the repository at commit time
 (at the end of each repository-writing command) any more (since borg 1.2.0).
 
-This causes a similar behaviour of the repository as if it was in append-only
-mode (see below) most of the time (until ``borg compact`` is invoked or an
-old client triggers auto-compaction).
-
 This has some notable consequences:
 
 - repository space is not freed immediately when deleting / pruning archives
@@ -197,133 +193,6 @@ This has some notable consequences:
 
 You can manually run compaction by invoking the ``borg compact`` command.
 
-.. _append_only_mode:
-
-Append-only mode (forbid compaction)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-A repository can be made "append-only", which means that Borg will never
-overwrite or delete committed data (append-only refers to the segment files,
-but borg will also reject to delete the repository completely).
-
-If ``borg compact`` command is used on a repo in append-only mode, there
-will be no warning or error, but no compaction will happen.
-
-append-only is useful for scenarios where a backup client machine backups
-remotely to a backup server using ``borg serve``, since a hacked client machine
-cannot delete backups on the server permanently.
-
-To activate append-only mode, set ``append_only`` to 1 in the repository config:
-
-::
-
-    borg config append_only 1
-
-Note that you can go back-and-forth between normal and append-only operation with
-``borg config``; it's not a "one way trip."
-
-In append-only mode Borg will create a transaction log in the ``transactions`` file,
-where each line is a transaction and a UTC timestamp.
-
-In addition, ``borg serve`` can act as if a repository is in append-only mode with
-its option ``--append-only``. This can be very useful for fine-tuning access control
-in ``.ssh/authorized_keys``:
-
-::
-
-    command="borg serve --append-only ..." ssh-rsa <key used for not-always-trustable backup clients>
-    command="borg serve ..." ssh-rsa <key used for backup management>
-
-Running ``borg repo-create`` via a ``borg serve --append-only`` server will *not* create
-an append-only repository. Running ``borg repo-create --append-only`` creates an append-only
-repository regardless of server settings.
-
-Example
-+++++++
-
-Suppose an attacker remotely deleted all backups, but your repository was in append-only
-mode. A transaction log in this situation might look like this:
-
-::
-
-    transaction 1, UTC time 2016-03-31T15:53:27.383532
-    transaction 5, UTC time 2016-03-31T15:53:52.588922
-    transaction 11, UTC time 2016-03-31T15:54:23.887256
-    transaction 12, UTC time 2016-03-31T15:55:54.022540
-    transaction 13, UTC time 2016-03-31T15:55:55.472564
-
-From your security logs you conclude the attacker gained access at 15:54:00 and all
-the backups where deleted or replaced by compromised backups. From the log you know
-that transactions 11 and later are compromised. Note that the transaction ID is the
-name of the *last* file in the transaction. For example, transaction 11 spans files 6
-to 11.
-
-In a real attack you'll likely want to keep the compromised repository
-intact to analyze what the attacker tried to achieve. It's also a good idea to make this
-copy just in case something goes wrong during the recovery. Since recovery is done by
-deleting some files, a hard link copy (``cp -al``) is sufficient.
-
-The first step to reset the repository to transaction 5, the last uncompromised transaction,
-is to remove the ``hints.N``, ``index.N`` and ``integrity.N`` files in the repository (these
-files are always expendable). In this example N is 13.
-
-Then remove or move all segment files from the segment directories in ``data/`` starting
-with file 6::
-
-    rm data/**/{6..13}
-
-That's all to do in the repository.
-
-If you want to access this rolled back repository from a client that already has
-a cache for this repository, the cache will reflect a newer repository state
-than what you actually have in the repository now, after the rollback.
-
-Thus, you need to clear the cache::
-
-    borg repo-delete --cache-only
-
-The cache will get rebuilt automatically. Depending on repo size and archive
-count, it may take a while.
-
-You also will need to remove ~/.config/borg/security/REPOID/manifest-timestamp.
-
-Drawbacks
-+++++++++
-
-As data is only appended, and nothing removed, commands like ``prune`` or ``delete``
-won't free disk space, they merely tag data as deleted in a new transaction.
-
-Be aware that as soon as you write to the repo in non-append-only mode (e.g. prune,
-delete or create archives from an admin machine), it will remove the deleted objects
-permanently (including the ones that were already marked as deleted, but not removed,
-in append-only mode). Automated edits to the repository (such as a cron job running
-``borg prune``) will render append-only mode moot if data is deleted.
-
-Even if an archive appears to be available, it is possible an attacker could delete
-just a few chunks from an archive and silently corrupt its data. While in append-only
-mode, this is reversible, but ``borg check`` should be run before a writing/pruning
-operation on an append-only repository to catch accidental or malicious corruption::
-
-    # run without append-only mode
-    borg check --verify-data && borg compact
-
-Aside from checking repository & archive integrity you may also want to check
-backups manually to ensure their content seems correct.
-
-Further considerations
-++++++++++++++++++++++
-
-Append-only mode is not respected by tools other than Borg. ``rm`` still works on the
-repository. Make sure that backup client machines only get to access the repository via
-``borg serve``.
-
-Ensure that no remote access is possible if the repository is temporarily set to normal mode
-for e.g. regular pruning.
-
-Further protections can be implemented, but are outside of Borg's scope. For example,
-file system snapshots or wrapping ``borg serve`` to set special permissions or ACLs on
-new data files.
-
 SSH batch mode
 ~~~~~~~~~~~~~~
 

+ 0 - 2
scripts/shell_completions/fish/borg.fish

@@ -88,7 +88,6 @@ complete -c borg         -l 'rsh'                   -d 'Use COMMAND instead of s
 # borg init options
 set -l encryption_modes "none keyfile keyfile-blake2 repokey repokey-blake2 authenticated authenticated-blake2"
 complete -c borg -f -s e -l 'encryption'            -d 'Encryption key MODE' -a "$encryption_modes" -n "__fish_seen_subcommand_from init"
-complete -c borg -f      -l 'append-only'           -d 'Create an append-only mode repository'      -n "__fish_seen_subcommand_from init"
 complete -c borg -f      -l 'make-parent-dirs'      -d 'Create parent directories'                  -n "__fish_seen_subcommand_from init"
 
 # borg create options
@@ -314,7 +313,6 @@ complete -c borg -f      -l 'strip-components'      -d 'Remove NUMBER of leading
 # borg serve
 complete -c borg         -l 'restrict-to-path'      -d 'Restrict repository access to PATH'         -n "__fish_seen_subcommand_from serve"
 complete -c borg         -l 'restrict-to-repository' -d 'Restrict repository access at PATH'        -n "__fish_seen_subcommand_from serve"
-complete -c borg -f      -l 'append-only'           -d 'Only allow appending to repository'         -n "__fish_seen_subcommand_from serve"
 
 # borg config
 complete -c borg -f -s c -l 'cache'                 -d 'Get/set/list values in the repo cache'      -n "__fish_seen_subcommand_from config"

+ 1 - 1
src/borg/archiver/__init__.py

@@ -401,7 +401,7 @@ class Archiver(
                 # client is allowed to specify the allowlisted options,
                 # everything else comes from the forced "borg serve" command (or the defaults).
                 # stuff from denylist must never be used from the client.
-                denylist = {"restrict_to_paths", "restrict_to_repositories", "append_only", "umask"}
+                denylist = {"restrict_to_paths", "restrict_to_repositories", "umask"}
                 allowlist = {"debug_topics", "lock_wait", "log_level"}
                 not_present = object()
                 for attr_name in allowlist:

+ 4 - 17
src/borg/archiver/_common.py

@@ -30,29 +30,19 @@ from ..logger import create_logger
 logger = create_logger(__name__)
 
 
-def get_repository(location, *, create, exclusive, lock_wait, lock, append_only, args, v1_or_v2):
+def get_repository(location, *, create, exclusive, lock_wait, lock, args, v1_or_v2):
     if location.proto in ("ssh", "socket"):
         RemoteRepoCls = LegacyRemoteRepository if v1_or_v2 else RemoteRepository
         repository = RemoteRepoCls(
-            location,
-            create=create,
-            exclusive=exclusive,
-            lock_wait=lock_wait,
-            lock=lock,
-            append_only=append_only,
-            args=args,
+            location, create=create, exclusive=exclusive, lock_wait=lock_wait, lock=lock, args=args
         )
 
     elif location.proto in ("sftp", "file", "rclone") and not v1_or_v2:  # stuff directly supported by borgstore
-        repository = Repository(
-            location, create=create, exclusive=exclusive, lock_wait=lock_wait, lock=lock, append_only=append_only
-        )
+        repository = Repository(location, create=create, exclusive=exclusive, lock_wait=lock_wait, lock=lock)
 
     else:
         RepoCls = LegacyRepository if v1_or_v2 else Repository
-        repository = RepoCls(
-            location.path, create=create, exclusive=exclusive, lock_wait=lock_wait, lock=lock, append_only=append_only
-        )
+        repository = RepoCls(location.path, create=create, exclusive=exclusive, lock_wait=lock_wait, lock=lock)
     return repository
 
 
@@ -114,7 +104,6 @@ def with_repository(
                 raise Error("missing repository, please use --repo or BORG_REPO env var!")
             assert isinstance(exclusive, bool)
             lock = getattr(args, "lock", _lock)
-            append_only = getattr(args, "append_only", False)
 
             repository = get_repository(
                 location,
@@ -122,7 +111,6 @@ def with_repository(
                 exclusive=exclusive,
                 lock_wait=self.lock_wait,
                 lock=lock,
-                append_only=append_only,
                 args=args,
                 v1_or_v2=False,
             )
@@ -190,7 +178,6 @@ def with_other_repository(manifest=False, cache=False, compatibility=None):
                 exclusive=True,
                 lock_wait=self.lock_wait,
                 lock=True,
-                append_only=False,
                 args=args,
                 v1_or_v2=v1_or_v2,
             )

+ 1 - 12
src/borg/archiver/repo_create_cmd.py

@@ -4,7 +4,7 @@ from ._common import with_repository, with_other_repository, Highlander
 from ..cache import Cache
 from ..constants import *  # NOQA
 from ..crypto.key import key_creator, key_argument_names
-from ..helpers import CancelledByUser, CommandError
+from ..helpers import CancelledByUser
 from ..helpers import location_validator, Location
 from ..manifest import Manifest
 
@@ -18,8 +18,6 @@ class RepoCreateMixIn:
     @with_other_repository(manifest=True, compatibility=(Manifest.Operation.READ,))
     def do_repo_create(self, args, repository, *, other_repository=None, other_manifest=None):
         """Create a new, empty repository"""
-        if args.append_only:
-            raise CommandError("append-only is not supported (yet?)")
         other_key = other_manifest.key if other_manifest is not None else None
         path = args.location.canonical_path()
         logger.info('Initializing repository at "%s"' % path)
@@ -224,15 +222,6 @@ class RepoCreateMixIn:
             action=Highlander,
             help="select encryption key mode **(required)**",
         )
-        subparser.add_argument(
-            "--append-only",
-            dest="append_only",
-            action="store_true",
-            help="create an append-only mode repository. Note that this only affects "
-            "the low level structure of the repository, and running `delete` "
-            "or `prune` will still be allowed. See :ref:`append_only_mode` in "
-            "Additional Notes for more details.",
-        )
         subparser.add_argument(
             "--copy-crypt-key",
             dest="copy_crypt_key",

+ 0 - 2
src/borg/archiver/repo_info_cmd.py

@@ -36,7 +36,6 @@ class RepoInfoMixIn:
             Repository ID: {id}
             Location: {location}
             Repository version: {version}
-            Append only: {append_only}
             {encryption}
             """
                 )
@@ -45,7 +44,6 @@ class RepoInfoMixIn:
                     id=bin_to_hex(repository.id),
                     location=repository._location.canonical_path(),
                     version=repository.version,
-                    append_only=repository.append_only,
                     encryption=info["encryption"],
                 )
             )

+ 0 - 13
src/borg/archiver/serve_cmd.py

@@ -1,7 +1,6 @@
 import argparse
 
 from ..constants import *  # NOQA
-from ..helpers import CommandError
 from ..remote import RepositoryServer
 
 from ..logger import create_logger
@@ -12,12 +11,9 @@ logger = create_logger()
 class ServeMixIn:
     def do_serve(self, args):
         """Start in server mode. This command is usually not used manually."""
-        if args.append_only:
-            raise CommandError("append-only is not supported (yet?)")
         RepositoryServer(
             restrict_to_paths=args.restrict_to_paths,
             restrict_to_repositories=args.restrict_to_repositories,
-            append_only=args.append_only,
             use_socket=args.use_socket,
         ).serve()
 
@@ -71,12 +67,3 @@ class ServeMixIn:
             "PATH may be an empty directory or the last element of PATH may not exist, in which case "
             "the client may initialize a repository there.",
         )
-        subparser.add_argument(
-            "--append-only",
-            dest="append_only",
-            action="store_true",
-            help="only allow appending to repository segment files. Note that this only "
-            "affects the low level structure of the repository, and running `delete` "
-            "or `prune` will still be allowed. See :ref:`append_only_mode` in Additional "
-            "Notes for more details.",
-        )

+ 2 - 7
src/borg/legacyremote.py

@@ -245,9 +245,7 @@ class LegacyRemoteRepository:
         def required_version(self):
             return self.args[1]
 
-    def __init__(
-        self, location, create=False, exclusive=False, lock_wait=None, lock=True, append_only=False, args=None
-    ):
+    def __init__(self, location, create=False, exclusive=False, lock_wait=None, lock=True, args=None):
         self.location = self._location = location
         self.preload_ids = []
         self.msgid = 0
@@ -329,12 +327,10 @@ class LegacyRemoteRepository:
                 lock_wait=lock_wait,
                 lock=lock,
                 exclusive=exclusive,
-                append_only=append_only,
                 v1_or_v2=True,  # make remote use LegacyRepository
             )
             info = self.info()
             self.version = info["version"]
-            self.append_only = info["append_only"]
 
         except Exception:
             self.close()
@@ -625,10 +621,9 @@ class LegacyRemoteRepository:
 
     @api(
         since=parse_version("1.0.0"),
-        append_only={"since": parse_version("1.0.7"), "previously": False},
         v1_or_v2={"since": parse_version("2.0.0b8"), "previously": True},  # TODO fix version
     )
-    def open(self, path, create=False, lock_wait=None, lock=True, exclusive=False, append_only=False, v1_or_v2=False):
+    def open(self, path, create=False, lock_wait=None, lock=True, exclusive=False, v1_or_v2=False):
         """actual remoting is done via self.call in the @api decorator"""
 
     @api(since=parse_version("2.0.0a3"))

+ 5 - 26
src/borg/legacyrepository.py

@@ -7,7 +7,6 @@ import struct
 import time
 from collections import defaultdict
 from configparser import ConfigParser
-from datetime import datetime, timezone
 from functools import partial
 from itertools import islice
 from typing import Callable, DefaultDict
@@ -190,9 +189,7 @@ class LegacyRepository:
 
         exit_mcode = 21
 
-    def __init__(
-        self, path, create=False, exclusive=False, lock_wait=None, lock=True, append_only=False, send_log_cb=None
-    ):
+    def __init__(self, path, create=False, exclusive=False, lock_wait=None, lock=True, send_log_cb=None):
         self.path = os.path.abspath(path)
         self._location = Location("file://%s" % self.path)
         self.version = None
@@ -218,7 +215,6 @@ class LegacyRepository:
         self.do_create = create
         self.created = False
         self.exclusive = exclusive
-        self.append_only = append_only
         self.transaction_doomed = None
         # v2 is the default repo version for borg 2.0
         # v1 repos must only be used in a read-only way, e.g. for
@@ -327,7 +323,6 @@ class LegacyRepository:
         config.set("repository", "version", str(self.version))
         config.set("repository", "segments_per_dir", str(DEFAULT_SEGMENTS_PER_DIR))
         config.set("repository", "max_segment_size", str(DEFAULT_MAX_SEGMENT_SIZE))
-        config.set("repository", "append_only", str(int(self.append_only)))
         config.set("repository", "additional_free_space", "0")
         config.set("repository", "id", bin_to_hex(os.urandom(32)))
         self.save_config(path, config)
@@ -385,8 +380,6 @@ class LegacyRepository:
 
     def destroy(self):
         """Destroy the repository at `self.path`"""
-        if self.append_only:
-            raise ValueError(self.path + " is in append-only mode")
         self.close()
         os.remove(os.path.join(self.path, "config"))  # kill config first
         shutil.rmtree(self.path)
@@ -468,9 +461,6 @@ class LegacyRepository:
             raise self.InvalidRepositoryConfig(path, "max_segment_size >= %d" % MAX_SEGMENT_SIZE_LIMIT)  # issue 3592
         self.segments_per_dir = self.config.getint("repository", "segments_per_dir")
         self.additional_free_space = parse_file_size(self.config.get("repository", "additional_free_space", fallback=0))
-        # append_only can be set in the constructor
-        # it shouldn't be overridden (True -> False) here
-        self.append_only = self.append_only or self.config.getboolean("repository", "append_only", fallback=False)
         self.id = hex_to_bin(self.config.get("repository", "id").strip(), length=32)
         self.io = LoggedIO(self.path, self.max_segment_size, self.segments_per_dir)
 
@@ -484,7 +474,7 @@ class LegacyRepository:
 
     def info(self):
         """return some infos about the repo (must be opened first)"""
-        info = dict(id=self.id, version=self.version, append_only=self.append_only)
+        info = dict(id=self.id, version=self.version)
         self._load_hints()
         return info
 
@@ -506,7 +496,7 @@ class LegacyRepository:
         segment = self.io.write_commit()
         self.segments.setdefault(segment, 0)
         self.compact[segment] += LoggedIO.header_fmt.size
-        if compact and not self.append_only:
+        if compact:
             self.compact_segments(threshold)
         self.write_index()
         self.rollback()
@@ -632,15 +622,6 @@ class LegacyRepository:
         transaction_id = self.io.get_segments_transaction_id()
         assert transaction_id is not None
 
-        # Log transaction in append-only mode
-        if self.append_only:
-            with open(os.path.join(self.path, "transactions"), "a") as log:
-                print(
-                    "transaction %d, UTC time %s"
-                    % (transaction_id, datetime.now(tz=timezone.utc).isoformat(timespec="microseconds")),
-                    file=log,
-                )
-
         # Write hints file
         hints_name = "hints.%d" % transaction_id
         hints_file = os.path.join(self.path, hints_name)
@@ -704,7 +685,7 @@ class LegacyRepository:
         required_free_space += hints_size
 
         required_free_space += self.additional_free_space
-        if not self.append_only:
+        if True:
             full_segment_size = self.max_segment_size + MAX_OBJECT_SIZE
             if len(self.compact) < 10:
                 # This is mostly for the test suite to avoid overestimated free space needs. This can be annoying
@@ -723,7 +704,7 @@ class LegacyRepository:
                 )
                 required_free_space += compact_working_space
             else:
-                # Keep one full worst-case segment free in non-append-only mode
+                # Keep one full worst-case segment free.
                 required_free_space += full_segment_size
 
         try:
@@ -1002,8 +983,6 @@ class LegacyRepository:
         This method verifies all segment checksums and makes sure
         the index is consistent with the data stored in the segments.
         """
-        if self.append_only and repair:
-            raise ValueError(self.path + " is in append-only mode")
         error_found = False
 
         def report_error(msg, *args):

+ 6 - 24
src/borg/remote.py

@@ -183,7 +183,7 @@ class RepositoryServer:  # pragma: no cover
         "store_move",
     )
 
-    def __init__(self, restrict_to_paths, restrict_to_repositories, append_only, use_socket):
+    def __init__(self, restrict_to_paths, restrict_to_repositories, use_socket):
         self.repository = None
         self.RepoCls = None
         self.rpc_methods = ("open", "close", "negotiate")
@@ -193,7 +193,6 @@ class RepositoryServer:  # pragma: no cover
         # i.e. it reflects local system policy and generally ranks higher than
         # whatever the client wants, except when initializing a new repository
         # (see RepositoryServer.open below).
-        self.append_only = append_only
         self.client_version = None  # we update this after client sends version information
         if use_socket is False:
             self.socket_path = None
@@ -371,7 +370,7 @@ class RepositoryServer:  # pragma: no cover
         path = os.path.realpath(path)
         return path
 
-    def open(self, path, create=False, lock_wait=None, lock=True, exclusive=None, append_only=False, v1_or_v2=False):
+    def open(self, path, create=False, lock_wait=None, lock=True, exclusive=None, v1_or_v2=False):
         self.RepoCls = LegacyRepository if v1_or_v2 else Repository
         self.rpc_methods = self._legacy_rpc_methods if v1_or_v2 else self._rpc_methods
         logging.debug("Resolving repository path %r", path)
@@ -395,18 +394,8 @@ class RepositoryServer:  # pragma: no cover
                     break
             else:
                 raise PathNotAllowed(path)
-        # "borg init" on "borg serve --append-only" (=self.append_only) does not create an append only repo,
-        # while "borg init --append-only" (=append_only) does, regardless of the --append-only (self.append_only)
-        # flag for serve.
-        append_only = (not create and self.append_only) or append_only
         self.repository = self.RepoCls(
-            path,
-            create,
-            lock_wait=lock_wait,
-            lock=lock,
-            append_only=append_only,
-            exclusive=exclusive,
-            send_log_cb=self.send_queued_log,
+            path, create, lock_wait=lock_wait, lock=lock, exclusive=exclusive, send_log_cb=self.send_queued_log
         )
         self.repository.__enter__()  # clean exit handled by serve() method
         return self.repository.id
@@ -574,7 +563,7 @@ class RemoteRepository:
         def required_version(self):
             return self.args[1]
 
-    def __init__(self, location, create=False, exclusive=False, lock_wait=1.0, lock=True, append_only=False, args=None):
+    def __init__(self, location, create=False, exclusive=False, lock_wait=1.0, lock=True, args=None):
         self.location = self._location = location
         self.preload_ids = []
         self.msgid = 0
@@ -651,16 +640,10 @@ class RemoteRepository:
                 raise Exception("Server insisted on using unsupported protocol version %s" % version)
 
             self.id = self.open(
-                path=self.location.path,
-                create=create,
-                lock_wait=lock_wait,
-                lock=lock,
-                exclusive=exclusive,
-                append_only=append_only,
+                path=self.location.path, create=create, lock_wait=lock_wait, lock=lock, exclusive=exclusive
             )
             info = self.info()
             self.version = info["version"]
-            self.append_only = info["append_only"]
 
         except Exception:
             self.close()
@@ -965,10 +948,9 @@ class RemoteRepository:
 
     @api(
         since=parse_version("1.0.0"),
-        append_only={"since": parse_version("1.0.7"), "previously": False},
         v1_or_v2={"since": parse_version("2.0.0b8"), "previously": True},  # TODO fix version
     )
-    def open(self, path, create=False, lock_wait=None, lock=True, exclusive=False, append_only=False, v1_or_v2=False):
+    def open(self, path, create=False, lock_wait=None, lock=True, exclusive=False, v1_or_v2=False):
         """actual remoting is done via self.call in the @api decorator"""
 
     @api(since=parse_version("2.0.0a3"))

+ 2 - 12
src/borg/repository.py

@@ -93,16 +93,7 @@ class Repository:
 
         exit_mcode = 21
 
-    def __init__(
-        self,
-        path_or_location,
-        create=False,
-        exclusive=False,
-        lock_wait=1.0,
-        lock=True,
-        append_only=False,
-        send_log_cb=None,
-    ):
+    def __init__(self, path_or_location, create=False, exclusive=False, lock_wait=1.0, lock=True, send_log_cb=None):
         if isinstance(path_or_location, Location):
             location = path_or_location
             if location.proto == "file":
@@ -139,7 +130,6 @@ class Repository:
         self.created = False
         self.acceptable_repo_versions = (3,)
         self.opened = False
-        self.append_only = append_only  # XXX not implemented / not implementable
         self.lock = None
         self.do_lock = lock
         self.lock_wait = lock_wait
@@ -254,7 +244,7 @@ class Repository:
         """return some infos about the repo (must be opened first)"""
         # note: don't do anything expensive here or separate the lock refresh into a separate method.
         self._lock_refresh()  # do not remove, see do_with_lock()
-        info = dict(id=self.id, version=self.version, append_only=self.append_only)
+        info = dict(id=self.id, version=self.version)
         return info
 
     def check(self, repair=False, max_duration=0):

+ 5 - 5
src/borg/testsuite/archiver/argparsing_test.py

@@ -126,7 +126,7 @@ class TestCommonOptions:
             formatter_class=argparse.RawDescriptionHelpFormatter,
         )
         subparser.set_defaults(func=1234)
-        subparser.add_argument("--append-only", dest="append_only", action="store_true")
+        subparser.add_argument("--foo-bar", dest="foo_bar", action="store_true")
 
         def parse_vars_from_line(*line):
             print(line)
@@ -149,19 +149,19 @@ class TestCommonOptions:
             "lock_wait": 1,
             "log_level": "critical",
             "progress": False,
-            "append_only": False,
+            "foo_bar": False,
             "func": 1234,
         }
 
         with pytest.raises(SystemExit):
-            parse_vars_from_line("--append-only", "subcommand")
+            parse_vars_from_line("--foo-bar", "subcommand")
 
         assert parse_vars_from_line("--append=foo", "--append", "bar", "subcommand", "--append", "baz") == {
             "append": ["foo", "bar", "baz"],
             "lock_wait": 1,
             "log_level": "warning",
             "progress": False,
-            "append_only": False,
+            "foo_bar": False,
             "func": 1234,
         }
 
@@ -180,7 +180,7 @@ class TestCommonOptions:
             "lock_wait": 1,
             "log_level": "warning",
             "progress": False,
-            "append_only": False,
+            "foo_bar": False,
             "func": 1234,
             args_key: args_value,
         }

+ 0 - 36
src/borg/testsuite/legacyrepository_test.py

@@ -511,39 +511,6 @@ def test_shadow_index_rollback(repository):
         assert repository.shadow_index[H(1)] == []  # because the deletion is considered unstable
 
 
-def test_destroy_append_only(repository):
-    with repository:
-        # can't destroy append only repo (via the API)
-        repository.append_only = True
-        with pytest.raises(ValueError):
-            repository.destroy()
-        assert repository.append_only
-
-
-def test_append_only(repository):
-    def segments_in_repository(repo):
-        return len(list(repo.io.segment_iterator()))
-
-    with repository:
-        repository.append_only = True
-        repository.put(H(0), fchunk(b"foo"))
-        repository.commit(compact=False)
-
-        repository.append_only = False
-        assert segments_in_repository(repository) == 2
-        repository.put(H(0), fchunk(b"foo"))
-        repository.commit(compact=True)
-        # normal: compact squashes the data together, only one segment
-        assert segments_in_repository(repository) == 2
-
-        repository.append_only = True
-        assert segments_in_repository(repository) == 2
-        repository.put(H(0), fchunk(b"foo"))
-        repository.commit(compact=False)
-        # append only: does not compact, only new segments written
-        assert segments_in_repository(repository) == 4
-
-
 def test_additional_free_space(repository):
     with repository:
         add_keys(repository)
@@ -680,7 +647,6 @@ def test_unknown_integrity_version(repository):
 
 def _subtly_corrupted_hints_setup(repository):
     with repository:
-        repository.append_only = True
         assert len(repository) == 1
         assert pdchunk(repository.get(H(0))) == b"foo"
         repository.put(H(1), fchunk(b"bar"))
@@ -703,7 +669,6 @@ def test_subtly_corrupted_hints(repository):
     make_auxiliary(repository)
     _subtly_corrupted_hints_setup(repository)
     with repository:
-        repository.append_only = False
         repository.put(H(3), fchunk(b"1234"))
         # do a compaction run, which succeeds since the failed checksum prompted a rebuild of the index+hints.
         repository.commit(compact=True)
@@ -719,7 +684,6 @@ def test_subtly_corrupted_hints_without_integrity(repository):
     integrity_path = os.path.join(repository.path, "integrity.5")
     os.unlink(integrity_path)
     with repository:
-        repository.append_only = False
         repository.put(H(3), fchunk(b"1234"))
         # do a compaction run, which fails since the corrupted refcount wasn't detected and causes an assertion failure.
         with pytest.raises(AssertionError) as exc_info: