Browse Source

Merge pull request #613 from ThomasWaldmann/xdg-base-dir-keys

use xdg base dir for keys
TW 9 years ago
parent
commit
5d93b6cda9
7 changed files with 88 additions and 16 deletions
  1. 7 2
      borg/archiver.py
  2. 2 2
      borg/helpers.py
  3. 20 2
      borg/testsuite/helpers.py
  4. 50 1
      borg/upgrader.py
  5. 1 1
      docs/internals.rst
  6. 1 1
      docs/quickstart.rst
  7. 7 7
      docs/usage.rst

+ 7 - 2
borg/archiver.py

@@ -23,7 +23,7 @@ from .helpers import Error, location_validator, format_time, format_file_size, \
 from .logger import create_logger, setup_logging
 from .logger import create_logger, setup_logging
 logger = create_logger()
 logger = create_logger()
 from .compress import Compressor, COMPR_BUFFER
 from .compress import Compressor, COMPR_BUFFER
-from .upgrader import AtticRepositoryUpgrader
+from .upgrader import AtticRepositoryUpgrader, BorgRepositoryUpgrader
 from .repository import Repository
 from .repository import Repository
 from .cache import Cache
 from .cache import Cache
 from .key import key_creator, RepoKey, PassphraseKey
 from .key import key_creator, RepoKey, PassphraseKey
@@ -557,6 +557,11 @@ class Archiver:
 
 
         # XXX: should auto-detect if it is an attic repository here
         # XXX: should auto-detect if it is an attic repository here
         repo = AtticRepositoryUpgrader(args.location.path, create=False)
         repo = AtticRepositoryUpgrader(args.location.path, create=False)
+        try:
+            repo.upgrade(args.dry_run, inplace=args.inplace, progress=args.progress)
+        except NotImplementedError as e:
+            print("warning: %s" % e)
+        repo = BorgRepositoryUpgrader(args.location.path, create=False)
         try:
         try:
             repo.upgrade(args.dry_run, inplace=args.inplace, progress=args.progress)
             repo.upgrade(args.dry_run, inplace=args.inplace, progress=args.progress)
         except NotImplementedError as e:
         except NotImplementedError as e:
@@ -1214,7 +1219,7 @@ class Archiver:
         It will change the magic strings in the repository's segments
         It will change the magic strings in the repository's segments
         to match the new Borg magic strings. The keyfiles found in
         to match the new Borg magic strings. The keyfiles found in
         $ATTIC_KEYS_DIR or ~/.attic/keys/ will also be converted and
         $ATTIC_KEYS_DIR or ~/.attic/keys/ will also be converted and
-        copied to $BORG_KEYS_DIR or ~/.borg/keys.
+        copied to $BORG_KEYS_DIR or ~/.config/borg/keys.
 
 
         The cache files are converted, from $ATTIC_CACHE_DIR or
         The cache files are converted, from $ATTIC_CACHE_DIR or
         ~/.cache/attic to $BORG_CACHE_DIR or ~/.cache/borg, but the
         ~/.cache/attic to $BORG_CACHE_DIR or ~/.cache/borg, but the

+ 2 - 2
borg/helpers.py

@@ -209,8 +209,8 @@ class Statistics:
 
 
 def get_keys_dir():
 def get_keys_dir():
     """Determine where to repository keys and cache"""
     """Determine where to repository keys and cache"""
-    return os.environ.get('BORG_KEYS_DIR',
-                          os.path.join(os.path.expanduser('~'), '.borg', 'keys'))
+    xdg_config = os.environ.get('XDG_CONFIG_HOME', os.path.join(os.path.expanduser('~'), '.config'))
+    return os.environ.get('BORG_KEYS_DIR', os.path.join(xdg_config, 'borg', 'keys'))
 
 
 
 
 def get_cache_dir():
 def get_cache_dir():

+ 20 - 2
borg/testsuite/helpers.py

@@ -10,7 +10,8 @@ import msgpack
 import msgpack.fallback
 import msgpack.fallback
 
 
 from ..helpers import Location, format_file_size, format_timedelta, make_path_safe, \
 from ..helpers import Location, format_file_size, format_timedelta, make_path_safe, \
-    prune_within, prune_split, get_cache_dir, Statistics, is_slow_msgpack, yes, TRUISH, FALSISH, DEFAULTISH, \
+    prune_within, prune_split, get_cache_dir, get_keys_dir, Statistics, is_slow_msgpack, \
+    yes, TRUISH, FALSISH, DEFAULTISH, \
     StableDict, int_to_bigint, bigint_to_int, parse_timestamp, CompressionSpec, ChunkerParams, \
     StableDict, int_to_bigint, bigint_to_int, parse_timestamp, CompressionSpec, ChunkerParams, \
     ProgressIndicatorPercent, ProgressIndicatorEndless, load_excludes, parse_pattern, \
     ProgressIndicatorPercent, ProgressIndicatorEndless, load_excludes, parse_pattern, \
     PatternMatcher, RegexPattern, PathPrefixPattern, FnmatchPattern, ShellPattern
     PatternMatcher, RegexPattern, PathPrefixPattern, FnmatchPattern, ShellPattern
