diff --git a/README.md b/README.md index c1074eb..285ab56 100644 --- a/README.md +++ b/README.md @@ -151,11 +151,14 @@ Platzbedarf: voll + delta (statt 2x voll) ## CLI Nutzung ```bash -# Backup jetzt erstellen und beenden +# Backup jetzt erstellen (fragt nach sudo-Passwort) x3-backup --backup +# Backup mit Passwort-Argument (z.B. fuer Cron) +x3-backup --backup --sudo-password "DEIN_PASSWORT" + # System-Backup (von Live-ISO) -x3-backup --system-backup +x3-backup --system-backup --sudo-password "DEIN_PASSWORT" # Log auf STDOUT ausgeben x3-backup --backup --log @@ -167,14 +170,26 @@ x3-backup --help ## Cronjob Beispiel ```bash +# WICHTIG: Sudo-Passwort in Datei speichern fuer Cron: +echo "DEIN_PASSWORT" > ~/.config/x3-backup/cron-pw.conf +chmod 600 ~/.config/x3-backup/cron-pw.conf + +# Script fuer Cron erstellen: +cat > ~/.config/x3-backup/run-backup.sh << 'EOF' +#!/bin/bash +PW=$(cat ~/.config/x3-backup/cron-pw.conf 2>/dev/null) +/usr/local/bin/x3-backup --backup --sudo-password "$PW" >> ~/.config/x3-backup/x3-backup.log 2>&1 +EOF +chmod +x ~/.config/x3-backup/run-backup.sh + # Taeglich um 2:00 Uhr -0 2 * * * /usr/local/bin/x3-backup --backup & +0 2 * * * ~/.config/x3-backup/run-backup.sh # Alle 6 Stunden -0 */6 * * * /usr/local/bin/x3-backup --backup & +0 */6 * * * ~/.config/x3-backup/run-backup.sh # Woechentlich Sonntag 3:00 Uhr -0 3 * * 0 /usr/local/bin/x3-backup --backup & +0 3 * * 0 ~/.config/x3-backup/run-backup.sh ``` ## Log Format @@ -190,17 +205,51 @@ x3-backup --help | Aktion | Benoetigte Rechte | |---|---| -| Backup erstellen | Normaler User (nur lesend auf Quelle) | +| Backup erstellen | sudo (fuer korrekte Berechtigungen) | | Datei wiederherstellen | sudo (fuer Root-Berechtigungen) | | System wiederherstellen | sudo | | Cronjob installieren | Normaler User (crontab -e) | +**Wichtig:** Backups werden mit sudo ausgefuehrt, um alle Dateiberechtigungen (UID/GID, rwx, ACLs, xattrs) +rekursiv korrekt zu uebernehmen. Ohne sudo waere das Backup unvollstaendig, da Root-Dateien nicht +gelesen und Berechtigungen nicht preserve werden koennen. + ## Abhaengigkeiten +### Build-Abhaengigkeiten + +**Ubuntu / Debian:** +```bash +sudo apt install build-essential autoconf automake pkg-config libgtk-3-dev libjson-glib-dev rsync +``` + +**Arch Linux:** +```bash +sudo pacman -S base-devel gtk3 json-glib rsync +``` + +**Gentoo Linux:** +```bash +sudo emerge --ask dev-build/autoconf dev-build/automake dev-util/pkgconfig x11-libs/gtk+:3 dev-libs/json-glib net-misc/rsync +``` + +**openSUSE:** +```bash +sudo zypper install -y gcc-c++ autoconf automake pkg-config gtk3-devel json-glib-devel rsync +``` + +**Fedora:** +```bash +sudo dnf install gcc-c++ autoconf automake pkgconfig gtk3-devel json-glib-devel rsync +``` + +### Laufzeit-Abhaengigkeiten + - GTK+ 3.0 (>= 3.24) - json-glib-1.0 - rsync -- crontab (optional) +- sudo (fuer Root-Rechte bei Backups) +- crontab (optional, fuer geplante Backups) ## Architektur diff --git a/src/main.cpp b/src/main.cpp index ad35efe..4d7d8aa 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -77,6 +77,7 @@ int main(int argc, char* argv[]) { bool backup_now = false; bool show_help = false; bool cli_mode = false; + std::string sudo_password; for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "--system-backup") == 0) { @@ -90,6 +91,8 @@ int main(int argc, char* argv[]) { show_help = true; } else if (strcmp(argv[i], "--log") == 0) { log_to_stdout = true; + } else if (strcmp(argv[i], "--sudo-password") == 0 && i + 1 < argc) { + sudo_password = argv[++i]; } } @@ -99,11 +102,14 @@ int main(int argc, char* argv[]) { g_print("x3-Backup - GTK3 Backup/Snapshot Tool\n\n"); g_print("Usage: x3-backup [OPTIONS]\n\n"); g_print("Options:\n"); - g_print(" --backup Create a backup immediately and exit\n"); - g_print(" --system-backup Create a full system backup and exit\n"); - g_print(" --log Print log to stdout\n"); - g_print(" --help, -h Show this help\n\n"); + g_print(" --backup Create a backup immediately and exit\n"); + g_print(" --system-backup Create a full system backup and exit\n"); + g_print(" --sudo-password PW Provide sudo password for backup (required for root permissions)\n"); + g_print(" --log Print log to stdout\n"); + g_print(" --help, -h Show this help\n\n"); g_print("Without options, x3-backup starts in GUI mode with system tray.\n\n"); + g_print("Note: Backups require sudo to preserve file ownership and permissions.\n"); + g_print(" Use --sudo-password or run interactively for password prompt.\n\n"); g_print("Log file: ~/.config/x3-backup/x3-backup.log\n"); return 0; } @@ -118,12 +124,32 @@ int main(int argc, char* argv[]) { g_message("System backup mode: backing up entire system"); } + /* Prompt for sudo password if not provided */ + if (sudo_password.empty()) { + g_print("Sudo password required for backup (to preserve file permissions):\n"); + g_print("Password: "); + fflush(stdout); + char* pw = nullptr; + size_t len = 0; + ssize_t nread = getline(&pw, &len, stdin); + if (nread > 0 && pw[nread - 1] == '\n') pw[nread - 1] = '\0'; + if (pw && *pw) { + sudo_password = std::string(pw); + } + free(pw); + if (sudo_password.empty()) { + g_warning("No password provided, backup may have incorrect permissions"); + } + } + RsyncManager rsync(config); SnapshotManager snapshots(config, rsync); - g_message("Starting backup..."); + g_message("Starting backup with sudo..."); snapshots.cleanup_old_snapshots(); - std::string ts = snapshots.create_snapshot(system_mode ? "System backup" : "Manual backup"); + std::string ts = snapshots.create_snapshot( + system_mode ? "System backup" : "Manual backup", + sudo_password); if (!ts.empty()) { g_message("Backup completed: %s", ts.c_str()); } else { diff --git a/src/main_window.cpp b/src/main_window.cpp index ac91cdc..9b4e763 100644 --- a/src/main_window.cpp +++ b/src/main_window.cpp @@ -673,7 +673,9 @@ void MainWindow::show_sudo_dialog(std::function callba GtkWidget* icon = gtk_image_new_from_icon_name("dialog-password", GTK_ICON_SIZE_DIALOG); gtk_box_pack_start(GTK_BOX(box), icon, FALSE, FALSE, 0); - GtkWidget* label = gtk_label_new("Root access is required to restore files.\nEnter your sudo password:"); + GtkWidget* label = gtk_label_new("Root access is required to preserve file permissions.\n" + "Backups will be created with sudo to ensure correct ownership and permissions.\n\n" + "Enter your sudo password:"); gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 0); @@ -689,6 +691,7 @@ void MainWindow::show_sudo_dialog(std::function callba if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { const char* pw = gtk_entry_get_text(GTK_ENTRY(pass_entry)); if (pw && *pw) { + current_sudo_password_ = std::string(pw); callback(std::string(pw)); } } @@ -874,15 +877,17 @@ void MainWindow::append_backup_line(const std::string& line, const char* tag) { void MainWindow::on_backup(GtkWidget*, gpointer d) { auto* self = static_cast(d); - self->tray_.set_status("x3-Backup - Creating backup...", true); - self->open_backup_window("Creating Snapshot..."); - gtk_label_set_text(GTK_LABEL(self->live_log_label_), "Backup running..."); + self->show_sudo_dialog([self](const std::string& pw) { + self->tray_.set_status("x3-Backup - Creating backup...", true); + self->open_backup_window("Creating Snapshot..."); + gtk_label_set_text(GTK_LABEL(self->live_log_label_), "Backup running..."); - g_thread_new("backup", [](gpointer data) -> gpointer { - auto* s = static_cast(data); - s->snapshots_.create_snapshot(); - return nullptr; - }, self); + g_thread_new("backup", [](gpointer data) -> gpointer { + auto* s = static_cast(data); + s->snapshots_.create_snapshot("", s->current_sudo_password_); + return nullptr; + }, self); + }); } void MainWindow::start_quick_backup() { diff --git a/src/main_window.h b/src/main_window.h index a306c39..9269764 100644 --- a/src/main_window.h +++ b/src/main_window.h @@ -99,6 +99,7 @@ private: std::string current_snapshot_; std::string current_path_; + std::string current_sudo_password_; std::vector path_stack_; int col_name = 0, col_size = 1, col_type = 2, col_permissions = 3, col_mtime = 4; diff --git a/src/rsync_manager.cpp b/src/rsync_manager.cpp index 4c0e6ab..779e648 100644 --- a/src/rsync_manager.cpp +++ b/src/rsync_manager.cpp @@ -54,7 +54,8 @@ std::vector RsyncManager::build_exclude_args() { bool RsyncManager::create_snapshot(const std::string& snapshot_name, const std::string& source, const std::string& dest, - const std::string& link_dest) { + const std::string& link_dest, + const std::string& sudo_password) { if (running_) return false; cancel_requested_ = false; running_ = true; @@ -74,19 +75,26 @@ bool RsyncManager::create_snapshot(const std::string& snapshot_name, int64_t estimated_size = estimate_backup_size(source); std::string target = dest + "/" + snapshot_name; - std::string cmd = "rsync -aAX --info=progress2"; + std::string rsync_flags = "rsync -aAX --info=progress2 --numeric-ids"; if (config_.settings().delete_removed) - cmd += " --delete"; + rsync_flags += " --delete"; if (!link_dest.empty()) - cmd += " --link-dest=\"" + link_dest + "\""; + rsync_flags += " --link-dest=\"" + link_dest + "\""; for (const auto& e : config_.settings().exclude_patterns) { - cmd += " --exclude=\"" + e + "\""; + rsync_flags += " --exclude=\"" + e + "\""; } - cmd += " \"" + source + "/\" \"" + target + "/\" 2>&1"; + rsync_flags += " \"" + source + "/\" \"" + target + "/\" 2>&1"; + + std::string cmd; + if (!sudo_password.empty()) { + cmd = "mkdir -p \"" + dest + "\" && echo '" + sudo_password + "' | sudo -S " + rsync_flags; + } else { + cmd = rsync_flags; + } /* Force line-buffered output so we get real-time progress from rsync */ std::string full_cmd = "stdbuf -oL " + cmd; diff --git a/src/rsync_manager.h b/src/rsync_manager.h index 11355d6..791d5bf 100644 --- a/src/rsync_manager.h +++ b/src/rsync_manager.h @@ -31,7 +31,8 @@ public: bool create_snapshot(const std::string& snapshot_name, const std::string& source, const std::string& dest, - const std::string& link_dest = ""); + const std::string& link_dest = "", + const std::string& sudo_password = ""); bool restore_files(const std::string& snapshot_path, const std::vector& files, diff --git a/src/snapshot_manager.cpp b/src/snapshot_manager.cpp index 8c7a798..4553d6d 100644 --- a/src/snapshot_manager.cpp +++ b/src/snapshot_manager.cpp @@ -20,15 +20,20 @@ std::string SnapshotManager::get_timestamp() { return std::string(buf); } -std::string SnapshotManager::create_snapshot(const std::string& description) { +std::string SnapshotManager::create_snapshot(const std::string& description, + const std::string& sudo_password) { std::string ts = get_timestamp(); std::string snapshots_dir = config_.snapshots_dir(); std::string metadata_dir = config_.metadata_dir(); - std::string cmd1 = "mkdir -p \"" + snapshots_dir + "\""; - std::string cmd2 = "mkdir -p \"" + metadata_dir + "\""; - system(cmd1.c_str()); - system(cmd2.c_str()); + std::string mkdir_cmd; + if (!sudo_password.empty()) { + mkdir_cmd = "echo '" + sudo_password + "' | sudo -S mkdir -p \"" + snapshots_dir + "\" && " + "echo '" + sudo_password + "' | sudo -S mkdir -p \"" + metadata_dir + "\""; + } else { + mkdir_cmd = "mkdir -p \"" + snapshots_dir + "\" && mkdir -p \"" + metadata_dir + "\""; + } + system(mkdir_cmd.c_str()); /* Find latest snapshot for hardlink delta */ std::string link_dest = ""; @@ -39,7 +44,7 @@ std::string SnapshotManager::create_snapshot(const std::string& description) { /* Do NOT overwrite rsync_.on_progress — MainWindow already set it */ - bool ok = rsync_.create_snapshot(ts, config_.settings().source_path, snapshots_dir, link_dest); + bool ok = rsync_.create_snapshot(ts, config_.settings().source_path, snapshots_dir, link_dest, sudo_password); if (ok) { SnapshotInfo info; @@ -55,11 +60,13 @@ std::string SnapshotManager::create_snapshot(const std::string& description) { void SnapshotManager::delete_snapshot(const std::string& name) { std::string path = config_.snapshots_dir() + "/" + name; - std::string cmd = "rm -rf \"" + path + "\""; + std::string meta = config_.metadata_dir() + "/" + name + ".json"; + + /* Try sudo first (snapshots are owned by root), fallback to normal rm */ + std::string cmd = "echo '' | sudo -S rm -rf \"" + path + "\" 2>/dev/null || rm -rf \"" + path + "\""; system(cmd.c_str()); - std::string meta = config_.metadata_dir() + "/" + name + ".json"; - std::string meta_cmd = "rm -f \"" + meta + "\""; + std::string meta_cmd = "echo '' | sudo -S rm -f \"" + meta + "\" 2>/dev/null || rm -f \"" + meta + "\""; system(meta_cmd.c_str()); } diff --git a/src/snapshot_manager.h b/src/snapshot_manager.h index 1df17a9..4c89e7c 100644 --- a/src/snapshot_manager.h +++ b/src/snapshot_manager.h @@ -31,7 +31,8 @@ class SnapshotManager { public: SnapshotManager(Config& config, RsyncManager& rsync); - std::string create_snapshot(const std::string& description = ""); + std::string create_snapshot(const std::string& description = "", + const std::string& sudo_password = ""); void delete_snapshot(const std::string& name); std::vector list_snapshots(); SnapshotInfo get_snapshot_info(const std::string& name);