diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4475741..af6a8ba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,6 +26,8 @@ jobs: - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable + with: + components: clippy - name: Cache Rust build uses: Swatinem/rust-cache@v2 @@ -48,6 +50,9 @@ jobs: - name: Build frontend run: pnpm -C apps/desktop build + - name: Run clippy + run: cargo clippy --workspace --all-targets -- -D warnings + - name: Run Rust tests run: cargo test --workspace @@ -162,3 +167,36 @@ jobs: - name: Run desktop E2E run: pnpm test:e2e + + fmt: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + + - name: Check formatting + run: cargo fmt --all -- --check + + audit: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install cargo-audit + uses: taiki-e/install-action@v2 + with: + tool: cargo-audit + + # Fails on security vulnerabilities. Unmaintained/unsound advisories + # (e.g. the GTK3 bindings Tauri requires on Linux) surface as warnings + # without failing the build. + - name: Run cargo audit + run: cargo audit diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index e8f6db2..f0a6d29 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -1,11 +1,13 @@ name: Release (Linux) -# Builds the OpenCherry desktop app as a Debian package (.deb). +# Builds the OpenCherry desktop app as Linux packages: Debian (.deb), AppImage, +# and RPM, each with a SHA-256 checksum. # # Triggers: -# - Push of a tag matching `v*` (e.g. v0.0.1) — attaches the .deb to the -# matching GitHub Release. -# - Manual `workflow_dispatch` — uploads the .deb as a build artifact only +# - Push of a tag matching `v*` (e.g. v0.0.1) — attaches the packages and +# checksums to the matching GitHub Release, sets the release notes from +# CHANGELOG.md, and (when enabled) publishes the AUR package. +# - Manual `workflow_dispatch` — uploads the packages as build artifacts only # (no release). on: @@ -33,7 +35,8 @@ jobs: libwebkit2gtk-4.1-dev \ libappindicator3-dev \ librsvg2-dev \ - patchelf + patchelf \ + rpm - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable @@ -59,19 +62,98 @@ jobs: run: pnpm install --frozen-lockfile working-directory: apps/desktop - - name: Build .deb package - run: pnpm tauri build --bundles deb + - name: Build Linux bundles (.deb, .AppImage, .rpm) + run: pnpm tauri build --bundles deb,appimage,rpm working-directory: apps/desktop + env: + # Stripping can corrupt the AppImage runtime on some toolchains. + NO_STRIP: "true" - - name: Upload .deb artifact + - name: Generate SHA-256 checksums + run: | + set -euo pipefail + for f in \ + target/release/bundle/deb/*.deb \ + target/release/bundle/appimage/*.AppImage \ + target/release/bundle/rpm/*.rpm; do + [ -e "$f" ] || continue + ( cd "$(dirname "$f")" && sha256sum "$(basename "$f")" > "$(basename "$f").sha256" ) + done + + - name: Upload Linux artifacts uses: actions/upload-artifact@v4 with: - name: opencherry-linux-deb - path: target/release/bundle/deb/*.deb + name: opencherry-linux-packages + path: | + target/release/bundle/deb/*.deb + target/release/bundle/deb/*.deb.sha256 + target/release/bundle/appimage/*.AppImage + target/release/bundle/appimage/*.AppImage.sha256 + target/release/bundle/rpm/*.rpm + target/release/bundle/rpm/*.rpm.sha256 if-no-files-found: error - - name: Attach .deb to release + - name: Extract release notes from CHANGELOG + if: startsWith(github.ref, 'refs/tags/v') + run: | + set -euo pipefail + version="${GITHUB_REF_NAME#v}" + awk -v ver="$version" ' + $0 ~ "^## \\[" ver "\\]" { capture=1; next } + capture && /^## \[/ { exit } + capture && /^\[[^]]+\]: / { exit } + capture { print } + ' CHANGELOG.md > changelog_section.md + if [ ! -s changelog_section.md ]; then + echo "Release ${GITHUB_REF_NAME}" > changelog_section.md + fi + + # The Linux workflow uniquely owns the release body; macOS/Windows attach + # files only. action-gh-release@v2 preserves an existing body when called + # without one, so this is race-safe across the three parallel workflows. + - name: Attach Linux assets to release if: startsWith(github.ref, 'refs/tags/v') uses: softprops/action-gh-release@v2 with: - files: target/release/bundle/deb/*.deb + body_path: changelog_section.md + files: | + target/release/bundle/deb/*.deb + target/release/bundle/deb/*.deb.sha256 + target/release/bundle/appimage/*.AppImage + target/release/bundle/appimage/*.AppImage.sha256 + target/release/bundle/rpm/*.rpm + target/release/bundle/rpm/*.rpm.sha256 + + # Publishes the AUR package `opencherry-bin`. Dormant until configured: set the + # repo variable PUBLISH_AUR=true and provide the AUR_SSH_PRIVATE_KEY secret plus + # AUR_COMMIT_USERNAME / AUR_COMMIT_EMAIL variables. See packaging/aur/README.md. + publish-aur: + needs: build-linux + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') && vars.PUBLISH_AUR == 'true' + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Inject version and checksum into PKGBUILD + run: | + set -euo pipefail + version="${GITHUB_REF_NAME#v}" + base="https://github.com/${GITHUB_REPOSITORY}/releases/download/${GITHUB_REF_NAME}" + sum_x86="$(curl -fsSL "${base}/opencherry_${version}_amd64.AppImage.sha256" | cut -d' ' -f1)" + [ -n "$sum_x86" ] || { echo "missing x86_64 AppImage checksum"; exit 1; } + sed -i \ + -e "s/^pkgver=.*/pkgver=${version}/" \ + -e "s/REPLACE_WITH_SHA256_X86_64/${sum_x86}/" \ + packaging/aur/PKGBUILD + + - name: Publish to AUR + uses: KSXGitHub/github-actions-deploy-aur@v4.1.3 + with: + pkgname: opencherry-bin + pkgbuild: packaging/aur/PKGBUILD + commit_username: ${{ vars.AUR_COMMIT_USERNAME }} + commit_email: ${{ vars.AUR_COMMIT_EMAIL }} + ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} + commit_message: "Update to ${{ github.ref_name }}" + updpkgsums: false diff --git a/.github/workflows/release-macos.yml b/.github/workflows/release-macos.yml index 62d0de2..ed4bb24 100644 --- a/.github/workflows/release-macos.yml +++ b/.github/workflows/release-macos.yml @@ -1,16 +1,19 @@ name: Release (macOS) # Builds the OpenCherry desktop app as a macOS disk image (.dmg), for both -# Apple Silicon (aarch64) and Intel (x86_64). +# Apple Silicon (aarch64) and Intel (x86_64), each with a SHA-256 checksum. # # Triggers: -# - Push of a tag matching `v*` (e.g. v0.0.1) — attaches the .dmg files to the -# matching GitHub Release. +# - Push of a tag matching `v*` (e.g. v0.0.1) — attaches the .dmg files and +# checksums to the matching GitHub Release. # - Manual `workflow_dispatch` — uploads the .dmg files as build artifacts # only (no release), useful for contributors without a Mac. # # The images are unsigned and not notarized: on first launch macOS Gatekeeper # may block the app (right-click → Open to bypass). +# +# This workflow attaches files only; the Linux release workflow owns the release +# notes body (action-gh-release@v2 preserves an existing body when not set here). on: push: @@ -68,15 +71,70 @@ jobs: run: pnpm tauri build --bundles dmg --target ${{ matrix.target }} working-directory: apps/desktop + - name: Generate SHA-256 checksum + run: | + set -euo pipefail + for f in target/${{ matrix.target }}/release/bundle/dmg/*.dmg; do + [ -e "$f" ] || continue + ( cd "$(dirname "$f")" && shasum -a 256 "$(basename "$f")" > "$(basename "$f").sha256" ) + done + - name: Upload .dmg artifact uses: actions/upload-artifact@v4 with: name: opencherry-macos-${{ matrix.arch }}-dmg - path: target/${{ matrix.target }}/release/bundle/dmg/*.dmg + path: | + target/${{ matrix.target }}/release/bundle/dmg/*.dmg + target/${{ matrix.target }}/release/bundle/dmg/*.dmg.sha256 if-no-files-found: error - name: Attach .dmg to release if: startsWith(github.ref, 'refs/tags/v') uses: softprops/action-gh-release@v2 with: - files: target/${{ matrix.target }}/release/bundle/dmg/*.dmg + files: | + target/${{ matrix.target }}/release/bundle/dmg/*.dmg + target/${{ matrix.target }}/release/bundle/dmg/*.dmg.sha256 + + # Publishes the Homebrew cask to the tap. Dormant until configured: set the + # repo variable PUBLISH_HOMEBREW=true and provide the HOMEBREW_TAP_TOKEN secret. + # See packaging/homebrew/README.md. + publish-homebrew: + needs: build-macos + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') && vars.PUBLISH_HOMEBREW == 'true' + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Render cask from release checksums + run: | + set -euo pipefail + version="${GITHUB_REF_NAME#v}" + base="https://github.com/${GITHUB_REPOSITORY}/releases/download/${GITHUB_REF_NAME}" + arm="$(curl -fsSL "${base}/opencherry_${version}_aarch64.dmg.sha256" | cut -d' ' -f1)" + intel="$(curl -fsSL "${base}/opencherry_${version}_x64.dmg.sha256" | cut -d' ' -f1)" + [ -n "$arm" ] && [ -n "$intel" ] || { echo "missing dmg checksum(s)"; exit 1; } + sed -e "s/^ version .*/ version \"${version}\"/" \ + -e "s/REPLACE_WITH_SHA256_ARM/${arm}/" \ + -e "s/REPLACE_WITH_SHA256_INTEL/${intel}/" \ + packaging/homebrew/opencherry.rb > opencherry.rb + + - name: Push cask to tap + env: + GH_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} + run: | + set -euo pipefail + git clone "https://x-access-token:${GH_TOKEN}@github.com/4nrry/homebrew-tap.git" tap + mkdir -p tap/Casks + cp opencherry.rb tap/Casks/opencherry.rb + cd tap + git config user.name "opencherry-release-bot" + git config user.email "noreply@opencherry.ai" + git add Casks/opencherry.rb + if git diff --cached --quiet; then + echo "cask unchanged; nothing to push" + else + git commit -m "opencherry ${GITHUB_REF_NAME}" + git push + fi diff --git a/.github/workflows/release-windows.yml b/.github/workflows/release-windows.yml index 44abbf7..2c498c9 100644 --- a/.github/workflows/release-windows.yml +++ b/.github/workflows/release-windows.yml @@ -53,15 +53,46 @@ jobs: run: pnpm tauri build --bundles nsis working-directory: apps/desktop + - name: Generate SHA-256 checksum + shell: pwsh + run: | + Get-ChildItem target/release/bundle/nsis/*-setup.exe | ForEach-Object { + $hash = (Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLower() + "$hash $($_.Name)" | Out-File -NoNewline -Encoding ascii "$($_.FullName).sha256" + } + - name: Upload installer artifact uses: actions/upload-artifact@v4 with: name: opencherry-windows-installer - path: target/release/bundle/nsis/*-setup.exe + path: | + target/release/bundle/nsis/*-setup.exe + target/release/bundle/nsis/*-setup.exe.sha256 if-no-files-found: error - name: Attach installer to release if: startsWith(github.ref, 'refs/tags/v') uses: softprops/action-gh-release@v2 with: - files: target/release/bundle/nsis/*-setup.exe + files: | + target/release/bundle/nsis/*-setup.exe + target/release/bundle/nsis/*-setup.exe.sha256 + + # Publishes to the Windows Package Manager (winget). Dormant until configured: + # set the repo variable PUBLISH_WINGET=true and provide the WINGET_TOKEN secret + # (a CLASSIC PAT with public_repo scope; fine-grained tokens are not supported). + # A fork of microsoft/winget-pkgs must exist under the repo owner, and the + # package's first version must already be in winget (submitted once manually). + # See packaging/winget/README.md. + publish-winget: + needs: build-windows + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') && vars.PUBLISH_WINGET == 'true' + steps: + - name: Publish to winget + uses: vedantmgoyal9/winget-releaser@v2 + with: + identifier: 4nrry.OpenCherryDesktop + installers-regex: '\.exe$' + release-tag: ${{ github.ref_name }} + token: ${{ secrets.WINGET_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..90bc56f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,26 @@ +# Changelog + +All notable changes to this project are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.0.1] - 2026-06-22 + +### Added + +- Initial public alpha of OpenCherry: an Ubuntu/GNOME-first, multi-repo × + multi-agent control tower for AI-assisted coding, built on Tauri 2. +- Multi-repo sidebar with per-repo Git status, grouped diffs (conflicted, + staged, unstaged, untracked), stage/unstage, commit, publish, and sync. +- Running-agent detection (Claude Code, OpenCode, Codex, Gemini CLI, Aider, …). +- Linux packages: `.deb`, `.AppImage`, and `.rpm`. +- macOS disk images (`.dmg`) for Apple Silicon and Intel. +- Windows NSIS installer (`.exe`). +- One-command Linux install script (`scripts/install.sh`) and SHA-256 checksums + attached to every release artifact. + +[Unreleased]: https://github.com/4nrry/opencherry/compare/v0.0.1...HEAD +[0.0.1]: https://github.com/4nrry/opencherry/releases/tag/v0.0.1 diff --git a/README.md b/README.md index 457fb66..38dd23b 100644 --- a/README.md +++ b/README.md @@ -168,11 +168,26 @@ just check ## Packaging & local install -OpenCherry ships as a plain Debian package (`.deb`) — no sandbox, no store, no -auto-updater. The app spawns external AI CLI agents and manages Git repos in -arbitrary filesystem locations, so an unsandboxed package is the right fit; -Flatpak/Snap are deferred (Snap with `confinement: classic` is the likely future -route). +OpenCherry ships as unsandboxed packages — no store, no auto-updater. The app +spawns external AI CLI agents and manages Git repos in arbitrary filesystem +locations, so an unsandboxed package is the right fit; Flatpak/Snap are deferred +(Snap with `confinement: classic` is the likely future route). Linux releases +are published as `.AppImage`, `.deb`, and `.rpm`; macOS as `.dmg`; Windows as an +NSIS `.exe`. Every release asset ships with a matching `.sha256` checksum. + +### Quick install (one command) + +The fastest way to get OpenCherry on Linux (x86_64): + +```sh +curl -fsSL https://raw.githubusercontent.com/4nrry/opencherry/main/scripts/install.sh | sh +``` + +This resolves the latest `.AppImage` for your architecture, verifies its +SHA-256, installs it to `~/.local/bin/opencherry`, and adds an application-menu +entry. AppImages need FUSE at runtime — if launch fails, install it with +`sudo apt install libfuse2` (Debian/Ubuntu) or run +`APPIMAGE_EXTRACT_AND_RUN=1 opencherry`. ### Build the `.deb` @@ -184,15 +199,26 @@ above. Then: ``` This runs `pnpm -C apps/desktop tauri build --bundles deb` and prints the path -of the generated package — `target/release/bundle/deb/OpenCherry__amd64.deb` +of the generated package — `target/release/bundle/deb/opencherry__amd64.deb` (the Cargo workspace `target/` directory at the repo root). The first release build is slow (`lto = "thin"`, `codegen-units = 1`); later builds reuse the cache. +### Build the AppImage + +```bash +just build-appimage # or: ./scripts/build-appimage.sh +``` + +This produces `target/release/bundle/appimage/opencherry__amd64.AppImage` +(Tauri uses the Debian `amd64` arch token for the AppImage and `.deb`). +Run it directly (it needs FUSE — `sudo apt install libfuse2`), or use the +one-command installer above to fetch a published build instead. + ### Install and run ```bash -sudo apt install "$(pwd)/target/release/bundle/deb/OpenCherry_0.0.1_amd64.deb" +sudo apt install "$(pwd)/target/release/bundle/deb/opencherry_0.0.1_amd64.deb" ``` Use `apt install` with a path (not `dpkg -i`) so runtime dependencies resolve @@ -236,7 +262,7 @@ needed. Then, from the repo root in PowerShell: ``` This runs `pnpm -C apps/desktop tauri build --bundles nsis` and prints the path -of the generated installer — `target\release\bundle\nsis\OpenCherry__x64-setup.exe`. +of the generated installer — `target\release\bundle\nsis\opencherry__x64-setup.exe`. Double-click it to install. The installer is unsigned, so Windows SmartScreen may warn on first run; choose **More info → Run anyway**. @@ -258,7 +284,8 @@ Then, from the repo root: ``` This runs `pnpm -C apps/desktop tauri build --bundles dmg` and prints the path -of the generated image — `target/release/bundle/dmg/OpenCherry__.dmg`. +of the generated image — `target/release/bundle/dmg/opencherry__.dmg` +(`` is `aarch64` or `x64`). Open it and drag **OpenCherry** to Applications. The app is unsigned and not notarized, so on first launch macOS Gatekeeper may block it; right-click the app → **Open**, or allow it in **System Settings → Privacy & Security**. diff --git a/apps/website/src/components/InstallGuide.astro b/apps/website/src/components/InstallGuide.astro index e7aae8f..1adf63d 100644 --- a/apps/website/src/components/InstallGuide.astro +++ b/apps/website/src/components/InstallGuide.astro @@ -28,9 +28,9 @@
# Instale usando o apt informando o caminho do .deb
- sudo apt install "$(pwd)/OpenCherry_0.0.1_amd64.deb" + sudo apt install "$(pwd)/opencherry_0.0.1_amd64.deb"