ready to use
This commit is contained in:
parent
3e6b0afc7a
commit
e626185d13
15 changed files with 683 additions and 250 deletions
178
Cargo.lock
generated
178
Cargo.lock
generated
|
@ -43,6 +43,15 @@ dependencies = [
|
|||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
|
@ -152,6 +161,12 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.38"
|
||||
|
@ -222,6 +237,19 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.15.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
|
||||
dependencies = [
|
||||
"encode_unicode",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"unicode-width",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.6"
|
||||
|
@ -266,6 +294,29 @@ dependencies = [
|
|||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
version = "3.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "672465ae37dc1bc6380a6547a8883d5dd397b0f1faaad4f265726cc7042a5345"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dialoguer"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de"
|
||||
dependencies = [
|
||||
"console",
|
||||
"shell-words",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
|
@ -277,6 +328,12 @@ dependencies = [
|
|||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.9"
|
||||
|
@ -417,6 +474,12 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.155"
|
||||
|
@ -436,15 +499,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||
|
||||
[[package]]
|
||||
name = "lzma-sys"
|
||||
version = "0.1.20"
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
|
@ -455,6 +513,18 @@ dependencies = [
|
|||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
|
@ -483,22 +553,16 @@ dependencies = [
|
|||
"aes-gcm",
|
||||
"chrono",
|
||||
"clap",
|
||||
"ctrlc",
|
||||
"dialoguer",
|
||||
"flate2",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
"regex",
|
||||
"sha2",
|
||||
"tar",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"xz2",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
||||
|
||||
[[package]]
|
||||
name = "polyval"
|
||||
version = "0.6.2"
|
||||
|
@ -547,6 +611,35 @@ dependencies = [
|
|||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.34"
|
||||
|
@ -560,6 +653,15 @@ dependencies = [
|
|||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.8"
|
||||
|
@ -571,6 +673,12 @@ dependencies = [
|
|||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shell-words"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
|
@ -649,6 +757,12 @@ version = "1.0.12"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
|
||||
|
||||
[[package]]
|
||||
name = "universal-hash"
|
||||
version = "0.5.1"
|
||||
|
@ -671,6 +785,16 @@ version = "0.9.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
|
@ -731,6 +855,15 @@ version = "0.2.92"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
|
@ -825,10 +958,7 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "xz2"
|
||||
version = "0.1.7"
|
||||
name = "zeroize"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2"
|
||||
dependencies = [
|
||||
"lzma-sys",
|
||||
]
|
||||
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
|
||||
|
|
17
Cargo.toml
17
Cargo.toml
|
@ -9,11 +9,20 @@ edition = "2021"
|
|||
aes-gcm = "0.10.3"
|
||||
chrono = "0.4.38"
|
||||
clap = { version = "4.5.4", features = ["derive"] }
|
||||
# cron = "0.12.1"
|
||||
ctrlc = "3.4.4"
|
||||
dialoguer = "0.11.0"
|
||||
flate2 = "1.0.30"
|
||||
hkdf = "0.12.4"
|
||||
hmac = "0.12.1"
|
||||
# hmac = "0.12.1"
|
||||
regex = { version = "1.10.5", features = ["use_std"] }
|
||||
sha2 = "0.10.8"
|
||||
tar = "0.4.41"
|
||||
tempfile = "3.10.1"
|
||||
thiserror = "1.0.61"
|
||||
xz2 = "0.1.7"
|
||||
walkdir = "2.5.0"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3 # Optimize for speed.
|
||||
lto = true # Enable Link Time Optimization.
|
||||
codegen-units = 1 # Fewer codegen units for better optimization.
|
||||
panic = "abort" # Abort on panic to reduce binary size.
|
||||
strip = true # Remove symbols from the binary.
|
||||
|
|
55
README.md
55
README.md
|
@ -14,55 +14,8 @@ and SSH keys.
|
|||
- Backup GPG and SSH keys
|
||||
- Restore from backups
|
||||
|
||||
## Roadmap
|
||||
### Phase 1: Core Functionality
|
||||
- [ ] Develop a command-line interface (CLI) for the application
|
||||
- [ ] Implement the functionality to backup installed applications using Pacman
|
||||
- [ ] Implement the functionality to backup the user's home directory
|
||||
- [ ] Implement the functionality to backup GPG and SSH keys
|
||||
|
||||
### Phase 2: Flatpak Integration
|
||||
|
||||
- [ ] Add an option to backup Flatpak packages
|
||||
- [ ] Integrate with the Flatpak package manager to list and backup installed Flatpak packages
|
||||
|
||||
### Phase 3: Restore Functionality
|
||||
|
||||
- [ ] Implement the functionality to restore backed-up applications using Pacman
|
||||
- [ ] Implement the functionality to restore the user's home directory
|
||||
- [ ] Implement the functionality to restore GPG and SSH keys
|
||||
- [ ] Implement the functionality to restore Flatpak packages (if backed up)
|
||||
|
||||
### Phase 4: User Interface
|
||||
|
||||
- [ ] Develop a graphical user interface (GUI) for the application
|
||||
- [ ] Integrate the CLI functionality into the GUI
|
||||
- [ ] Provide options to schedule backups and set backup locations
|
||||
|
||||
### Phase 5: Optimization and Testing
|
||||
|
||||
- [ ] Optimize the backup and restore processes for performance and efficiency
|
||||
- [ ] Conduct thorough testing, including edge cases and error handling
|
||||
- [ ] Implement error reporting and logging mechanisms
|
||||
|
||||
### Phase 6: Documentation and Release
|
||||
|
||||
- [ ] Write comprehensive documentation for users and developers
|
||||
- [ ] Package the application for distribution
|
||||
- [ ] Release the application to the ParchLinux community
|
||||
|
||||
## Dependencies
|
||||
- Pacman / libalpm
|
||||
- Flatpak (optional)
|
||||
|
||||
## Potential Challenges
|
||||
- Handling large home directories and optimizing backup/restore times
|
||||
- Ensuring compatibility with different versions of Pacman / AUR helpers
|
||||
- Handling edge cases and error scenarios gracefully
|
||||
- Providing a user-friendly and intuitive interface
|
||||
|
||||
## Future Plans
|
||||
- Support for incremental backups
|
||||
- Integration with cloud storage services for remote backups (Nextcloud/Gdrive and ....)
|
||||
- Support for encrypted backups
|
||||
- Backup and restore of system configurations and settings
|
||||
## TODO
|
||||
- [ ] Supporting scheduling
|
||||
- [ ] Writing PKGBUILD
|
||||
- [ ] Writing README
|
||||
|
|
|
@ -3,64 +3,72 @@ use crate::cli::BackupArgs;
|
|||
use crate::flatpak::flatpak;
|
||||
use crate::pm::paru;
|
||||
use crate::system::{home, keys};
|
||||
use std::path::Path;
|
||||
use crate::utils::compression::ARCHIVE_EXT;
|
||||
use dialoguer::{Confirm, Password};
|
||||
use regex::Regex;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Clean up backup files matching the pattern *_backup.EXTENSION in the current directory.
|
||||
fn cleanup_backup_files() {
|
||||
let pattern = format!(r".*_backup\.{}", ARCHIVE_EXT);
|
||||
let re = Regex::new(&pattern).unwrap();
|
||||
|
||||
for entry in fs::read_dir(".").unwrap() {
|
||||
if let Ok(entry) = entry {
|
||||
let path = entry.path();
|
||||
if let Some(file_name) = path.file_name().and_then(|f| f.to_str()) {
|
||||
if re.is_match(file_name) {
|
||||
let _ = fs::remove_file(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_backup(args: &BackupArgs) {
|
||||
if !args.apps && !args.home && !args.flatpak && !args.keys {
|
||||
eprintln!("No backup options specified. Please provide at least one backup option.");
|
||||
return;
|
||||
}
|
||||
|
||||
let home_dir = std::env::var("HOME").expect("HOME environment variable not set");
|
||||
let home_path = Path::new(&home_dir);
|
||||
|
||||
let mut backup_files = Vec::new();
|
||||
|
||||
// Atomic flag to indicate if the process was interrupted.
|
||||
let interrupted = Arc::new(AtomicBool::new(false));
|
||||
let interrupt_clone = Arc::clone(&interrupted);
|
||||
|
||||
ctrlc::set_handler(move || {
|
||||
interrupt_clone.store(true, Ordering::SeqCst);
|
||||
})
|
||||
.expect("Error setting Ctrl-C handler");
|
||||
|
||||
if args.apps {
|
||||
println!("Backing up installed apps...");
|
||||
match paru::list_installed_apps() {
|
||||
Ok(file) => {
|
||||
println!("Installed apps backed up successfully.");
|
||||
backup_files.push(("appsb", file));
|
||||
},
|
||||
Err(e) => eprintln!("Failed to backup installed apps: {}", e),
|
||||
}
|
||||
backup_apps(&mut backup_files);
|
||||
}
|
||||
|
||||
if args.home {
|
||||
println!("Backing up home directory...");
|
||||
match home::backup_home(home_path) {
|
||||
Ok(file) => {
|
||||
println!("Home directory backed up successfully.");
|
||||
backup_files.push(("homeb", file));
|
||||
},
|
||||
Err(e) => eprintln!("Failed to backup home directory: {}", e),
|
||||
}
|
||||
backup_home(
|
||||
&mut backup_files,
|
||||
home_path,
|
||||
&args.exclude_dir,
|
||||
&interrupted,
|
||||
);
|
||||
}
|
||||
|
||||
if args.flatpak {
|
||||
println!("Backing up Flatpak applications...");
|
||||
match flatpak::list_installed_flatpak_apps() {
|
||||
Ok(file) => {
|
||||
println!("Installed Flatpak apps backed up successfully.");
|
||||
backup_files.push(("flatpakb", file));
|
||||
},
|
||||
Err(e) => eprintln!("Failed to backup Flatpak apps: {}", e),
|
||||
}
|
||||
backup_flatpak(&mut backup_files);
|
||||
}
|
||||
|
||||
if args.keys {
|
||||
println!("Backing up GPG keys...");
|
||||
match keys::backup_gpg_keys(home_path) {
|
||||
Ok(file) => backup_files.push(("gnupgb", file)),
|
||||
Err(e) => eprintln!("Failed to backup GPG keys: {}", e),
|
||||
}
|
||||
backup_keys(&mut backup_files, home_path, &interrupted);
|
||||
}
|
||||
|
||||
println!("Backing up SSH keys...");
|
||||
match keys::backup_ssh_keys(home_path) {
|
||||
Ok(file) => backup_files.push(("sshb", file)),
|
||||
Err(e) => eprintln!("Failed to backup SSH keys: {}", e),
|
||||
}
|
||||
// Check for interruption after initial backups.
|
||||
if interrupted.load(Ordering::SeqCst) {
|
||||
exit_gracefully();
|
||||
return;
|
||||
}
|
||||
|
||||
if !backup_files.is_empty() {
|
||||
|
@ -68,7 +76,145 @@ pub fn handle_backup(args: &BackupArgs) {
|
|||
Ok(_) => println!("All backups consolidated successfully."),
|
||||
Err(e) => eprintln!("Failed to consolidate backups: {}", e),
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
eprintln!("No backups to consolidate.");
|
||||
// Prompt the user for additional actions
|
||||
if Confirm::new()
|
||||
.with_prompt(
|
||||
"Do you want to backup with all functionality (apps, home, keys, flatpak)?",
|
||||
)
|
||||
.interact()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let backup_key = if Confirm::new()
|
||||
.with_prompt("Do you want to encrypt the backup?")
|
||||
.interact()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
Some(
|
||||
Password::new()
|
||||
.with_prompt("Enter the encryption key")
|
||||
.with_confirmation("Confirm the encryption key", "Keys mismatch!")
|
||||
.interact()
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// Backup with all functionality
|
||||
backup_apps(&mut backup_files);
|
||||
backup_home(
|
||||
&mut backup_files,
|
||||
home_path,
|
||||
&args.exclude_dir,
|
||||
&interrupted,
|
||||
);
|
||||
backup_flatpak(&mut backup_files);
|
||||
backup_keys(&mut backup_files, home_path, &interrupted);
|
||||
|
||||
if let Some(key) = backup_key {
|
||||
match consolidate::consolidate_backups(
|
||||
&backup_files,
|
||||
&BackupArgs {
|
||||
archive_path: args.archive_path.clone(),
|
||||
apps: true,
|
||||
home: true,
|
||||
exclude_dir: args.exclude_dir.clone(),
|
||||
flatpak: true,
|
||||
keys: true,
|
||||
encrypt: true,
|
||||
encrypt_key: Some(key),
|
||||
..*args
|
||||
},
|
||||
) {
|
||||
Ok(_) => println!("All backups consolidated successfully."),
|
||||
Err(e) => eprintln!("Failed to consolidate backups: {}", e),
|
||||
}
|
||||
} else {
|
||||
match consolidate::consolidate_backups(
|
||||
&backup_files,
|
||||
&BackupArgs {
|
||||
archive_path: args.archive_path.clone(),
|
||||
apps: true,
|
||||
home: true,
|
||||
exclude_dir: args.exclude_dir.clone(),
|
||||
flatpak: true,
|
||||
keys: true,
|
||||
encrypt: false,
|
||||
encrypt_key: None,
|
||||
..*args
|
||||
},
|
||||
) {
|
||||
Ok(_) => println!("All backups consolidated successfully."),
|
||||
Err(e) => eprintln!("Failed to consolidate backups: {}", e),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
exit_gracefully();
|
||||
}
|
||||
}
|
||||
}
|
||||
fn backup_apps(backup_files: &mut Vec<(&str, PathBuf)>) {
|
||||
println!("Backing up installed apps...");
|
||||
match paru::list_installed_apps() {
|
||||
Ok(file) => {
|
||||
println!("Installed apps backed up successfully.");
|
||||
backup_files.push(("appsb", file));
|
||||
}
|
||||
Err(e) => eprintln!("Failed to backup installed apps: {}", e),
|
||||
}
|
||||
}
|
||||
fn backup_home(
|
||||
backup_files: &mut Vec<(&str, PathBuf)>,
|
||||
home_path: &Path,
|
||||
exclude_dirs: &[String],
|
||||
interrupted: &Arc<AtomicBool>,
|
||||
) {
|
||||
println!("Backing up home directory...");
|
||||
match home::backup_home(home_path, &exclude_dirs, &interrupted) {
|
||||
Ok(file) => {
|
||||
println!("Home directory backed up successfully.");
|
||||
backup_files.push(("homeb", file));
|
||||
}
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::Interrupted {
|
||||
cleanup_backup_files();
|
||||
return;
|
||||
} else {
|
||||
eprintln!("Failed to backup home directory: {}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn backup_flatpak(backup_files: &mut Vec<(&str, PathBuf)>) {
|
||||
println!("Backing up Flatpak applications...");
|
||||
match flatpak::list_installed_flatpak_apps() {
|
||||
Ok(file) => {
|
||||
println!("Installed Flatpak apps backed up successfully.");
|
||||
backup_files.push(("flatpakb", file));
|
||||
}
|
||||
Err(e) => eprintln!("Failed to backup Flatpak apps: {}", e),
|
||||
}
|
||||
}
|
||||
fn backup_keys(
|
||||
backup_files: &mut Vec<(&str, PathBuf)>,
|
||||
home_path: &Path,
|
||||
interrupted: &Arc<AtomicBool>,
|
||||
) {
|
||||
println!("Backing up GPG keys...");
|
||||
match keys::backup_gpg_keys(home_path, &interrupted) {
|
||||
Ok(file) => backup_files.push(("gnupgb", file)),
|
||||
Err(e) => eprintln!("Failed to backup GPG keys: {}", e),
|
||||
}
|
||||
|
||||
println!("Backing up SSH keys...");
|
||||
match keys::backup_ssh_keys(home_path, &interrupted) {
|
||||
Ok(file) => backup_files.push(("sshb", file)),
|
||||
Err(e) => eprintln!("Failed to backup SSH keys: {}", e),
|
||||
}
|
||||
}
|
||||
fn exit_gracefully() {
|
||||
cleanup_backup_files();
|
||||
eprintln!("Operation canceled.");
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
use chrono::Utc;
|
||||
|
||||
use crate::cli::BackupArgs;
|
||||
use crate::utils::compression::create_tar_gz_archive;
|
||||
use crate::utils::security::{self, CryptoError};
|
||||
use chrono::Local;
|
||||
use std::fs::{self, File};
|
||||
use std::io;
|
||||
use std::{fs, io};
|
||||
use std::path::{Path, PathBuf};
|
||||
use tar::Builder;
|
||||
|
||||
/// Consolidates individual backups into a single archive file.
|
||||
pub fn consolidate_backups(backup_files: &[(&str, PathBuf)], args: &BackupArgs) -> io::Result<()> {
|
||||
// Prepare timestamp and components for archive name
|
||||
let timestamp = Local::now().format("%Y-%m-%d-%H:%M:%S");
|
||||
let timestamp = Utc::now().format("%Y-%m-%d-%H:%M:%S");
|
||||
let components = {
|
||||
let mut components = String::new();
|
||||
if args.apps {
|
||||
|
@ -31,35 +31,49 @@ pub fn consolidate_backups(backup_files: &[(&str, PathBuf)], args: &BackupArgs)
|
|||
};
|
||||
|
||||
// Construct archive name
|
||||
let archive_name = format!("backup-{}-{}.{}", timestamp, components, crate::utils::compression::TAR_GZ);
|
||||
let archive_name = format!(
|
||||
"backup-{}-{}.{}",
|
||||
timestamp,
|
||||
components,
|
||||
crate::utils::compression::ARCHIVE_EXT
|
||||
);
|
||||
|
||||
// Create the archive file
|
||||
let archive_file = File::create(&archive_name)?;
|
||||
let enc = flate2::write::GzEncoder::new(archive_file, flate2::Compression::default());
|
||||
let mut tar = Builder::new(enc);
|
||||
// Determine the full archive path
|
||||
let archive_path = if let Some(ref path) = args.archive_path {
|
||||
Path::new(path).join(&archive_name)
|
||||
} else {
|
||||
Path::new(&archive_name).to_path_buf()
|
||||
};
|
||||
|
||||
// Add each backup file to the archive and remove original files
|
||||
for (subdir, file) in backup_files {
|
||||
let path_in_archive = Path::new(subdir).join(file.file_name().unwrap());
|
||||
tar.append_path_with_name(file, path_in_archive)?;
|
||||
fs::remove_file(file)?;
|
||||
// Create directory if it doesn't exist
|
||||
if let Some(parent_dir) = archive_path.parent() {
|
||||
if !parent_dir.exists() {
|
||||
fs::create_dir_all(parent_dir)?;
|
||||
println!("Created directory: {}", parent_dir.display());
|
||||
}
|
||||
}
|
||||
|
||||
// Finalize the archive
|
||||
tar.finish()?;
|
||||
// Convert &str elements in backup_files to String
|
||||
let backup_files: Vec<(String, PathBuf)> = backup_files
|
||||
.iter()
|
||||
.map(|(subdir, path)| (subdir.to_string(), path.clone()))
|
||||
.collect();
|
||||
|
||||
// Create the archive
|
||||
create_tar_gz_archive(archive_path.to_str().unwrap(), &backup_files)?;
|
||||
|
||||
// Encrypt the consolidated backup file if encryption is enabled
|
||||
if let Some(key) = &args.encrypt_key {
|
||||
if let Err(e) = security::encrypt_file(Path::new(&archive_name), key.as_bytes()) {
|
||||
if let Err(e) = security::encrypt_file(&archive_path, key.as_bytes()) {
|
||||
match e {
|
||||
CryptoError::FileRead(_) => eprintln!("Failed to read the archive file: {:?}", e),
|
||||
CryptoError::FileWrite(_) => {
|
||||
eprintln!("Failed to write the encrypted file: {:?}", e)
|
||||
}
|
||||
_ => {}
|
||||
CryptoError::FileWrite(_) => eprintln!("Failed to write the encrypted file: {:?}", e),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("Archive located at: {}", archive_path.display());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
57
src/cli.rs
57
src/cli.rs
|
@ -5,7 +5,10 @@ use clap::{Args, Parser, Subcommand};
|
|||
author = "DanielcoderX",
|
||||
version = "1.0",
|
||||
about = "ParchBackup",
|
||||
long_about = "Koonam goshade"
|
||||
long_about = "The ParchLinux Backup Application is a utility designed to simplify the backup
|
||||
process for ParchLinux users. It provides a comprehensive solution for backing
|
||||
up installed applications, home directory, Flatpak packages (optional), and GPG
|
||||
and SSH keys."
|
||||
)]
|
||||
pub struct Cli {
|
||||
#[command(subcommand)]
|
||||
|
@ -18,18 +21,23 @@ pub enum Commands {
|
|||
Backup(BackupArgs),
|
||||
/// Restore functionality
|
||||
Restore(RestoreArgs),
|
||||
/// Schedule backup
|
||||
Schedule(ScheduleArgs),
|
||||
// Schedule(ScheduleArgs),
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct BackupArgs {
|
||||
/// Backup archive location
|
||||
#[arg(long, help = "Backup archive location", default_value = "/home/backup")]
|
||||
pub archive_path: Option<String>,
|
||||
/// Backup installed apps names
|
||||
#[arg(long, help = "Backup installed apps names")]
|
||||
pub apps: bool,
|
||||
/// Backup home directory
|
||||
#[arg(long, help = "Backup home directory")]
|
||||
pub home: bool,
|
||||
/// Execluded directories from backup
|
||||
#[arg(long, help = "Excluded directories from backup", num_args(1..))]
|
||||
pub exclude_dir: Vec<String>,
|
||||
/// Backup flatpak applications
|
||||
#[arg(long, help = "Backup flatpak applications")]
|
||||
pub flatpak: bool,
|
||||
|
@ -65,13 +73,42 @@ pub struct RestoreArgs {
|
|||
pub decrypt_key: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct ScheduleArgs {
|
||||
/// Cron expression for scheduling
|
||||
#[arg(short, long, help = "Cron expression for scheduling")]
|
||||
pub cron: String,
|
||||
}
|
||||
// #[derive(Args)]
|
||||
// pub struct ScheduleArgs {
|
||||
// /// Cron expression for scheduling
|
||||
// /// sec min hour day of month month day of week year
|
||||
// #[arg(
|
||||
// short,
|
||||
// long,
|
||||
// help = "Cron expression for scheduling backup functionality: \nExample: 'sec min hour day of month month day of week year'\n"
|
||||
// )]
|
||||
// pub cron: String,
|
||||
// /// Backup installed apps names
|
||||
// #[arg(long, help = "Backup installed apps names")]
|
||||
// pub apps: bool,
|
||||
// /// Backup home directory
|
||||
// #[arg(long, help = "Backup home directory")]
|
||||
// pub home: bool,
|
||||
// /// Execluded directories from backup
|
||||
// #[arg(long, help = "Excluded directories from backup", num_args(1..))]
|
||||
// pub exclude_dir: Vec<String>,
|
||||
// /// Backup flatpak applications
|
||||
// #[arg(long, help = "Backup flatpak applications")]
|
||||
// pub flatpak: bool,
|
||||
// /// Backup keys
|
||||
// #[arg(long, help = "Backup keys")]
|
||||
// pub keys: bool,
|
||||
// /// Use Encryption
|
||||
// #[arg(
|
||||
// long,
|
||||
// help = "Use Encryption, pass the password",
|
||||
// requires = "encrypt_key"
|
||||
// )]
|
||||
// pub encrypt: bool,
|
||||
// /// Encryption key
|
||||
// #[arg(long, help = "Encryption key", requires = "encrypt")]
|
||||
// pub encrypt_key: Option<String>,
|
||||
// }
|
||||
|
||||
pub fn parse_cli() -> Cli {
|
||||
Cli::parse()
|
||||
|
|
13
src/main.rs
13
src/main.rs
|
@ -24,11 +24,12 @@ fn main() {
|
|||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
Commands::Schedule(args) => {
|
||||
// Handle schedule functionality
|
||||
println!("Schedule called with cron: {}", args.cron);
|
||||
// Call the appropriate function from the system module
|
||||
// system::schedule_backup(args.cron);
|
||||
}
|
||||
// Commands::Schedule(args) => {
|
||||
// let result = system::schedule::schedule_backup(&args);
|
||||
// if let Err(e) = result {
|
||||
// eprintln!("Error: {}", e);
|
||||
// std::process::exit(1);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,13 @@ pub fn list_installed_apps() -> io::Result<PathBuf> {
|
|||
.output()?;
|
||||
|
||||
if output.status.success() {
|
||||
let installed_apps = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
// Process the output to remove versions
|
||||
let installed_apps = String::from_utf8_lossy(&output.stdout)
|
||||
.lines()
|
||||
.map(|line| line.split_whitespace().next().unwrap_or(""))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
let apps_list_path = PathBuf::from(APPS_LIST_FILE);
|
||||
let mut file = File::create(&apps_list_path)?;
|
||||
file.write_all(installed_apps.as_bytes())?;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::cli::RestoreArgs;
|
||||
use crate::flatpak::flatpak;
|
||||
use crate::pm::paru;
|
||||
use crate::utils::compression::open_and_decode_archive;
|
||||
use crate::utils::security;
|
||||
use crate::utils::security::CryptoError;
|
||||
use flate2::read::GzDecoder;
|
||||
|
@ -12,91 +13,128 @@ use tar::Archive;
|
|||
|
||||
/// Restores files from a backup archive.
|
||||
pub fn handle_restore(args: &RestoreArgs) -> io::Result<()> {
|
||||
let archive_path_for_restore = PathBuf::from(&args.archive_path); // Start with original path
|
||||
let archive_path_for_restore = PathBuf::from(&args.archive_path);
|
||||
|
||||
if args.archive_path.contains("e") {
|
||||
// Decrypt the archive if decryption is enabled
|
||||
if args.decrypt {
|
||||
let key = args
|
||||
.decrypt_key
|
||||
.as_ref()
|
||||
.expect("Decryption key not provided");
|
||||
if let Err(e) = security::decrypt_file(&archive_path_for_restore, key.as_bytes()) {
|
||||
match e {
|
||||
CryptoError::FileRead(_) => {
|
||||
eprintln!("Failed to read the archive file: {:?}", e)
|
||||
let key = args.decrypt_key.as_ref().expect("Decryption key not provided");
|
||||
|
||||
let decrypted_data = match security::decrypt_file(&archive_path_for_restore, key.as_bytes()) {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
match e {
|
||||
CryptoError::FileRead(_) => eprintln!("Failed to read the archive file: {:?}", e),
|
||||
CryptoError::FileWrite(_) => eprintln!("Failed to write the decrypted file: {:?}", e),
|
||||
_ => eprintln!("Incorrect decryption key"),
|
||||
}
|
||||
CryptoError::FileWrite(_) => {
|
||||
eprintln!("Failed to write the decrypted file: {:?}", e)
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let mut tar = Archive::new(GzDecoder::new(&decrypted_data[..]));
|
||||
|
||||
let mut apps_to_install = Vec::new();
|
||||
let mut flatpak_apps_to_install = Vec::new();
|
||||
|
||||
for entry in tar.entries()? {
|
||||
let mut entry = entry?;
|
||||
let entry_path = entry.path()?;
|
||||
println!("Restoring {:?}", entry_path);
|
||||
|
||||
let dest_path = determine_restore_path(entry_path.to_path_buf(), &args)?;
|
||||
|
||||
if let Some(parent) = dest_path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
if let Some(subdir) = entry_path.iter().next().and_then(|s| s.to_str()) {
|
||||
match subdir {
|
||||
"appsb" => collect_apps_list_from_entry(&mut entry, &mut apps_to_install)?,
|
||||
"flatpakb" => collect_apps_list_from_entry(&mut entry, &mut flatpak_apps_to_install)?,
|
||||
_ => {
|
||||
if entry_path.extension() == Some(std::ffi::OsStr::new("gz")) {
|
||||
extract_nested_tarball(&dest_path, &mut entry)?;
|
||||
} else {
|
||||
entry.unpack(&dest_path)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !apps_to_install.is_empty() {
|
||||
paru::restore_installed_apps(&apps_to_install)?;
|
||||
} else {
|
||||
eprintln!("No Paru apps found in backup.");
|
||||
}
|
||||
|
||||
if !flatpak_apps_to_install.is_empty() {
|
||||
flatpak::restore_installed_flatpak_apps(&flatpak_apps_to_install)?;
|
||||
} else {
|
||||
eprintln!("No Flatpak apps found in backup.");
|
||||
}
|
||||
|
||||
println!("Restore completed successfully.");
|
||||
|
||||
if let Some(key) = &args.decrypt_key {
|
||||
if let Err(e) = security::encrypt_file(&archive_path_for_restore, key.as_bytes()) {
|
||||
match e {
|
||||
CryptoError::FileRead(_) => eprintln!("Failed to read the archive file: {:?}", e),
|
||||
CryptoError::FileWrite(_) => eprintln!("Failed to write the encrypted file: {:?}", e),
|
||||
_ => {}
|
||||
}
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
eprintln!("Decryption is not enabled. Please enable decryption by passing the --decrypt flag.");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
// Open the archive file
|
||||
let archive_file = fs::File::open(&archive_path_for_restore)?;
|
||||
let archive_decoder = GzDecoder::new(archive_file);
|
||||
let mut tar = Archive::new(archive_decoder);
|
||||
} else {
|
||||
let mut tar = open_and_decode_archive(&archive_path_for_restore)?;
|
||||
|
||||
// In-memory collections for apps and flatpak apps
|
||||
let mut apps_to_install = Vec::new();
|
||||
let mut flatpak_apps_to_install = Vec::new();
|
||||
let mut apps_to_install = Vec::new();
|
||||
let mut flatpak_apps_to_install = Vec::new();
|
||||
|
||||
// Iterate over each entry in the archive
|
||||
for entry in tar.entries()? {
|
||||
let mut entry = entry?;
|
||||
for entry in tar.entries()? {
|
||||
let mut entry = entry?;
|
||||
let entry_path = entry.path()?;
|
||||
println!("Restoring {:?}", entry_path);
|
||||
|
||||
// Get the path of the entry in the archive
|
||||
let entry_path = entry.path()?;
|
||||
println!("Restoring {:?}", entry_path);
|
||||
let dest_path = determine_restore_path(entry_path.to_path_buf(), &args)?;
|
||||
|
||||
// Determine the destination path where the entry will be restored
|
||||
let dest_path = determine_restore_path(entry_path.to_path_buf(), &args)?;
|
||||
if let Some(parent) = dest_path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
// Create parent directories if they don't exist
|
||||
if let Some(parent) = dest_path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
// Collect applications to install without writing to disk
|
||||
if let Some(subdir) = entry_path.iter().next().and_then(|s| s.to_str()) {
|
||||
match subdir {
|
||||
"appsb" => collect_apps_list_from_entry(&mut entry, &mut apps_to_install)?,
|
||||
"flatpakb" => {
|
||||
collect_apps_list_from_entry(&mut entry, &mut flatpak_apps_to_install)?
|
||||
}
|
||||
_ => {
|
||||
// Extract other entries to the destination path
|
||||
if entry_path.extension() == Some(std::ffi::OsStr::new("gz")) {
|
||||
// Handle nested tarball
|
||||
extract_nested_tarball(&dest_path, &mut entry)?;
|
||||
} else {
|
||||
// Otherwise, unpack as usual
|
||||
entry.unpack(&dest_path)?;
|
||||
if let Some(subdir) = entry_path.iter().next().and_then(|s| s.to_str()) {
|
||||
match subdir {
|
||||
"appsb" => collect_apps_list_from_entry(&mut entry, &mut apps_to_install)?,
|
||||
"flatpakb" => collect_apps_list_from_entry(&mut entry, &mut flatpak_apps_to_install)?,
|
||||
_ => {
|
||||
if entry_path.extension() == Some(std::ffi::OsStr::new("gz")) {
|
||||
extract_nested_tarball(&dest_path, &mut entry)?;
|
||||
} else {
|
||||
entry.unpack(&dest_path)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Restore installed applications
|
||||
if !apps_to_install.is_empty() {
|
||||
paru::restore_installed_apps(&apps_to_install)?;
|
||||
} else {
|
||||
eprintln!("No Paru apps found in backup.");
|
||||
}
|
||||
if !apps_to_install.is_empty() {
|
||||
paru::restore_installed_apps(&apps_to_install)?;
|
||||
} else {
|
||||
eprintln!("No Paru apps found in backup.");
|
||||
}
|
||||
|
||||
// Restore installed Flatpak applications
|
||||
if !flatpak_apps_to_install.is_empty() {
|
||||
flatpak::restore_installed_flatpak_apps(&flatpak_apps_to_install)?;
|
||||
} else {
|
||||
eprintln!("No Flatpak apps found in backup.");
|
||||
}
|
||||
if !flatpak_apps_to_install.is_empty() {
|
||||
flatpak::restore_installed_flatpak_apps(&flatpak_apps_to_install)?;
|
||||
} else {
|
||||
eprintln!("No Flatpak apps found in backup.");
|
||||
}
|
||||
|
||||
println!("Restore completed successfully.");
|
||||
println!("Restore completed successfully.");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -2,10 +2,16 @@ use crate::utils::compression;
|
|||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Backs up the user's home directory.
|
||||
pub fn backup_home(home_dir: &Path) -> io::Result<PathBuf> {
|
||||
let file_extension = compression::TAR_GZ;
|
||||
pub fn backup_home(
|
||||
home_dir: &Path,
|
||||
exclude_dir: &[String],
|
||||
interrupted: &Arc<AtomicBool>,
|
||||
) -> io::Result<PathBuf> {
|
||||
let file_extension = compression::ARCHIVE_EXT;
|
||||
let file_name = format!("home_backup.{}", file_extension);
|
||||
let backup_file = PathBuf::from(file_name);
|
||||
|
||||
|
@ -15,7 +21,11 @@ pub fn backup_home(home_dir: &Path) -> io::Result<PathBuf> {
|
|||
}
|
||||
|
||||
// Compress the home directory
|
||||
compression::compress_directory(home_dir, &backup_file)?;
|
||||
|
||||
Ok(backup_file)
|
||||
match compression::compress_directory(home_dir, &backup_file, Some(exclude_dir), interrupted) {
|
||||
Ok(_) => Ok(backup_file),
|
||||
Err(e) => {
|
||||
eprintln!("Failed to backup home directory: {}", e);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::utils::compression;
|
||||
|
||||
|
@ -8,9 +10,9 @@ const GPG_DIR: &str = ".gnupg";
|
|||
const SSH_DIR: &str = ".ssh";
|
||||
|
||||
/// Backs up the user's GPG keys.
|
||||
pub fn backup_gpg_keys(home_dir: &Path) -> io::Result<PathBuf> {
|
||||
pub fn backup_gpg_keys(home_dir: &Path, interrupted: &Arc<AtomicBool>) -> io::Result<PathBuf> {
|
||||
let gpg_path = home_dir.join(GPG_DIR);
|
||||
let file_extension = compression::TAR_GZ;
|
||||
let file_extension = compression::ARCHIVE_EXT;
|
||||
let file_name = format!("gnupg_backup.{}", file_extension);
|
||||
let backup_file = PathBuf::from(file_name);
|
||||
if !gpg_path.exists() {
|
||||
|
@ -25,15 +27,15 @@ pub fn backup_gpg_keys(home_dir: &Path) -> io::Result<PathBuf> {
|
|||
}
|
||||
|
||||
// Compress the GPG directory
|
||||
compression::compress_directory(&gpg_path, &backup_file)?;
|
||||
compression::compress_directory(&gpg_path, &backup_file, None, interrupted)?;
|
||||
|
||||
Ok(backup_file)
|
||||
}
|
||||
|
||||
/// Backs up the user's SSH keys.
|
||||
pub fn backup_ssh_keys(home_dir: &Path) -> io::Result<PathBuf> {
|
||||
pub fn backup_ssh_keys(home_dir: &Path, interrupted: &Arc<AtomicBool>) -> io::Result<PathBuf> {
|
||||
let ssh_path = home_dir.join(SSH_DIR);
|
||||
let file_extension = compression::TAR_GZ;
|
||||
let file_extension = compression::ARCHIVE_EXT;
|
||||
let file_name = format!("ssh_backup.{}", file_extension);
|
||||
let backup_file = PathBuf::from(file_name);
|
||||
if !ssh_path.exists() {
|
||||
|
@ -48,7 +50,7 @@ pub fn backup_ssh_keys(home_dir: &Path) -> io::Result<PathBuf> {
|
|||
}
|
||||
|
||||
// Compress the SSH directory
|
||||
compression::compress_directory(&ssh_path, &backup_file)?;
|
||||
compression::compress_directory(&ssh_path, &backup_file, None, interrupted)?;
|
||||
|
||||
Ok(backup_file)
|
||||
}
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
pub mod home;
|
||||
pub mod keys;
|
||||
// pub mod schedule;
|
||||
|
|
7
src/system/schedule.rs
Normal file
7
src/system/schedule.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
// use std::io;
|
||||
|
||||
// use crate::cli::ScheduleArgs;
|
||||
// pub fn schedule_backup(args: &ScheduleArgs) -> io::Result<()> {
|
||||
// // develop backup schedule
|
||||
// return Ok(());
|
||||
// }
|
|
@ -1,17 +1,101 @@
|
|||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use flate2::write::GzEncoder;
|
||||
use flate2::Compression;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
pub const TAR_GZ: &str = "tar.gz";
|
||||
/// Compresses the contents of a directory into a tar.gz file.
|
||||
pub fn compress_directory<P: AsRef<Path>>(source_dir: P, target_file: P) -> io::Result<()> {
|
||||
pub static ARCHIVE_EXT: &str = "tar.gz";
|
||||
|
||||
pub fn compress_directory<P: AsRef<Path>>(
|
||||
source_dir: P,
|
||||
target_file: P,
|
||||
exclude_dirs: Option<&[String]>,
|
||||
should_stop: &AtomicBool,
|
||||
) -> io::Result<()> {
|
||||
let tar_gz = File::create(target_file)?;
|
||||
let enc = GzEncoder::new(tar_gz, Compression::default());
|
||||
let mut tar = tar::Builder::new(enc);
|
||||
|
||||
tar.append_dir_all(".", source_dir)?;
|
||||
let source_dir = source_dir.as_ref();
|
||||
let exclude_paths: Vec<PathBuf> = exclude_dirs
|
||||
.unwrap_or(&[])
|
||||
.iter()
|
||||
.map(|d| source_dir.join(d))
|
||||
.collect();
|
||||
|
||||
for entry in WalkDir::new(source_dir).into_iter().filter_map(|e| e.ok()) {
|
||||
if should_stop.load(Ordering::SeqCst) {
|
||||
return Err(io::Error::new(io::ErrorKind::Interrupted, "Operation canceled"));
|
||||
}
|
||||
|
||||
let entry_path = entry.path();
|
||||
|
||||
// Skip directories that need to be excluded
|
||||
if exclude_paths.iter().any(|d| entry_path.starts_with(d)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let path_in_archive = match entry_path.strip_prefix(source_dir) {
|
||||
Ok(p) => p,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
if path_in_archive.components().count() == 0 {
|
||||
// Skip empty paths
|
||||
continue;
|
||||
}
|
||||
|
||||
if entry.file_type().is_dir() {
|
||||
tar.append_dir(path_in_archive, entry_path)?;
|
||||
} else {
|
||||
match File::open(entry_path) {
|
||||
Ok(mut file) => {
|
||||
tar.append_file(path_in_archive, &mut file)?;
|
||||
}
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => {
|
||||
eprintln!("Failed to backup file: {} - {}", entry_path.display(), e);
|
||||
continue; // Skip not found files and continue the loop
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tar.finish()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a compressed tar archive (tar.gz) from specified files.
|
||||
pub fn create_tar_gz_archive<P: AsRef<Path>>(
|
||||
archive_name: P,
|
||||
backup_files: &[(String, PathBuf)],
|
||||
) -> io::Result<()> {
|
||||
// Create the archive file
|
||||
let archive_file = File::create(&archive_name)?;
|
||||
let enc = GzEncoder::new(archive_file, Compression::default());
|
||||
let mut tar = tar::Builder::new(enc);
|
||||
|
||||
// Add each backup file to the archive and remove original files
|
||||
for (subdir, file) in backup_files {
|
||||
let path_in_archive = Path::new(subdir).join(file.file_name().unwrap());
|
||||
tar.append_path_with_name(file, path_in_archive)?;
|
||||
std::fs::remove_file(file)?;
|
||||
}
|
||||
|
||||
// Finalize the archive
|
||||
tar.finish()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
/// Opens and decodes a tar.gz archive file.
|
||||
pub fn open_and_decode_archive<P: AsRef<Path>>(
|
||||
archive_path: P,
|
||||
) -> io::Result<tar::Archive<flate2::read::GzDecoder<File>>> {
|
||||
// Open the archive file
|
||||
let archive_file = File::open(archive_path)?;
|
||||
let archive_decoder = flate2::read::GzDecoder::new(archive_file);
|
||||
let tar = tar::Archive::new(archive_decoder);
|
||||
Ok(tar)
|
||||
}
|
||||
|
|
|
@ -46,18 +46,13 @@ pub fn encrypt_file<P: AsRef<Path>>(file_path: P, key: &[u8]) -> Result<(), Cryp
|
|||
}
|
||||
|
||||
/// Decrypts a file using AES-GCM.
|
||||
pub fn decrypt_file<P: AsRef<Path>>(file_path: P, key: &[u8]) -> Result<(), CryptoError> {
|
||||
let cipher_text = fs::read(file_path.as_ref()).map_err(CryptoError::FileRead)?;
|
||||
println!("cipher_text: {:?}", cipher_text);
|
||||
pub fn decrypt_file<P: AsRef<Path>>(file_path: P, key: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
||||
let data = fs::read(file_path.as_ref()).map_err(CryptoError::FileRead)?;
|
||||
let derived_key = derive_key(key);
|
||||
println!("{:?}",derived_key);
|
||||
let cipher = Aes256Gcm::new(GenericArray::from_slice(&derived_key));
|
||||
let nonce = GenericArray::from_slice(b"unique nonce"); // Must be the same nonce used for encryption
|
||||
println!("{:?}", nonce);
|
||||
let nonce = GenericArray::from_slice(b"unique nonce");
|
||||
let plaintext = cipher
|
||||
.decrypt(nonce, Payload { msg: &cipher_text, aad: b"" })
|
||||
.decrypt(nonce, Payload { msg: &data, aad: b"" })
|
||||
.map_err(|_| CryptoError::Decryption)?;
|
||||
|
||||
fs::write(file_path.as_ref(), &plaintext).map_err(CryptoError::FileWrite)?;
|
||||
Ok(())
|
||||
Ok(plaintext)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue