diff --git a/docs/deployment/central-backup-server.rst b/docs/deployment/central-backup-server.rst index 740c6a6f3..431bd531e 100644 --- a/docs/deployment/central-backup-server.rst +++ b/docs/deployment/central-backup-server.rst @@ -81,7 +81,7 @@ The options which are added to the key will perform the following: Due to the ``cd`` command we use, the server automatically changes the current working directory. Then client doesn't need to have knowledge of the absolute or relative remote repository path and can directly access the repositories at -``ssh://@/./``. +``@:``. .. note:: The setup above ignores all client given commandline parameters which are normally appended to the `borg serve` command. @@ -93,21 +93,21 @@ The client needs to initialize the `pictures` repository like this: :: - borg init ssh://backup@backup01.srv.local/./pictures + borg init backup@backup01.srv.local:pictures Or with the full path (should actually never be used, as only for demonstrational purposes). The server should automatically change the current working directory to the `` folder. :: - borg init ssh://backup@backup01.srv.local/home/backup/repos/johndoe.clnt.local/pictures + borg init backup@backup01.srv.local:/home/backup/repos/johndoe.clnt.local/pictures When `johndoe.clnt.local` tries to access a not restricted path the following error is raised. John Doe tries to backup into the Web 01 path: :: - borg init ssh://backup@backup01.srv.local/home/backup/repos/web01.srv.local/pictures + borg init backup@backup01.srv.local:/home/backup/repos/web01.srv.local/pictures :: diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 33eeda41d..dd9d45eaa 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -405,7 +405,7 @@ Borg can initialize and access repositories on remote hosts if the host is accessible using SSH. This is fastest and easiest when Borg is installed on the remote host, in which case the following syntax is used:: - $ borg init ssh://user@hostname/path/to/repo + $ borg init user@hostname:/path/to/repo Note: please see the usage chapter for a full documentation of repo URLs. diff --git a/docs/usage/general/repository-urls.rst.inc b/docs/usage/general/repository-urls.rst.inc index 153a03979..2855c19b2 100644 --- a/docs/usage/general/repository-urls.rst.inc +++ b/docs/usage/general/repository-urls.rst.inc @@ -14,16 +14,32 @@ Note: you may also prepend a ``file://`` to a filesystem path to get URL style. **Remote repositories** accessed via ssh user@host: -``ssh://user@host:port/path/to/repo`` - remote repo, absolute path +``user@host:/path/to/repo`` - remote repo, absolute path + +``ssh://user@host:port/path/to/repo`` - same, alternative syntax, port can be given + **Remote repositories with relative paths** can be given using this syntax: +``user@host:path/to/repo`` - path relative to current directory + +``user@host:~/path/to/repo`` - path relative to user's home directory + +``user@host:~other/path/to/repo`` - path relative to other's home directory + +Note: giving ``user@host:/./path/to/repo`` or ``user@host:/~/path/to/repo`` or +``user@host:/~other/path/to/repo`` is also supported, but not required here. + + +**Remote repositories with relative paths, alternative syntax with port**: + ``ssh://user@host:port/./path/to/repo`` - path relative to current directory ``ssh://user@host:port/~/path/to/repo`` - path relative to user's home directory ``ssh://user@host:port/~other/path/to/repo`` - path relative to other's home directory + If you frequently need the same repo URL, it is a good idea to set the ``BORG_REPO`` environment variable to set a default for the repo URL: @@ -36,31 +52,3 @@ to use the default - it will be read from BORG_REPO then. Use ``::`` syntax to give the repo URL when syntax requires giving a positional argument for the repo (e.g. ``borg mount :: /mnt``). - -Converting from scp-style repo URLs -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Borg does not support scp-style repo URLs any more. - -Here is how you can convert to URL style: - -:: - - user@host:path/to/repo # relative to cwd - --> - ssh://user@host:22/./path/to/repo # relative to cwd - or (usually the cwd is the home dir after ssh login) - ssh://user@host:22/~/path/to/repo # relative to home dir - - user@host:/path/to/repo # absolute repo path - --> - ssh://user@host:22/path/to/repo # absolute repo path - -Notes: - -Port 22 is the default, so you can omit the ``:22`` if you like. - -If you used some hack to use a non-standard port (which was not directly -supported by the scp-style repo specification), you can now do it that way -with the ``ssh:`` URL and remove the hack (usually in ssh configuration or -given via ``--rsh`` borg option). diff --git a/docs/usage/init.rst b/docs/usage/init.rst index 844bba4cb..73e62bb79 100644 --- a/docs/usage/init.rst +++ b/docs/usage/init.rst @@ -15,8 +15,8 @@ Examples # Remote repository (accesses a remote borg via ssh) # repokey: stores the (encrypted) key into /config - $ borg init --encryption=repokey-aes-ocb ssh://user@hostname/./backup + $ borg init --encryption=repokey-aes-ocb user@hostname:backup # Remote repository (accesses a remote borg via ssh) # keyfile: stores the (encrypted) key into ~/.config/borg/keys/ - $ borg init --encryption=keyfile-aes-ocb ssh://user@hostname/./backup + $ borg init --encryption=keyfile-aes-ocb user@hostname:backup diff --git a/src/borg/helpers/parseformat.py b/src/borg/helpers/parseformat.py index 3e145ada2..b68157128 100644 --- a/src/borg/helpers/parseformat.py +++ b/src/borg/helpers/parseformat.py @@ -315,7 +315,7 @@ class Location: # path must not contain :: (it ends at :: or string end), but may contain single colons. # to avoid ambiguities with other regexes, it must also not start with ":" nor with "//" nor with "ssh://". - local_path_re = r""" + scp_path_re = r""" (?!(:|//|ssh://)) # not starting with ":" or // or ssh:// (?P([^:]|(:(?!:)))+) # any chars, but no "::" """ @@ -361,8 +361,13 @@ class Location: (?Pfile):// # file:// """ + file_path_re + optional_archive_re, re.VERBOSE) # servername/path, path or path::archive - local_re = re.compile( - local_path_re + optional_archive_re, re.VERBOSE) # local path with optional archive + # note: scp_re is also used for local paths + scp_re = re.compile(r""" + ( + """ + optional_user_re + host_re + r""" # user@ (optional), host name or address + : # : (required!) + )? # user@host: part is optional + """ + scp_path_re + optional_archive_re, re.VERBOSE) # path with optional archive # get the repo from BORG_REPO env and the optional archive from param. # if the syntax requires giving REPOSITORY (see "borg mount"), @@ -438,11 +443,13 @@ class Location: self.path = normpath_special(m.group('path')) self.archive = m.group('archive') return True - m = self.local_re.match(text) + m = self.scp_re.match(text) if m: + self.user = m.group('user') + self._host = m.group('host') self.path = normpath_special(m.group('path')) self.archive = m.group('archive') - self.proto = 'file' + self.proto = self._host and 'ssh' or 'file' return True return False diff --git a/src/borg/testsuite/archiver.py b/src/borg/testsuite/archiver.py index 450ba1757..0983a271c 100644 --- a/src/borg/testsuite/archiver.py +++ b/src/borg/testsuite/archiver.py @@ -4014,7 +4014,7 @@ class ManifestAuthenticationTest(ArchiverTestCaseBase): class RemoteArchiverTestCase(ArchiverTestCase): - prefix = 'ssh://__testsuite__' + prefix = '__testsuite__:' def open_repository(self): return RemoteRepository(Location(self.repository_location)) diff --git a/src/borg/testsuite/helpers.py b/src/borg/testsuite/helpers.py index b181da564..8014101a5 100644 --- a/src/borg/testsuite/helpers.py +++ b/src/borg/testsuite/helpers.py @@ -113,6 +113,32 @@ class TestLocationWithoutEnv: "Location(proto='file', user=None, host=None, port=None, path='/some/path', archive=None)" assert Location('file:///some/path').to_key_filename() == keys_dir + 'some_path' + def test_scp(self, monkeypatch, keys_dir): + monkeypatch.delenv('BORG_REPO', raising=False) + assert repr(Location('user@host:/some/path::archive')) == \ + "Location(proto='ssh', user='user', host='host', port=None, path='/some/path', archive='archive')" + assert repr(Location('user@host:/some/path')) == \ + "Location(proto='ssh', user='user', host='host', port=None, path='/some/path', archive=None)" + assert repr(Location('user@[::]:/some/path::archive')) == \ + "Location(proto='ssh', user='user', host='::', port=None, path='/some/path', archive='archive')" + assert repr(Location('user@[::]:/some/path')) == \ + "Location(proto='ssh', user='user', host='::', port=None, path='/some/path', archive=None)" + assert repr(Location('user@[2001:db8::]:/some/path::archive')) == \ + "Location(proto='ssh', user='user', host='2001:db8::', port=None, path='/some/path', archive='archive')" + assert repr(Location('user@[2001:db8::]:/some/path')) == \ + "Location(proto='ssh', user='user', host='2001:db8::', port=None, path='/some/path', archive=None)" + assert repr(Location('user@[2001:db8::c0:ffee]:/some/path::archive')) == \ + "Location(proto='ssh', user='user', host='2001:db8::c0:ffee', port=None, path='/some/path', archive='archive')" + assert repr(Location('user@[2001:db8::c0:ffee]:/some/path')) == \ + "Location(proto='ssh', user='user', host='2001:db8::c0:ffee', port=None, path='/some/path', archive=None)" + assert repr(Location('user@[2001:db8::192.0.2.1]:/some/path::archive')) == \ + "Location(proto='ssh', user='user', host='2001:db8::192.0.2.1', port=None, path='/some/path', archive='archive')" + assert repr(Location('user@[2001:db8::192.0.2.1]:/some/path')) == \ + "Location(proto='ssh', user='user', host='2001:db8::192.0.2.1', port=None, path='/some/path', archive=None)" + assert Location('user@[2001:db8::192.0.2.1]:/some/path').to_key_filename() == keys_dir + '2001_db8__192_0_2_1__some_path' + assert repr(Location('user@[2a02:0001:0002:0003:0004:0005:0006:0007]:/some/path')) == \ + "Location(proto='ssh', user='user', host='2a02:0001:0002:0003:0004:0005:0006:0007', port=None, path='/some/path', archive=None)" + def test_smb(self, monkeypatch, keys_dir): monkeypatch.delenv('BORG_REPO', raising=False) assert repr(Location('file:////server/share/path::archive')) == \ @@ -171,6 +197,8 @@ class TestLocationWithoutEnv: def test_user_parsing(self): # see issue #1930 + assert repr(Location('host:path::2016-12-31@23:59:59')) == \ + "Location(proto='ssh', user=None, host='host', port=None, path='path', archive='2016-12-31@23:59:59')" assert repr(Location('ssh://host/path::2016-12-31@23:59:59')) == \ "Location(proto='ssh', user=None, host='host', port=None, path='/path', archive='2016-12-31@23:59:59')" @@ -254,6 +282,15 @@ class TestLocationWithEnv: assert repr(Location()) == \ "Location(proto='file', user=None, host=None, port=None, path='/some/path', archive=None)" + def test_scp(self, monkeypatch): + monkeypatch.setenv('BORG_REPO', 'user@host:/some/path') + assert repr(Location('::archive')) == \ + "Location(proto='ssh', user='user', host='host', port=None, path='/some/path', archive='archive')" + assert repr(Location('::')) == \ + "Location(proto='ssh', user='user', host='host', port=None, path='/some/path', archive=None)" + assert repr(Location()) == \ + "Location(proto='ssh', user='user', host='host', port=None, path='/some/path', archive=None)" + def test_folder(self, monkeypatch): monkeypatch.setenv('BORG_REPO', 'path') assert repr(Location('::archive')) == \ diff --git a/src/borg/testsuite/repository.py b/src/borg/testsuite/repository.py index b4944e58a..83cf728e4 100644 --- a/src/borg/testsuite/repository.py +++ b/src/borg/testsuite/repository.py @@ -861,7 +861,7 @@ class RemoteRepositoryTestCase(RepositoryTestCase): repository = None # type: RemoteRepository def open(self, create=False): - return RemoteRepository(Location('ssh://__testsuite__' + os.path.join(self.tmppath, 'repository')), + return RemoteRepository(Location('__testsuite__:' + os.path.join(self.tmppath, 'repository')), exclusive=True, create=create) def _get_mock_args(self): @@ -937,11 +937,12 @@ class RemoteRepositoryTestCase(RepositoryTestCase): def test_ssh_cmd(self): args = self._get_mock_args() self.repository._args = args + assert self.repository.ssh_cmd(Location('example.com:foo')) == ['ssh', 'example.com'] assert self.repository.ssh_cmd(Location('ssh://example.com/foo')) == ['ssh', 'example.com'] assert self.repository.ssh_cmd(Location('ssh://user@example.com/foo')) == ['ssh', 'user@example.com'] assert self.repository.ssh_cmd(Location('ssh://user@example.com:1234/foo')) == ['ssh', '-p', '1234', 'user@example.com'] os.environ['BORG_RSH'] = 'ssh --foo' - assert self.repository.ssh_cmd(Location('ssh://example.com/foo')) == ['ssh', '--foo', 'example.com'] + assert self.repository.ssh_cmd(Location('example.com:foo')) == ['ssh', '--foo', 'example.com'] def test_borg_cmd(self): assert self.repository.borg_cmd(None, testing=True) == [sys.executable, '-m', 'borg.archiver', 'serve'] @@ -963,7 +964,7 @@ class RemoteRepositoryTestCase(RepositoryTestCase): '--storage-quota=314159265'] args.rsh = 'ssh -i foo' self.repository._args = args - assert self.repository.ssh_cmd(Location('ssh://example.com/foo')) == ['ssh', '-i', 'foo', 'example.com'] + assert self.repository.ssh_cmd(Location('example.com:foo')) == ['ssh', '-i', 'foo', 'example.com'] class RemoteLegacyFree(RepositoryTestCaseBase): @@ -971,7 +972,7 @@ class RemoteLegacyFree(RepositoryTestCaseBase): def open(self, create=False): with patch.object(RemoteRepository, 'dictFormat', True): - return RemoteRepository(Location('ssh://__testsuite__' + os.path.join(self.tmppath, 'repository')), + return RemoteRepository(Location('__testsuite__:' + os.path.join(self.tmppath, 'repository')), exclusive=True, create=create) def test_legacy_free(self): @@ -994,7 +995,7 @@ class RemoteLegacyFree(RepositoryTestCaseBase): class RemoteRepositoryCheckTestCase(RepositoryCheckTestCase): def open(self, create=False): - return RemoteRepository(Location('ssh://__testsuite__' + os.path.join(self.tmppath, 'repository')), + return RemoteRepository(Location('__testsuite__:' + os.path.join(self.tmppath, 'repository')), exclusive=True, create=create) def test_crash_before_compact(self):