Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@ New features:
- WIP packs project, major repo format changes, you must create new repos! #8572
- rest:// repository URLs - connect via ssh to remote borgstore REST server,
talking http via stdio, #9593
- removed ssh:// and socket:// support for current repositories; use a rest://
repository instead (it can tunnel over ssh). ssh:// and ``borg serve`` remain
available only for legacy (borg 1.x / v1) repositories, e.g. for
``borg transfer --from-borg1 --other-repo ssh://...``.
- prune: show total vs matching archives in output, #9262
- prune: add --json option, #9222
- archive: preserve cwd archive metadata, #9495
Expand Down
8 changes: 8 additions & 0 deletions docs/deployment/central-backup-server.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ Central repository server with Ansible or Salt
This section gives an example of how to set up a Borg repository server for multiple
clients.

.. note::

This example predates Borg 2 and uses the legacy ``ssh://`` transport (served
by ``borg serve``) and ``borg init``. With Borg 2, the ``ssh://`` transport is
only used for legacy borg 1.x (v1) repositories; for current repositories use a
``rest://`` repository instead (Borg connects via ssh and runs a borgstore REST
server on the remote host), and use ``borg repo-create`` instead of ``borg init``.

Machines
--------

Expand Down
28 changes: 16 additions & 12 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ If you only back up your own files, run it as your normal user (i.e. not root).

For a local repository always use the same user to invoke borg.

For a remote repository: always use e.g., ssh://borg@remote_host. You can use this
For a remote repository: always use e.g., rest://borg@remote_host (Borg connects
via ssh and runs a borgstore REST server on the remote). You can use this
from different local users; the remote user running borg and accessing the
repo will always be `borg`.

Expand Down Expand Up @@ -142,7 +143,7 @@ backed up and that the ``prune`` command keeps and deletes the correct backups.
#!/bin/sh

# Setting this, so the repo does not need to be given on the commandline:
export BORG_REPO=ssh://username@example.com:2022/~/backup/main
export BORG_REPO=rest://username@example.com:2022/path/to/backup/main

# See the section "Passphrase notes" for more infos.
export BORG_PASSPHRASE='XYZl0ngandsecurepa_55_phrasea&&123'
Expand Down Expand Up @@ -361,19 +362,21 @@ Remote repositories

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::
is installed on the remote host, in which case a ``rest://`` repository URL is
used. Borg connects via SSH and runs a borgstore REST server on the remote host
(talking HTTP over stdio)::

$ borg -r ssh://user@hostname:port/path/to/repo repo-create ...
$ borg -r rest://user@hostname:port/path/to/repo repo-create ...

Note: Please see the usage chapter for a full documentation of repo URLs. Also
see :ref:`ssh_configuration` for recommended settings to avoid disconnects and hangs.

Remote operations over SSH can be automated with SSH keys. You can restrict the
use of the SSH keypair by prepending a forced command to the SSH public key in
the remote server's `authorized_keys` file. This example will start Borg
in server mode and limit it to a specific filesystem path::
.. note::

command="borg serve --restrict-to-path /path/to/repo",restrict ssh-rsa AAAAB3[...]
The legacy ``ssh://`` transport, served by ``borg serve`` on the remote host,
is now only used to access legacy borg 1.x (v1) repositories (e.g. via
``borg transfer --from-borg1 --other-repo ssh://...``). For current
repositories, use a ``rest://`` repository as shown above.

If it is not possible to install Borg on the remote host,
it is still possible to use the remote host to store a repository by
Expand Down Expand Up @@ -530,7 +533,8 @@ Example with **borg extract**:
Difference when using a **remote borg backup server**:

It is basically all the same as with the local repository, but you need to
refer to the repo using a ``ssh://`` URL.
refer to the repo using a ``rest://`` URL (Borg connects via ssh and runs a
borgstore REST server on the remote host).

In the given example, ``borg`` is the user name used to log into the machine
``backup.example.org`` which runs ssh on port ``2222`` and has the borg repo
Expand All @@ -544,6 +548,6 @@ case if unattended, automated backups were done).

::

