add functions in bash skript

This commit is contained in:
2025-09-14 00:00:16 +02:00
parent ff08c9b646
commit 507c1554ee
3 changed files with 131 additions and 25 deletions

View File

@@ -176,6 +176,21 @@ do_unmount_all() {
log "Bulk unmount complete."
}
do_mount_all() {
log "Mounting all provided profiles."
# Don't exit on error inside the loop
set +e
for profile_info in "$@"; do
# Split the info string container_path:mapper_name:mount_point:uid:gid
IFS=':' read -r container_path mapper_name mount_point uid gid <<< "$profile_info"
log "Attempting to mount $container_path"
# Call do_mount with the parsed arguments. Note: keyfile not supported in bulk mount.
do_mount "$container_path" "$mapper_name" "$mount_point" "$uid" "$gid" ""
done
set -e
log "Bulk mount process complete."
}
# --- Main Command Dispatcher ---
case "$COMMAND" in
@@ -194,10 +209,13 @@ case "$COMMAND" in
unmount_all)
do_unmount_all "$@"
;;
mount_all)
do_mount_all "$@"
;;
*)
log "Unknown command: $COMMAND"
exit 1
;;
esac
exit 0
exit 0

View File

@@ -347,6 +347,45 @@ class EncryptionManager:
return success
def mount_all_profiles(self, profiles: list, password: str) -> bool:
"""Attempts to mount a list of encrypted profiles using a single password."""
if not profiles:
return True
self.logger.log(f"Attempting to bulk mount {len(profiles)} profiles.")
profile_info_strings = []
for profile in profiles:
# Create the info string: container_path:mapper_name:mount_point:uid:gid
username = os.path.basename(profile['base_dest_path'].rstrip('/'))
mapper_name = f"pybackup_luks_{username}_{profile['profile_name']}"
container_path = self.get_container_path(profile['base_dest_path'], profile['profile_name'])
mount_point = self.get_mount_point(profile['base_dest_path'], profile['profile_name'])
uid, gid = self._get_chown_ids(profile['is_system'])
# Keyfiles are not supported in bulk mount, as it relies on a single shared password.
info_str = f'{container_path}:{mapper_name}:{mount_point}:{uid or ""}:{gid or ""}'
profile_info_strings.append(info_str)
helper_path = os.path.join(os.path.dirname(__file__), 'encryption_helper.sh')
script = f'"{helper_path}" mount_all {" ".join(f"\"{info}\"" for info in profile_info_strings)}'
success = self._execute_as_root(script, password)
if success:
# After a bulk attempt, we need to refresh the internal state for all of them
for profile in profiles:
if os.path.ismount(self.get_mount_point(profile['base_dest_path'], profile['profile_name'])):
self.mounted_destinations.add((profile['base_dest_path'], profile['profile_name']))
self.add_to_lock_file(profile['base_dest_path'], profile['profile_name'], f"pybackup_luks_{os.path.basename(profile['base_dest_path'].rstrip('/'))}_{profile['profile_name']}")
self.logger.log("Bulk mount script executed.")
else:
self.logger.log("Bulk mount script failed to execute.")
if self.app and hasattr(self.app, 'header_frame'):
self.app.header_frame.refresh_status()
return success
def _get_chown_ids(self, is_system: bool) -> Tuple[Optional[int], Optional[int]]:
if not is_system:
try:

View File