@@ -579,7 +580,7 @@ class TestParseTimestamp(BaseTestCase):
 
 
 
 
 def test_get_cache_dir():
 def test_get_cache_dir():
-    """test that get_cache_dir respects environement"""
+    """test that get_cache_dir respects environment"""
     # reset BORG_CACHE_DIR in order to test default
     # reset BORG_CACHE_DIR in order to test default
     old_env = None
     old_env = None
     if os.environ.get('BORG_CACHE_DIR'):
     if os.environ.get('BORG_CACHE_DIR'):
@@ -595,6 +596,23 @@ def test_get_cache_dir():
         os.environ['BORG_CACHE_DIR'] = old_env
         os.environ['BORG_CACHE_DIR'] = old_env
 
 
 
 
+def test_get_keys_dir():
+    """test that get_keys_dir respects environment"""
+    # reset BORG_KEYS_DIR in order to test default
+    old_env = None
+    if os.environ.get('BORG_KEYS_DIR'):
+        old_env = os.environ['BORG_KEYS_DIR']
+        del(os.environ['BORG_KEYS_DIR'])
+    assert get_keys_dir() == os.path.join(os.path.expanduser('~'), '.config', 'borg', 'keys')
+    os.environ['XDG_CONFIG_HOME'] = '/var/tmp/.config'
+    assert get_keys_dir() == os.path.join('/var/tmp/.config', 'borg', 'keys')
+    os.environ['BORG_KEYS_DIR'] = '/var/tmp'
+    assert get_keys_dir() == '/var/tmp'
+    # reset old env
+    if old_env is not None:
+        os.environ['BORG_KEYS_DIR'] = old_env
+
+
 @pytest.fixture()
 @pytest.fixture()
 def stats():
 def stats():
     stats = Statistics()
     stats = Statistics()

+ 50 - 1
borg/upgrader.py

@@ -136,7 +136,7 @@ class AtticRepositoryUpgrader(Repository):
         replacement pattern is `s/ATTIC KEY/BORG_KEY/` in
         replacement pattern is `s/ATTIC KEY/BORG_KEY/` in
         `get_keys_dir()`, that is `$ATTIC_KEYS_DIR` or
         `get_keys_dir()`, that is `$ATTIC_KEYS_DIR` or
         `$HOME/.attic/keys`, and moved to `$BORG_KEYS_DIR` or
         `$HOME/.attic/keys`, and moved to `$BORG_KEYS_DIR` or
-        `$HOME/.borg/keys`.
+        `$HOME/.config/borg/keys`.
 
 
         no need to decrypt to convert. we need to rewrite the whole
         no need to decrypt to convert. we need to rewrite the whole
         key file because magic string length changed, but that's not a
         key file because magic string length changed, but that's not a
@@ -275,3 +275,52 @@ class AtticKeyfileKey(KeyfileKey):
                 if line and line.startswith(cls.FILE_ID) and line[10:] == id:
                 if line and line.startswith(cls.FILE_ID) and line[10:] == id:
                     return filename
                     return filename
         raise KeyfileNotFoundError(repository.path, keys_dir)
         raise KeyfileNotFoundError(repository.path, keys_dir)
+
+
+class BorgRepositoryUpgrader(Repository):
+    def upgrade(self, dryrun=True, inplace=False, progress=False):
+        """convert an old borg repository to a current borg repository
+        """
+        logger.info("converting borg 0.xx to borg current")
+        try:
+            keyfile = self.find_borg0xx_keyfile()
+        except KeyfileNotFoundError:
+            logger.warning("no key file found for repository")
+        else:
+            self.move_keyfiles(keyfile, dryrun)
+
+    def find_borg0xx_keyfile(self):
+        return Borg0xxKeyfileKey.find_key_file(self)
+
+    def move_keyfiles(self, keyfile, dryrun):
+        filename = os.path.basename(keyfile)
+        new_keyfile = os.path.join(get_keys_dir(), filename)
+        try:
+            os.rename(keyfile, new_keyfile)
+        except FileExistsError:
+            # likely the attic -> borg upgrader already put it in the final location
+            pass
+
+
+class Borg0xxKeyfileKey(KeyfileKey):
+    """backwards compatible borg 0.xx key file parser"""
+
+    @staticmethod
+    def get_keys_dir():
+        return os.environ.get('BORG_KEYS_DIR',
+                              os.path.join(os.path.expanduser('~'), '.borg', 'keys'))
+
+    @classmethod
+    def find_key_file(cls, repository):
+        get_keys_dir = cls.get_keys_dir
+        id = hexlify(repository.id).decode('ascii')
+        keys_dir = get_keys_dir()
+        if not os.path.exists(keys_dir):
+            raise KeyfileNotFoundError(repository.path, keys_dir)
+        for name in os.listdir(keys_dir):
+            filename = os.path.join(keys_dir, name)
+            with open(filename, 'r') as fd:
+                line = fd.readline().strip()
+                if line and line.startswith(cls.FILE_ID) and line[len(cls.FILE_ID)+1:] == id:
+                    return filename
+        raise KeyfileNotFoundError(repository.path, keys_dir)

