diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e975aa4 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,32 @@ +# Changelog Backuppy + +## [1.02.000] - 2021-05-05 +### Added +- Now fully functional as the shell version + +### Changed +- Updated "README.md" concerning installation and usage +- Changed filename "Changelog.MD" to "CHANGELOG.md" + +### Fixed +- Fixed problem with the Bash/ZSH config + +## [1.01.001] - 2021-05-04 +### Changed +- Use E-MAIL constant + +## [1.01.000] - 2021-05-04 +### Added +- Graphical installer based on Python3 with PySide2 GUI framework (Qt-bindings for Python) +- Changelog.MD + +### Changed +- Directory ".languages" renamed to "languages" due to python module import syntax constraints +- Files "english.txt" and "german.txt" renamed to .-py due to follow the python filename schema + +# Changelog Backuppy + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). diff --git a/install.py b/install.py new file mode 100644 index 0000000..e99284f --- /dev/null +++ b/install.py @@ -0,0 +1,336 @@ +#!/usr/bin/env python3 +""" +project: Backuppy +version: 1.02.000 +file: install.py +summary: main entry python file +""" + +# Standard library imports +import os + +# Third party imports +from PySide2 import QtCore +from PySide2 import QtGui +from PySide2 import QtCore, QtWidgets + +# local imports +from languages import english +from languages import german + +# local globals +VERSION: str = "1.02.000" +EMAIL: str = "fotocoder@joschu.ch" +TARGET_SCRIPT: str = "Backuppy.sh" +EXCLUDE_FILE: str = "exclude.txt" +LANG_EN: str = "English" +LANG_DE: str = "German" +LANGUAGE: str = LANG_EN +MYDIR: str = os.environ.get("mydir") +if not MYDIR: + MYDIR = os.getcwd() +SOURCEDIR: str = None +EXCLUDE: bool = True +TARGETDIR: str = None +RSYNC_CMD: str = None + +def get_lang_text(search_str: str): + """ Returns a string from the appropriate language file. """ + return_str: str = eval("english." + search_str) + global LANGUAGE + if LANGUAGE == LANG_DE: + return_str = eval("german." + search_str) + return return_str + +def trace(message_txt: str): + """ Print a formatted message to std out. """ + print(f"[ OK ]", message_txt) + +class BackuppyWizard(QtWidgets.QWizard): + def __init__(self, parent=None): + super().__init__(parent) + + self.addPage(Page01(self)) + self.addPage(Page02(self)) + self.addPage(Page03(self)) + self.addPage(Page04(self)) + self.addPage(Page05(self)) + self.addPage(Page06(self)) + self.addPage(Page07(self)) + self.addPage(Page08(self)) + self.addPage(Page09(self)) + self.addPage(Page10(self)) + + self.setWindowTitle(english.intromsg1) + self.resize(640, 480) + +class Page01(QtWidgets.QWizardPage): + def __init__(self, parent=None): + super().__init__(parent) + self.label = QtWidgets.QLabel("Hello, first of all, which language do you prefer: English or German?") + # self.comboBox = QIComboBox(self) + self.comboBox = QtWidgets.QComboBox(self) + self.comboBox.addItem(LANG_EN, LANG_EN) + self.comboBox.addItem(LANG_DE, LANG_DE) + + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.label) + layout.addWidget(self.comboBox) + self.setLayout(layout) + + def validatePage(self): + global LANGUAGE + if self.comboBox.currentText() == LANG_DE: + LANGUAGE = LANG_DE + else: + LANGUAGE = LANG_EN + return True + + +class Page02(QtWidgets.QWizardPage): + def __init__(self, parent=None): + super().__init__(parent) + self.label1 = QtWidgets.QLabel() + self.label2 = QtWidgets.QLabel() + + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.label1) + layout.addWidget(self.label2) + self.setLayout(layout) + + def initializePage(self): + self.label1.setText(get_lang_text("languagepack")) + self.label2.setText(get_lang_text("intromsg2")) + + +class Page03(QtWidgets.QWizardPage): + def __init__(self, parent=None): + super().__init__(parent) + self.label = QtWidgets.QLabel() + self.groupbox = QtWidgets.QGroupBox() + self.groupbox.setFlat(True) + self.radio1 = QtWidgets.QRadioButton() + self.radio2 = QtWidgets.QRadioButton() + + vbox = QtWidgets.QVBoxLayout() + vbox.addWidget(self.radio1) + vbox.addWidget(self.radio2) + vbox.addStretch(1) + self.groupbox.setLayout(vbox) + + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.label) + layout.addWidget(self.groupbox) + self.setLayout(layout) + + def initializePage(self): + self.label.setText(get_lang_text("excludefile1")) + self.radio1.setText(get_lang_text("Yes")) + self.radio2.setText(get_lang_text("No")) + self.radio1.setChecked(True) + + def validatePage(self): + global EXCLUDE + if self.radio1.isChecked(): + EXCLUDE = True + else: + EXCLUDE = False + return True + +class Page04(QtWidgets.QWizardPage): + def __init__(self, parent=None): + super().__init__(parent) + self.label = QtWidgets.QLabel() + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.label) + self.setLayout(layout) + + def initializePage(self): + global EXCLUDE + if EXCLUDE: + self.label.setText(get_lang_text("excludefile2")) + else: + self.label.setText(get_lang_text("excludefile3")) + +class Page05(QtWidgets.QWizardPage): + def __init__(self, parent=None): + super().__init__(parent) + self.label1 = QtWidgets.QLabel() + self.label2 = QtWidgets.QLabel() + self.edit = QtWidgets.QLineEdit() + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.label1) + layout.addWidget(self.label2) + layout.addWidget(self.edit) + self.setLayout(layout) + + def initializePage(self): + self.label1.setText(get_lang_text("srcdir1")) + self.label2.setText(get_lang_text("srcdir2")) + + def validatePage(self): + global SOURCEDIR + SOURCEDIR = self.edit.text() + + return True + + +class Page06(QtWidgets.QWizardPage): + def __init__(self, parent=None): + super().__init__(parent) + self.label1 = QtWidgets.QLabel() + self.label2 = QtWidgets.QLabel() + self.label3 = QtWidgets.QLabel() + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.label1) + layout.addWidget(self.label2) + layout.addWidget(self.label3) + self.setLayout(layout) + + def initializePage(self): + self.label1.setText(get_lang_text("srcdir3_1")) + bold_text = f"