borg -r ssh://borg@backup.example.org:2222/path/to/repo mount /mnt/borg
borg -r rest://borg@backup.example.org:2222/path/to/repo mount /mnt/borg
# or
borg -r ssh://borg@backup.example.org:2222/path/to/repo extract archive
borg -r rest://borg@backup.example.org:2222/path/to/repo extract archive
5 changes: 2 additions & 3 deletions src/borg/archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@
from .item import Item, ArchiveItem, ItemDiff
from . import platform
from .platform import acl_get, acl_set, set_flags, get_flags, swidth
from .remote import RemoteRepository, cache_if_remote
from .repository import Repository, NoManifestError
from .repository import Repository, NoManifestError, cache_if_remote
from .repoobj import RepoObj

has_link = hasattr(os, "link")
Expand Down Expand Up @@ -1773,7 +1772,7 @@ def check(
:param oldest/newest: only check archives older/newer than timedelta from oldest/newest archive timestamp
:param verify_data: integrity verification of data referenced by archives
"""
if not isinstance(repository, (Repository, RemoteRepository)):
if not isinstance(repository, Repository):
logger.error("Checking legacy repositories is not supported.")
return False
logger.info("Starting archive consistency check...")
Expand Down
6 changes: 3 additions & 3 deletions src/borg/archiver/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
from ..helpers import sig_int
from ..helpers import get_config_dir
from ..platformflags import is_msystem
from ..remote import RemoteRepository
from ..legacy.remote import LegacyRemoteRepository
from ..selftest import selftest
except BaseException:
# an unhandled exception in the try-block would cause the borg cli command to exit with rc 1 due to python's
Expand Down Expand Up @@ -538,7 +538,7 @@ def sig_trace_handler(sig_no, stack): # pragma: no cover

def format_tb(exc):
qualname = type(exc).__qualname__
remote = isinstance(exc, RemoteRepository.RPCError)
remote = isinstance(exc, LegacyRemoteRepository.RPCError)
if remote:
prefix = "Borg server: "
trace_back = "\n".join(prefix + line for line in exc.exception_full.splitlines())
Expand Down Expand Up @@ -632,7 +632,7 @@ def main(): # pragma: no cover
tb_log_level = logging.ERROR if e.traceback else logging.DEBUG
tb = format_tb(e)
exit_code = e.exit_code
except RemoteRepository.RPCError as e:
except LegacyRemoteRepository.RPCError as e:
important = e.traceback
msg = e.exception_full if important else e.get_message()
msgid = e.exception_class
Expand Down
26 changes: 9 additions & 17 deletions src/borg/archiver/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from ..helpers.nanorst import rst_to_terminal
from ..manifest import Manifest, AI_HUMAN_SORT_KEYS
from ..patterns import PatternMatcher
from ..remote import RemoteRepository
from ..repository import Repository
from ..repoobj import RepoObj
from ..patterns import (
Expand All @@ -30,16 +29,19 @@


def get_repository(location, *, create, exclusive, lock_wait, lock, args, v1_legacy):
if location.proto in ("ssh", "socket"):
if location.proto == "ssh":
if v1_legacy:
from ..legacy.remote import LegacyRemoteRepository

RemoteRepoCls = LegacyRemoteRepository
repository = LegacyRemoteRepository(
location, create=create, exclusive=exclusive, lock_wait=lock_wait, lock=lock, args=args
)
else:
RemoteRepoCls = RemoteRepository
repository = RemoteRepoCls(
location, create=create, exclusive=exclusive, lock_wait=lock_wait, lock=lock, args=args
)
raise Error(
"ssh:// is no longer supported for current repositories; use rest:// instead "
"(it can tunnel over ssh). ssh:// remains available only for legacy v1 repositories "
"via --from-borg1."
)

elif (
location.proto in ("rest", "sftp", "file", "http", "https", "rclone", "s3", "b2") and not v1_legacy
Expand Down Expand Up @@ -584,16 +586,6 @@ def define_common_options(add_common_option):
action=Highlander,
help="Use this command to connect to the 'borg serve' process (default: 'ssh')",
)
add_common_option(
"--socket",
metavar="PATH",
dest="use_socket",
default=False,
const=True,
nargs="?",
action=Highlander,
help="Use UNIX DOMAIN (IPC) socket at PATH for client/server communication with socket: protocol.",
)
add_common_option(
"-r",
"--repo",
Expand Down
3 changes: 1 addition & 2 deletions src/borg/archiver/analyze_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from ..helpers import ProgressIndicatorPercent
from ..helpers.argparsing import ArgumentParser
from ..manifest import Manifest
from ..remote import RemoteRepository
from ..repository import Repository

from ..logger import create_logger
Expand All @@ -20,7 +19,7 @@ class ArchiveAnalyzer:
def __init__(self, args, repository, manifest):
self.args = args
self.repository = repository
assert isinstance(repository, (Repository, RemoteRepository))
assert isinstance(repository, Repository)
self.manifest = manifest
self.difference_by_path = defaultdict(int) # directory path -> count of chunks changed

Expand Down
3 changes: 1 addition & 2 deletions src/borg/archiver/compact_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from ..helpers import set_ec, EXIT_ERROR, format_file_size, bin_to_hex
from ..helpers import ProgressIndicatorPercent
from ..manifest import Manifest
from ..remote import RemoteRepository
from ..repository import Repository, repo_lister

from ..logger import create_logger
Expand All @@ -22,7 +21,7 @@
class ArchiveGarbageCollector:
def __init__(self, repository, manifest, *, stats, iec):
self.repository = repository
assert isinstance(repository, (Repository, RemoteRepository))
assert isinstance(repository, Repository)
self.manifest = manifest
self.chunks = None # a ChunkIndex, here used for: id -> (is_used, stored_size)
self.total_files = None # overall number of source files written to all archives in this repo
Expand Down
2 changes: 1 addition & 1 deletion src/borg/archiver/mount_cmds.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from ..helpers import umount
from ..helpers.argparsing import ArgumentParser
from ..manifest import Manifest
from ..remote import cache_if_remote
from ..repository import cache_if_remote

from ..logger import create_logger

Expand Down
17 changes: 7 additions & 10 deletions src/borg/archiver/serve_cmd.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from ..constants import * # NOQA
from ..remote import RepositoryServer
from ..legacy.remote import RepositoryServer

from ..logger import create_logger
from ..helpers.argparsing import ArgumentParser
Expand All @@ -13,7 +13,6 @@ def do_serve(self, args):
RepositoryServer(
restrict_to_paths=args.restrict_to_paths,
restrict_to_repositories=args.restrict_to_repositories,
use_socket=args.use_socket,
permissions=args.permissions,
).serve()

Expand All @@ -24,15 +23,13 @@ def build_parser_serve(self, subparsers, common_parser, mid_common_parser):
"""
This command starts a repository server process.

`borg serve` currently supports:
`borg serve` is only used to serve legacy (borg 1.x / v1) repositories over SSH, so that
such repositories can still be accessed remotely (e.g. for `borg transfer --from-borg1`).
Current repositories are accessed via a rest:// repository instead (which can itself tunnel
over SSH), so they do not use `borg serve`.

