From 62e301320ecdc400c42ecdb63f72298ab1ec6176 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Thu, 25 Jun 2026 20:37:01 +0200 Subject: [PATCH] support BORG_HOSTNAME and BORG_USERNAME env vars, fixes #9651 When set, these override the hostname/username that is stored in newly created archives and that is used for the {hostname}/{user} placeholders (e.g. in archive names, prune --glob-archives, check). Useful to run borg on host A but impersonate host B. fqdn/hostid and the auto-detection are intentionally left untouched. Co-Authored-By: Claude Opus 4.8 --- docs/changes.rst | 2 ++ docs/usage/general/environment.rst.inc | 8 ++++++++ src/borg/archive.py | 4 ++-- src/borg/helpers/parseformat.py | 4 ++-- src/borg/testsuite/archiver.py | 12 ++++++++++++ 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index da817da437..17aa53b053 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -426,6 +426,8 @@ New features: and repository chunk statistics, #9579, #9757 - prune: show total vs matching archives in output, #9262 - create --exclude-dataless: macOS: skip cloud files not materialized locally, #9746 +- support BORG_HOSTNAME and BORG_USERNAME env vars to override the hostname/username stored + in archives and used by the {hostname}/{user} placeholders, #9651 - Minimal implementation of "related repositories", #9645 This feature allows multiple repositories to share deduplication-relevant secrets diff --git a/docs/usage/general/environment.rst.inc b/docs/usage/general/environment.rst.inc index 08efb8f50a..230bd54a40 100644 --- a/docs/usage/general/environment.rst.inc +++ b/docs/usage/general/environment.rst.inc @@ -47,6 +47,14 @@ General: So, if you have a all-zero MAC address or other reasons to better externally control the host id, just set this environment variable to a unique value. If all your FQDNs are unique, you can just use the FQDN. If not, use fqdn@uniqueid. + BORG_HOSTNAME + When set, use this value as the hostname (instead of the auto-detected one), e.g. to run borg + on one host, but impersonate another host (see #9651). This affects the hostname stored in newly + created archives as well as the ``{hostname}`` placeholder. + BORG_USERNAME + When set, use this value as the username (instead of the auto-detected one), e.g. to run borg + as one user, but impersonate another user. This affects the username stored in newly created + archives as well as the ``{user}`` placeholder. BORG_LOGGING_CONF When set, use the given filename as INI_-style logging configuration. A basic example conf can be found at ``docs/misc/logging.conf``. diff --git a/src/borg/archive.py b/src/borg/archive.py index 20cef9f677..12eac60d41 100644 --- a/src/borg/archive.py +++ b/src/borg/archive.py @@ -632,8 +632,8 @@ def save(self, name=None, comment=None, timestamp=None, stats=None, additional_m 'comment': comment or '', 'items': self.items_buffer.chunks, 'cmdline': sys.argv, - 'hostname': hostname, - 'username': getuser(), + 'hostname': os.environ.get('BORG_HOSTNAME') or hostname, + 'username': os.environ.get('BORG_USERNAME') or getuser(), 'time': start.strftime(ISO_FORMAT), 'time_end': end.strftime(ISO_FORMAT), 'cwd': self.cwd, diff --git a/src/borg/helpers/parseformat.py b/src/borg/helpers/parseformat.py index cf5e117e55..89e8cfbc57 100644 --- a/src/borg/helpers/parseformat.py +++ b/src/borg/helpers/parseformat.py @@ -211,11 +211,11 @@ def replace_placeholders(text, overrides={}): 'pid': os.getpid(), 'fqdn': fqdn, 'reverse-fqdn': '.'.join(reversed(fqdn.split('.'))), - 'hostname': hostname, + 'hostname': os.environ.get('BORG_HOSTNAME') or hostname, 'now': DatetimeWrapper(current_time.astimezone(None)), 'utcnow': DatetimeWrapper(current_time), 'unixtime': int(current_time.timestamp()), - 'user': getosusername(), + 'user': os.environ.get('BORG_USERNAME') or getosusername(), 'uuid4': str(uuid.uuid4()), 'borgversion': borg_version, 'borgmajor': '%d' % borg_version_tuple[:1], diff --git a/src/borg/testsuite/archiver.py b/src/borg/testsuite/archiver.py index dc07367fdc..1d5c4c0172 100644 --- a/src/borg/testsuite/archiver.py +++ b/src/borg/testsuite/archiver.py @@ -2251,6 +2251,18 @@ def test_create_json(self): assert len(archive['id']) == 64 assert 'stats' in archive + def test_create_hostname_username_override(self): + self.cmd('init', '--encryption=repokey', self.repository_location) + self.create_regular_file('file1', size=1024 * 80) + with environment_variable(BORG_HOSTNAME='foo_host', BORG_USERNAME='bar_user'): + # the override is also used to fill the {hostname}/{user} placeholders in the archive name: + self.cmd('create', self.repository_location + '::{hostname}-{user}', 'input') + info = json.loads(self.cmd('info', '--json', self.repository_location + '::foo_host-bar_user')) + archive = info['archives'][0] + assert archive['name'] == 'foo_host-bar_user' + assert archive['hostname'] == 'foo_host' + assert archive['username'] == 'bar_user' + def test_create_topical(self): self.create_regular_file('file1', size=1024 * 80) time.sleep(1) # file2 must have newer timestamps than file1