diff --git a/npbackup/configuration.py b/npbackup/configuration.py index 34af228..af04acd 100644 --- a/npbackup/configuration.py +++ b/npbackup/configuration.py @@ -7,7 +7,7 @@ __intname__ = "npbackup.configuration" __author__ = "Orsiris de Jong" __copyright__ = "Copyright (C) 2022-2024 NetInvent" __license__ = "GPL-3.0-only" -__build__ = "2024061701" +__build__ = "2024090101" __version__ = "npbackup 3.0.0+" MIN_CONF_VERSION = 3.0 @@ -153,6 +153,7 @@ empty_config_dict = { "backup_opts": { "paths": [], "source_type": None, + "stdin_from_command": None, "tags": [], "compression": "auto", "use_fs_snapshot": True, diff --git a/npbackup/core/runner.py b/npbackup/core/runner.py index 25c253f..ed299ae 100644 --- a/npbackup/core/runner.py +++ b/npbackup/core/runner.py @@ -1011,9 +1011,10 @@ class NPBackupRunner: """ # Possible warnings to add to json output warnings = [] - - # Preflight checks - if not read_from_stdin: + + stdin_from_command = self.repo_config.g("backup_opts.stdin_from_command") + if not read_from_stdin and not stdin_from_command: + # Preflight checks paths = self.repo_config.g("backup_opts.paths") if not paths: msg = ( @@ -1112,7 +1113,17 @@ class NPBackupRunner: self.restic_runner.verbose = self.verbose # Run backup here - if not read_from_stdin: + if stdin_from_command: + self.write_logs( + f"Running backup of given command stdout as name {stdin_filename} to repo {self.repo_config.g('name')}", + level="info" + ) + elif stdin_filename: + self.write_logs( + f"Running backup of piped stdin data as name {stdin_filename} to repo {self.repo_config.g('name')}", + level="info", + ) + else: if source_type not in ["folder_list", None]: self.write_logs( f"Running backup of files in {paths} list to repo {self.repo_config.g('name')}", @@ -1123,11 +1134,6 @@ class NPBackupRunner: f"Running backup of {paths} to repo {self.repo_config.g('name')}", level="info", ) - else: - self.write_logs( - f"Running backup of piped stdin data as name {stdin_filename} to repo {self.repo_config.g('name')}", - level="info", - ) pre_exec_commands_success = True if pre_exec_commands: @@ -1149,7 +1155,21 @@ class NPBackupRunner: level="info", ) - if not read_from_stdin: + if read_from_stdin: + result = self.restic_runner.backup( + read_from_stdin=read_from_stdin, + stdin_filename=stdin_filename, + tags=tags, + additional_backup_only_parameters=additional_backup_only_parameters, + ) + elif stdin_from_command: + result = self.restic_runner.backup( + stdin_from_command=stdin_from_command, + stdin_filename=stdin_filename, + tags=tags, + additional_backup_only_parameters=additional_backup_only_parameters, + ) + else: result = self.restic_runner.backup( paths=paths, source_type=source_type, @@ -1163,13 +1183,6 @@ class NPBackupRunner: tags=tags, additional_backup_only_parameters=additional_backup_only_parameters, ) - else: - result = self.restic_runner.backup( - read_from_stdin=read_from_stdin, - stdin_filename=stdin_filename, - tags=tags, - additional_backup_only_parameters=additional_backup_only_parameters, - ) self.write_logs( f"Restic output:\n{self.restic_runner.backup_result_content}", level="debug" diff --git a/npbackup/gui/config.py b/npbackup/gui/config.py index 80eb13a..b7b0b28 100644 --- a/npbackup/gui/config.py +++ b/npbackup/gui/config.py @@ -100,6 +100,7 @@ def config_gui(full_config: dict, config_file: str): "files_from": _t("config_gui.files_from"), "files_from_verbatim": _t("config_gui.files_from_verbatim"), "files_from_raw": _t("config_gui.files_from_raw"), + "stdin_from_command": _t("config_gui.stdin_from_command"), }, "backup_opts.priority": { "low": _t("config_gui.low"), @@ -847,6 +848,7 @@ def config_gui(full_config: dict, config_file: str): list(combo_boxes["backup_opts.source_type"].values()), key="backup_opts.source_type", size=(48, 1), + enable_events=True, ), ], [ @@ -859,12 +861,24 @@ def config_gui(full_config: dict, config_file: str): expand_y=True, ) ], + [ + sg.Text(_t("config_gui.stdin_from_command")) + ], + [ + sg.Image( + NON_INHERITED_ICON, + key="inherited.backup_opts.stdin_from_command", + tooltip=_t("config_gui.group_inherited"), + pad=1, + ), + sg.Input(key="backup_opts.stdin_from_command", size=(100, 1)), + ], [ sg.Input(visible=False, key="--ADD-PATHS-FILE--", enable_events=True), - sg.FilesBrowse(_t("generic.add_files"), target="--ADD-PATHS-FILE--"), + sg.FilesBrowse(_t("generic.add_files"), target="--ADD-PATHS-FILE--", key="--ADD-PATHS-FILE-BUTTON--"), sg.Input(visible=False, key="--ADD-PATHS-FOLDER--", enable_events=True), sg.FolderBrowse( - _t("generic.add_folder"), target="--ADD-PATHS-FOLDER--" + _t("generic.add_folder"), target="--ADD-PATHS-FOLDER--", key="--ADD-PATHS-FOLDER-BUTTON--" ), sg.Button(_t("generic.add_manually"), key="--ADD-PATHS-MANUALLY--"), sg.Button(_t("generic.remove_selected"), key="--REMOVE-PATHS--"), @@ -2098,6 +2112,30 @@ Google Cloud storage: GOOGLE_PROJECT_ID GOOGLE_APPLICATION_CREDENTIALS\n\ ) update_global_gui(full_config, unencrypted=False) continue + if event == "backup_opts.source_type": + value = get_key_from_value(combo_boxes["backup_opts.source_type"], values["backup_opts.source_type"]) + if value == "stdin_from_command": + window["backup_opts.paths"].update(visible=False) + window["--ADD-PATHS-FILE-BUTTON--"].update(disabled=True) + window["--ADD-PATHS-FOLDER-BUTTON--"].update(disabled=True) + window["--ADD-PATHS-MANUALLY--"].update(disabled=True) + window["--REMOVE-PATHS--"].update(disabled=True) + window["backup_opts.stdin_from_command"].update(disabled=False) + elif value == "folder_list": + window["backup_opts.paths"].update(visible=True) + window["--ADD-PATHS-FILE-BUTTON--"].update(disabled=False) + window["--ADD-PATHS-FOLDER-BUTTON--"].update(disabled=False) + window["--ADD-PATHS-MANUALLY--"].update(disabled=False) + window["--REMOVE-PATHS--"].update(disabled=False) + window["backup_opts.stdin_from_command"].update(disabled=True) + elif value in ("files_from", "files_from_verbatim", "files_from_raw"): + window["backup_opts.paths"].update(visible=True) + window["--ADD-PATHS-FILE-BUTTON--"].update(disabled=False) + window["--ADD-PATHS-FOLDER-BUTTON--"].update(disabled=True) + window["--ADD-PATHS-MANUALLY--"].update(disabled=False) + window["--REMOVE-PATHS--"].update(disabled=False) + window["backup_opts.stdin_from_command"].update(disabled=True) + continue if event in ( "--ADD-PATHS-FILE--", "--ADD-PATHS-FOLDER--", diff --git a/npbackup/restic_wrapper/__init__.py b/npbackup/restic_wrapper/__init__.py index 960301e..f2a406d 100644 --- a/npbackup/restic_wrapper/__init__.py +++ b/npbackup/restic_wrapper/__init__.py @@ -295,7 +295,7 @@ class ResticRunner: if self.additional_parameters else "" ) - _cmd = f'"{self._binary}" {additional_parameters}{cmd}{self.generic_arguments}' + _cmd = f'"{self._binary}"{additional_parameters}{self.generic_arguments} {cmd}' self._executor_running = True self.write_logs(f"Running command: [{_cmd}]", level="debug") @@ -781,6 +781,7 @@ class ResticRunner: tags: List[str] = [], one_file_system: bool = False, read_from_stdin: bool = False, + stdin_from_command: str = None, stdin_filename: str = "stdin.data", additional_backup_only_parameters: str = None, ) -> Union[bool, str, dict]: @@ -790,11 +791,9 @@ class ResticRunner: kwargs = locals() kwargs.pop("self") - if read_from_stdin: - cmd = "backup --stdin" - if stdin_filename: - cmd += f' --stdin-filename "{stdin_filename}"' - else: + cmd = "backup" + + if not read_from_stdin and not stdin_from_command: # Handle various source types if source_type in [ "files_from", @@ -883,6 +882,8 @@ class ResticRunner: "Parameter --use-fs-snapshot was given, which is only compatible with Windows", level="warning", ) + + for tag in tags: if tag: tag = tag.strip() @@ -890,6 +891,15 @@ class ResticRunner: if additional_backup_only_parameters: cmd += " {}".format(additional_backup_only_parameters) + if read_from_stdin: + cmd += " --stdin" + if stdin_filename: + cmd += f' --stdin-filename "{stdin_filename}"' + if stdin_from_command: + if stdin_filename: + cmd += f' --stdin-filename "{stdin_filename}"' + cmd += f" --stdin-from-command -- {stdin_from_command}" + # Run backup without json output, as we could not compute the cloud errors in json output via regexes json_output = self.json_output self.json_output = False diff --git a/npbackup/translations/config_gui.en.yml b/npbackup/translations/config_gui.en.yml index 796e42c..2b95989 100644 --- a/npbackup/translations/config_gui.en.yml +++ b/npbackup/translations/config_gui.en.yml @@ -108,6 +108,7 @@ en: files_from: From file files_from_verbatim: From verbatim files_from_raw: From raw + stdin_from_command: Standard input from command # retention policiy retention_policy: Retention policy diff --git a/npbackup/translations/config_gui.fr.yml b/npbackup/translations/config_gui.fr.yml index 75304cd..2e7b557 100644 --- a/npbackup/translations/config_gui.fr.yml +++ b/npbackup/translations/config_gui.fr.yml @@ -109,6 +109,7 @@ fr: files_from: Liste depuis un fichier files_from_verbatim: Liste depuis un fichier "exact" files_from_raw: Liste depuis un fichier "raw" + stdin_from_command: Entrée standard depuis une commande # retention policy retention_policy: Politique de rétention