From 24413844ae1c7b9b24b69f452e76cf73da662c34 Mon Sep 17 00:00:00 2001 From: Ragnor Comerford Date: Sat, 30 May 2026 14:23:40 +0200 Subject: [PATCH] Add Windows release binaries (#127) * Add Windows release binaries * Fix Windows installer downloads --- .github/workflows/ci.yml | 57 +++++++++++ .github/workflows/release-edge.yml | 46 ++++++++- .github/workflows/release.yml | 47 ++++++++- docs/dev/ci.md | 5 +- docs/user/deployment.md | 8 +- docs/user/install.md | 62 +++++++++++- scripts/install.ps1 | 151 +++++++++++++++++++++++++++++ scripts/local-rustfs-bootstrap.sh | 3 - 8 files changed, 364 insertions(+), 15 deletions(-) create mode 100644 scripts/install.ps1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3dc2e80..918a472 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -249,6 +249,63 @@ jobs: if: needs.classify_changes.outputs.run_full_ci == 'true' run: cargo test --locked -p omnigraph-server --features aws + test_windows_binaries: + name: Test Windows release binaries + needs: classify_changes + runs-on: windows-latest + timeout-minutes: 75 + permissions: + contents: read + env: + CARGO_TERM_COLOR: always + steps: + - name: Skip for text-only changes + if: needs.classify_changes.outputs.run_full_ci != 'true' + run: Write-Host "Text-only change detected; skipping Windows binary build." + + - name: Checkout source + if: needs.classify_changes.outputs.run_full_ci == 'true' + uses: actions/checkout@v5.0.1 + + - name: Install system dependencies + if: needs.classify_changes.outputs.run_full_ci == 'true' + run: choco install protoc -y + + - name: Install Rust stable + if: needs.classify_changes.outputs.run_full_ci == 'true' + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + + - name: Cache Rust build data + if: needs.classify_changes.outputs.run_full_ci == 'true' + uses: Swatinem/rust-cache@v2 + with: + workspaces: | + . -> target + key: windows-release-binaries + + - name: Build Windows binaries + if: needs.classify_changes.outputs.run_full_ci == 'true' + run: cargo build --release --locked -p omnigraph-cli -p omnigraph-server + + - name: Smoke test Windows binaries + if: needs.classify_changes.outputs.run_full_ci == 'true' + run: | + & ./target/release/omnigraph.exe version + & ./target/release/omnigraph-server.exe --help + + - name: Check PowerShell installer syntax + if: needs.classify_changes.outputs.run_full_ci == 'true' + run: | + $tokens = $null + $errors = $null + [System.Management.Automation.Language.Parser]::ParseFile("scripts/install.ps1", [ref]$tokens, [ref]$errors) | Out-Null + if ($errors.Count -gt 0) { + $errors | Format-List + exit 1 + } + rustfs_integration: name: RustFS S3 Integration needs: diff --git a/.github/workflows/release-edge.yml b/.github/workflows/release-edge.yml index 6147646..3996e65 100644 --- a/.github/workflows/release-edge.yml +++ b/.github/workflows/release-edge.yml @@ -43,6 +43,8 @@ jobs: asset_name: omnigraph-linux-x86_64 - runner: macos-14 asset_name: omnigraph-macos-arm64 + - runner: windows-latest + asset_name: omnigraph-windows-x86_64 env: CARGO_TERM_COLOR: always steps: @@ -59,6 +61,10 @@ jobs: if: runner.os == 'macOS' run: brew install protobuf + - name: Install Windows dependencies + if: runner.os == 'Windows' + run: choco install protoc -y + - name: Install Rust stable uses: dtolnay/rust-toolchain@stable with: @@ -73,7 +79,8 @@ jobs: - name: Build release binaries run: cargo build --release --locked -p omnigraph-cli -p omnigraph-server - - name: Package release archive + - name: Package Unix release archive + if: runner.os != 'Windows' run: | mkdir -p release install -m 0755 target/release/omnigraph release/omnigraph @@ -81,6 +88,22 @@ jobs: tar -C release -czf "${{ matrix.asset_name }}.tar.gz" omnigraph omnigraph-server shasum -a 256 "${{ matrix.asset_name }}.tar.gz" > "${{ matrix.asset_name }}.sha256" + - name: Package Windows release archive + if: runner.os == 'Windows' + run: | + New-Item -ItemType Directory -Force -Path release | Out-Null + Copy-Item target/release/omnigraph.exe release/omnigraph.exe + Copy-Item target/release/omnigraph-server.exe release/omnigraph-server.exe + Compress-Archive -Path release/omnigraph.exe, release/omnigraph-server.exe -DestinationPath "${{ matrix.asset_name }}.zip" -Force + $hash = (Get-FileHash "${{ matrix.asset_name }}.zip" -Algorithm SHA256).Hash.ToLowerInvariant() + "$hash ${{ matrix.asset_name }}.zip" | Out-File -FilePath "${{ matrix.asset_name }}.sha256" -Encoding ascii + New-Item -ItemType Directory -Force -Path verify | Out-Null + Expand-Archive -Path "${{ matrix.asset_name }}.zip" -DestinationPath verify -Force + $items = Get-ChildItem -Path verify -File + if ($items.Count -ne 2 -or !(Test-Path verify/omnigraph.exe) -or !(Test-Path verify/omnigraph-server.exe)) { + throw "Windows release archive is missing expected binaries" + } + - name: Publish edge release assets uses: softprops/action-gh-release@v2.5.0 with: @@ -91,5 +114,22 @@ jobs: body: | Rolling prerelease from `${{ github.sha }}`. files: | - ${{ matrix.asset_name }}.tar.gz - ${{ matrix.asset_name }}.sha256 + ${{ matrix.asset_name }}.* + + smoke_windows_installer: + name: Smoke Windows installer + needs: build_release + runs-on: windows-latest + permissions: + contents: read + steps: + - name: Checkout source + uses: actions/checkout@v5.0.1 + + - name: Install from edge release + run: ./scripts/install.ps1 -ReleaseChannel edge -InstallDir "$env:RUNNER_TEMP/omnigraph-bin" + + - name: Smoke installed binaries + run: | + & "$env:RUNNER_TEMP/omnigraph-bin/omnigraph.exe" version + & "$env:RUNNER_TEMP/omnigraph-bin/omnigraph-server.exe" --help diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e7fc75f..48ab38c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,6 +20,8 @@ jobs: asset_name: omnigraph-linux-x86_64 - runner: macos-14 asset_name: omnigraph-macos-arm64 + - runner: windows-latest + asset_name: omnigraph-windows-x86_64 env: CARGO_TERM_COLOR: always steps: @@ -36,6 +38,10 @@ jobs: if: runner.os == 'macOS' run: brew install protobuf + - name: Install Windows dependencies + if: runner.os == 'Windows' + run: choco install protoc -y + - name: Install Rust stable uses: dtolnay/rust-toolchain@stable with: @@ -50,7 +56,8 @@ jobs: - name: Build release binaries run: cargo build --release --locked -p omnigraph-cli -p omnigraph-server - - name: Package release archive + - name: Package Unix release archive + if: runner.os != 'Windows' run: | mkdir -p release install -m 0755 target/release/omnigraph release/omnigraph @@ -58,12 +65,27 @@ jobs: tar -C release -czf "${{ matrix.asset_name }}.tar.gz" omnigraph omnigraph-server shasum -a 256 "${{ matrix.asset_name }}.tar.gz" > "${{ matrix.asset_name }}.sha256" + - name: Package Windows release archive + if: runner.os == 'Windows' + run: | + New-Item -ItemType Directory -Force -Path release | Out-Null + Copy-Item target/release/omnigraph.exe release/omnigraph.exe + Copy-Item target/release/omnigraph-server.exe release/omnigraph-server.exe + Compress-Archive -Path release/omnigraph.exe, release/omnigraph-server.exe -DestinationPath "${{ matrix.asset_name }}.zip" -Force + $hash = (Get-FileHash "${{ matrix.asset_name }}.zip" -Algorithm SHA256).Hash.ToLowerInvariant() + "$hash ${{ matrix.asset_name }}.zip" | Out-File -FilePath "${{ matrix.asset_name }}.sha256" -Encoding ascii + New-Item -ItemType Directory -Force -Path verify | Out-Null + Expand-Archive -Path "${{ matrix.asset_name }}.zip" -DestinationPath verify -Force + $items = Get-ChildItem -Path verify -File + if ($items.Count -ne 2 -or !(Test-Path verify/omnigraph.exe) -or !(Test-Path verify/omnigraph-server.exe)) { + throw "Windows release archive is missing expected binaries" + } + - name: Publish GitHub release assets uses: softprops/action-gh-release@v2.5.0 with: files: | - ${{ matrix.asset_name }}.tar.gz - ${{ matrix.asset_name }}.sha256 + ${{ matrix.asset_name }}.* update_homebrew_tap: name: Update Homebrew tap @@ -113,3 +135,22 @@ jobs: git add Formula/omnigraph.rb git commit -m "Update Omnigraph formula to ${GITHUB_REF_NAME}" git push origin HEAD:main + + smoke_windows_installer: + name: Smoke Windows installer + needs: build_release + if: startsWith(github.ref, 'refs/tags/v') + runs-on: windows-latest + permissions: + contents: read + steps: + - name: Checkout source + uses: actions/checkout@v5.0.1 + + - name: Install from tagged release + run: ./scripts/install.ps1 -Version "$env:GITHUB_REF_NAME" -InstallDir "$env:RUNNER_TEMP/omnigraph-bin" + + - name: Smoke installed binaries + run: | + & "$env:RUNNER_TEMP/omnigraph-bin/omnigraph.exe" version + & "$env:RUNNER_TEMP/omnigraph-bin/omnigraph-server.exe" --help diff --git a/docs/dev/ci.md b/docs/dev/ci.md index 8495d5e..1124cb4 100644 --- a/docs/dev/ci.md +++ b/docs/dev/ci.md @@ -4,7 +4,8 @@ - **ci.yml**: text-only changes skip; otherwise `cargo test --workspace --locked` on ubuntu-latest with protobuf compiler. OpenAPI-drift check that auto-commits the regenerated `openapi.json` for same-repository PRs. Also runs the AGENTS.md cross-link integrity check (`scripts/check-agents-md.sh`). - **AWS feature build job**: `cargo build/test -p omnigraph-server --features aws` on ubuntu-latest. +- **Windows binary build job**: `cargo build --release --locked -p omnigraph-cli -p omnigraph-server` on windows-latest with smoke checks for `omnigraph.exe version`, `omnigraph-server.exe --help`, and PowerShell installer syntax. - **RustFS S3 integration**: spins up RustFS in Docker, runs `s3_storage`, `server_opens_s3_graph_directly_and_serves_snapshot_and_read`, and `local_cli_s3_end_to_end_init_load_read_flow`. -- **release-edge.yml**: on every push to main, retags `edge`, builds Linux x86_64 / macOS arm64 archives + sha256, publishes a rolling prerelease. -- **release.yml**: on `v*` tags, builds the Linux x86_64 / macOS arm64 release matrix and updates the Homebrew tap (`scripts/update-homebrew-formula.sh`) by pushing the regenerated formula to `ModernRelay/homebrew-tap`. +- **release-edge.yml**: on every push to main, retags `edge`, builds Linux x86_64 / macOS arm64 archives and Windows x86_64 zip + sha256, publishes a rolling prerelease, then smoke-tests the Windows PowerShell installer against `edge`. +- **release.yml**: on `v*` tags, builds the Linux x86_64 / macOS arm64 archives and Windows x86_64 zip release matrix, updates the Homebrew tap (`scripts/update-homebrew-formula.sh`) by pushing the regenerated formula to `ModernRelay/homebrew-tap`, and smoke-tests the Windows PowerShell installer against the tag. - **package.yml**: manual ECR image build; emits two image tags per commit (``, `-aws`) via CodeBuild. diff --git a/docs/user/deployment.md b/docs/user/deployment.md index fc5ee08..8613dec 100644 --- a/docs/user/deployment.md +++ b/docs/user/deployment.md @@ -20,6 +20,8 @@ Build or install: - `omnigraph` - `omnigraph-server` +On Windows, the binaries are `omnigraph.exe` and `omnigraph-server.exe`. + Run against a local graph: ```bash @@ -141,8 +143,10 @@ The server binary ships in two flavors: | **AWS** | `cargo build --release --features aws` | Adds AWS Secrets Manager backend for bearer tokens | Tagged release archives contain the default `omnigraph` and -`omnigraph-server` binaries. AWS-enabled server binaries are built from source -with `cargo build --release --features aws -p omnigraph-server` when needed. +`omnigraph-server` binaries on macOS / Linux, and `omnigraph.exe` plus +`omnigraph-server.exe` on Windows. AWS-enabled server binaries are built from +source with `cargo build --release --features aws -p omnigraph-server` when +needed. The AWS build adds ~150 transitive deps and ~30-60s of first-build compile time. Default builds don't pay that cost. diff --git a/docs/user/install.md b/docs/user/install.md index ea9fb8c..4a11372 100644 --- a/docs/user/install.md +++ b/docs/user/install.md @@ -2,16 +2,29 @@ ## Quick Install +macOS / Linux: + ```bash curl -fsSL https://raw.githubusercontent.com/ModernRelay/omnigraph/main/scripts/install.sh | bash ``` +Windows PowerShell: + +```powershell +powershell -NoProfile -ExecutionPolicy Bypass -Command "iwr -UseBasicParsing https://raw.githubusercontent.com/ModernRelay/omnigraph/main/scripts/install.ps1 | iex" +``` + By default the installer places: - `omnigraph` - `omnigraph-server` -in `~/.local/bin`. +in `~/.local/bin` on macOS / Linux, or: + +- `omnigraph.exe` +- `omnigraph-server.exe` + +in `%USERPROFILE%\.local\bin` on Windows. The default installer is binary-only. It downloads a published release asset, verifies the SHA256 checksum, and unpacks it. It does not build from source. @@ -39,6 +52,13 @@ Rolling edge binaries from `main`: curl -fsSL https://raw.githubusercontent.com/ModernRelay/omnigraph/main/scripts/install.sh | RELEASE_CHANNEL=edge bash ``` +Windows rolling edge binaries: + +```powershell +iwr -UseBasicParsing https://raw.githubusercontent.com/ModernRelay/omnigraph/main/scripts/install.ps1 -OutFile install.ps1 +powershell -NoProfile -ExecutionPolicy Bypass -File .\install.ps1 -ReleaseChannel edge +``` + Install from source: ```bash @@ -53,12 +73,24 @@ Install to a different directory: curl -fsSL https://raw.githubusercontent.com/ModernRelay/omnigraph/main/scripts/install.sh | INSTALL_DIR="$HOME/bin" bash ``` +Windows: + +```powershell +powershell -NoProfile -ExecutionPolicy Bypass -File .\install.ps1 -InstallDir "$env:USERPROFILE\bin" +``` + Install a specific tag: ```bash curl -fsSL https://raw.githubusercontent.com/ModernRelay/omnigraph/main/scripts/install.sh | VERSION=v0.1.0 bash ``` +Windows: + +```powershell +powershell -NoProfile -ExecutionPolicy Bypass -File .\install.ps1 -Version v0.1.0 +``` + Build from a specific git ref: ```bash @@ -67,27 +99,53 @@ curl -fsSL https://raw.githubusercontent.com/ModernRelay/omnigraph/main/scripts/ ## Manual Source Build +macOS / Linux: + ```bash cargo build --release --locked -p omnigraph-cli -p omnigraph-server install -m 0755 target/release/omnigraph ~/.local/bin/omnigraph install -m 0755 target/release/omnigraph-server ~/.local/bin/omnigraph-server ``` +Windows: + +```powershell +cargo build --release --locked -p omnigraph-cli -p omnigraph-server +New-Item -ItemType Directory -Force "$env:USERPROFILE\.local\bin" | Out-Null +Copy-Item target\release\omnigraph.exe "$env:USERPROFILE\.local\bin\omnigraph.exe" +Copy-Item target\release\omnigraph-server.exe "$env:USERPROFILE\.local\bin\omnigraph-server.exe" +``` + ## Release Assets Tagged releases are expected to publish: - `omnigraph-linux-x86_64.tar.gz` - `omnigraph-macos-arm64.tar.gz` +- `omnigraph-windows-x86_64.zip` -Each archive contains both binaries: +The macOS / Linux archives contain both binaries: - `omnigraph` - `omnigraph-server` +The Windows archive contains: + +- `omnigraph.exe` +- `omnigraph-server.exe` + ## Verify The Install +macOS / Linux: + ```bash omnigraph version omnigraph-server --help ``` + +Windows: + +```powershell +omnigraph.exe version +omnigraph-server.exe --help +``` diff --git a/scripts/install.ps1 b/scripts/install.ps1 new file mode 100644 index 0000000..3bfd0f1 --- /dev/null +++ b/scripts/install.ps1 @@ -0,0 +1,151 @@ +param( + [string]$RepoSlug = "ModernRelay/omnigraph", + [string]$InstallDir = "$env:USERPROFILE\.local\bin", + [ValidateSet("stable", "edge")] + [string]$ReleaseChannel = "stable", + [string]$Version = "" +) + +$ErrorActionPreference = "Stop" + +$assetName = "omnigraph-windows-x86_64.zip" +$assetStem = "omnigraph-windows-x86_64" +$workDir = Join-Path ([System.IO.Path]::GetTempPath()) ("omnigraph-install-" + [System.Guid]::NewGuid().ToString("N")) +$selectedChannel = "" + +function Write-Log { + param([string]$Message) + Write-Host "==> $Message" +} + +function Get-ReleaseBaseUrl { + param([string]$Channel) + + if ($Version -ne "") { + return "https://github.com/$RepoSlug/releases/download/$Version" + } + + if ($Channel -eq "stable") { + return "https://github.com/$RepoSlug/releases/latest/download" + } + + if ($Channel -eq "edge") { + return "https://github.com/$RepoSlug/releases/download/edge" + } + + throw "unsupported ReleaseChannel '$Channel' (expected stable or edge)" +} + +function Download-ReleaseFiles { + param( + [string]$BaseUrl, + [string]$ArchivePath, + [string]$ChecksumPath + ) + + try { + Invoke-WebRequest -UseBasicParsing -Uri "$BaseUrl/$assetName" -OutFile $ArchivePath + Invoke-WebRequest -UseBasicParsing -Uri "$BaseUrl/$assetStem.sha256" -OutFile $ChecksumPath + return $true + } catch { + return $false + } +} + +function Verify-Checksum { + param( + [string]$ArchivePath, + [string]$ChecksumPath + ) + + $checksumText = (Get-Content -Path $ChecksumPath -Raw).Trim() + $expected = ($checksumText -split "\s+")[0].ToLowerInvariant() + if ($expected -eq "") { + throw "checksum file did not contain a SHA256 digest" + } + + $actual = (Get-FileHash -Path $ArchivePath -Algorithm SHA256).Hash.ToLowerInvariant() + if ($actual -ne $expected) { + throw "checksum verification failed for $assetName" + } +} + +function Install-FromDirectory { + param([string]$SourceDir) + + New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null + Copy-Item -Path (Join-Path $SourceDir "omnigraph.exe") -Destination (Join-Path $InstallDir "omnigraph.exe") -Force + Copy-Item -Path (Join-Path $SourceDir "omnigraph-server.exe") -Destination (Join-Path $InstallDir "omnigraph-server.exe") -Force +} + +function Install-FromRelease { + New-Item -ItemType Directory -Force -Path $workDir | Out-Null + + $archivePath = Join-Path $workDir $assetName + $checksumPath = Join-Path $workDir "$assetStem.sha256" + + if ($Version -ne "") { + $script:selectedChannel = $Version + $baseUrl = Get-ReleaseBaseUrl -Channel $ReleaseChannel + Write-Log "Downloading $assetName from $Version" + if (!(Download-ReleaseFiles -BaseUrl $baseUrl -ArchivePath $archivePath -ChecksumPath $checksumPath)) { + throw "no published binary found for $assetName at release $Version" + } + } else { + $script:selectedChannel = $ReleaseChannel + $baseUrl = Get-ReleaseBaseUrl -Channel $selectedChannel + Write-Log "Downloading $assetName from $selectedChannel" + if (!(Download-ReleaseFiles -BaseUrl $baseUrl -ArchivePath $archivePath -ChecksumPath $checksumPath)) { + if ($ReleaseChannel -ne "stable") { + throw "no published binary found for $assetName on channel $ReleaseChannel" + } + + Write-Log "Stable release binaries are not published yet; falling back to edge" + $script:selectedChannel = "edge" + $baseUrl = Get-ReleaseBaseUrl -Channel $selectedChannel + if (!(Download-ReleaseFiles -BaseUrl $baseUrl -ArchivePath $archivePath -ChecksumPath $checksumPath)) { + throw "no published binary found for $assetName on stable or edge; build from source" + } + } + } + + Verify-Checksum -ArchivePath $archivePath -ChecksumPath $checksumPath + + $extractDir = Join-Path $workDir "extract" + New-Item -ItemType Directory -Force -Path $extractDir | Out-Null + Expand-Archive -Path $archivePath -DestinationPath $extractDir -Force + Install-FromDirectory -SourceDir $extractDir +} + +function Print-Summary { + $omnigraphPath = Join-Path $InstallDir "omnigraph.exe" + $serverPath = Join-Path $InstallDir "omnigraph-server.exe" + + Write-Host "" + Write-Host "Installed:" + Write-Host " $omnigraphPath" + Write-Host " $serverPath" + Write-Host "" + Write-Host "Verify:" + Write-Host " $omnigraphPath version" + Write-Host " $serverPath --help" + Write-Host "" + + if ($selectedChannel -ne "") { + Write-Host "Installed from release channel: $selectedChannel" + } + + $pathParts = $env:Path -split [System.IO.Path]::PathSeparator + if ($pathParts -notcontains $InstallDir) { + Write-Host "Add $InstallDir to PATH if needed." + } +} + +try { + Install-FromRelease + Print-Summary +} finally { + if (Test-Path $workDir) { + Remove-Item -Path $workDir -Recurse -Force + } +} diff --git a/scripts/local-rustfs-bootstrap.sh b/scripts/local-rustfs-bootstrap.sh index 6327f77..29427de 100755 --- a/scripts/local-rustfs-bootstrap.sh +++ b/scripts/local-rustfs-bootstrap.sh @@ -74,9 +74,6 @@ platform_asset_name() { Linux/x86_64) printf 'omnigraph-linux-x86_64.tar.gz\n' ;; - Darwin/x86_64) - printf 'omnigraph-macos-x86_64.tar.gz\n' - ;; Darwin/arm64) printf 'omnigraph-macos-arm64.tar.gz\n' ;;