@@ -1,6 +1,7 @@
import tkinter as tk
from tkinter import ttk
import os
import inspect
from shared_libs.animated_icon import AnimatedIcon
from core.pbp_app_config import Msg
from pyimage_ui.system_backup_content_frame import SystemBackupContentFrame
@@ -82,41 +83,84 @@ class BackupContentFrame(ttk.Frame):
action_button_frame = ttk.Frame(self, padding=10)
action_button_frame.grid(row=2, column=0, sticky="ew")
action_button_frame.columnconfigure(1, weight=1) # Make middle column expandable
# --- Mount Controls ---
self.mount_labelframe = ttk.LabelFrame(action_button_frame, text="Mount Encrypt")
# Blue border frame inside the LabelFrame
mount_border_frame = tk.Frame(self.mount_labelframe, background="#0078D7")
mount_border_frame.pack(padx=5, pady=5)
# Content frame inside the border frame
theme_bg_color = self.winfo_toplevel().style.lookup('TFrame', 'background')
mount_content_frame = tk.Frame(mount_border_frame, background=theme_bg_color)
mount_content_frame.pack(padx=2, pady=2)
mount_label = ttk.Label(mount_content_frame, text="Profile:")
mount_label.grid(row=0, column=0, sticky="w", padx=5, pady=5)
self.mount_all_button = ttk.Button(
mount_content_frame, text="Mount All", command=self._mount_all_profiles)
self.mount_all_button.grid(row=0, column=1, sticky="ew", padx=5, pady=5)
self.profile_combobox = ttk.Combobox(
mount_content_frame, state="readonly", width=20)
self.profile_combobox.grid(row=1, column=0, sticky="w", padx=5, pady=5)
self.mount_button = ttk.Button(
mount_content_frame, text="Mount", command=self._mount_selected_profile)
self.mount_button.grid(row=1, column=1, sticky="ew", padx=5, pady=5)
# --- Action Buttons ---
self.action_buttons_container = ttk.Frame(action_button_frame)
self.restore_button = ttk.Button(
action_button_frame, text=Msg.STR["restore"], command=self._restore_selected, state="disabled")
self.action_buttons_container, text=Msg.STR["restore"], command=self._restore_selected, state="disabled", style="Success.TButton")
self.restore_button.pack(side=tk.LEFT, padx=5)
self.delete_button = ttk.Button(
action_button_frame, text=Msg.STR["delete"], command=self._delete_selected, state="disabled")
self.action_buttons_container, text=Msg.STR["delete"], command=self._delete_selected, state="disabled", style="Danger.TButton")
self.delete_button.pack(side=tk.LEFT, padx=5)
self.edit_comment_button = ttk.Button(
action_button_frame, text=Msg.STR["comment"], command=self._edit_comment, state="disabled")
self.action_buttons_container, text=Msg.STR["comment"], command=self._edit_comment, state="disabled")
self.edit_comment_button.pack(side=tk.LEFT, padx=5)
# Mount controls for encrypted profiles, now at the bottom
self.mount_frame = ttk.Frame(action_button_frame)
self.mount_frame.pack(side=tk.RIGHT, padx=10)
mount_label = ttk.Label(self.mount_frame, text="Encrypted Profile:")
mount_label.pack(side=tk.LEFT, padx=(0, 5))
self.profile_combobox = ttk.Combobox(
self.mount_frame, state="readonly", width=20)
self.profile_combobox.pack(side=tk.LEFT)
self.mount_button = ttk.Button(
self.mount_frame, text="Mount", command=self._mount_selected_profile)
self.mount_button.pack(side=tk.LEFT, padx=5)
self._switch_view(0)
def _mount_selected_profile(self):
profile_name = self.profile_combobox.get()
if not profile_name:
return
self._mount_profile(profile_name, single_mount=True)
def _mount_all_profiles(self):
unmounted_profiles = [{'profile_name': name, **data} for name, data in self.encrypted_profiles.items() if not data['is_mounted']]
if not unmounted_profiles:
MessageDialog(message_type="info", title="Info", text="All encrypted profiles are already mounted.").show()
return
# Get password once. The username for the password prompt is taken from the destination path, which is common.
username = os.path.basename(self.base_backup_path.rstrip('/'))
password = self.backup_manager.encryption_manager.get_password(username, confirm=False)
if not password:
app_logger.log("Mount All cancelled: No password provided.")
return
self.app.config(cursor="watch")
self.update()
self.backup_manager.encryption_manager.mount_all_profiles(unmounted_profiles, password)
self.app.config(cursor="")
self.show(self.base_backup_path, self.current_view_index)
def _mount_profile(self, profile_name: str, single_mount: bool = False) -> bool:
profile_data = self.encrypted_profiles.get(profile_name)
if not profile_data:
return
return False
self.app.config(cursor="watch")
self.update()
@@ -129,12 +173,14 @@ class BackupContentFrame(ttk.Frame):
self.app.config(cursor="")
if success:
# Refresh the view
self.show(self.base_backup_path, self.current_view_index)
else:
if not success:
MessageDialog(message_type="error", title="Error",
text=f"Failed to mount profile '{profile_name}'.\nPlease check the password and try again.").show()
if single_mount:
self.show(self.base_backup_path, self.current_view_index)
return success
def update_button_state(self, is_selected):
self.restore_button.config(
@@ -190,7 +236,8 @@ class BackupContentFrame(ttk.Frame):
f"Backup path {pybackup_dir} does not exist or is not a directory.")
self.system_backups_frame.show(backup_path, [])
self.user_backups_frame.show(backup_path, [])
self.mount_frame.pack_forget()
self.mount_labelframe.grid_remove()
self.action_buttons_container.grid_remove()
return
backup_data = self.backup_manager.list_all_backups(backup_path)
@@ -205,11 +252,13 @@ class BackupContentFrame(ttk.Frame):
]
if unmounted_profiles:
self.mount_labelframe.grid(row=0, column=0, sticky="w")
self.profile_combobox['values'] = unmounted_profiles
self.profile_combobox.set(unmounted_profiles[0])
self.mount_frame.pack(side=tk.RIGHT, padx=10)
else:
self.mount_frame.pack_forget()
self.mount_labelframe.grid_remove()
self.action_buttons_container.grid(row=0, column=1, sticky="") # Center the buttons
self.after(10, lambda: self._switch_view(initial_tab_index))
@@ -225,4 +274,4 @@ class BackupContentFrame(ttk.Frame):
def hide_deletion_status(self):
app_logger.log("Hiding deletion status text.")
self.deletion_status_label.config(text="")
self.deletion_animated_icon.stop("DISABLE")
self.deletion_animated_icon.stop("DISABLE")