Repository: borgbackup/borg

Issue:
Cannot configure a remote keyfile repository
## Have you checked borgbackup docs, FAQ, and open Github issues?

Yes

## Is this a BUG / ISSUE report or a QUESTION?

More of an impossibility, not a bug per se.

## Describe the problem you're observing.

I created a remote repository with a local key:

```sh
borg init --remote-path=borg1 --encryption=keyfile userid@hostname:borg-repo-name
```

I cannot use `borg config` on that repository because it is remote:
```text
borg config --remote-path=borg1 userid@hostname:borg-repo-name additional_free_space 2G
[...usage...]
borg config: error: argument REPOSITORY: "userid@hostname:borg-repo-name": Repository must be local
```

I also cannot use `borg config` remotely either because Borg thinks it requires a key to do so (which it doesn't actually, as the config file is in clear text):
```text
$ ssh userid@hostname borg1 config borg-repo-name additional_free_space 2G
No key file for repository /data2/home/userid/borg-repo-name found in /data2/home/userid/.config/borg/keys.
```

My recourse will be to download the file, modify it manually and upload it back.  Ideally, one of the two methods above should be made to work.


Discussion:
Hmm, that is strange. The last command should be made working.

Resolving patch:
diff --git a/src/borg/archiver.py b/src/borg/archiver.py
--- a/src/borg/archiver.py
+++ b/src/borg/archiver.py
@@ -1718,8 +1718,8 @@ def do_with_lock(self, args, repository):
             # see issue #1867.
             repository.commit()
 
-    @with_repository(exclusive=True, cache=True, compatibility=(Manifest.Operation.WRITE,))
-    def do_config(self, args, repository, manifest, key, cache):
+    @with_repository(exclusive=True, manifest=False)
+    def do_config(self, args, repository):
         """get, set, and delete values in a repository or cache config file"""
 
         def repo_validate(section, name, value=None, check_value=True):
@@ -1770,34 +1770,43 @@ def cache_validate(section, name, value=None, check_value=True):
             name = args.name
 
         if args.cache:
-            cache.cache_config.load()
-            config = cache.cache_config._config
-            save = cache.cache_config.save
-            validate = cache_validate
-        else:
-            config = repository.config
-            save = lambda: repository.save_config(repository.path, repository.config)
-            validate = repo_validate
-
-        if args.delete:
-            validate(section, name, check_value=False)
-            config.remove_option(section, name)
-            if len(config.options(section)) == 0:
-                config.remove_section(section)
-            save()
-        elif args.value:
-            validate(section, name, args.value)
-            if section not in config.sections():
-                config.add_section(section)
-            config.set(section, name, args.value)
-            save()
-        else:
-            try:
-                print(config.get(section, name))
-            except (configparser.NoOptionError, configparser.NoSectionError) as e:
-                print(e, file=sys.stderr)
-                return EXIT_WARNING
-        return EXIT_SUCCESS
+            manifest, key = Manifest.load(repository, (Manifest.Operation.WRITE,))
+            assert_secure(repository, manifest)
+            cache = Cache(repository, key, manifest, lock_wait=self.lock_wait)
+
+        try:
+            if args.cache:
+                cache.cache_config.load()
+                config = cache.cache_config._config
+                save = cache.cache_config.save
+                validate = cache_validate
+            else:
+                config = repository.config
+                save = lambda: repository.save_config(repository.path, repository.config)
+                validate = repo_validate
+
+            if args.delete:
+                validate(section, name, check_value=False)
+                config.remove_option(section, name)
+                if len(config.options(section)) == 0:
+                    config.remove_section(section)
+                save()
+            elif args.value:
+                validate(section, name, args.value)
+                if section not in config.sections():
+                    config.add_section(section)
+                config.set(section, name, args.value)
+                save()
+            else:
+                try:
+                    print(config.get(section, name))
+                except (configparser.NoOptionError, configparser.NoSectionError) as e:
+                    print(e, file=sys.stderr)
+                    return EXIT_WARNING
+            return EXIT_SUCCESS
+        finally:
+            if args.cache:
+                cache.close()
 
     def do_debug_info(self, args):
         """display system information for debugging / bug reports"""


Test patch: