- Introduces `pybackup-cli.py` as a command-line interface for non-interactive backups. - Adds support for LUKS key files in `EncryptionManager` for passwordless container operations. - Updates `BackupManager` to pass key file arguments to encryption routines. - Modifies `AdvancedSettingsFrame` to provide a GUI for creating and managing key files. - Integrates `pybackup-cli.py` and key file options into `schedule_job_dialog.py` for cronjob generation.
214 lines
9.1 KiB
Python
214 lines
9.1 KiB
Python
import tkinter as tk
|
|
from tkinter import ttk
|
|
import os
|
|
|
|
from shared_libs.message import MessageDialog
|
|
from shared_libs.custom_file_dialog import CustomFileDialog
|
|
from pbp_app_config import Msg
|
|
|
|
|
|
class ScheduleJobDialog(tk.Toplevel):
|
|
def __init__(self, parent, backup_manager):
|
|
super().__init__(parent)
|
|
self.parent = parent
|
|
self.backup_manager = backup_manager
|
|
self.result = None
|
|
|
|
self.title(Msg.STR["add_job_title"])
|
|
self.geometry("550x550") # Increased size
|
|
self.transient(parent)
|
|
self.grab_set()
|
|
|
|
self.backup_type = tk.StringVar(value="system")
|
|
self.destination = tk.StringVar()
|
|
self.user_sources = {
|
|
Msg.STR["cat_images"]: tk.BooleanVar(value=False),
|
|
Msg.STR["cat_documents"]: tk.BooleanVar(value=False),
|
|
Msg.STR["cat_music"]: tk.BooleanVar(value=False),
|
|
Msg.STR["cat_videos"]: tk.BooleanVar(value=False)
|
|
}
|
|
self.frequency = tk.StringVar(value="daily")
|
|
self.use_key_file = tk.BooleanVar(value=False)
|
|
self.key_file_path = tk.StringVar()
|
|
|
|
self._create_widgets()
|
|
|
|
self.protocol("WM_DELETE_WINDOW", self._on_cancel)
|
|
self.wait_window()
|
|
|
|
def _create_widgets(self):
|
|
main_frame = ttk.Frame(self, padding=10)
|
|
main_frame.pack(fill=tk.BOTH, expand=True)
|
|
|
|
# Backup Type
|
|
type_frame = ttk.LabelFrame(
|
|
main_frame, text=Msg.STR["backup_type"], padding=10)
|
|
type_frame.pack(fill=tk.X, padx=5, pady=5)
|
|
ttk.Radiobutton(type_frame, text=Msg.STR["system_backup_menu"], variable=self.backup_type,
|
|
value="system", command=self._toggle_user_sources).pack(anchor=tk.W)
|
|
ttk.Radiobutton(type_frame, text=Msg.STR["user_backup_menu"], variable=self.backup_type,
|
|
value="user", command=self._toggle_user_sources).pack(anchor=tk.W)
|
|
|
|
# Destination
|
|
dest_frame = ttk.LabelFrame(
|
|
main_frame, text=Msg.STR["dest_folder"], padding=10)
|
|
dest_frame.pack(fill=tk.X, padx=5, pady=5)
|
|
ttk.Entry(dest_frame, textvariable=self.destination, state="readonly",
|
|
width=50).pack(side=tk.LEFT, fill=tk.X, expand=True)
|
|
ttk.Button(dest_frame, text=Msg.STR["browse"], command=self._select_destination).pack(
|
|
side=tk.RIGHT)
|
|
|
|
# User Sources (initially hidden)
|
|
self.user_sources_frame = ttk.LabelFrame(
|
|
main_frame, text=Msg.STR["source_folders"], padding=10)
|
|
for name, var in self.user_sources.items():
|
|
ttk.Checkbutton(self.user_sources_frame, text=name,
|
|
variable=var).pack(anchor=tk.W)
|
|
self._toggle_user_sources() # Set initial visibility
|
|
|
|
# Key File Options
|
|
key_file_frame = ttk.LabelFrame(main_frame, text="Key File for Encrypted Backups", padding=10)
|
|
key_file_frame.pack(fill=tk.X, padx=5, pady=5)
|
|
|
|
ttk.Checkbutton(key_file_frame, text="Use Key File for automated access", variable=self.use_key_file, command=self._toggle_key_file_entry).pack(anchor=tk.W)
|
|
|
|
self.key_file_entry = ttk.Entry(key_file_frame, textvariable=self.key_file_path, state="disabled", width=50)
|
|
self.key_file_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
|
|
self.key_file_browse_button = ttk.Button(key_file_frame, text=Msg.STR["browse"], command=self._select_key_file, state="disabled")
|
|
self.key_file_browse_button.pack(side=tk.RIGHT)
|
|
|
|
# Frequency
|
|
freq_frame = ttk.LabelFrame(
|
|
main_frame, text=Msg.STR["frequency"], padding=10)
|
|
freq_frame.pack(fill=tk.X, padx=5, pady=5)
|
|
ttk.Radiobutton(freq_frame, text=Msg.STR["freq_daily"],
|
|
variable=self.frequency, value="daily").pack(anchor=tk.W)
|
|
ttk.Radiobutton(freq_frame, text=Msg.STR["freq_weekly"],
|
|
variable=self.frequency, value="weekly").pack(anchor=tk.W)
|
|
ttk.Radiobutton(freq_frame, text=Msg.STR["freq_monthly"],
|
|
variable=self.frequency, value="monthly").pack(anchor=tk.W)
|
|
|
|
# Buttons
|
|
button_frame = ttk.Frame(main_frame)
|
|
button_frame.pack(pady=10)
|
|
ttk.Button(button_frame, text=Msg.STR["save"], command=self._on_save).pack(
|
|
side=tk.LEFT, padx=5)
|
|
ttk.Button(button_frame, text=Msg.STR["cancel"], command=self._on_cancel).pack(
|
|
side=tk.LEFT, padx=5)
|
|
|
|
def _toggle_user_sources(self):
|
|
if self.backup_type.get() == "user":
|
|
self.user_sources_frame.pack(fill=tk.X, padx=5, pady=5)
|
|
else:
|
|
self.user_sources_frame.pack_forget()
|
|
|
|
def _toggle_key_file_entry(self):
|
|
state = "normal" if self.use_key_file.get() else "disabled"
|
|
self.key_file_entry.config(state=state)
|
|
self.key_file_browse_button.config(state=state)
|
|
|
|
def _select_destination(self):
|
|
dialog = CustomFileDialog(
|
|
self, mode="dir", title=Msg.STR["select_dest_folder_title"])
|
|
self.wait_window(dialog)
|
|
result = dialog.get_result()
|
|
if result:
|
|
self.destination.set(result)
|
|
|
|
def _select_key_file(self):
|
|
dialog = CustomFileDialog(
|
|
self, mode="file", title="Select Key File", filetypes=[("Key Files", "*.key"), ("All Files", "*.*")]
|
|
)
|
|
self.wait_window(dialog)
|
|
result = dialog.get_result()
|
|
if result:
|
|
self.key_file_path.set(result)
|
|
|
|
def _on_save(self):
|
|
dest = self.destination.get()
|
|
if not dest:
|
|
MessageDialog(master=self, message_type="error",
|
|
title=Msg.STR["error"], text=Msg.STR["err_no_dest_folder"])
|
|
return
|
|
|
|
job_type = self.backup_type.get()
|
|
job_frequency = self.frequency.get()
|
|
job_sources = []
|
|
|
|
if job_type == "user":
|
|
job_sources = [name for name,
|
|
var in self.user_sources.items() if var.get()]
|
|
if not job_sources:
|
|
MessageDialog(master=self, message_type="error",
|
|
title=Msg.STR["error"], text=Msg.STR["err_no_source_folder"])
|
|
return
|
|
|
|
# Check if destination is an encrypted container
|
|
is_encrypted_container = os.path.exists(os.path.join(dest, "pybackup", "pybackup_encrypted.luks"))
|
|
|
|
if is_encrypted_container and not self.use_key_file.get():
|
|
MessageDialog(master=self, message_type="error",
|
|
title=Msg.STR["error"], text="For encrypted destinations, 'Use Key File' must be selected for automated backups.")
|
|
return
|
|
|
|
if self.use_key_file.get() and not self.key_file_path.get():
|
|
MessageDialog(master=self, message_type="error",
|
|
title=Msg.STR["error"], text="Please select a key file path.")
|
|
return
|
|
|
|
# Construct the CLI command
|
|
script_path = os.path.abspath(os.path.join(
|
|
os.path.dirname(__file__), "pybackup-cli.py")) # Call the CLI script
|
|
command = f"python3 {script_path} --backup-type {job_type} --destination \"{dest}\""
|
|
|
|
if job_type == "user":
|
|
# For CLI, we assume a single source path for simplicity
|
|
if len(job_sources) > 1:
|
|
MessageDialog(master=self, message_type="warning",
|
|
title="Warning", text="For user backups, only the first selected source will be used in the automated job.")
|
|
if job_sources:
|
|
# Use the actual path from AppConfig.FOLDER_PATHS
|
|
source_folder_name = job_sources[0]
|
|
source_path = str(AppConfig.FOLDER_PATHS.get(source_folder_name, ""))
|
|
if not source_path:
|
|
MessageDialog(master=self, message_type="error",
|
|
title=Msg.STR["error"], text=f"Unknown source folder: {source_folder_name}")
|
|
return
|
|
command += f" --source \"{source_path}\""
|
|
else:
|
|
MessageDialog(master=self, message_type="error",
|
|
title=Msg.STR["error"], text="Please select a source folder for user backup.")
|
|
return
|
|
|
|
if is_encrypted_container:
|
|
command += " --encrypted"
|
|
if self.use_key_file.get():
|
|
command += f" --key-file \"{self.key_file_path.get()}\""
|
|
# No --password option for cronjobs, as it's insecure
|
|
|
|
# Construct the cron job comment
|
|
comment = f"{self.backup_manager.app_tag}; type:{job_type}; freq:{job_frequency}; dest:{dest}"
|
|
if job_type == "user":
|
|
comment += f"; source:{source_path}"
|
|
if is_encrypted_container:
|
|
comment += "; encrypted"
|
|
if self.use_key_file.get():
|
|
comment += "; key_file"
|
|
|
|
self.result = {
|
|
"command": command,
|
|
"comment": comment,
|
|
"type": job_type,
|
|
"frequency": job_frequency,
|
|
"destination": dest,
|
|
"sources": job_sources # Keep original sources for display if needed
|
|
}
|
|
self.destroy()
|
|
|
|
def _on_cancel(self):
|
|
self.result = None
|
|
self.destroy()
|
|
|
|
def show(self):
|
|
self.parent.wait_window(self)
|
|
return self.result |