Skip to content

Commit 62e3013

Browse files
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 <noreply@anthropic.com>
1 parent 94de5ae commit 62e3013

5 files changed

Lines changed: 26 additions & 4 deletions

File tree

docs/changes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,8 @@ New features:
426426
and repository chunk statistics, #9579, #9757
427427
- prune: show total vs matching archives in output, #9262
428428
- create --exclude-dataless: macOS: skip cloud files not materialized locally, #9746
429+
- support BORG_HOSTNAME and BORG_USERNAME env vars to override the hostname/username stored
430+
in archives and used by the {hostname}/{user} placeholders, #9651
429431
- Minimal implementation of "related repositories", #9645
430432

431433
This feature allows multiple repositories to share deduplication-relevant secrets

docs/usage/general/environment.rst.inc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ General:
4747
So, if you have a all-zero MAC address or other reasons to better externally control the host id, just set this
4848
environment variable to a unique value. If all your FQDNs are unique, you can just use the FQDN. If not,
4949
use fqdn@uniqueid.
50+
BORG_HOSTNAME
51+
When set, use this value as the hostname (instead of the auto-detected one), e.g. to run borg
52+
on one host, but impersonate another host (see #9651). This affects the hostname stored in newly
53+
created archives as well as the ``{hostname}`` placeholder.
54+
BORG_USERNAME
55+
When set, use this value as the username (instead of the auto-detected one), e.g. to run borg
56+
as one user, but impersonate another user. This affects the username stored in newly created
57+
archives as well as the ``{user}`` placeholder.
5058
BORG_LOGGING_CONF
5159
When set, use the given filename as INI_-style logging configuration.
5260
A basic example conf can be found at ``docs/misc/logging.conf``.

src/borg/archive.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -632,8 +632,8 @@ def save(self, name=None, comment=None, timestamp=None, stats=None, additional_m
632632
'comment': comment or '',
633633
'items': self.items_buffer.chunks,
634634
'cmdline': sys.argv,
635-
'hostname': hostname,
636-
'username': getuser(),
635+
'hostname': os.environ.get('BORG_HOSTNAME') or hostname,
636+
'username': os.environ.get('BORG_USERNAME') or getuser(),
637637
'time': start.strftime(ISO_FORMAT),
638638
'time_end': end.strftime(ISO_FORMAT),
639639
'cwd': self.cwd,

src/borg/helpers/parseformat.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,11 +211,11 @@ def replace_placeholders(text, overrides={}):
211211
'pid': os.getpid(),
212212
'fqdn': fqdn,
213213
'reverse-fqdn': '.'.join(reversed(fqdn.split('.'))),
214-
'hostname': hostname,
214+
'hostname': os.environ.get('BORG_HOSTNAME') or hostname,
215215
'now': DatetimeWrapper(current_time.astimezone(None)),
216216
'utcnow': DatetimeWrapper(current_time),
217217
'unixtime': int(current_time.timestamp()),
218-
'user': getosusername(),
218+
'user': os.environ.get('BORG_USERNAME') or getosusername(),
219219
'uuid4': str(uuid.uuid4()),
220220
'borgversion': borg_version,
221221
'borgmajor': '%d' % borg_version_tuple[:1],

src/borg/testsuite/archiver.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2251,6 +2251,18 @@ def test_create_json(self):
22512251
assert len(archive['id']) == 64
22522252
assert 'stats' in archive
22532253

2254+
def test_create_hostname_username_override(self):
2255+
self.cmd('init', '--encryption=repokey', self.repository_location)
2256+
self.create_regular_file('file1', size=1024 * 80)
2257+
with environment_variable(BORG_HOSTNAME='foo_host', BORG_USERNAME='bar_user'):
2258+
# the override is also used to fill the {hostname}/{user} placeholders in the archive name:
2259+
self.cmd('create', self.repository_location + '::{hostname}-{user}', 'input')
2260+
info = json.loads(self.cmd('info', '--json', self.repository_location + '::foo_host-bar_user'))
2261+
archive = info['archives'][0]
2262+
assert archive['name'] == 'foo_host-bar_user'
2263+
assert archive['hostname'] == 'foo_host'
2264+
assert archive['username'] == 'bar_user'
2265+
22542266
def test_create_topical(self):
22552267
self.create_regular_file('file1', size=1024 * 80)
22562268
time.sleep(1) # file2 must have newer timestamps than file1

0 commit comments

Comments
 (0)