{SOURCEDIR}

" + self.label2.setText(bold_text) + self.label3.setText(get_lang_text("srcdir3_2")) + + +class Page07(QtWidgets.QWizardPage): + def __init__(self, parent=None): + super().__init__(parent) + self.label1 = QtWidgets.QLabel() + self.edit = QtWidgets.QLineEdit() + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.label1) + layout.addWidget(self.edit) + self.setLayout(layout) + + def initializePage(self): + self.label1.setText(get_lang_text("targetdir1")) + + def validatePage(self): + global TARGETDIR + TARGETDIR = self.edit.text() + + return True + + +class Page08(QtWidgets.QWizardPage): + def __init__(self, parent=None): + super().__init__(parent) + self.label1 = QtWidgets.QLabel() + self.label2 = QtWidgets.QLabel() + self.label3 = QtWidgets.QLabel() + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.label1) + layout.addWidget(self.label2) + layout.addWidget(self.label3) + self.setLayout(layout) + + def initializePage(self): + self.label1.setText(get_lang_text("targetdir2_1")) + bold_text = f"

{TARGETDIR}

" + self.label2.setText(bold_text) + self.label3.setText(get_lang_text("targetdir2_2")) + + def validatePage(self): + global RSYNC_CMD, MYDIR, SOURCEDIR, TARGETDIR + RSYNC_CMD = f"rsync -aqp --exclude-from={MYDIR}/exclude.txt {SOURCEDIR} {TARGETDIR}" + + return True + + +class Page09(QtWidgets.QWizardPage): + def __init__(self, parent=None): + super().__init__(parent) + self.label1 = QtWidgets.QLabel() + self.label2 = QtWidgets.QLabel() + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.label1) + layout.addWidget(self.label2) + self.setLayout(layout) + + def initializePage(self): + self.label1.setText(get_lang_text("collect")) + global RSYNC_CMD + #self.label2.setText(f"{RSYNC_CMD}") + bold_text = f"

{RSYNC_CMD}

