diff --git a/examples/npbackup.conf.dist b/examples/npbackup.conf.dist index 342e9b2..80d83cf 100644 --- a/examples/npbackup.conf.dist +++ b/examples/npbackup.conf.dist @@ -59,8 +59,8 @@ env: options: auto_upgrade: true - auto_upgrade_server_url: - auto_upgrade_server_username: - auto_upgrade_server_password: + server_url: + server_username: + server_password: # every 10 NPBackup runs, we'll try an autoupgrade. Never set this lower than 2 since failed upgrades will prevent backups from succeeding - auto_upgrade_interval: 10 \ No newline at end of file + interval: 10 \ No newline at end of file diff --git a/npbackup/__main__.py b/npbackup/__main__.py index ec6cbd9..de49a16 100644 --- a/npbackup/__main__.py +++ b/npbackup/__main__.py @@ -39,7 +39,8 @@ from npbackup.gui.main import main_gui from npbackup.core.runner import NPBackupRunner from npbackup.core.i18n_helper import _t from npbackup.path_helper import CURRENT_DIR, CURRENT_EXECUTABLE -from npbackup.upgrade_client.upgrader import auto_upgrader, need_upgrade +from npbackup.upgrade_client.upgrader import need_upgrade +from npbackup.core.upgrade_runner import run_upgrade del sys.path[0] @@ -324,36 +325,20 @@ This is free software, and you are welcome to redistribute it under certain cond except KeyError: auto_upgrade = True try: - auto_upgrade_interval = config_dict["options"]["auto_upgrade_interval"] + auto_upgrade_interval = config_dict["options"]["ainterval"] except KeyError: auto_upgrade_interval = 10 if (auto_upgrade and need_upgrade(auto_upgrade_interval)) or args.auto_upgrade: - try: - auto_upgrade_upgrade_url = config_dict["options"]["auto_upgrade_server_url"] - auto_upgrade_username = config_dict["options"][ - "auto_upgrade_server_username" - ] - auto_upgrade_password = config_dict["options"][ - "auto_upgrade_server_password" - ] - except KeyError as exc: - logger.error("Missing auto upgrade info: %s, cannot launch auto upgrade", exc) + if args.auto_upgrade: + logger.info("Running user initiated auto upgrade") else: - if args.auto_upgrade: - logger.info("Running user initiated auto upgrade") - else: - logger.info("Running program initiated auto upgrade") - result = auto_upgrader( - upgrade_url=auto_upgrade_upgrade_url, - username=auto_upgrade_username, - password=auto_upgrade_password, - ) - if args.auto_upgrade: - if result: - sys.exit(0) - else: - sys.exit(23) + logger.info("Running program initiated auto upgrade") + result = run_upgrade(config_dict) + if result: + sys.exit(0) + elif args.auto_upgrade: + sys.exit(23) dry_run = False if args.dry_run: diff --git a/npbackup/configuration.py b/npbackup/configuration.py index 09c202c..4c98070 100644 --- a/npbackup/configuration.py +++ b/npbackup/configuration.py @@ -37,14 +37,16 @@ ENCRYPTED_OPTIONS = [ {"section": "repo", "name": "password", "type": str}, {"section": "prometheus", "name": "http_username", "type": str}, {"section": "prometheus", "name": "http_password", "type": str}, - {"section": "options", "name": "auto_upgrade_server_username", "type": str}, - {"section": "options", "name": "auto_upgrade_server_password", "type": str}, + {"section": "options", "name": "server_username", "type": str}, + {"section": "options", "name": "server_password", "type": str}, ] empty_config_dict = {"backup": { + "compression": "auto", "use_fs_snapshot": True, "ignore_cloud_files": True, - "exclude_cache_dirs": True, + "exclude_caches": True, + "priority": "low", }, "repo": { "minimum_backup_age": 86400 }, "prometheus": {}, "env": {}, "options": {}} diff --git a/npbackup/core/upgrade_runner.py b/npbackup/core/upgrade_runner.py new file mode 100644 index 0000000..6ee2faf --- /dev/null +++ b/npbackup/core/upgrade_runner.py @@ -0,0 +1,38 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# +# This file is part of npbackup + +__intname__ = "npbackup.gui.core.upgrade_runner" +__author__ = "Orsiris de Jong" +__copyright__ = "Copyright (C) 2022-2023 NetInvent" +__license__ = "GPL-3.0-only" +__build__ = "2023011701" + + +from logging import getLogger +from npbackup.upgrade_client.upgrader import auto_upgrader + + +logger = getLogger(__intname__) + + +def run_upgrade(config_dict): + try: + auto_upgrade_upgrade_url = config_dict["options"]["server_url"] + auto_upgrade_username = config_dict["options"][ + "server_username" + ] + auto_upgrade_password = config_dict["options"][ + "server_password" + ] + except KeyError as exc: + logger.error("Missing auto upgrade info: %s, cannot launch auto upgrade", exc) + return False + else: + result = auto_upgrader( + upgrade_url=auto_upgrade_upgrade_url, + username=auto_upgrade_username, + password=auto_upgrade_password, + ) + return result \ No newline at end of file diff --git a/npbackup/gui/config.py b/npbackup/gui/config.py index 49f3b02..4fb34c3 100644 --- a/npbackup/gui/config.py +++ b/npbackup/gui/config.py @@ -7,14 +7,16 @@ __intname__ = "npbackup.gui.config" __author__ = "Orsiris de Jong" __copyright__ = "Copyright (C) 2022-2023 NetInvent" __license__ = "GPL-3.0-only" -__build__ = "2023012601" +__build__ = "2023020101" +import sys from logging import getLogger import PySimpleGUI as sg import npbackup.configuration as configuration from npbackup.core.i18n_helper import _t + logger = getLogger(__intname__) @@ -43,6 +45,8 @@ def config_gui(config_dict: dict, config_file: str): "http_password", "repository", "password", + "server_username", + "server_password", ]: try: if config_dict[section][entry] is None: @@ -68,7 +72,11 @@ def config_gui(config_dict: dict, config_file: str): for key, value in values.items(): if value == ENCRYPTED_DATA_PLACEHOLDER: continue - section, entry = key.split("---") + try: + section, entry = key.split("---") + except ValueError: + # Don't bother with keys that don't begin with "---" + continue # check whether we need to split into list if not isinstance(value, bool): result = value.split("\n") @@ -96,7 +104,6 @@ def config_gui(config_dict: dict, config_file: str): right_click_menu = ["", [_t("generic.decrypt")]] backup_col = [ - [sg.Text(_t("config_gui.backup"), font="helvetica 16")], [ sg.Text(_t("config_gui.compression"), size=(30, 1)), sg.Combo(["auto", "max", "off"], key="backup---compression", size=(48, 1)), @@ -117,7 +124,7 @@ def config_gui(config_dict: dict, config_file: str): ), size=(30, 2), ), - sg.Checkbox("", key="backup---use_fs_snapshot", size=(0, 1)), + sg.Checkbox("", key="backup---use_fs_snapshot", size=(41, 1)), ], [ sg.Text( @@ -126,7 +133,7 @@ def config_gui(config_dict: dict, config_file: str): ), size=(30, 2), ), - sg.Checkbox("", key="backup---ignore_cloud_files", size=(0, 1)), + sg.Checkbox("", key="backup---ignore_cloud_files", size=(41, 1)), ], [ sg.Text( @@ -145,15 +152,15 @@ def config_gui(config_dict: dict, config_file: str): ), size=(30, 2), ), - sg.Checkbox("", key="backup---exclude_case_ignore", size=(0, 1)), + sg.Checkbox("", key="backup---exclude_case_ignore", size=(41, 1)), ], [ sg.Text(_t("config_gui.exclude_cache_dirs"), size=(30, 1)), - sg.Checkbox("", key="backup---exclude_caches", size=(0, 1)), + sg.Checkbox("", key="backup---exclude_caches", size=(41, 1)), ], [ sg.Text(_t("config_gui.one_file_system"), size=(30, 1)), - sg.Checkbox("", key="backup---one_file_system", size=(0, 1)), + sg.Checkbox("", key="backup---one_file_system", size=(41, 1)), ], [ sg.Text(_t("config_gui.pre_exec_command"), size=(30, 1)), @@ -165,7 +172,7 @@ def config_gui(config_dict: dict, config_file: str): ], [ sg.Text(_t("config_gui.exec_failure_is_fatal"), size=(30, 1)), - sg.Checkbox("", key="backup---pre_exec_failure_is_fatal", size=(0, 1)), + sg.Checkbox("", key="backup---pre_exec_failure_is_fatal", size=(41, 1)), ], [ sg.Text(_t("config_gui.post_exec_command"), size=(30, 1)), @@ -177,7 +184,7 @@ def config_gui(config_dict: dict, config_file: str): ], [ sg.Text(_t("config_gui.exec_failure_is_fatal"), size=(30, 1)), - sg.Checkbox("", key="backup---post_exec_failure_is_fatal", size=(0, 1)), + sg.Checkbox("", key="backup---post_exec_failure_is_fatal", size=(41, 1)), ], [ sg.Text( @@ -197,7 +204,6 @@ def config_gui(config_dict: dict, config_file: str): ] repo_col = [ - [sg.Text(_t("config_gui.backup_destination"), font="helvetica 16")], [ sg.Text( "{}\n({})".format( @@ -229,12 +235,11 @@ def config_gui(config_dict: dict, config_file: str): ], ] - options_col = [ - [sg.Text(_t("config_gui.prometheus_config"), font="helvetica 16")], + prometheus_col = [ [sg.Text(_t("config_gui.explanation"))], [ sg.Text(_t("config_gui.enable_prometheus"), size=(30, 1)), - sg.Checkbox("", key="prometheus---metrics", size=(0, 1)), + sg.Checkbox("", key="prometheus---metrics", size=(41, 1)), ], [ sg.Text(_t("config_gui.job_name"), size=(30, 1)), @@ -274,7 +279,6 @@ def config_gui(config_dict: dict, config_file: str): ] env_col = [ - [sg.Text(_t("config_gui.environment_variables"), font="helvetica 16")], [ sg.Text( "{}\n({}\n{})".format( @@ -288,6 +292,29 @@ def config_gui(config_dict: dict, config_file: str): ], ] + options_col = [ + [ + sg.Text(_t("config_gui.auto_upgrade"), size=(30, 1)), + sg.Checkbox("", key="options---auto_upgrade", size=(41, 1)), + ], + [ + sg.Text(_t("config_gui.auto_upgrade_server_url"), size=(30, 1)), + sg.Input(key="options---server_url", size=(50, 1)), + ], + [ + sg.Text(_t("config_gui.auto_upgrade_server_username"), size=(30, 1)), + sg.Input(key="options---server_username", size=(50, 1)), + ], + [ + sg.Text(_t("config_gui.auto_upgrade_server_password"), size=(30, 1)), + sg.Input(key="options---server_password", size=(50, 1)), + ], + [ + sg.Text(_t("config_gui.auto_upgrade_interval"), size=(30, 1)), + sg.Input(key="options---interval", size=(50, 1)), + ], + ] + buttons = [ [ sg.Text(" " * 135), @@ -296,66 +323,24 @@ def config_gui(config_dict: dict, config_file: str): ] ] + tab_group_layout = [ + [sg.Tab(_t("config_gui.backup"), backup_col, font="helvetica 16", key="--tab-backup--", element_justification='C')], + [sg.Tab(_t("config_gui.backup_destination"),repo_col, font="helvetica 16", key="--tab-repo--", element_justification='C')], + [sg.Tab(_t("config_gui.prometheus_config"), prometheus_col, font="helvetica 16", key="--tab-prometheus--", element_justification='C')], + [sg.Tab(_t("config_gui.environment_variables"), env_col, font="helvetica 16", key="--tab-env--", element_justification='C')], + [sg.Tab(_t("generic.options"), options_col, font="helvetica 16", key="--tab-options--", element_justification='C')], + ] + layout = [ - [ - sg.Column( - [ - [ - sg.Column( - backup_col, - element_justification="L", - scrollable=False, - vertical_scroll_only=False, - size=(620, 610), - vertical_alignment="top", - ), - ] - ], - vertical_alignment="top", - ), - sg.Column( - [ - [ - sg.Column( - repo_col, - element_justification="L", - scrollable=False, - vertical_scroll_only=False, - size=(620, 210), - vertical_alignment="top", - ) - ], - [ - sg.Column( - options_col, - element_justification="L", - scrollable=False, - vertical_scroll_only=False, - size=(620, 300), - vertical_alignment="top", - ) - ], - [ - sg.Column( - env_col, - element_justification="L", - scrollable=False, - vertical_scroll_only=False, - size=(620, 100), - vertical_alignment="top", - ) - ], - ], - vertical_alignment="top", - ), - ], + [sg.TabGroup(tab_group_layout, enable_events=True, key="--tabgroup--")], [sg.Column(buttons, element_justification="C")], ] + window = sg.Window( "Configuration", layout, - text_justification="L", + text_justification="C", auto_size_text=True, auto_size_buttons=False, no_titlebar=False, diff --git a/npbackup/gui/main.py b/npbackup/gui/main.py index 2221899..33ab329 100644 --- a/npbackup/gui/main.py +++ b/npbackup/gui/main.py @@ -7,10 +7,11 @@ __intname__ = "npbackup.gui.main" __author__ = "Orsiris de Jong" __copyright__ = "Copyright (C) 2022-2023 NetInvent" __license__ = "GPL-3.0-only" -__build__ = "2023012501" +__build__ = "2023020101" from typing import List, Optional, Tuple +import sys import os from logging import getLogger import re @@ -33,6 +34,7 @@ from npbackup.customization import ( from npbackup.gui.config import config_gui from npbackup.core.runner import NPBackupRunner from npbackup.core.i18n_helper import _t +from npbackup.core.upgrade_runner import run_upgrade logger = getLogger(__intname__) @@ -42,7 +44,7 @@ logger = getLogger(__intname__) THREAD_SHARED_DICT = {} -def _about_gui(version_string: str) -> None: +def _about_gui(version_string: str, config_dict: dict) -> None: license_content = LICENSE_TEXT try: with open(LICENSE_FILE, "r") as file_handle: @@ -52,16 +54,28 @@ def _about_gui(version_string: str) -> None: layout = [ [sg.Text(version_string)], + [ + sg.Button(_t("config_gui.auto_upgrade_launch"), key="autoupgrade", size=(12, 2)) + ], [sg.Text("License: GNU GPLv3")], [sg.Multiline(license_content, size=(65, 20))], [sg.Button(_t("generic.accept"), key="exit")], ] - window = sg.Window(_t("generic.about"), layout, keep_on_top=True) + window = sg.Window(_t("generic.about"), layout, keep_on_top=True, element_justification='C') while True: event, _ = window.read() if event in [sg.WIN_CLOSED, "exit"]: break + elif event == "autoupgrade": + result = sg.PopupOKCancel(_t("config_gui.auto_ugprade_will_quit"), keep_on_top=True) + if result == 'OK': + logger.info("Running GUI initiated upgrade") + sub_result = run_upgrade(config_dict) + if sub_result: + sys.exit(0) + else: + sg.Popup(_t("config_gui.auto_upgrade_failed")) window.close() @@ -574,7 +588,7 @@ def main_gui(config_dict: dict, config_file: str, version_string: str): except (TypeError, KeyError): sg.PopupNoFrame(_t("main_gui.unknown_repo")) if event == "about": - _about_gui(version_string) + _about_gui(version_string, config_dict) # Update GUI on every window.read timeout = every minute or everytime an event happens, including the "uptodate" button current_state, snapshot_list = get_gui_data(config_dict) diff --git a/npbackup/translations/config_gui.en.yml b/npbackup/translations/config_gui.en.yml index 3bbfad4..79fca4f 100644 --- a/npbackup/translations/config_gui.en.yml +++ b/npbackup/translations/config_gui.en.yml @@ -49,4 +49,13 @@ en: configuration_saved: Configuration saved enter_backup_admin_password: Enter backup administrator password - wrong_password: Wrong password \ No newline at end of file + wrong_password: Wrong password + + auto_upgrade: Auto upgrade + auto_upgrade_server_url: Server URL + auto_upgrade_server_username: Server username + auto_upgrade_server_password: Server password + auto_upgrade_interval: Auto upgrade interval + auto_upgrade_launch: Launch auto upgrade + auto_ugprade_will_quit: Warning, launching an upgrade procedure will quit this program without notice. You will have to wait 5 minutes before launching it again for the upgrade to complete + auto_upgrade_failed: Auto upgrade procedure failed, see logs for further details \ No newline at end of file diff --git a/npbackup/translations/config_gui.fr.yml b/npbackup/translations/config_gui.fr.yml index cbf0dc4..df517c5 100644 --- a/npbackup/translations/config_gui.fr.yml +++ b/npbackup/translations/config_gui.fr.yml @@ -22,7 +22,7 @@ fr: backup_destination: Destination de sauvegarde maximum_backup_age: Age minimal d'une sauvegarde - backup_repo_uri: URL / chemin dépot de sauvegarde + backup_repo_uri: URL / chemin local dépot de sauvegarde backup_repo_password: Mot de passe dépot de sauvegarde upload_speed: Vitesse limite de téléversement (KB/s) download_speed: Vitesse limite de téléchargement (KB/s) @@ -49,4 +49,13 @@ fr: configuration_saved: Configuration sauvegardée enter_backup_admin_password: Veuillez entrer le mot de passe administrateur de sauvegarde - wrong_password: Mot de passe érroné \ No newline at end of file + wrong_password: Mot de passe érroné + + auto_upgrade: Mise à niveau + auto_upgrade_server_url: Serveur de mise à niveau + auto_upgrade_server_username: Nom d'utilisateur serveur + auto_upgrade_server_password: Mot de passe serveur + auto_upgrade_interval: Intervalle de mise à niveau + auto_upgrade_launch: Lancer une mise à niveau + auto_ugprade_will_quit: Attnetion, la procédure de mise à niveau va quitter ce programme sans notification. Vous devrez attendre 5 minutes pour laisser la procédure se terminer avant de relancer le programme + auto_upgrade_failed: Procédure de mise à niveau échouée, veuillez consulter les journaux pour plus de détails \ No newline at end of file diff --git a/npbackup/translations/generic.en.yml b/npbackup/translations/generic.en.yml index 3cd3996..f3d5935 100644 --- a/npbackup/translations/generic.en.yml +++ b/npbackup/translations/generic.en.yml @@ -4,6 +4,7 @@ en: quit: Exit configure: Configure about: About + options: Options _yes: Yes _no: No diff --git a/npbackup/translations/generic.fr.yml b/npbackup/translations/generic.fr.yml index fd5cb0e..e6dfe1b 100644 --- a/npbackup/translations/generic.fr.yml +++ b/npbackup/translations/generic.fr.yml @@ -4,6 +4,7 @@ fr: quit: Quitter configure: Configurer about: A propos + options: Options _yes: Oui _no: Non diff --git a/npbackup/upgrade_client/upgrader.py b/npbackup/upgrade_client/upgrader.py index 6b4daea..74a061c 100644 --- a/npbackup/upgrade_client/upgrader.py +++ b/npbackup/upgrade_client/upgrader.py @@ -170,11 +170,12 @@ def auto_upgrader(upgrade_url: str, username: str, password: str) -> bool: logger.info("Upgrade file written to %s", executable) log_file = os.path.join(tempfile.gettempdir(), file_info["filename"] + ".log") + logger.info("Logging upgrade to %s", log_file) # Actual upgrade process new_executable = os.path.join(CURRENT_DIR, os.path.basename(CURRENT_EXECUTABLE)) - cmd = 'del "{}"; move "{}" "{}"; del "{}" > {}'.format( - CURRENT_EXECUTABLE, executable, new_executable, executable, log_file + cmd = 'del "{}" > "{}" && move "{}" "{}" >> "{}" && del "{}" >> "{}" && "{}" --upgrade-conf >> "{}"'.format( + CURRENT_EXECUTABLE, log_file, executable, log_file, new_executable, log_file, executable, log_file, new_executable, log_file ) logger.info( "Launching upgrade. Current process will quit. Upgrade starts in %s seconds. Upgrade is done by OS logged in %s",