diff --git a/core/build-image.sh b/core/build-image.sh index 51fb4fb9a..a602500cf 100755 --- a/core/build-image.sh +++ b/core/build-image.sh @@ -121,7 +121,7 @@ buildah rm "${container}" images+=("${repobase}/${reponame}") echo "Building the restic/rclone image..." -container=$(buildah from docker.io/library/alpine:3.18.4) +container=$(buildah from docker.io/library/alpine:3.20.3) reponame="restic" buildah add "${container}" restic/ / buildah run ${container} sh <<'EOF' diff --git a/core/imageroot/usr/local/agent/bin/rclone-wrapper b/core/imageroot/usr/local/agent/bin/rclone-wrapper index 49918611f..abcfafc3b 100755 --- a/core/imageroot/usr/local/agent/bin/rclone-wrapper +++ b/core/imageroot/usr/local/agent/bin/rclone-wrapper @@ -10,6 +10,14 @@ import agent import os from urllib.parse import urlparse +def extract_region_code(hostname, position, default=""): + """Obtain the region code for S3 backends by looking at the domain + parts of the endpoint FQDN.""" + try: + return hostname.split('.')[position] + except ValueError: + return default + core_env = agent.read_envfile("/etc/nethserver/core.env") rclone_image = core_env["RESTIC_IMAGE"] @@ -60,8 +68,22 @@ elif uscheme == "s3": if orepo['provider'] == 'aws': rclone_env['RCLONE_S3_PROVIDER'] = 'AWS' rclone_env['RCLONE_S3_REGION'] = orepo.get("aws_default_region", "") - elif orepo['provider'] == 'digitalocean': + elif orepo['provider'] == 'generic-s3' and 'digitalocean' in s3_endpoint: rclone_env['RCLONE_S3_PROVIDER'] = 'DigitalOcean' + elif orepo['provider'] == 'generic-s3' and 'ovh.net' in s3_endpoint: + rclone_env['RCLONE_S3_PROVIDER'] = 'Other' + rclone_env['RCLONE_S3_REGION'] = orepo.get("aws_default_region", extract_region_code(s3_endpoint, 1)) + rclone_env['RCLONE_S3_LOCATION_CONSTRAINT'] = rclone_env['RCLONE_S3_REGION'] + elif orepo['provider'] == 'generic-s3' and 'wasabi' in s3_endpoint: + rclone_env['RCLONE_S3_PROVIDER'] = 'Wasabi' + rclone_env['RCLONE_S3_REGION'] = orepo.get("aws_default_region", extract_region_code(s3_endpoint, 1)) + elif orepo['provider'] == 'generic-s3' and 'synology' in s3_endpoint: + rclone_env['RCLONE_S3_PROVIDER'] = 'Synology' + rclone_env['RCLONE_S3_REGION'] = orepo.get("aws_default_region", extract_region_code(s3_endpoint, 0)) + rclone_env['RCLONE_S3_NO_CHECK_BUCKET'] = "1" + else: + rclone_env['RCLONE_S3_PROVIDER'] = 'Other' + elif uscheme == "azure": rclone_path = ':azureblob:' + upath.rstrip(":") rclone_env = { diff --git a/core/imageroot/usr/local/agent/pypkg/cluster/backup.py b/core/imageroot/usr/local/agent/pypkg/cluster/backup.py index 36ae6b13e..ebc920789 100644 --- a/core/imageroot/usr/local/agent/pypkg/cluster/backup.py +++ b/core/imageroot/usr/local/agent/pypkg/cluster/backup.py @@ -29,8 +29,6 @@ def get_default_backup_repository_name(provider, url, rid=""): name = "AWS " + url.split('/', 1)[1] elif provider == "backblaze": name = "Backblaze " + url.split(':', 1)[1] - elif provider == "digitalocean": - name = "DigitalOcean " + url.split('/', 1)[1] except: pass diff --git a/core/imageroot/var/lib/nethserver/cluster/actions/add-backup-repository/10validate b/core/imageroot/var/lib/nethserver/cluster/actions/add-backup-repository/10validate index d09de66c9..712d943ae 100755 --- a/core/imageroot/var/lib/nethserver/cluster/actions/add-backup-repository/10validate +++ b/core/imageroot/var/lib/nethserver/cluster/actions/add-backup-repository/10validate @@ -53,6 +53,7 @@ renv['url'] = request['url'] # add url renv['provider'] = request['provider'] # add provider pvalidate = subprocess.run(['rclone-wrapper', '0', '--low-level-retries', '1', '--contimeout', '10s', 'size', '-q', '--json', 'REMOTE_PATH'], text=True, capture_output=True, env=renv) if pvalidate.returncode != 0: + print(agent.SD_DEBUG, "The rclone-wrapper probe command has failed:", pvalidate.stderr, file=sys.stderr) errors.append({'field':'parameters','parameter':'parameters','value':pvalidate.stderr,'error':'backup_repository_not_accessible'}) if errors: diff --git a/core/imageroot/var/lib/nethserver/cluster/actions/add-backup-repository/validate-input.json b/core/imageroot/var/lib/nethserver/cluster/actions/add-backup-repository/validate-input.json index 87d5cd586..c35cde7fa 100644 --- a/core/imageroot/var/lib/nethserver/cluster/actions/add-backup-repository/validate-input.json +++ b/core/imageroot/var/lib/nethserver/cluster/actions/add-backup-repository/validate-input.json @@ -25,16 +25,6 @@ "aws_secret_access_key": "edfjksof798r7fsdfiougvf7df" } }, - { - "name": "repository 3", - "provider": "digitalocean", - "password": "", - "url": "s3:ams3.digitaloceanspaces.com/mybucket/mybackup", - "parameters": { - "aws_access_key_id": "XJEMERNGRIWGN", - "aws_secret_access_key": "897wergjkegher987geriugheruiger789" - } - }, { "name": "repository 4", "provider": "azure", @@ -120,9 +110,12 @@ { "title": "S3-based provider schema", "properties": { + "url": { + "pattern": "/" + }, "provider": { "title": "S3 providers", - "enum": ["aws", "digitalocean", "generic-s3"] + "enum": ["aws", "generic-s3"] }, "parameters": { "$ref": "#/$defs/s3_parameters" diff --git a/core/imageroot/var/lib/nethserver/cluster/actions/alter-backup-repository/validate-input.json b/core/imageroot/var/lib/nethserver/cluster/actions/alter-backup-repository/validate-input.json index 4d8160e90..c65870303 100644 --- a/core/imageroot/var/lib/nethserver/cluster/actions/alter-backup-repository/validate-input.json +++ b/core/imageroot/var/lib/nethserver/cluster/actions/alter-backup-repository/validate-input.json @@ -72,7 +72,6 @@ "title": "S3 providers", "enum": [ "aws", - "digitalocean", "generic-s3" ] }, diff --git a/core/imageroot/var/lib/nethserver/cluster/actions/read-backup-repositories/50read b/core/imageroot/var/lib/nethserver/cluster/actions/read-backup-repositories/50read index fa649cc18..acd54f157 100755 --- a/core/imageroot/var/lib/nethserver/cluster/actions/read-backup-repositories/50read +++ b/core/imageroot/var/lib/nethserver/cluster/actions/read-backup-repositories/50read @@ -46,8 +46,13 @@ for krepo in rdb.scan_iter('cluster/backup_repository/*'): ] orepo = rdb.hgetall(krepo) repositories[repo_uuid] = orepo - proot = subprocess.Popen(rclone_lsjson_cmd, **popen_args) - for oroot in json.load(proot.stdout): + try: + proot = subprocess.Popen(rclone_lsjson_cmd, **popen_args) + lsjson_data = json.load(proot.stdout) + except Exception as ex: + lsjson_data = [] + print(agent.SD_WARNING, 'Failed invocation of', *rclone_lsjson_cmd, ':', ex, file=sys.stderr) + for oroot in lsjson_data: restic_prefix, restic_uuid, _ = oroot["Path"].split("/", 2) try: # Obtain from lsjson the repository creation timestamp diff --git a/core/ui/public/i18n/ar/translation.json b/core/ui/public/i18n/ar/translation.json index 131b89313..99563836b 100644 --- a/core/ui/public/i18n/ar/translation.json +++ b/core/ui/public/i18n/ar/translation.json @@ -322,7 +322,7 @@ "no_instance_to_restore_description": "It looks like there is no app instance backed up on your repositories", "choose_app_instances_to_backup": "Choose app instances to backup.", "delete_backup_explanation_1": "{instanceName} won't be backed up anymore. If you change your mind, you'll have to schedule a new backup. | {numInstances} app instances won't be backed up anymore. If you change your mind, you'll have to schedule a new backup.", - "generic_s3": "S3 compatible provider", + "generic-s3": "S3 compatible provider", "app_instances_not_backed_up": "Apps not backed up", "cluster_placeholder": "Choose a node in the cluster", "backup_enabled": "Backup enabled", diff --git a/core/ui/public/i18n/de/translation.json b/core/ui/public/i18n/de/translation.json index e33e9d16d..313f56e20 100644 --- a/core/ui/public/i18n/de/translation.json +++ b/core/ui/public/i18n/de/translation.json @@ -896,7 +896,7 @@ "custom_schedule_tooltip_systemd_time_documentation": "Siehe Systemd Timer Dokumentation", "no_instance_to_restore_description": "Es ist keine App-Instanz im Ziel gesichert", "delete_backup_explanation_1": "{instanceName} wird nicht mehr gesichert. Soll erneut eine Datensicherung erfolgen so muss eine neue Sicherung geplant werden. | {numInstances} app Instanzen werden nicht mehr gesichert. Soll erneut eine Datensicherung erfolgen so muss eine neue Sicherung geplant werden.", - "generic_s3": "S3-kompatibler Anbieter", + "generic-s3": "S3-kompatibler Anbieter", "no_backup_configured": "Keine Datensicherung konfiguriert", "retention": "Aufbewahrung", "disabled_backups_description": "Es ist {num} Datensicherung deaktiviert | Es sind {num} Datensicherungen deaktiviert", diff --git a/core/ui/public/i18n/en/translation.json b/core/ui/public/i18n/en/translation.json index 4cd2d0aff..915b437eb 100644 --- a/core/ui/public/i18n/en/translation.json +++ b/core/ui/public/i18n/en/translation.json @@ -421,7 +421,7 @@ "aws": "Amazon S3", "smb": "Windows File Share (SMB2/3)", "cluster": "Local storage", - "generic_s3": "S3 compatible provider", + "generic-s3": "S3 compatible provider", "azure": "Microsoft Azure blob storage", "backblaze_short": "B2", "aws_short": "S3", @@ -582,6 +582,7 @@ "cluster_placeholder": "Choose a node in the cluster", "parameters_format": "Incorrect value", "url": "URL", + "url_pattern": "The S3-compatible hostname and bucket name must be separated by a slash '/'", "from_node_name_of_this_cluster": "From node {name} of this cluster", "from_node_name_of_different_cluster": "From node {name} of a different cluster", "from_this_cluster": "From this cluster", diff --git a/core/ui/public/i18n/es/translation.json b/core/ui/public/i18n/es/translation.json index f0e9b1831..13c60651b 100644 --- a/core/ui/public/i18n/es/translation.json +++ b/core/ui/public/i18n/es/translation.json @@ -328,7 +328,7 @@ "backblaze": "Backblaze B2", "no_instance_to_restore_description": "Parece que no hay una copia de seguridad de ninguna instancia de la aplicación en tus repositorios", "choose_app_instances_to_backup": "Elige instancias de aplicaciones para realizar una copia de seguridad.", - "generic_s3": "Proveedor compatible con S3", + "generic-s3": "Proveedor compatible con S3", "cluster_placeholder": "Elige un nodo en el clúster", "backup_enabled": "Copia de seguridad habilitada", "no_backup_configured": "No hay copias de seguridad configuradas", diff --git a/core/ui/public/i18n/eu/translation.json b/core/ui/public/i18n/eu/translation.json index b87343f60..c25db5dee 100644 --- a/core/ui/public/i18n/eu/translation.json +++ b/core/ui/public/i18n/eu/translation.json @@ -268,7 +268,7 @@ "no_instance_to_restore_description": "It looks like there is no app instance backed up on your repositories", "choose_app_instances_to_backup": "Choose app instances to backup.", "delete_backup_explanation_1": "{instanceName} won't be backed up anymore. If you change your mind, you'll have to schedule a new backup. | {numInstances} app instances won't be backed up anymore. If you change your mind, you'll have to schedule a new backup.", - "generic_s3": "S3 compatible provider", + "generic-s3": "S3 compatible provider", "cluster_placeholder": "Choose a node in the cluster", "backup_enabled": "Backup enabled", "no_backup_configured": "No backup configured", diff --git a/core/ui/public/i18n/it/translation.json b/core/ui/public/i18n/it/translation.json index e45cfef56..10512e91e 100644 --- a/core/ui/public/i18n/it/translation.json +++ b/core/ui/public/i18n/it/translation.json @@ -688,7 +688,7 @@ "hourly": "Ogni ora", "weekly": "Settimanale", "monthly": "Mensilmente", - "generic_s3": "Provider compatibile con S3", + "generic-s3": "Provider compatibile con S3", "total_size": "Dimensioni totali", "minute": "Minuto", "schedule_action": "Pianifica", diff --git a/core/ui/public/i18n/pt/translation.json b/core/ui/public/i18n/pt/translation.json index a493eb20d..6d1e5cf20 100644 --- a/core/ui/public/i18n/pt/translation.json +++ b/core/ui/public/i18n/pt/translation.json @@ -529,7 +529,7 @@ "no_instance_to_restore_description": "It looks like there is no app instance backed up on your repositories", "choose_app_instances_to_backup": "Choose app instances to backup.", "delete_backup_explanation_1": "{instanceName} won't be backed up anymore. If you change your mind, you'll have to schedule a new backup. | {numInstances} app instances won't be backed up anymore. If you change your mind, you'll have to schedule a new backup.", - "generic_s3": "S3 compatible provider", + "generic-s3": "S3 compatible provider", "app_instances_not_backed_up": "Apps not backed up", "cluster_placeholder": "Choose a node in the cluster", "backup_enabled": "Backup enabled", diff --git a/core/ui/public/i18n/pt_BR/translation.json b/core/ui/public/i18n/pt_BR/translation.json index ab77c0a6c..02e6e3ac3 100644 --- a/core/ui/public/i18n/pt_BR/translation.json +++ b/core/ui/public/i18n/pt_BR/translation.json @@ -942,7 +942,7 @@ "no_instance_to_restore_description": "It looks like there is no app instance backed up on your repositories", "choose_app_instances_to_backup": "Choose app instances to backup.", "delete_backup_explanation_1": "{instanceName} won't be backed up anymore. If you change your mind, you'll have to schedule a new backup. | {numInstances} app instances won't be backed up anymore. If you change your mind, you'll have to schedule a new backup.", - "generic_s3": "S3 compatible provider", + "generic-s3": "S3 compatible provider", "app_instances_not_backed_up": "Apps not backed up", "cluster_placeholder": "Choose a node in the cluster", "backup_enabled": "Backup enabled", diff --git a/core/ui/src/components/backup/AddRepositoryModal.vue b/core/ui/src/components/backup/AddRepositoryModal.vue index 082b8c3da..323720a88 100644 --- a/core/ui/src/components/backup/AddRepositoryModal.vue +++ b/core/ui/src/components/backup/AddRepositoryModal.vue @@ -113,7 +113,7 @@ />