Skip to content

Commit

Permalink
v0.3
Browse files Browse the repository at this point in the history
- Subcommands: ls, diff, rm
- New options: -q, -f
- User app data directory usable via '-' as DIR
- User app data directory is selected if no permission to copy to DIR
- OS-specific support for Windows, MacOS, and Linux
- Release for Windows via Wix
  • Loading branch information
roylaurie committed May 22, 2024
1 parent 199d0ba commit 4a38133
Show file tree
Hide file tree
Showing 14 changed files with 1,344 additions and 198 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/target
/Cargo.lock
/.vscode
*.snap
*.bak
*.bak.*
Expand Down
30 changes: 20 additions & 10 deletions Building.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,27 @@ cargo generate-rpm --target=$TARGET
```


## Building for Windows
## Windows

1. Install a Windows 11 VM.
2. Update system.
2. Install the OpenSSH Server feature:
### VM Setup
1. Install a Windows 11 VM. Update it.
2. Install OpenSSH Server feature:
- Reference: https://learn.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse
1. Settings > System > Features > View Features > Add Feature > "OpenSSH Server"
2. Services > "OpenSSH Server" > Properties > Start: Automatically
3. To configure Powershell as the default shell:
1. SSH into Windows
2. Run: `powershell`
3. Run: `New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -PropertyType String -Force`
4. Install:
- rust (rustup)
3. Configure Powershell as the default OpenSSH shell: `New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -PropertyType String -Force`
4. Configure OpenSSH for pubkey authentication: [Microsoft: Key-based authentication in OpenSSH for Windows](https://learn.microsoft.com/en-us/windows-server/administration/openssh/openssh_keymanagement)


### Development Setup
5. Install [rustup](https://rustup.rs).
- After installing VS 2022, manually install the *C++ Desktop Development* component to it.
6. Install [WiX v3](https://github.com/wixtoolset/wix3/releases)
7. Install cargo-wx: `cargo install cargo-wix`


### Building & Packaging
```
cargo build --release
cargo wix
```
15 changes: 9 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bak9"
version = "0.2.1"
version = "0.3.0"
edition = "2021"
description = "Creates a backup .bak copy of a file"
authors = ["Asmov LLC <[email protected]>"]
Expand All @@ -16,11 +16,6 @@ path = "src/main.rs"
[profile.release]
strip = "symbols"

[package.metadata.generate-rpm]
assets = [
{ source = "target/release/bak", dest = "/usr/bin/bak", mode = "755" }
]

[dependencies]
clap = { version = "4", features = ["derive"] }
colored = "2"
Expand All @@ -32,3 +27,11 @@ thiserror = "1"
function_name = "0"
file_diff = "1"

[package.metadata.generate-rpm]
assets = [
{ source = "target/release/bak", dest = "/usr/bin/bak", mode = "755" }
]

[package.metadata.packager]


39 changes: 28 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,46 @@ Creates a backup `.bak` copy of a file.
Usage
--------------------------------------------------------------------------------

`bak [OPTION]... FILE [DIR]`
`bak [OPTIONS] FILE [DIR] [COMMAND]`

Creates a backup `.bak` copy of **FILE**.

If **DIR** is not specified, the copy is created in the same directory as FILE.

If *multiple* backups of FILE exist, the filename extension used will be: `.bak.N`.
If DIR is specifed as `-`, or if the user lacks permissions to copy to DIR, the
user's app data directory will be used instead.

With multiple backups, the most recent backup will be always `bak.0`. Previous
copies will have their filename extension shifted by 1 (e.g., `bak.1` -> `bak.2`).
If *multiple* backups of FILE exist, the rotating filename extension used will be: `.bak.N`.

Pruning (deletion) occurs after `-n NUM` backups.
The most recent rotating backup will be always `.bak.0`.

If the current backup is no *diff*erent than its predecessor, copying will be skipped.
Pruning of rotating backups occurs after `-n NUM` backups.

### Options
If the current backup is no different than its predecessor, copying will be skipped.

Additional **COMMAND**s may be appended to list, compare, or delete backups.

- `-d`
Deletes all backup files for the source FILE.
### Options

- `-n NUM`
Creates at most **NUM** backup files.
If not specified, defaults to 10 (0-9).
Creates at most **NUM** backup files. [default: 10]

- `-q`
Quiet. Suppresses output.

- `-f`
Force the operation without confirmation.

### Commands

- `ls`
Lists all backups of FILE in DIR.

- `diff N`
Shows the differences of FILE and its `bak.N` copy in DIR. [default: 0]

- `rm`
Deletes all backups of FILE in DIR.

License (GPL3)
--------------------------------------------------------------------------------
Expand Down
50 changes: 42 additions & 8 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,57 @@
use std::path::PathBuf;
use clap::Parser;
use clap::{Parser, Subcommand};

use crate::PathExt;
use crate::{PathExt, E_STR};

#[derive(Parser)]
#[command(version, about)]
pub struct Cli {
#[command(subcommand)]
pub subcommand: Option<Command>,

#[arg(value_parser = validate_file)]
pub file: PathBuf,

#[arg(value_parser = validate_dir, help = "If not specified, the backup is created in the same directory as FILE.")]
#[arg(value_parser = validate_dir, help = "[default: Same directory as FILE. '-': The user's app data directory]")]
pub dir: Option<PathBuf>,

#[arg(short, default_value_t = false, help = "Delete all backups of FILE")]
pub delete: bool,

#[arg(short, value_parser = clap::value_parser!(u8).range(1..),
default_value_t = 10, help = "Number of backups to keep before pruning")]
pub num: u8,

#[arg(short, help = "Force the operation without confirmation")]
pub force: bool,

#[arg(short, help = "Suppress all output")]
pub quiet: bool
}


#[derive(Subcommand)]
pub enum Command {
#[command(name = "ls", about = "List all backups of FILE in DIR")]
List,
#[command(name = "rm", about = "Deletes all backups of FILE in DIR")]
Wipe,
#[command(name = "diff", about = "Shows the differences between FILE and BAK.N")]
Diff {
#[arg(default_value_t = 0, help = "The BAK index to compare FILE with")]
index: u8,
}
}

impl Cli {
pub fn dir(&self) -> PathBuf {
match &self.dir {
Some(dir) => dir.clone(),
Some(dir) => {
// handle passing Cli parameters manually
if dir.to_str().expect(E_STR) == "-" {
crate::os::user_app_data_dir(true, crate::BAK9.into())
.expect("Failed to get user app data directory")
} else {
dir.clone()
}
},
None => self.file.parent().expect("Expected parent directory").to_path_buf().clone(),
}
}
Expand Down Expand Up @@ -53,7 +81,13 @@ fn validate_file(path: &str) -> Result<PathBuf, String> {
}

fn validate_dir(path: &str) -> Result<PathBuf, String> {
let path = validate_path(path, "Directory")?;
let path = if path == "-" {
crate::os::user_app_data_dir(true, crate::BAK9.into())
.map_err(|e| e.to_string())?
} else {
validate_path(path, "Directory")?
};

if !path.is_dir() {
return Err(format!("Destination path is not a directory: {:?}", path))
} else {
Expand Down
Loading

0 comments on commit 4a38133

Please sign in to comment.