diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..8639a04 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,69 @@ +name: 'publish' + +on: + workflow_dispatch: + push: + branches: + - master + pull_request: + types: + - closed + +jobs: + publish-tauri: + permissions: + contents: write + strategy: + fail-fast: false + matrix: + include: + - platform: 'macos-latest' + args: '--target aarch64-apple-darwin' + - platform: 'macos-latest' + args: '--target x86_64-apple-darwin' + - platform: 'ubuntu-22.04' + args: '--target x86_64-unknown-linux-gnu' + + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 8 + + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }} + + - name: Install dependencies (ubuntu only) + if: matrix.platform == 'ubuntu-22.04' + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf + + - name: Install frontend dependencies + run: pnpm install + + - name: Get version + id: get_version + run: echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV + + - name: Build Tauri app + uses: tauri-apps/tauri-action@v0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tagName: app-v${{ env.VERSION }} + releaseName: 'App v${{ env.VERSION }}' + releaseBody: 'See the assets to download this version and install.' + releaseDraft: false # Change to true if you want to keep it as a draft + prerelease: false + args: ${{ matrix.args }} diff --git a/.github/workflows/build_aarch64.yml b/.github/workflows/build_aarch64.yml new file mode 100644 index 0000000..bff9eb8 --- /dev/null +++ b/.github/workflows/build_aarch64.yml @@ -0,0 +1,73 @@ +name: AArch64 compile and build +on: + workflow_dispatch: + push: + branches: + - master # Explicitly set to master + pull_request: + types: + - closed + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: pguyot/arm-runner-action@v2.5.2 + with: + base_image: https://dietpi.com/downloads/images/DietPi_RPi-ARMv8-Bullseye.img.xz + cpu: cortex-a53 + bind_mount_repository: true + image_additional_mb: 10240 + optimize_image: false + commands: | + # Rust complains (rightly) that $HOME doesn't match eid home + export HOME=/root + # Workaround to CI worker being stuck on Updating crates.io index + export CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse + # Install setup prerequisites + apt-get update -y --allow-releaseinfo-change + apt-get upgrade -y + apt-get autoremove -y + apt-get install curl + curl https://sh.rustup.rs -sSf | sh -s -- -y + . "$HOME/.cargo/env" + curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash + # Install framework specific packages + apt-get install -y nodejs + # Use pnpm instead of npm + npm install -g pnpm + pnpm install next@latest react@latest react-dom@latest eslint-config-next@latest + # Install build tools and tauri-cli requirements + apt-get install -y libwebkit2gtk-4.0-dev build-essential wget libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev patchelf + cargo install tauri-cli --version 1.6.2 + # Install frontend dependencies + pnpm install + # Build the application + cargo tauri build --target aarch64-unknown-linux-gnu --bundles deb,rpm + + # Debug: Print current directory and list files + pwd + echo "=== Listing build artifacts ===" + find . -name "*.deb" -o -name "*.rpm" + + - name: Get application name and version + id: get_info + run: | + cd src-tauri + APP_NAME=$(cargo metadata --format-version 1 --no-deps | jq -r '.packages[0].name') + APP_VERSION=$(cargo metadata --format-version 1 --no-deps | jq -r '.packages[0].version') + echo "APP_NAME=$APP_NAME" >> $GITHUB_ENV + echo "APP_VERSION=$APP_VERSION" >> $GITHUB_ENV + + - name: Upload deb bundle + uses: actions/upload-artifact@v3 + with: + name: Debian Bundle + path: ${{ github.workspace }}/src-tauri/target/aarch64-unknown-linux-gnu/release/bundle/deb/calimero-node-manager_0.1.0_arm64.deb + + - name: Upload rpm bundle + uses: actions/upload-artifact@v3 + with: + name: RPM Bundle + path: ${{ github.workspace }}/src-tauri/target/aarch64-unknown-linux-gnu/release/bundle/rpm/calimero-node-manager-0.1.0-1.aarch64.rpm diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 2e7f019..a5b4a06 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -58,9 +58,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.89" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "arrayref" @@ -1985,6 +1985,7 @@ checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" name = "node-multiplatform-tauri" version = "0.1.0" dependencies = [ + "anyhow", "auto-launch", "chrono", "eyre", @@ -2000,6 +2001,7 @@ dependencies = [ "tauri", "tauri-build", "tauri-plugin-store", + "tokio", "toml 0.8.19", ] @@ -3695,9 +3697,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 56d2acb..2f72682 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -13,8 +13,10 @@ tauri = { version = "1" } flate2 = "1.0.34" eyre = "0.6.12" tar = "0.4.42" -reqwest = "0.12.8" +reqwest = { version = "0.12.8", features = ["json"] } shared_utils = { path = "./shared_utils" } +serde_json = "1" +tokio = "1.42.0" [dependencies] auto-launch = "0.5.0" @@ -32,6 +34,7 @@ flate2 = "1.0.34" tar = "0.4.42" reqwest = "0.12.8" shared_utils = { path = "./shared_utils" } +anyhow = "1.0.94" [features] # This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!! diff --git a/src-tauri/build.rs b/src-tauri/build.rs index 28eb3a6..b9d17d4 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -9,6 +9,7 @@ use std::io::BufReader; use std::io::Read; use std::path::Path; use tar::Archive; +use std::time::Duration; fn main() { tauri::async_runtime::block_on(setup_binary()).unwrap(); @@ -17,14 +18,20 @@ fn main() { async fn setup_binary() -> Result<()> { let (os, arch, target) = determine_bin_data(); - let binary_name = "meroctl"; + let binary_name = "merod"; let cache_dir = std::env::temp_dir().join(binary_name); std::fs::create_dir_all(&cache_dir).expect("Failed to create cache directory"); + // Get the latest merod release tag + let latest_release = get_latest_merod_release().await?; + let url = format!( - "https://github.com/calimero-network/core/releases/latest/download/{}.tar.gz", + "https://github.com/calimero-network/core/releases/download/{}/{}.tar.gz", + latest_release, target ); + println!("Downloading from URL: {}", url); + let cache_bin_path = cache_dir.join(format!("{}.tar.gz", binary_name)); let bin_dir = std::env::current_dir()?.join("bin").join(os).join(arch); let resource_path = bin_dir.join(binary_name); @@ -63,3 +70,57 @@ async fn download_and_extract(url: &str, cache_bin_path: &Path, bin_dir: &Path) Ok(()) } + +async fn get_latest_merod_release() -> Result { + let client = reqwest::Client::new(); + let github_token = std::env::var("GITHUB_TOKEN").ok(); + + // Maximum number of retries + let max_retries = 3; + let mut retry_count = 0; + + loop { + let mut request = client + .get("https://api.github.com/repos/calimero-network/core/releases") + .header("User-Agent", "calimero-node-manager-build"); + + if let Some(token) = &github_token { + request = request.header("Authorization", format!("token {}", token)); + } + + let response = request.send().await?; + + match response.status() { + status if status.is_success() => { + let releases: Vec = response.json().await?; + + if let Some(latest_merod) = releases.iter().find(|release| { + release["tag_name"] + .as_str() + .map_or(false, |tag| tag.starts_with("merod")) + }) { + if let Some(tag_name) = latest_merod["tag_name"].as_str() { + return Ok(tag_name.to_string()); + } + } + bail!("No merod release found in the response"); + }, + status if status.as_u16() == 403 => { + if retry_count >= max_retries { + let error_text = response.text().await?; + bail!("GitHub API rate limit exceeded after {} retries: {}", max_retries, error_text); + } + + // Exponential backoff: wait longer between each retry + let wait_time = Duration::from_secs(2u64.pow(retry_count as u32)); + tokio::time::sleep(wait_time).await; + retry_count += 1; + continue; + }, + status => { + let error_text = response.text().await?; + bail!("GitHub API error: {} - {}", status, error_text); + } + } + } +} diff --git a/src-tauri/shared_utils/src/lib.rs b/src-tauri/shared_utils/src/lib.rs index 10eb5a0..821f21d 100644 --- a/src-tauri/shared_utils/src/lib.rs +++ b/src-tauri/shared_utils/src/lib.rs @@ -6,7 +6,7 @@ pub fn determine_bin_data() -> (String, String, String) { let parts: Vec<&str> = target.split('-').collect(); let os = parts[2]; let arch = parts[0]; - let binary_name = format!("meroctl_{}", target); + let binary_name = format!("merod_{}", target); (os.to_string(), arch.to_string(), binary_name) } else { let os = match OS { @@ -21,10 +21,10 @@ pub fn determine_bin_data() -> (String, String, String) { pub fn map_os_arch_to_binary_name(os: &str, arch: &str) -> String { match (os, arch) { - ("windows", "x86_64") => "meroctl-x86_64-pc-windows-msvc", - ("darwin", "x86_64") => "meroctl_x86_64-apple-darwin", - ("darwin", "aarch64") => "meroctl_aarch64-apple-darwin", - ("linux", "x86_64") => "meroctl_x86_64-unknown-linux-gnu", + ("windows", "x86_64") => "merod-x86_64-pc-windows-msvc", + ("darwin", "x86_64") => "merod_x86_64-apple-darwin", + ("darwin", "aarch64") => "merod_aarch64-apple-darwin", + ("linux", "x86_64") => "merod_x86_64-unknown-linux-gnu", // Add more combinations as needed _ => panic!("Unsupported OS/architecture combination: {}/{}", os, arch), } diff --git a/src-tauri/src/utils/mod.rs b/src-tauri/src/utils/mod.rs index 273620f..264de6c 100644 --- a/src-tauri/src/utils/mod.rs +++ b/src-tauri/src/utils/mod.rs @@ -44,9 +44,9 @@ pub fn get_binary_path(app_handle: &AppHandle) -> Result { .join("bin") .join(os) .join(arch) - .join("meroctl")) + .join("merod")) } else { - let relative_path = format!("bin/{}/{}/meroctl", os, arch); + let relative_path = format!("bin/{}/{}/merod", os, arch); app_handle .path_resolver() .resolve_resource(&relative_path) @@ -60,7 +60,7 @@ pub fn is_node_process_running(app_handle: &AppHandle, node_name: &str) -> Resul ); let pattern = format!( - r"meroctl.*--node-name\s+\b{}\b.*run", + r"merod.*--node-name\s+\b{}\b.*run", regex_escape(node_name) ); let re = Regex::new(&pattern).map_err(|e| eyre!("Failed to create regex: {}", e))?; @@ -170,7 +170,7 @@ pub fn check_ports_availability(config: &NodeConfig) -> Result<()> { // Kill the node process pub fn kill_node_process(node_name: &str) -> std::io::Result<()> { let output = Command::new("pkill") - .args(&["-f", &format!("meroctl.*--node-name {}.*run", node_name)]) + .args(&["-f", &format!("merod.*--node-name {}.*run", node_name)]) .output()?; if !output.status.success() { diff --git a/src-tauri/src/utils/setup.rs b/src-tauri/src/utils/setup.rs index bade1e7..03b4678 100644 --- a/src-tauri/src/utils/setup.rs +++ b/src-tauri/src/utils/setup.rs @@ -120,7 +120,15 @@ fn get_auto_launch(app: &AppHandle) -> Result