diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b830105f4..c3a64fcf9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,6 +7,7 @@ on: push: branches: - main + tags: [ 'v*', '*' ] jobs: build-rustfs: @@ -60,9 +61,12 @@ jobs: # Create package mkdir -p ${ARTIFACT_NAME} cp "$bin_path" ${ARTIFACT_NAME}/rustfs - # cp -r static ${ARTIFACT_NAME}/ zip -r ${ARTIFACT_NAME}.zip ${ARTIFACT_NAME} ls -la + + # Copy files to the specified directory + mkdir -p cli/rustfs-gui/embedded-rustfs + cp -r ${ARTIFACT_NAME}/rustfs cli/rustfs-gui/embedded-rustfs/ - uses: actions/upload-artifact@v4 with: @@ -70,9 +74,40 @@ jobs: path: ${{ steps.package.outputs.artifact_name }}.zip retention-days: 7 - merge: + build-rustfs-gui: runs-on: ubuntu-latest needs: build-rustfs + if: startsWith(github.ref, 'refs/tags/') + steps: + - uses: actions/checkout@v4 + - name: Install dioxus-cli + run: cargo install dioxus-cli + + - name: Build and Bundle rustfs-gui + run: | + relaese_path = "target/${{ matrix.variant.target }}" + mkdir -p ${relaese_path} + cd cli/rustfs-gui + if [[ "${{ matrix.variant.target }}" == *"apple-darwin"* ]]; then + dx bundle --platform macos --package-types "macos" --package-types "dmg" --package-types "ios" --release --profile release --out-dir ../../${relaese_path} + elif [[ "${{ matrix.variant.target }}" == *"windows-msvc"* ]]; then + dx bundle --platform windows --package-types "msi" --release --profile release --out-dir ../../target/${relaese_path} + fi + cd ../.. + GUI_ARTIFACT_NAME="rustfs-gui-${{ matrix.variant.profile }}-${{ matrix.variant.target }}" + zip -r ${GUI_ARTIFACT_NAME}.zip target/${{ matrix.variant.target }}/rustfs-gui + echo "gui_artifact_name=${GUI_ARTIFACT_NAME}" >> $GITHUB_OUTPUT + ls -la + + - uses: actions/upload-artifact@v4 + with: + name: ${{ steps.package.outputs.gui_artifact_name }} + path: ${{ steps.package.outputs.gui_artifact_name }}.zip + retention-days: 7 + + merge: + runs-on: ubuntu-latest + needs: [ build-rustfs, build-rustfs-gui ] steps: - uses: actions/upload-artifact/merge@v4 with: diff --git a/.gitignore b/.gitignore index 81d415f64..faba350da 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ /logs .devcontainer rustfs/static/* +vendor \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index c8cae648d..18642dc6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5468,11 +5468,14 @@ dependencies = [ "dioxus", "dirs 6.0.0", "futures-util", + "hex", "keyring", "reqwest", "rfd 0.15.2", + "rust-embed", "serde", "serde_json", + "sha2 0.10.8", "tokio", "tracing-appender", "tracing-subscriber", diff --git a/Cargo.toml b/Cargo.toml index 539489414..c034c32dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,18 +1,18 @@ [workspace] members = [ - "madmin", # Management dashboard and admin API interface - "rustfs", # Core file system implementation - "ecstore", # Erasure coding storage implementation - "e2e_test", # End-to-end test suite - "common/common", # Shared utilities and data structures - "common/lock", # Distributed locking implementation - "common/protos", # Protocol buffer definitions - "api/admin", # Admin HTTP API endpoints - "reader", # Object reading service - "common/workers", # Worker thread pools and task scheduling - "iam", # Identity and Access Management - "crypto", # Cryptography and security features - "cli/rustfs-gui", # Graphical user interface client + "madmin", # Management dashboard and admin API interface + "rustfs", # Core file system implementation + "ecstore", # Erasure coding storage implementation + "e2e_test", # End-to-end test suite + "common/common", # Shared utilities and data structures + "common/lock", # Distributed locking implementation + "common/protos", # Protocol buffer definitions + "api/admin", # Admin HTTP API endpoints + "reader", # Object reading service + "common/workers", # Worker thread pools and task scheduling + "iam", # Identity and Access Management + "crypto", # Cryptography and security features + "cli/rustfs-gui", # Graphical user interface client ] resolver = "2" @@ -45,6 +45,7 @@ futures = "0.3.31" futures-util = "0.3.31" common = { path = "./common/common" } reader = { path = "./reader" } +hex = "0.4.3" hyper = "1.6.0" hyper-util = { version = "0.1.10", features = [ "tokio", @@ -57,6 +58,7 @@ humantime = "2.1.0" keyring = { version = "3.6.1", features = ["apple-native", "windows-native", "sync-secret-service"] } lock = { path = "./common/lock" } lazy_static = "1.5.0" +local-ip-address = "0.6.3" mime = "0.3.17" netif = "0.1.6" pin-project-lite = "0.2" @@ -71,6 +73,7 @@ reqwest = { version = "0.12.12", default-features = false, features = ["rustls-t rfd = { version = "0.15.2", default-features = false, features = ["xdg-portal", "tokio"] } rmp = "0.8.14" rmp-serde = "1.3.0" +rust-embed = "8.6.0" s3s = { git = "https://github.com/Nugine/s3s.git", rev = "ab139f72fe768fb9d8cecfe36269451da1ca9779", default-features = true, features = [ "tower", ] } @@ -78,6 +81,7 @@ s3s-policy = { git = "https://github.com/Nugine/s3s.git", rev = "ab139f72fe768fb shadow-rs = { version = "0.38.0", default-features = false } serde = { version = "1.0.217", features = ["derive"] } serde_json = "1.0.138" +sha2 = "0.10.8" tempfile = "3.16.0" thiserror = "2.0.11" time = { version = "0.3.37", features = [ diff --git a/cli/rustfs-gui/Cargo.toml b/cli/rustfs-gui/Cargo.toml index 4caa5a972..33690e76c 100644 --- a/cli/rustfs-gui/Cargo.toml +++ b/cli/rustfs-gui/Cargo.toml @@ -11,11 +11,14 @@ chrono = { workspace = true } dioxus = { workspace = true, features = ["router"] } dirs = { workspace = true } futures-util = { workspace = true } +hex = { workspace = true } keyring = { workspace = true } reqwest = { workspace = true } rfd = { workspace = true } +rust-embed = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } +sha2 = { workspace = true } tokio = { workspace = true, features = ["io-util", "net", "process", "sync"] } tracing-subscriber = { workspace = true, features = ["fmt", "env-filter", "tracing-log", "time", "local-time", "json"] } tracing-appender = { workspace = true } diff --git a/cli/rustfs-gui/embedded-rustfs/README.md b/cli/rustfs-gui/embedded-rustfs/README.md new file mode 100644 index 000000000..190bfa361 --- /dev/null +++ b/cli/rustfs-gui/embedded-rustfs/README.md @@ -0,0 +1 @@ +rustfs bin path, do not delete \ No newline at end of file diff --git a/cli/rustfs-gui/src/utils/helper.rs b/cli/rustfs-gui/src/utils/helper.rs index b597ef81e..7968a2971 100644 --- a/cli/rustfs-gui/src/utils/helper.rs +++ b/cli/rustfs-gui/src/utils/helper.rs @@ -1,14 +1,32 @@ use crate::utils::RustFSConfig; use dioxus::logger::tracing::{debug, error, info}; use futures_util::TryStreamExt; +use lazy_static::lazy_static; +use rust_embed::RustEmbed; +use sha2::{Digest, Sha256}; use std::error::Error; -use std::fs::File; use std::path::{Path, PathBuf}; use std::process::Command as StdCommand; use std::time::Duration; +use tokio::fs; +use tokio::fs::File; use tokio::io::AsyncWriteExt; use tokio::net::TcpStream; -use tokio::sync::mpsc; +use tokio::sync::{mpsc, Mutex}; + +#[derive(RustEmbed)] +#[folder = "$CARGO_MANIFEST_DIR/embedded-rustfs/"] +struct Asset; + +// Use `lazy_static` to cache the checksum of embedded resources +lazy_static! { + static ref RUSTFS_HASH: Mutex = { + let rustfs_file = if cfg!(windows) { "rustfs.exe" } else { "rustfs" }; + let rustfs_data = Asset::get(rustfs_file).expect("RustFs binary not embedded"); + let hash = hex::encode(Sha256::digest(&rustfs_data.data)); + Mutex::new(hash) + }; +} /// Service command /// This enum represents the commands that can be sent to the service manager @@ -159,45 +177,37 @@ impl ServiceManager { } } - let executable_path = if cfg!(windows) { - bin_dir.join("rustfs.exe") - } else { - bin_dir.join("rustfs") - }; + let rustfs_file = if cfg!(windows) { "rustfs.exe" } else { "rustfs" }; + let executable_path = bin_dir.join(rustfs_file); + let hash_path = bin_dir.join("embedded_rustfs.sha256"); - // If the executable file doesn't exist, download and unzip it - if !executable_path.exists() { - // download the file - let tmp_zip = rustfs_dir.join("rustfs.zip"); - let file_download_url = if cfg!(windows) { - "https://api.xmb.xyz/download/rustfs-win.zip" - } else { - "https://api.xmb.xyz/download/rustfs.zip" - }; - - let download_task = Self::download_file(file_download_url, &tmp_zip); - let unzip_task = async { - download_task.await?; - Self::unzip_file(&tmp_zip, &bin_dir)?; - tokio::fs::remove_file(&tmp_zip).await?; - Ok::<(), Box>(()) - }; - unzip_task.await?; - - // delete the temporary zip file - tokio::fs::remove_file(&tmp_zip).await?; - - // set execution permissions on unix systems - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let mut perms = std::fs::metadata(&executable_path)?.permissions(); - perms.set_mode(0o755); - std::fs::set_permissions(&executable_path, perms)?; + if executable_path.exists() && hash_path.exists() { + let cached_hash = fs::read_to_string(&hash_path).await?; + let expected_hash = RUSTFS_HASH.lock().await; + if cached_hash == *expected_hash { + println!("Use cached rustfs: {:?}", executable_path); + return Ok(executable_path); } - Self::show_info("服务程序已成功下载并准备就绪"); } + // Extract and write files + let rustfs_data = Asset::get(rustfs_file).expect("RustFS binary not embedded"); + let mut file = File::create(&executable_path).await?; + file.write_all(&rustfs_data.data).await?; + let expected_hash = hex::encode(Sha256::digest(&rustfs_data.data)); + fs::write(&hash_path, expected_hash).await?; + + // set execution permissions on unix systems + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let mut perms = std::fs::metadata(&executable_path)?.permissions(); + perms.set_mode(0o755); + std::fs::set_permissions(&executable_path, perms)?; + } + // Self::show_info("服务程序已成功下载并准备就绪"); + // } + Ok(executable_path) } @@ -236,7 +246,8 @@ impl ServiceManager { /// let extract_path = Path::new("rustfs"); /// unzip_file(zip_path, extract_path); /// ``` - fn unzip_file(zip_path: &Path, extract_path: &Path) -> Result<(), Box> { + async fn unzip_file(zip_path: &Path, extract_path: &Path) -> Result<(), Box> { + use std::fs::File; let file = File::open(zip_path)?; let mut archive = zip::ZipArchive::new(file)?; diff --git a/rustfs/Cargo.toml b/rustfs/Cargo.toml index eb5182abc..e27c59575 100644 --- a/rustfs/Cargo.toml +++ b/rustfs/Cargo.toml @@ -73,9 +73,9 @@ iam = { path = "../iam" } jsonwebtoken = "9.3.0" tower-http = { version = "0.6.2", features = ["cors"] } mime_guess = "2.0.5" -rust-embed = { version = "8.6.0", features = ["interpolate-folder-path"] } -local-ip-address = "0.6.3" -chrono = "0.4" +rust-embed = { workspace = true, features = ["interpolate-folder-path"] } +local-ip-address = { workspace = true } +chrono = { workspace = true } [build-dependencies] prost-build.workspace = true @@ -87,7 +87,7 @@ futures-util.workspace = true # uuid = { version = "1.8.0", features = ["v4", "fast-rng", "serde"] } ecstore = { path = "../ecstore" } s3s.workspace = true -clap = { version = "4.5.31", features = ["derive","env"] } +clap = { version = "4.5.31", features = ["derive", "env"] } tracing-subscriber = { version = "0.3.19", features = ["env-filter", "time"] } hyper-util = { version = "0.1.10", features = [ "tokio",