- Being automatically started via SSH when the borg client uses an ssh://...
remote repository. In this mode, `borg serve` will run until that SSH connection
is terminated.

- Being started by some other means (not by the borg client) as a long-running socket
server to be used for borg clients using a socket://... repository (see the `--socket`
option if you do not want to use the default path for the socket and PID file).
It is automatically started via SSH when a borg client uses an ssh://... repository.
In this mode, `borg serve` will run until that SSH connection is terminated.

Please note that `borg serve` does not support providing a specific repository via the
`--repo` option or the `BORG_REPO` environment variable. It is always the borg client that
Expand Down
19 changes: 10 additions & 9 deletions src/borg/archiver/version_cmd.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from .. import __version__
from ..constants import * # NOQA
from ..helpers.argparsing import ArgumentParser
from ..remote import RemoteRepository

from ..logger import create_logger

Expand All @@ -14,8 +13,10 @@ def do_version(self, args):
from borg.version import parse_version, format_version

client_version = parse_version(__version__)
if args.location.proto in ("ssh", "socket"):
with RemoteRepository(args.location, lock=False, args=args) as repository:
if args.location.proto == "ssh" and getattr(args, "v1_legacy", False):
from ..legacy.remote import LegacyRemoteRepository

with LegacyRemoteRepository(args.location, lock=False, args=args) as repository:
server_version = repository.server_version
else:
server_version = client_version
Expand All @@ -28,20 +29,20 @@ def build_parser_version(self, subparsers, common_parser, mid_common_parser):
"""
This command displays the Borg client and server versions.

If a local repository is given, the client code directly accesses the repository,
so the client version is also shown as the server version.
For current repositories the client code directly accesses the repository (also for
rest:// repositories), so the client version is shown as the server version, too.

If a remote repository is given (e.g., ssh:), the remote Borg is queried, and
its version is displayed as the server version.
If a legacy (borg 1.x / v1) repository is given via ssh: together with --from-borg1,
the remote Borg is queried, and its version is displayed as the server version.

Examples::

# local repository (client uses 1.4.0 alpha version)
$ borg version /mnt/backup
1.4.0a / 1.4.0a

# remote repository (client uses 1.4.0 alpha, server uses 1.2.7 release)
$ borg version ssh://borg@borgbackup:repo
# legacy remote repository (client uses 1.4.0 alpha, server uses 1.2.7 release)
$ borg version --from-borg1 ssh://borg@borgbackup:repo
1.4.0a / 1.2.7

Due to the version tuple format used in Borg client/server negotiation, only
Expand Down
3 changes: 1 addition & 2 deletions src/borg/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
from .crypto.file_integrity import IntegrityCheckedFile, FileIntegrityError
from .manifest import Manifest
from .platform import SaveFile
from .remote import RemoteRepository
from .repository import LIST_SCAN_LIMIT, Repository, StoreObjectNotFound, repo_lister
from .security import SecurityManager, assert_secure # noqa: F401

Expand Down Expand Up @@ -651,7 +650,7 @@ def build_chunkindex_from_repo(repository, *, disable_caches=False, cache_immedi
flags=ChunkIndex.F_USED, size=0, pack_id=pack_id, obj_offset=0, obj_size=obj_size
)
# Cache does not contain the manifest.
if not isinstance(repository, (Repository, RemoteRepository)):
if not isinstance(repository, Repository):
del chunks[Manifest.MANIFEST_ID]
duration = perf_counter() - t0 or 0.001
# Chunk IDs in a list are encoded in 34 bytes: 1 byte msgpack header, 1 byte length, 32 ID bytes.
Expand Down
2 changes: 1 addition & 1 deletion src/borg/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def __init__(self):
self.patterns_file_path: str | None = None

def get_kind(self) -> str:
if self.repository_location.startswith("ssh://__testsuite__") or self.repository_location.startswith("rest://"):
if self.repository_location.startswith("rest://"):
return "remote"
elif self.EXE == "borg.exe":
return "binary"
Expand Down
2 changes: 1 addition & 1 deletion src/borg/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
# we use it in all places where we need to detect or create all-zero buffers
zeros = bytes(MAX_DATA_SIZE)

# borg.remote read() buffer size
# borg serve (borg.legacy.remote) read() buffer size
BUFSIZE = 10 * 1024 * 1024

# To use a safe, limited unpacker, we need to set an upper limit to the archive count in the manifest.
Expand Down
14 changes: 5 additions & 9 deletions src/borg/fuse.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,14 @@ def async_wrapper(fn):
from .archiver._common import build_matcher, build_filter
from .archive import Archive, get_item_uid_gid
from .hashindex import FuseVersionsIndex
from .helpers import daemonize, daemonizing, signal_handler, format_file_size, bin_to_hex, Error
from .helpers import daemonizing, signal_handler, format_file_size, bin_to_hex, Error
from .helpers import HardLinkManager
from .helpers import msgpack
from .helpers.lrucache import LRUCache
from .item import Item
from .platform import uid2user, gid2group
from .platformflags import is_darwin
from .repository import Repository
from .remote import RemoteRepository


def fuse_main():
Expand Down Expand Up @@ -596,13 +595,10 @@ def pop_option(options, key, present, not_present, wanted_type, int_base=0):
"that do not reflect the archive content."
)
if not foreground:
if isinstance(self.repository_uncached, RemoteRepository):
daemonize()
else:
with daemonizing(show_rc=show_rc) as (old_id, new_id):
# local repo: the locking process' PID is changing, migrate it:
logger.debug("fuse: mount local repo, going to background: migrating lock.")
self.repository_uncached.migrate_lock(old_id, new_id)
with daemonizing(show_rc=show_rc) as (old_id, new_id):
# the locking process' PID is changing, migrate it:
logger.debug("fuse: mount repo, going to background: migrating lock.")
self.repository_uncached.migrate_lock(old_id, new_id)

# If the file system crashes, we do not want to umount because in that
# case the mountpoint suddenly appears to become empty. This can have
Expand Down
Loading
Loading