Fix B2 restic URL: use colon separator (b2:bucket:path) for restic 0.16.x

restic 0.16.x changed the B2 URL format to use a colon between bucket and
path (b2:bucket:prefix) instead of a slash. The middlewared URL builder
produces b2:/bucket/path; restic then validates the full string after 'b2:'
as a bucket name, which fails because the slash is not in [a-z0-9-].

Fix the restic.py wrapper to strip the leading slash and replace the first
slash with a colon: b2:/bucket/path -> b2:bucket:path.

Also fix the hasattr bug in sitecustomize.py _patch_b2: hasattr() returns
True for methods inherited from the base class (which raises
NotImplementedError), causing the patch to be silently skipped. Use
'get_restic_config' not in cls.__dict__ instead.
This commit is contained in:
2026-06-16 16:41:26 +00:00
parent c8a0c42762
commit e8b0f961c7
3 changed files with 65 additions and 50 deletions
+28 -18
View File
@@ -239,25 +239,35 @@ def get_restic_config(cloud_backup):
cmd = list(result.cmd)
for i, part in enumerate(cmd):
if part.startswith("--repo=") or part.startswith("--repository="):
prefix, _, url = part.partition("=")
prefix += "="
scheme, sep, rest = url.partition(":")
if sep and rest.startswith("/") and not rest.startswith("//"):
cmd[i] = f"{prefix}{scheme}:{rest[1:]}"
try:
return _dc.replace(result, cmd=cmd)
except TypeError:
return result._replace(cmd=cmd)
break
if i and cmd[i - 1] in ("-r", "--repo", "--repository"):
scheme, sep, rest = part.partition(":")
if sep and rest.startswith("/") and not rest.startswith("//"):
cmd[i] = f"{scheme}:{rest[1:]}"
try:
return _dc.replace(result, cmd=cmd)
except TypeError:
return result._replace(cmd=cmd)
pfx, _, url = part.partition("=")
pfx += "="
elif i and cmd[i - 1] in ("-r", "--repo", "--repository"):
pfx = None
url = part
else:
continue
scheme, sep, rest = url.partition(":")
if not sep:
break
changed = False
# Strip stray leading slash: b2:/bucket -> b2:bucket
if rest.startswith("/") and not rest.startswith("//"):
rest = rest[1:]
changed = True
# restic 0.16.x B2 uses colon to separate bucket from path:
# b2:bucket:prefix (not b2:bucket/prefix)
# middlewared builds the slash form; fix the separator.
if scheme == "b2" and "/" in rest:
rest = rest.replace("/", ":", 1)
changed = True
if changed:
new_url = scheme + ":" + rest
cmd[i] = pfx + new_url if pfx is not None else new_url
try:
return _dc.replace(result, cmd=cmd)
except TypeError:
return result._replace(cmd=cmd)
break
return result
get_restic_config._truecloud_patched = True