commit 4d422ada8c9ac51a297191acdf95b9030aecc1b9 Author: Keuronde Date: Tue Jul 14 10:43:32 2026 +0200 Version 0.1.0, fonctionnelle mais rustique diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d7ced65 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +config.ini +dist/ +__pycache__ \ No newline at end of file diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..9e6246e --- /dev/null +++ b/Readme.md @@ -0,0 +1,48 @@ +Une interface pour récupérer ses sauvegardes Borgbackup +======================================================= + +Voici une interface pour accéder facilement à vos sauvegardes BorgBackup + +- Renseignez une fois vos paramètres (ils seront sauvegardés) +- Choisissez votre sauvegarde +- Parcourez-là avec votre navigateur de fichier habituel + +![Image de l'interface graphique](doc/Interface.png) + + +Toutes les commandes indiquées ci-dessous sont faites pour être exécutées dans le dossier contenant ce `Readme.md` + +Développement +------------- + +Ce projet est en Python3 / Qt. + +1. Créez un environnement virtuel python3 `python -m venv .venv` +2. Activez-le `source .venv/bin/activate` +3. Installez les dépendances `pip install -r requirements` + +Génération du package +--------------------- + +1. Activez l'environnement de développement comme décrit ci-dessus +2. Si vous avez modifié le code, éditer `pyproject.toml`, changez la version. +3. Utilisez la commande `python -m build` + +Les fichiers générés se trouve dans le repertoire `dist/` + +Installation +------------ + +L'installation sur le système se fait avec pipx. + + sudo apt install pipx + + # adaptez le nom à la version générée + pipx install borgbackup_gui-0.1.0-py3-none-any.whl + +Intégration au bureau +--------------------- + +cp borgbackupgui.desktop $HOME/.local/share/applications + + diff --git a/borgbackup_gui.desktop b/borgbackup_gui.desktop new file mode 100755 index 0000000..60a5529 --- /dev/null +++ b/borgbackup_gui.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Name=BorgBackup Gui +Comment=An application to view your backups. +Exec=borgbackup-gui +Icon= +Type=Application +Categories=Graphics;Utility; diff --git a/borgbackup_gui/BorgBackupGui.py b/borgbackup_gui/BorgBackupGui.py new file mode 100644 index 0000000..521f08f --- /dev/null +++ b/borgbackup_gui/BorgBackupGui.py @@ -0,0 +1,157 @@ +import os +import sys +from pathlib import Path +from borgbackup_gui.ConfigRepoGUI import ConfigRepoGUI +from borgbackup_gui.ListBackupGUI import ListBackupGUI +from PySide6.QtWidgets import QApplication, QWidget,QHBoxLayout, QVBoxLayout, QPlainTextEdit +from PySide6.QtCore import QObject, Slot, Signal, QThread +import configparser +import subprocess + + +class BorgBackupGui(QWidget): + def __init__(self): + super().__init__() + self.config_file = configparser.ConfigParser() + self.setWindowTitle("BorgBackup Gui") + + passphrase = "" + depot = "" + remote_path = "/usr/local/bin/borg" + + if Path("config.ini").exists(): + self.config_file.read("config.ini") + try: + passphrase = self.config_file["DEFAULT"]["passphrase"] + depot = self.config_file["DEFAULT"]["depot"] + remote_path = self.config_file["DEFAULT"]["remote_path"] + except KeyError: + pass + + self.backups:list[str] = [] + self.config = ConfigRepoGUI( + passphrase=passphrase, + depot=depot, + remote_path=remote_path) + self.config.valider.connect(self.lister_depot_thread) + self.log_console = QPlainTextEdit() + self.log_console.setReadOnly(True) + self.layout_h = QHBoxLayout() + self.layout_v = QVBoxLayout() + self.layout_v.addWidget(self.config) + self.layout_v.addWidget(self.log_console) + self.layout_h.addLayout(self.layout_v) + self.setLayout(self.layout_h) + self.show() + + @Slot() + def lister_depot_thread(self): + self.passphrase = self.config.get_passphrase() + self.depot = self.config.get_repo() + self.remote_path = self.config.get_remote_path() + + # On sauvegarde la configuration dans le fichier de config + self.config_file['DEFAULT'] = { + 'passphrase': self.passphrase, + 'depot': self.depot, + 'remote_path': self.remote_path} + with open('config.ini', 'w') as configfile: + self.config_file.write(configfile) + + self.worker = ListerBackup(passphrase=self.passphrase, + depot = self.depot, + remote_path = self.remote_path) + + self.worker.finished.connect(self.afficher_liste_depot) + self.worker.log_str.connect(self.log_console.appendPlainText) + self.worker.start() + + def afficher_liste_depot(self): + self.config.task_ended() + self.list_backup_gui = ListBackupGUI(self.worker.get_backups()) + self.list_backup_gui.selected.connect(self.afficher_la_sauvegarde) + self.layout_h.addWidget(self.list_backup_gui) + self.adjustSize() + + def afficher_la_sauvegarde(self, nom_backup): + self.afficheur = MountAndDisplayBackup(self.passphrase, self.depot, self.remote_path, nom_backup) + self.afficheur.log_str.connect(self.log_console.appendPlainText) + self.afficheur.finished.connect(self.list_backup_gui.activate) + self.afficheur.start() + + + + +class MountAndDisplayBackup(QThread): + log_str = Signal(str) + + def __init__(self, passphrase, depot, remote_path, backup): + super().__init__() + self.passphrase = passphrase + self.depot = depot + self.remote_path = remote_path + self.backup = backup + + def run(self): + # mkdir /tmp/localborg/$(backup) + base_dir = "/tmp/localborg/" + backup_dir = base_dir + self.backup + custom_env = os.environ.copy() + custom_env["BORG_PASSPHRASE"] = self.passphrase + self.log_str.emit("mkdir -p " + backup_dir) + result = subprocess.run(["mkdir", "-p", backup_dir], capture_output=True, text=True) + self.log_str.emit(result.stderr) + self.log_str.emit(result.stdout) + # borg mount nas.local:/volume1/NetBackup/chezmoi/client::2020-04-16 /tmp/localborg + self.log_str.emit("borg mount " + self.depot + "::" + self.backup + " " + backup_dir) + self.log_str.emit("Veuillez patienter, cette étape peut être longue") + result = subprocess.run(["borg", "mount", self.depot + "::" + self.backup, "--remote-path", self.remote_path, backup_dir], env=custom_env, capture_output=True, text=True) + self.log_str.emit(result.stderr) + self.log_str.emit(result.stdout) + self.log_str.emit("xdg-open" + backup_dir) + result = subprocess.run(["xdg-open", backup_dir], capture_output=True, text=True) + self.log_str.emit(result.stderr) + self.log_str.emit(result.stdout) + + + + +class ListerBackup(QThread): + log_str = Signal(str) + + def __init__(self, passphrase, depot, remote_path): + super().__init__() + self.passphrase = passphrase + self.depot = depot + self.remote_path = remote_path + + def run(self): + self.lister_depot() + + def lister_depot(self): + custom_env = os.environ.copy() + custom_env["BORG_PASSPHRASE"] = self.passphrase + + self.log_str.emit("borg list " + self.depot + " --remote-path" + self.remote_path) + result = subprocess.run(["borg", "list", self.depot, "--remote-path", self.remote_path], env=custom_env, capture_output=True, text=True) + self.log_str.emit(result.stderr) + self.log_str.emit(result.stdout) + liste_backup = result.stdout.split("\n") + self.backups:list[str] = [] + for backup in liste_backup: + try: + self.backups.append(backup.split()[0]) + except IndexError: + pass + + def get_backups(self): + return self.backups + + +if __name__ == "__main__": + import sys + app = QApplication([]) + widget = BorgBackupGui() + + sys.exit(app.exec()) + \ No newline at end of file diff --git a/borgbackup_gui/ConfigRepoGUI.py b/borgbackup_gui/ConfigRepoGUI.py new file mode 100644 index 0000000..21ce744 --- /dev/null +++ b/borgbackup_gui/ConfigRepoGUI.py @@ -0,0 +1,59 @@ +from PySide6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QLabel, QApplication, QPushButton, QLineEdit +from PySide6.QtCore import QSize, Signal, Slot + +class ConfigRepoGUI(QWidget): + valider = Signal() + def __init__(self, passphrase="", remote_path="/usr/local/bin/borg", depot=""): + super().__init__() + layout = QVBoxLayout() + layout.addWidget(QLabel("Configuration")) + self.repo = ConfigElementGUI("Depot", depot) + self.passphrase = ConfigElementGUI("Borg_passphrase", passphrase) + self.remote_path = ConfigElementGUI("--remote_path", remote_path) + layout.addWidget(self.repo) + layout.addWidget(self.passphrase) + layout.addWidget(self.remote_path) + self.valider_button = QPushButton("Valider") + layout.addWidget(self.valider_button) + self.valider_button.clicked.connect(self.envoyer_valider) + self.setLayout(layout) + + def get_repo(self)->str: + return self.repo.field.text() + + def get_passphrase(self)->str: + return self.passphrase.field.text() + + def get_remote_path(self)->str: + return self.remote_path.field.text() + + def task_ended(self): + self.valider_button.setEnabled(True) + + @Slot() + def envoyer_valider(self): + print("envoyer_valider") + self.valider.emit() + self.valider_button.setEnabled(False) + + +class ConfigElementGUI(QWidget): + def __init__(self, nom:str, valeur_par_défaut:str=""): + super().__init__() + layout = QHBoxLayout() + layout.addWidget(QLabel(nom)) + self.field = QLineEdit(valeur_par_défaut) + layout.addWidget(self.field) + self.setLayout(layout) + + def sizeHint(self): + return QSize(600, 50) + + +if __name__ == "__main__": + import sys + app = QApplication([]) + widget = ConfigRepoGUI() + widget.show() + + sys.exit(app.exec()) \ No newline at end of file diff --git a/borgbackup_gui/ListBackupGUI.py b/borgbackup_gui/ListBackupGUI.py new file mode 100644 index 0000000..52542a2 --- /dev/null +++ b/borgbackup_gui/ListBackupGUI.py @@ -0,0 +1,72 @@ +from PySide6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QLabel, QApplication, QPushButton, QScrollArea +from PySide6.QtCore import Signal, QSize + +class ListBackupGUI(QWidget): + selected = Signal(str) + + def __init__(self, list_backup:list[str]): + super().__init__() + layout = QVBoxLayout() + layout.addWidget(QLabel("Liste des sauvegardes")) + + w_list_backup_elements = QWidget() + layout_list_backup_elements = QVBoxLayout() + + + self.listBackupElements:list[ListBackupElement] = [] + for list_backup_element_name in list_backup: + list_backup_element = ListBackupElement(list_backup_element_name) + list_backup_element.selected.connect(self.is_selected) + self.listBackupElements.append(list_backup_element) + layout_list_backup_elements.addWidget(list_backup_element) + + w_list_backup_elements.setLayout(layout_list_backup_elements) + + scrollArea = QScrollArea() + scrollArea.setWidget(w_list_backup_elements) + layout.addWidget(scrollArea) + self.setLayout(layout) + + def is_selected(self, nom): + print(nom) + self.deactivate() + self.selected.emit(nom) + + def deactivate(self): + for element in self.listBackupElements: + element.button.setEnabled(False) + + def activate(self): + for element in self.listBackupElements: + element.button.setEnabled(True) + + def sizeHint(self): + return QSize(400, 400) + + + +class ListBackupElement(QWidget): + selected = Signal(str) + + def __init__(self, nom_backup:str): + super().__init__() + layout = QHBoxLayout() + self.nom = nom_backup + layout.addWidget(QLabel(nom_backup)) + self.button = QPushButton("Select") + self.button.clicked.connect(self.is_selected) + layout.addWidget(self.button) + self.setLayout(layout) + + def is_selected(self): + self.selected.emit(self.nom) + + +if __name__ == "__main__": + import sys + app = QApplication([]) + widget = ListBackupGUI(["Aujourd'hui", "hier"]) + widget.show() + + sys.exit(app.exec()) + diff --git a/borgbackup_gui/__init__.py b/borgbackup_gui/__init__.py new file mode 100644 index 0000000..d36c1d0 --- /dev/null +++ b/borgbackup_gui/__init__.py @@ -0,0 +1,9 @@ +import sys +from PySide6.QtWidgets import QApplication +from .BorgBackupGui import BorgBackupGui + +def run_borgbackup_gui(): + app = QApplication([]) + widget = BorgBackupGui() + + sys.exit(app.exec()) \ No newline at end of file diff --git a/dist/borgbackup_gui-0.1.0-py3-none-any.whl b/dist/borgbackup_gui-0.1.0-py3-none-any.whl new file mode 100644 index 0000000..11e0fdf Binary files /dev/null and b/dist/borgbackup_gui-0.1.0-py3-none-any.whl differ diff --git a/doc/Interface.png b/doc/Interface.png new file mode 100644 index 0000000..708c2b2 Binary files /dev/null and b/doc/Interface.png differ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3d1bea2 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,9 @@ +[project] +name = "borgbackup_gui" +version = "0.1.0" +dependencies = [ + "PySide6" +] + +[project.scripts] +borgbackup-gui = "borgbackup_gui:run_borgbackup_gui" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..88b95af --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +borgbackup_gui @ file:///home/samuel/Projets/borgbackup_gui/dist/borgbackup_gui-0.1.0-py3-none-any.whl#sha256=5b2bebba4c3c9d2f08916803f8fb36641d845ccd0a45dc0c9baae605165eb0ba +build==1.5.1 +packaging==26.2 +pyproject_hooks==1.2.0 +PySide6==6.11.1 +PySide6_Addons==6.11.1 +PySide6_Essentials==6.11.1 +setuptools==83.0.0 +shiboken6==6.11.1