+ 1 - 1
docs/internals.rst

@@ -358,7 +358,7 @@ Key files
 ---------
 ---------
 
 
 When initialized with the ``init -e keyfile`` command, |project_name|
 When initialized with the ``init -e keyfile`` command, |project_name|
-needs an associated file in ``$HOME/.borg/keys`` to read and write
+needs an associated file in ``$HOME/.config/borg/keys`` to read and write
 the repository. The format is based on msgpack_, base64 encoding and
 the repository. The format is based on msgpack_, base64 encoding and
 PBKDF2_ SHA256 hashing, which is then encoded again in a msgpack_.
 PBKDF2_ SHA256 hashing, which is then encoded again in a msgpack_.
 
 

+ 1 - 1
docs/quickstart.rst

@@ -167,7 +167,7 @@ is being made.
     protection. The repository server never sees the plaintext key.
     protection. The repository server never sees the plaintext key.
 
 
 ``keyfile`` mode
 ``keyfile`` mode
-    The key is stored on your local disk (in ``~/.borg/keys/``).
+    The key is stored on your local disk (in ``~/.config/borg/keys/``).
     Use this mode if you want "passphrase and having-the-key" security.
     Use this mode if you want "passphrase and having-the-key" security.
 
 
 In both modes, the key is stored in encrypted form and can be only decrypted
 In both modes, the key is stored in encrypted form and can be only decrypted

+ 7 - 7
docs/usage.rst

@@ -85,7 +85,7 @@ Some automatic "answerers" (if set, they automatically answer confirmation quest
 
 
 Directories:
 Directories:
     BORG_KEYS_DIR
     BORG_KEYS_DIR
-        Default to '~/.borg/keys'. This directory contains keys for encrypted repositories.
+        Default to '~/.config/borg/keys'. This directory contains keys for encrypted repositories.
     BORG_CACHE_DIR
     BORG_CACHE_DIR
         Default to '~/.cache/borg'. This directory contains the local cache and might need a lot
         Default to '~/.cache/borg'. This directory contains the local cache and might need a lot
         of space for dealing with big repositories).
         of space for dealing with big repositories).
@@ -203,9 +203,9 @@ be stored inside the repository (in its "config" file). In above mentioned
 attack scenario, the attacker will have the key (but not the passphrase).
 attack scenario, the attacker will have the key (but not the passphrase).
 
 
 If you want "passphrase and having-the-key" security, use the ``keyfile`` mode.
 If you want "passphrase and having-the-key" security, use the ``keyfile`` mode.
-The key will be stored in your home directory (in ``.borg/keys``). In the attack
-scenario, the attacker who has just access to your repo won't have the key (and
-also not the passphrase).
+The key will be stored in your home directory (in ``.config/borg/keys``). In
+the attack scenario, the attacker who has just access to your repo won't have
+the key (and also not the passphrase).
 
 
 Make a backup copy of the key file (``keyfile`` mode) or repo config file
 Make a backup copy of the key file (``keyfile`` mode) or repo config file
 (``repokey`` mode) and keep it at a safe place, so you still have the key in
 (``repokey`` mode) and keep it at a safe place, so you still have the key in
@@ -411,15 +411,15 @@ Examples
     Initializing repository at "/mnt/backup"
     Initializing repository at "/mnt/backup"
     Enter passphrase (empty for no passphrase):
     Enter passphrase (empty for no passphrase):
     Enter same passphrase again: 
     Enter same passphrase again: 
-    Key file "/home/USER/.borg/keys/mnt_backup" created.
+    Key file "/home/USER/.config/borg/keys/mnt_backup" created.
     Keep this file safe. Your data will be inaccessible without it.
     Keep this file safe. Your data will be inaccessible without it.
 
 
     # Change key file passphrase
     # Change key file passphrase
     $ borg change-passphrase /mnt/backup
     $ borg change-passphrase /mnt/backup
-    Enter passphrase for key file /home/USER/.borg/keys/mnt_backup:
+    Enter passphrase for key file /home/USER/.config/borg/keys/mnt_backup:
     New passphrase: 
     New passphrase: 
     Enter same passphrase again: 
     Enter same passphrase again: 
-    Key file "/home/USER/.borg/keys/mnt_backup" updated
+    Key file "/home/USER/.config/borg/keys/mnt_backup" updated
 
 
 
 
 .. include:: usage/serve.rst.inc
 .. include:: usage/serve.rst.inc