From bdc36d451f733a6f4a36a3f07154e9adfea02961 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Thu, 29 May 2025 23:50:39 +0200 Subject: [PATCH] serve: add --permissions option as an alternative to BORG_REPO_PERMISSIONS env var --- src/borg/archiver/__init__.py | 2 +- src/borg/archiver/serve_cmd.py | 7 +++++++ src/borg/remote.py | 10 ++++++---- src/borg/repository.py | 15 ++++++++++++--- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/borg/archiver/__init__.py b/src/borg/archiver/__init__.py index 68d343cef..c78ab7704 100644 --- a/src/borg/archiver/__init__.py +++ b/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", "umask"} + denylist = {"restrict_to_paths", "restrict_to_repositories", "umask", "permissions"} allowlist = {"debug_topics", "lock_wait", "log_level"} not_present = object() for attr_name in allowlist: diff --git a/src/borg/archiver/serve_cmd.py b/src/borg/archiver/serve_cmd.py index 6e6c410fa..a5b7e81d3 100644 --- a/src/borg/archiver/serve_cmd.py +++ b/src/borg/archiver/serve_cmd.py @@ -15,6 +15,7 @@ class ServeMixIn: restrict_to_paths=args.restrict_to_paths, restrict_to_repositories=args.restrict_to_repositories, use_socket=args.use_socket, + permissions=args.permissions, ).serve() def build_parser_serve(self, subparsers, common_parser, mid_common_parser): @@ -71,3 +72,9 @@ 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( + "--permissions", + dest="permissions", + choices=["all", "no-delete", "write-only", "read-only"], + help="Set repository permission mode. Equivalent to setting BORG_REPO_PERMISSIONS environment variable.", + ) diff --git a/src/borg/remote.py b/src/borg/remote.py index 9aceb14d9..4f315c78a 100644 --- a/src/borg/remote.py +++ b/src/borg/remote.py @@ -164,12 +164,13 @@ class RepositoryServer: # pragma: no cover "store_move", ) - def __init__(self, restrict_to_paths, restrict_to_repositories, use_socket): + def __init__(self, restrict_to_paths, restrict_to_repositories, use_socket, permissions=None): self.repository = None self.RepoCls = None self.rpc_methods = ("open", "close", "negotiate") self.restrict_to_paths = restrict_to_paths self.restrict_to_repositories = restrict_to_repositories + self.permissions = permissions # This flag is parsed from the serve command line via Archiver.do_serve, # i.e. it reflects local system policy and generally ranks higher than # whatever the client wants, except when initializing a new repository @@ -375,9 +376,10 @@ class RepositoryServer: # pragma: no cover break else: raise PathNotAllowed(path) - self.repository = self.RepoCls( - path, create, lock_wait=lock_wait, lock=lock, exclusive=exclusive, send_log_cb=self.send_queued_log - ) + kwargs = dict(lock_wait=lock_wait, lock=lock, exclusive=exclusive, send_log_cb=self.send_queued_log) + if not v1_or_v2: + kwargs["permissions"] = self.permissions + self.repository = self.RepoCls(path, create, **kwargs) self.repository.__enter__() # clean exit handled by serve() method return self.repository.id diff --git a/src/borg/repository.py b/src/borg/repository.py index 9770d3d1b..5f9c7cf18 100644 --- a/src/borg/repository.py +++ b/src/borg/repository.py @@ -92,7 +92,16 @@ class Repository: exit_mcode = 21 - def __init__(self, path_or_location, create=False, exclusive=False, lock_wait=1.0, lock=True, send_log_cb=None): + def __init__( + self, + path_or_location, + create=False, + exclusive=False, + lock_wait=1.0, + lock=True, + send_log_cb=None, + permissions=None, + ): if isinstance(path_or_location, Location): location = path_or_location if location.proto == "file": @@ -114,8 +123,8 @@ class Repository: "keys/": [0], "locks/": [0], } - # Get permissions from environment variable - permissions = os.environ.get("BORG_REPO_PERMISSIONS", "all") + # Get permissions from parameter or environment variable + permissions = permissions if permissions is not None else os.environ.get("BORG_REPO_PERMISSIONS", "all") if permissions == "all": permissions = None # permissions system will not be used