This commit is contained in:
Shelvacu
2025-08-04 19:39:28 -07:00
committed by Shelvacu on fw
parent 2729ae23bb
commit 69373479e1
5 changed files with 118 additions and 1 deletions

View File

@@ -0,0 +1,87 @@
diff --git i/src/borg/archiver/_common.py w/src/borg/archiver/_common.py
index 7900d5f1..8252c1f3 100644
--- i/src/borg/archiver/_common.py
+++ w/src/borg/archiver/_common.py
@@ -37,7 +37,7 @@ def get_repository(location, *, create, exclusive, lock_wait, lock, args, v1_or_
location, create=create, exclusive=exclusive, lock_wait=lock_wait, lock=lock, args=args
)
- elif location.proto in ("sftp", "file", "rclone") and not v1_or_v2: # stuff directly supported by borgstore
+ elif location.proto in ("sftp", "file", "rclone", "s3") and not v1_or_v2: # stuff directly supported by borgstore
repository = Repository(location, create=create, exclusive=exclusive, lock_wait=lock_wait, lock=lock)
else:
diff --git i/src/borg/helpers/parseformat.py w/src/borg/helpers/parseformat.py
index 8f9385e2..9724caea 100644
--- i/src/borg/helpers/parseformat.py
+++ w/src/borg/helpers/parseformat.py
@@ -478,6 +478,21 @@ class Location:
local_re = re.compile(local_path_re, re.VERBOSE)
+ s3_re_text = r"""
+ (?P<s3type>(s3|b2)):
+ ((?P<auth>
+ (?P<profile>[^@:]+) # profile (no colons allowed)
+ |
+ (?P<access_key_id>[^:@]+):(?P<access_key_secret>[^@]+) # access key and secret
+ )@)? # optional authentication
+ (?P<schema>[^:/]+)://
+ (?P<hostname>[^:/]+)
+ (:(?P<port>\d+))?/
+ (?P<bucket>[^/]+)/ # bucket name
+ (?P<path>.+) # path
+ """
+ s3_re = re.compile(s3_re_text, re.VERBOSE)
+
def __init__(self, text="", overrides={}, other=False):
self.repo_env_var = "BORG_OTHER_REPO" if other else "BORG_REPO"
self.valid = False
@@ -506,6 +521,29 @@ def parse(self, text, overrides={}):
raise ValueError('Invalid location format: "%s"' % self.processed)
def _parse(self, text):
+ m = self.s3_re.match(text)
+ if m:
+ self.proto = "s3"
+ self._s3type = m.group("s3type")
+ if (profile := m.group("profile")) is not None:
+ self.user = f"profile:{profile}"
+ if (key_id := m.group("access_key_id")) is not None:
+ self.user = f"key_id:{key_id}"
+ self._s3auth = m.group("auth")
+ self._s3schema = m.group("schema")
+ self._s3hostname = m.group("hostname")
+ self._s3port = m.group("port")
+ if (host := m.group("hostname")) is not None:
+ authority = host
+ if (port := m.group("port")) is not None:
+ authority += f":{port}"
+ self._host = authority
+ self._s3bucket = m.group("bucket")
+ self._s3path = m.group("path")
+ assert self._s3bucket is not None
+ assert self._s3path is not None
+ self.path = f"{self._s3bucket}/{self._s3path}"
+ return True
m = self.ssh_or_sftp_re.match(text)
if m:
self.proto = m.group("proto")
@@ -543,7 +581,7 @@ def __str__(self):
def to_key_filename(self):
name = re.sub(r"[^\w]", "_", self.path.rstrip("/"))
- if self.proto not in ("file", "socket", "rclone"):
+ if self.proto not in ("file", "socket", "rclone", "s3"):
name = re.sub(r"[^\w]", "_", self.host) + "__" + name
if len(name) > 120:
# Limit file names to some reasonable length. Most file systems
@@ -566,6 +604,8 @@ def canonical_path(self):
return self.path
if self.proto == "rclone":
return f"{self.proto}:{self.path}"
+ if self.proto == "s3":
+ return self.processed
if self.proto in ("sftp", "ssh"):
return (
f"{self.proto}://"

View File

@@ -12,7 +12,6 @@
xxHash,
zstd,
installShellFiles,
nixosTests,
borgstore,
borghash,
@@ -33,6 +32,8 @@ python.pkgs.buildPythonApplication rec {
hash = "sha256-oCVf/FcWJ0vPQrZ3Chd2UrWzRRZK1KJr1XpXpQU+qZ4=";
};
patches = [ ./fix-s3.patch ];
build-system = with python.pkgs; [
cython
setuptools-scm

View File

@@ -17,6 +17,8 @@ python.pkgs.buildPythonPackage {
hash = "sha256-R92m5yXj6FrHPjkQ3JzJG8ZwMLSaGC40dfkvvDkophc=";
};
patches = [ ./require-boto3.patch ];
build-system = [ python.pkgs.setuptools-scm ];
dependencies = with python.pkgs; [

View File

@@ -0,0 +1,25 @@
diff --git i/src/borgstore/backends/s3.py w/src/borgstore/backends/s3.py
index 87e19fe..e0b6a27 100644
--- i/src/borgstore/backends/s3.py
+++ w/src/borgstore/backends/s3.py
@@ -1,8 +1,5 @@
-try:
- import boto3
- from botocore.client import Config
-except ImportError:
- boto3 = None
+import boto3
+from botocore.client import Config
import re
from typing import Optional
@@ -16,9 +13,6 @@ from .errors import ObjectNotFound
def get_s3_backend(url: str):
- if boto3 is None:
- return None
-
# (s3|b2):[profile|(access_key_id:access_key_secret)@][schema://hostname[:port]]/bucket/path
s3_regex = r"""
(?P<s3type>(s3|b2)):

View File

@@ -10,6 +10,8 @@
capacity = "10T";
};
services.garage.settings.s3_api.root_domain = ".s3.garage.prophecy.shelvacu.com";
users.users.${config.services.caddy.user}.extraGroups = [ "garage-sockets" "garage" ];
services.caddy.virtualHosts = {