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/usage/general/environment.rst.inc
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,10 @@ Directories and files:
`XDG env var`_ ``XDG_DATA_HOME`` is set, then ``$XDG_DATA_HOME/borg`` is being used instead.
This directory contains all borg data directories, see the FAQ
for a security advisory about the data in this directory: :ref:`home_data_borg`
BORG_RUNTIME_DIR
Defaults to ``$BORG_BASE_DIR/.cache/borg``. If ``BORG_BASE_DIR`` is not explicitly set while
`XDG env var`_ ``XDG_RUNTIME_DIR`` is set, then ``$XDG_RUNTIME_DIR/borg`` is being used instead.
This directory contains borg runtime files, like e.g. the socket file.
BORG_SECURITY_DIR
Defaults to ``$BORG_DATA_DIR/security``.
This directory contains security relevant data.
Expand Down
12 changes: 11 additions & 1 deletion src/borg/archiver/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@


def get_repository(location, *, create, exclusive, lock_wait, lock, append_only, make_parent_dirs, storage_quota, args):
if location.proto == "ssh":
if location.proto in ("ssh", "socket"):
repository = RemoteRepository(
location,
create=create,
Expand Down Expand Up @@ -573,6 +573,16 @@ 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
13 changes: 12 additions & 1 deletion src/borg/archiver/serve_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def do_serve(self, args):
restrict_to_repositories=args.restrict_to_repositories,
append_only=args.append_only,
storage_quota=args.storage_quota,
use_socket=args.use_socket,
).serve()
return EXIT_SUCCESS

Expand All @@ -27,7 +28,17 @@ def build_parser_serve(self, subparsers, common_parser, mid_common_parser):

serve_epilog = process_epilog(
"""
This command starts a repository server process. This command is usually not used manually.
This command starts a repository server process.

borg serve can currently support:

- Getting automatically started via ssh when the borg client uses a ssh://...
remote repository. In this mode, `borg serve` will live until that ssh connection
gets terminated.

- Getting 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).
"""
)
subparser = subparsers.add_parser(
Expand Down
3 changes: 2 additions & 1 deletion src/borg/helpers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
from .checks import check_extension_modules, check_python
from .datastruct import StableDict, Buffer, EfficientCollectionQueue
from .errors import Error, ErrorWithTraceback, IntegrityError, DecompressionError
from .fs import ensure_dir, get_security_dir, get_keys_dir, get_base_dir, join_base_dir, get_cache_dir, get_config_dir
from .fs import ensure_dir, join_base_dir, get_socket_filename
from .fs import get_security_dir, get_keys_dir, get_base_dir, get_cache_dir, get_config_dir, get_runtime_dir
from .fs import dir_is_tagged, dir_is_cachedir, make_path_safe, scandir_inorder
from .fs import secure_erase, safe_unlink, dash_open, os_open, os_stat, umount
from .fs import O_, flags_root, flags_dir, flags_special_follow, flags_special, flags_base, flags_normal, flags_noatime
Expand Down
16 changes: 16 additions & 0 deletions src/borg/helpers/fs.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,22 @@ def get_data_dir(*, legacy=False):
return data_dir


def get_runtime_dir(*, legacy=False):
"""Determine where to store runtime files, like sockets, PID files, ..."""
assert legacy is False, "there is no legacy variant of the borg runtime dir"
runtime_dir = os.environ.get(
"BORG_RUNTIME_DIR", join_base_dir(".cache", "borg", legacy=legacy) or platformdirs.user_runtime_dir("borg")
)

# Create path if it doesn't exist yet
ensure_dir(runtime_dir)
return runtime_dir


def get_socket_filename():
return os.path.join(get_runtime_dir(), "borg.sock")


def get_cache_dir(*, legacy=False):
"""Determine where to repository keys and cache"""

Expand Down
19 changes: 16 additions & 3 deletions src/borg/helpers/parseformat.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,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"""
(?!(:|//|ssh://)) # not starting with ":" or // or ssh://
(?!(:|//|ssh://|socket://)) # not starting with ":" or // or ssh:// or socket://
(?P<path>([^:]|(:(?!:)))+) # any chars, but no "::"
"""

Expand Down Expand Up @@ -429,6 +429,14 @@ class Location:
re.VERBOSE,
) # path

socket_re = re.compile(
r"""
(?P<proto>socket):// # socket://
"""
+ abs_path_re,
re.VERBOSE,
) # path

file_re = re.compile(
r"""
(?P<proto>file):// # file://
Expand Down Expand Up @@ -493,6 +501,11 @@ def normpath_special(p):
self.path = normpath_special(m.group("path"))
return True
m = self.file_re.match(text)
if m:
self.proto = m.group("proto")
self.path = normpath_special(m.group("path"))
return True
m = self.socket_re.match(text)
if m:
self.proto = m.group("proto")
self.path = normpath_special(m.group("path"))
Expand All @@ -516,7 +529,7 @@ def __str__(self):

def to_key_filename(self):
name = re.sub(r"[^\w]", "_", self.path).strip("_")
if self.proto != "file":
if self.proto not in ("file", "socket"):
name = re.sub(r"[^\w]", "_", self.host) + "__" + name
if len(name) > 100:
# Limit file names to some reasonable length. Most file systems
Expand All @@ -535,7 +548,7 @@ def host(self):
return self._host.lstrip("[").rstrip("]")

def canonical_path(self):
if self.proto == "file":
if self.proto in ("file", "socket"):
return self.path
else:
if self.path and self.path.startswith("~"):
Expand Down
30 changes: 26 additions & 4 deletions src/borg/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,24 @@

* what is output on INFO level is additionally controlled by commandline
flags

Logging setup is a bit complicated in borg, as it needs to work under misc. conditions:
- purely local, not client/server (easy)
- client/server: RemoteRepository ("borg serve" process) writes log records into a global
queue, which is then sent to the client side by the main serve loop (via the RPC protocol,
either over ssh stdout, more directly via process stdout without ssh [used in the tests]
or via a socket. On the client side, the log records are fed into the clientside logging
system. When remote_repo.close() is called, server side must send all queued log records
via the RPC channel before returning the close() call's return value (as the client will
then shut down the connection).
- progress output is always given as json to the logger (including the plain text inside
the json), but then formatted by the logging system's formatter as either plain text or
json depending on the cli args given (--log-json?).
- tests: potentially running in parallel via pytest-xdist, capturing borg output into a
given stream.
- logging might be short-lived (e.g. when invoking a single borg command via the cli)
or long-lived (e.g. borg serve --socket or when running the tests)
- logging is global and exists only once per process.
"""

import inspect
Expand Down Expand Up @@ -115,10 +133,14 @@ def remove_handlers(logger):
logger.removeHandler(handler)


def teardown_logging():
global configured
logging.shutdown()
configured = False
def flush_logging():
# make sure all log output is flushed,
# this is especially important for the "borg serve" RemoteRepository logging:
# all log output needs to be sent via the ssh / socket connection before closing it.
for logger_name in "borg.output.progress", "":
logger = logging.getLogger(logger_name)
for handler in logger.handlers:
handler.flush()


def setup_logging(
Expand Down
Loading