Version 0.1.0, fonctionnelle mais rustique

This commit is contained in:
Keuronde 2026-07-14 10:43:32 +02:00
commit 4d422ada8c
11 changed files with 373 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
config.ini
dist/
__pycache__

48
Readme.md Normal file
View File

@ -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

7
borgbackup_gui.desktop Executable file
View File

@ -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;

View File

@ -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())

View File

@ -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())

View File

@ -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())

View File

@ -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())

Binary file not shown.

BIN
doc/Interface.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

9
pyproject.toml Normal file
View File

@ -0,0 +1,9 @@
[project]
name = "borgbackup_gui"
version = "0.1.0"
dependencies = [
"PySide6"
]
[project.scripts]
borgbackup-gui = "borgbackup_gui:run_borgbackup_gui"

9
requirements.txt Normal file
View File

@ -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