" + self.label2.setText(bold_text) + + +class Page10(QtWidgets.QWizardPage): + def __init__(self, parent=None): + super().__init__(parent) + self.label1 = QtWidgets.QLabel() + self.label2 = QtWidgets.QLabel() + self.label3 = QtWidgets.QLabel() + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.label1) + layout.addWidget(self.label2) + layout.addWidget(self.label3) + self.setLayout(layout) + self.setFinalPage(True) + + def initializePage(self): + self.label1.setText(get_lang_text("outro1")) + self.label2.setText(get_lang_text("outro2")) + urlLink = f"{EMAIL}" + self.label3.setText(urlLink) + self.label3.setOpenExternalLinks(True) + + def validatePage(self): + # create shell script with rsync command + create_shell_script(RSYNC_CMD) + + return True + + +def create_shell_script(command_str: str): + rc_filepath: str = None + + global MYDIR, EXCLUDE, TARGET_SCRIPT + + TARGET_SCRIPT = os.path.join(MYDIR, TARGET_SCRIPT) + ALIAS_STR = f"alias backuppy='sudo {TARGET_SCRIPT}'" + current_shell = os.environ.get("SHELL") + user_home = os.environ.get("HOME") + + ## STEP 1: update shell config scripts with alias for backuppy + # .zshrc case1 and case2 + if current_shell in ("/usr/bin/zsh", "/bin/zsh"): + # Appending to bash config file + rc_filepath = os.path.join(user_home, ".zshrc") + + # .bashrc case1 and case2 + elif current_shell in ("/usr/bin/bash", "/bin/bash"): + # Appending to zsh config file + rc_filepath = os.path.join(user_home, ".bashrc") + + if os.path.isfile(rc_filepath): + with open(rc_filepath, 'a') as file1: + trace(f"Writing {ALIAS_STR} to config file '{rc_filepath}'.") + file1.write("\n# Following line was created by Backuppy\n" + ALIAS_STR + "\n") + + ## STEP 2: Create the exclude script if desired + if EXCLUDE: + global EXCLUDE_FILE + EXCLUDE_FILE = os.path.join(MYDIR, EXCLUDE_FILE) + with open(EXCLUDE_FILE, "w") as file2: + trace(f"creating exclude-file '{EXCLUDE_FILE}'.") + file2.writelines("\n") + + ## STEP 3: enter the rsync command in Backuppy.sh + with open(TARGET_SCRIPT, "w") as file3: + trace(f"Writing rsync-command to file '{TARGET_SCRIPT}':\n {command_str}") + file3.writelines("#!/bin/bash" + "\n") + file3.writelines(command_str + "\n") + + +if __name__ == '__main__': + trace(f"Starting Backuppy install.py v{VERSION}") + app = QtWidgets.QApplication() + wizard = BackuppyWizard() + wizard.show() + + app.exec_() + + trace("Ending Backuppy install.py") diff --git a/languages/english.py b/languages/english.py index 891ef03..e04cfe2 100644 --- a/languages/english.py +++ b/languages/english.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 -""" -project: Backuppy -file: languages/english.py -summary: english language file -""" +# """ +# project: Backuppy +# file: languages/english.py +# summary: english language file +# """ Yes="Yes" @@ -17,7 +17,7 @@ intromsg2="The installer will now ask you a few things to adapt your backup scri rsyncopt="rsync offers various options, but to simplify the installation process, I have activated the options -a, -q and -p. \n If you want to set more options, you can do thjat in the file 'Backuppy.sh'." -excludefile1="Now I need to know if you want to exclude one or more files/directories from your backups.\nThen you can adjust this in the 'exclude.txt'.\nThere you can exclude directories and files in the format '/directory' '/file.txt'.\nDo you want to exclude files/directories or not?" +excludefile1="Now I need to know if you want to exclude one or more files/directories from your backups.\nThen you can adjust this in the 'exclude.txt'.\nThere you can exclude directories and files in the format '/directory' '/file.txt'.\nDo you want to exclude files/directories or not? [Y/N]" excludefile2="Perfect, then you can enter your files/directories to be excluded in the file 'exclude.txt' after completing the installation of Backuppy." diff --git a/languages/german.py b/languages/german.py index bf4081e..07508c9 100644 --- a/languages/german.py +++ b/languages/german.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 -""" -project: Backuppy -file: languages/german.py -summary: german language file -""" +# """ +# project: Backuppy +# file: languages/german.py +# summary: german language file +# """ Yes="Ja" @@ -17,7 +17,7 @@ intromsg2="Der Installer wird dich nun einige Dinge abfragen, um dein Backup-Skr rsyncopt="rsync bietet verschiedene Optionen an, um das Ganze jedoch zu vereinfachen, habe ich die Optionen -a, -q und -p aktiviert. \n Wenn du mehr einstellen willst, kannst du das in der Datei 'Backuppy.sh' machen." -excludefile1="Nun muss ich noch wissen, ob du ein oder mehrere Dateien/Verzeichnisse vom Backup ausschliessen möchtest.\nDann kannst du das in der 'exclude.txt' anpassen.\nDort kannst du dann im Format '/Verzeichnis' '/Datei.txt' Verzeichnisse und Dateien ausschliessen.\nMöchtest du Dateien/Verzeichnisse ausschliessen oder nicht?" +excludefile1="Nun muss ich noch wissen, ob du ein oder mehrere Dateien/Verzeichnisse vom Backup ausschliessen möchtest.\nDann kannst du das in der 'exclude.txt' anpassen.\nDort kannst du dann im Format '/Verzeichnis' '/Datei.txt' Verzeichnisse und Dateien ausschliessen.\nMöchtest du Dateien/Verzeichnisse ausschliessen oder nicht? [J/N]" excludefile2="Perfekt, dann kannst du nach der Fertigstellung der Installation von Backuppy deine auszuschliessenden\nDateien/Verzeichnisse in der Datei 'exclude.txt eintragen."