From a1a38860758ca35a0332a15cef1680cd94260398 Mon Sep 17 00:00:00 2001 From: Timna Brown Date: Fri, 24 Apr 2026 18:18:02 -0400 Subject: [PATCH 1/6] fix(download): real OS icons and macOS notarization pipeline --- .github/workflows/desktop-app.yml | 132 +++++++++++++++++++++++++++++- docs/index.html | 27 ++---- 2 files changed, 137 insertions(+), 22 deletions(-) diff --git a/.github/workflows/desktop-app.yml b/.github/workflows/desktop-app.yml index 5cf167c..4c26d01 100644 --- a/.github/workflows/desktop-app.yml +++ b/.github/workflows/desktop-app.yml @@ -106,8 +106,74 @@ jobs: - name: Build Tauri bundle run: cargo tauri build --target ${{ matrix.target }} + - name: Import Apple signing certificate + if: runner.os == 'macOS' && secrets.APPLE_SIGNING_CERT_B64 != '' && secrets.APPLE_SIGNING_CERT_PASSWORD != '' && secrets.APPLE_SIGNING_IDENTITY != '' + shell: bash + env: + APPLE_SIGNING_CERT_B64: ${{ secrets.APPLE_SIGNING_CERT_B64 }} + APPLE_SIGNING_CERT_PASSWORD: ${{ secrets.APPLE_SIGNING_CERT_PASSWORD }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + run: | + set -euo pipefail + KEYCHAIN_PASSWORD="${KEYCHAIN_PASSWORD:-temp-keychain-pass}" + echo "$APPLE_SIGNING_CERT_B64" | base64 --decode > signing-cert.p12 + security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain + security set-keychain-settings -t 3600 -u build.keychain + security import signing-cert.p12 -k build.keychain -P "$APPLE_SIGNING_CERT_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security + security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" build.keychain + + - name: Sign macOS app and DMG (Developer ID) + if: runner.os == 'macOS' && secrets.APPLE_SIGNING_CERT_B64 != '' && secrets.APPLE_SIGNING_CERT_PASSWORD != '' && secrets.APPLE_SIGNING_IDENTITY != '' + shell: bash + env: + APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} + run: | + set -euo pipefail + BUNDLE_ROOT="src-tauri/target/${{ matrix.target }}/release/bundle" + if [ -d "$BUNDLE_ROOT/macos" ]; then + for app in "$BUNDLE_ROOT/macos"/*.app; do + [ -d "$app" ] || continue + echo "codesign app: $app" + codesign --force --deep --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" "$app" + codesign --verify --deep --strict --verbose=2 "$app" + done + fi + if [ -d "$BUNDLE_ROOT/dmg" ]; then + for dmg in "$BUNDLE_ROOT/dmg"/*.dmg; do + [ -f "$dmg" ] || continue + echo "codesign dmg: $dmg" + codesign --force --timestamp --sign "$APPLE_SIGNING_IDENTITY" "$dmg" + done + fi + + - name: Notarize and staple DMG + if: runner.os == 'macOS' && secrets.APPLE_SIGNING_CERT_B64 != '' && secrets.APPLE_SIGNING_CERT_PASSWORD != '' && secrets.APPLE_SIGNING_IDENTITY != '' && secrets.APPLE_ID != '' && secrets.APPLE_TEAM_ID != '' && secrets.APPLE_APP_SPECIFIC_PASSWORD != '' + shell: bash + env: + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} + run: | + set -euo pipefail + BUNDLE_ROOT="src-tauri/target/${{ matrix.target }}/release/bundle/dmg" + if [ -d "$BUNDLE_ROOT" ]; then + for dmg in "$BUNDLE_ROOT"/*.dmg; do + [ -f "$dmg" ] || continue + echo "notarizing: $dmg" + xcrun notarytool submit "$dmg" \ + --apple-id "$APPLE_ID" \ + --team-id "$APPLE_TEAM_ID" \ + --password "$APPLE_APP_SPECIFIC_PASSWORD" \ + --wait + xcrun stapler staple "$dmg" + xcrun stapler validate "$dmg" + done + fi + - name: Ad-hoc codesign macOS bundle - if: runner.os == 'macOS' + if: runner.os == 'macOS' && (secrets.APPLE_SIGNING_CERT_B64 == '' || secrets.APPLE_SIGNING_CERT_PASSWORD == '' || secrets.APPLE_SIGNING_IDENTITY == '') shell: bash run: | # Apple Silicon and modern Intel macOS refuse to launch unsigned @@ -197,7 +263,71 @@ jobs: - name: Build Tauri bundle run: cargo tauri build --target x86_64-apple-darwin + - name: Import Apple signing certificate + if: secrets.APPLE_SIGNING_CERT_B64 != '' && secrets.APPLE_SIGNING_CERT_PASSWORD != '' && secrets.APPLE_SIGNING_IDENTITY != '' + shell: bash + env: + APPLE_SIGNING_CERT_B64: ${{ secrets.APPLE_SIGNING_CERT_B64 }} + APPLE_SIGNING_CERT_PASSWORD: ${{ secrets.APPLE_SIGNING_CERT_PASSWORD }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + run: | + set -euo pipefail + KEYCHAIN_PASSWORD="${KEYCHAIN_PASSWORD:-temp-keychain-pass}" + echo "$APPLE_SIGNING_CERT_B64" | base64 --decode > signing-cert.p12 + security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain + security set-keychain-settings -t 3600 -u build.keychain + security import signing-cert.p12 -k build.keychain -P "$APPLE_SIGNING_CERT_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security + security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" build.keychain + + - name: Sign macOS app and DMG (Developer ID) + if: secrets.APPLE_SIGNING_CERT_B64 != '' && secrets.APPLE_SIGNING_CERT_PASSWORD != '' && secrets.APPLE_SIGNING_IDENTITY != '' + shell: bash + env: + APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} + run: | + set -euo pipefail + BUNDLE_ROOT="src-tauri/target/x86_64-apple-darwin/release/bundle" + if [ -d "$BUNDLE_ROOT/macos" ]; then + for app in "$BUNDLE_ROOT/macos"/*.app; do + [ -d "$app" ] || continue + codesign --force --deep --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" "$app" + codesign --verify --deep --strict --verbose=2 "$app" + done + fi + if [ -d "$BUNDLE_ROOT/dmg" ]; then + for dmg in "$BUNDLE_ROOT/dmg"/*.dmg; do + [ -f "$dmg" ] || continue + codesign --force --timestamp --sign "$APPLE_SIGNING_IDENTITY" "$dmg" + done + fi + + - name: Notarize and staple DMG + if: secrets.APPLE_SIGNING_CERT_B64 != '' && secrets.APPLE_SIGNING_CERT_PASSWORD != '' && secrets.APPLE_SIGNING_IDENTITY != '' && secrets.APPLE_ID != '' && secrets.APPLE_TEAM_ID != '' && secrets.APPLE_APP_SPECIFIC_PASSWORD != '' + shell: bash + env: + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} + run: | + set -euo pipefail + BUNDLE_ROOT="src-tauri/target/x86_64-apple-darwin/release/bundle/dmg" + if [ -d "$BUNDLE_ROOT" ]; then + for dmg in "$BUNDLE_ROOT"/*.dmg; do + [ -f "$dmg" ] || continue + xcrun notarytool submit "$dmg" \ + --apple-id "$APPLE_ID" \ + --team-id "$APPLE_TEAM_ID" \ + --password "$APPLE_APP_SPECIFIC_PASSWORD" \ + --wait + xcrun stapler staple "$dmg" + xcrun stapler validate "$dmg" + done + fi + - name: Ad-hoc codesign macOS bundle + if: secrets.APPLE_SIGNING_CERT_B64 == '' || secrets.APPLE_SIGNING_CERT_PASSWORD == '' || secrets.APPLE_SIGNING_IDENTITY == '' shell: bash run: | BUNDLE_ROOT="src-tauri/target/x86_64-apple-darwin/release/bundle" diff --git a/docs/index.html b/docs/index.html index b3b39fe..f9f7886 100644 --- a/docs/index.html +++ b/docs/index.html @@ -466,6 +466,7 @@ color: var(--fg); } .platform-card .platform-icon svg { width: 100%; height: 100%; display: block; } + .platform-card .platform-icon img { width: 100%; height: 100%; display: block; object-fit: contain; } .platform-icon--macos { color: #111111; } .platform-icon--windows { color: #0f6cbd; } .platform-icon--linux { color: #111111; } @@ -833,10 +834,7 @@

Download DevOpster

macOS

.dmg recommended  ·  .tar.gz advanced

@@ -844,20 +842,15 @@

macOS

  1. Step 1: Download the latest .dmg and drag DevOpster.app to /Applications.
  2. -
  3. Step 2: Try opening the app. If Gatekeeper blocks it, run this once: +
  4. Step 2: For stable releases, open normally. If Gatekeeper blocks a prerelease, run this once:
    xattr -cr "/Applications/DevOpster.app"
  5. -
  6. Step 3: Open DevOpster.app again and continue setup.
  7. +
  8. Step 3: Open DevOpster.app again. Signed and notarized builds should launch without warnings.

Windows

.zip + .ico  ·  DevOpster-GUI.cmd

@@ -873,15 +866,7 @@

Windows

Linux

.tar.gz  ·  .desktop  ·  AppStream

From 969d2ff0f1c29adcf5728143c814564ada0d9e3b Mon Sep 17 00:00:00 2001 From: Timna Brown Date: Fri, 24 Apr 2026 18:18:50 -0400 Subject: [PATCH 2/6] fix(ci): avoid secrets in if expressions for desktop workflow --- .github/workflows/desktop-app.yml | 52 ++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/.github/workflows/desktop-app.yml b/.github/workflows/desktop-app.yml index 4c26d01..f2da027 100644 --- a/.github/workflows/desktop-app.yml +++ b/.github/workflows/desktop-app.yml @@ -107,7 +107,7 @@ jobs: run: cargo tauri build --target ${{ matrix.target }} - name: Import Apple signing certificate - if: runner.os == 'macOS' && secrets.APPLE_SIGNING_CERT_B64 != '' && secrets.APPLE_SIGNING_CERT_PASSWORD != '' && secrets.APPLE_SIGNING_IDENTITY != '' + if: runner.os == 'macOS' shell: bash env: APPLE_SIGNING_CERT_B64: ${{ secrets.APPLE_SIGNING_CERT_B64 }} @@ -115,6 +115,10 @@ jobs: KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} run: | set -euo pipefail + if [ -z "${APPLE_SIGNING_CERT_B64:-}" ] || [ -z "${APPLE_SIGNING_CERT_PASSWORD:-}" ]; then + echo "Apple signing cert secrets are not set; skipping Developer ID import." + exit 0 + fi KEYCHAIN_PASSWORD="${KEYCHAIN_PASSWORD:-temp-keychain-pass}" echo "$APPLE_SIGNING_CERT_B64" | base64 --decode > signing-cert.p12 security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain @@ -125,12 +129,16 @@ jobs: security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" build.keychain - name: Sign macOS app and DMG (Developer ID) - if: runner.os == 'macOS' && secrets.APPLE_SIGNING_CERT_B64 != '' && secrets.APPLE_SIGNING_CERT_PASSWORD != '' && secrets.APPLE_SIGNING_IDENTITY != '' + if: runner.os == 'macOS' shell: bash env: APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} run: | set -euo pipefail + if [ -z "${APPLE_SIGNING_IDENTITY:-}" ]; then + echo "APPLE_SIGNING_IDENTITY is not set; skipping Developer ID signing." + exit 0 + fi BUNDLE_ROOT="src-tauri/target/${{ matrix.target }}/release/bundle" if [ -d "$BUNDLE_ROOT/macos" ]; then for app in "$BUNDLE_ROOT/macos"/*.app; do @@ -149,7 +157,7 @@ jobs: fi - name: Notarize and staple DMG - if: runner.os == 'macOS' && secrets.APPLE_SIGNING_CERT_B64 != '' && secrets.APPLE_SIGNING_CERT_PASSWORD != '' && secrets.APPLE_SIGNING_IDENTITY != '' && secrets.APPLE_ID != '' && secrets.APPLE_TEAM_ID != '' && secrets.APPLE_APP_SPECIFIC_PASSWORD != '' + if: runner.os == 'macOS' shell: bash env: APPLE_ID: ${{ secrets.APPLE_ID }} @@ -157,6 +165,10 @@ jobs: APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} run: | set -euo pipefail + if [ -z "${APPLE_ID:-}" ] || [ -z "${APPLE_TEAM_ID:-}" ] || [ -z "${APPLE_APP_SPECIFIC_PASSWORD:-}" ]; then + echo "Apple notarization secrets are not set; skipping notarization." + exit 0 + fi BUNDLE_ROOT="src-tauri/target/${{ matrix.target }}/release/bundle/dmg" if [ -d "$BUNDLE_ROOT" ]; then for dmg in "$BUNDLE_ROOT"/*.dmg; do @@ -173,9 +185,17 @@ jobs: fi - name: Ad-hoc codesign macOS bundle - if: runner.os == 'macOS' && (secrets.APPLE_SIGNING_CERT_B64 == '' || secrets.APPLE_SIGNING_CERT_PASSWORD == '' || secrets.APPLE_SIGNING_IDENTITY == '') + if: runner.os == 'macOS' shell: bash + env: + APPLE_SIGNING_CERT_B64: ${{ secrets.APPLE_SIGNING_CERT_B64 }} + APPLE_SIGNING_CERT_PASSWORD: ${{ secrets.APPLE_SIGNING_CERT_PASSWORD }} + APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} run: | + if [ -n "${APPLE_SIGNING_CERT_B64:-}" ] && [ -n "${APPLE_SIGNING_CERT_PASSWORD:-}" ] && [ -n "${APPLE_SIGNING_IDENTITY:-}" ]; then + echo "Developer ID signing is configured; skipping ad-hoc codesign fallback." + exit 0 + fi # Apple Silicon and modern Intel macOS refuse to launch unsigned # apps with the cryptic "DevOpster.app is damaged" alert. An ad-hoc # signature (sign identity "-") satisfies the loader so the app @@ -264,7 +284,6 @@ jobs: run: cargo tauri build --target x86_64-apple-darwin - name: Import Apple signing certificate - if: secrets.APPLE_SIGNING_CERT_B64 != '' && secrets.APPLE_SIGNING_CERT_PASSWORD != '' && secrets.APPLE_SIGNING_IDENTITY != '' shell: bash env: APPLE_SIGNING_CERT_B64: ${{ secrets.APPLE_SIGNING_CERT_B64 }} @@ -272,6 +291,10 @@ jobs: KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} run: | set -euo pipefail + if [ -z "${APPLE_SIGNING_CERT_B64:-}" ] || [ -z "${APPLE_SIGNING_CERT_PASSWORD:-}" ]; then + echo "Apple signing cert secrets are not set; skipping Developer ID import." + exit 0 + fi KEYCHAIN_PASSWORD="${KEYCHAIN_PASSWORD:-temp-keychain-pass}" echo "$APPLE_SIGNING_CERT_B64" | base64 --decode > signing-cert.p12 security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain @@ -282,12 +305,15 @@ jobs: security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" build.keychain - name: Sign macOS app and DMG (Developer ID) - if: secrets.APPLE_SIGNING_CERT_B64 != '' && secrets.APPLE_SIGNING_CERT_PASSWORD != '' && secrets.APPLE_SIGNING_IDENTITY != '' shell: bash env: APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} run: | set -euo pipefail + if [ -z "${APPLE_SIGNING_IDENTITY:-}" ]; then + echo "APPLE_SIGNING_IDENTITY is not set; skipping Developer ID signing." + exit 0 + fi BUNDLE_ROOT="src-tauri/target/x86_64-apple-darwin/release/bundle" if [ -d "$BUNDLE_ROOT/macos" ]; then for app in "$BUNDLE_ROOT/macos"/*.app; do @@ -304,7 +330,6 @@ jobs: fi - name: Notarize and staple DMG - if: secrets.APPLE_SIGNING_CERT_B64 != '' && secrets.APPLE_SIGNING_CERT_PASSWORD != '' && secrets.APPLE_SIGNING_IDENTITY != '' && secrets.APPLE_ID != '' && secrets.APPLE_TEAM_ID != '' && secrets.APPLE_APP_SPECIFIC_PASSWORD != '' shell: bash env: APPLE_ID: ${{ secrets.APPLE_ID }} @@ -312,6 +337,10 @@ jobs: APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} run: | set -euo pipefail + if [ -z "${APPLE_ID:-}" ] || [ -z "${APPLE_TEAM_ID:-}" ] || [ -z "${APPLE_APP_SPECIFIC_PASSWORD:-}" ]; then + echo "Apple notarization secrets are not set; skipping notarization." + exit 0 + fi BUNDLE_ROOT="src-tauri/target/x86_64-apple-darwin/release/bundle/dmg" if [ -d "$BUNDLE_ROOT" ]; then for dmg in "$BUNDLE_ROOT"/*.dmg; do @@ -327,9 +356,16 @@ jobs: fi - name: Ad-hoc codesign macOS bundle - if: secrets.APPLE_SIGNING_CERT_B64 == '' || secrets.APPLE_SIGNING_CERT_PASSWORD == '' || secrets.APPLE_SIGNING_IDENTITY == '' shell: bash + env: + APPLE_SIGNING_CERT_B64: ${{ secrets.APPLE_SIGNING_CERT_B64 }} + APPLE_SIGNING_CERT_PASSWORD: ${{ secrets.APPLE_SIGNING_CERT_PASSWORD }} + APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} run: | + if [ -n "${APPLE_SIGNING_CERT_B64:-}" ] && [ -n "${APPLE_SIGNING_CERT_PASSWORD:-}" ] && [ -n "${APPLE_SIGNING_IDENTITY:-}" ]; then + echo "Developer ID signing is configured; skipping ad-hoc codesign fallback." + exit 0 + fi BUNDLE_ROOT="src-tauri/target/x86_64-apple-darwin/release/bundle" if [ -d "$BUNDLE_ROOT/macos" ]; then for app in "$BUNDLE_ROOT/macos"/*.app; do From 4569f3b1a3ea78e9b03ccba883cca7d0dee7d105 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 26 Apr 2026 03:05:58 +0000 Subject: [PATCH 3/6] docs: update visitor count --- README.md | 4 ++-- docs/index.html | 2 +- metrics.json | 5 +++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9b57727..5637b46 100644 --- a/README.md +++ b/README.md @@ -319,7 +319,7 @@ blueprint:
- Total views -

Refresh Date: 2026-04-24

+ Total views +

Refresh Date: 2026-04-26

\ No newline at end of file diff --git a/docs/index.html b/docs/index.html index f9f7886..8e66770 100644 --- a/docs/index.html +++ b/docs/index.html @@ -719,7 +719,7 @@

DevOpster

CI status - Total views + Total views
diff --git a/metrics.json b/metrics.json index c3e91d4..5f6067c 100644 --- a/metrics.json +++ b/metrics.json @@ -73,5 +73,10 @@ "date": "2026-04-23", "count": 34, "uniques": 1 + }, + { + "date": "2026-04-24", + "count": 162, + "uniques": 1 } ] \ No newline at end of file From 061601e9d2e837ae76b3e591957443bac352d291 Mon Sep 17 00:00:00 2001 From: Timna Brown Date: Sat, 25 Apr 2026 23:07:59 -0400 Subject: [PATCH 4/6] chore(release): align desktop publishing with install-focused release flow --- .github/workflows/desktop-app.yml | 54 +++++++++++++++++++++++++++++-- README.md | 3 +- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/.github/workflows/desktop-app.yml b/.github/workflows/desktop-app.yml index f2da027..cb564ba 100644 --- a/.github/workflows/desktop-app.yml +++ b/.github/workflows/desktop-app.yml @@ -418,12 +418,60 @@ jobs: find artifacts -type f -exec cp {} flat/ \; ls -la flat + - name: Compute release metadata + id: release_meta + shell: bash + run: | + if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then + TAG_NAME="${{ inputs.release_tag }}" + else + TAG_NAME="${GITHUB_REF_NAME}" + fi + echo "tag_name=${TAG_NAME}" >> "$GITHUB_OUTPUT" + echo "release_name=DevOpster Desktop ${TAG_NAME}" >> "$GITHUB_OUTPUT" + + - name: Delete existing release if present + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG="${{ steps.release_meta.outputs.tag_name }}" + gh release delete "${TAG}" --repo "${{ github.repository }}" --yes 2>/dev/null \ + && echo "Deleted existing release for ${TAG}." \ + || echo "No existing release for ${TAG}, creating fresh." + - name: Create GitHub release uses: softprops/action-gh-release@v2 with: - tag_name: ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref_name }} + tag_name: ${{ steps.release_meta.outputs.tag_name }} files: flat/* - generate_release_notes: true - name: DevOpster Desktop ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref_name }} + fail_on_unmatched_files: true + name: ${{ steps.release_meta.outputs.release_name }} + body: | + ## Install DevOpster Desktop ${{ steps.release_meta.outputs.tag_name }} + + Download the installer that matches your OS. + + | Platform | File | Install notes | + |---|---|---| + | **macOS** | `*.dmg` | Recommended. Drag `DevOpster.app` to Applications | + | **macOS** | `*.app.tar.gz` | Advanced/manual extraction | + | **Windows** | `*-setup.exe` | Recommended NSIS installer | + | **Windows** | `*.msi` | MSI deployment/install option | + | **Linux** | `*.AppImage` | `chmod +x DevOpster*.AppImage && ./DevOpster*.AppImage` | + | **Linux** | `*.deb` | `sudo apt install ./DevOpster*.deb` | + | **Linux** | `*.rpm` | `sudo rpm -i DevOpster*.rpm` | + + ### macOS trust and verification + + - Stable releases are signed and notarized when Apple credentials are configured in CI. + - If you are testing an unsigned prerelease build and Gatekeeper blocks launch, run: + + ```bash + xattr -cr "/Applications/DevOpster.app" + ``` + + ### Windows SmartScreen + + If SmartScreen appears for an unsigned build, choose **More info** and then **Run anyway**. draft: false prerelease: ${{ github.event_name == 'workflow_dispatch' }} diff --git a/README.md b/README.md index 5637b46..38dfff4 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,8 @@ Current downloadable outputs: - `devopster-macos-x86_64.tar.gz` (CLI binary + `.icns` icon) - `devopster-macos.dmg` (includes CLI files plus native `DevOpster GUI.app` bundle) -Desktop GUI installer packaging is planned as a next phase. Today, GUI mode is the interactive terminal launcher (`devopster gui`). +Desktop installers are published by the desktop release workflow (`desktop-v*` tags or manual dispatch with publish enabled). +Download from GitHub Releases and install using the platform package (`.dmg`, `-setup.exe`/`.msi`, `.deb`/`.AppImage`/`.rpm`). End-user launch modes now included in artifacts: From cba6a8873b4629da4d9d527abab9a6ecaadbb107 Mon Sep 17 00:00:00 2001 From: Timna Brown Date: Sat, 25 Apr 2026 23:46:44 -0400 Subject: [PATCH 5/6] docs(macOS): support custom install path and path-aware xattr command --- .github/workflows/desktop-app.yml | 7 ++++--- docs/index.html | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/desktop-app.yml b/.github/workflows/desktop-app.yml index cb564ba..dfe3660 100644 --- a/.github/workflows/desktop-app.yml +++ b/.github/workflows/desktop-app.yml @@ -453,7 +453,7 @@ jobs: | Platform | File | Install notes | |---|---|---| - | **macOS** | `*.dmg` | Recommended. Drag `DevOpster.app` to Applications | + | **macOS** | `*.dmg` | Recommended. Drag `DevOpster.app` to `/Applications` or `~/Applications` | | **macOS** | `*.app.tar.gz` | Advanced/manual extraction | | **Windows** | `*-setup.exe` | Recommended NSIS installer | | **Windows** | `*.msi` | MSI deployment/install option | @@ -464,10 +464,11 @@ jobs: ### macOS trust and verification - Stable releases are signed and notarized when Apple credentials are configured in CI. - - If you are testing an unsigned prerelease build and Gatekeeper blocks launch, run: + - If you are testing an unsigned prerelease build and Gatekeeper blocks launch, run with your chosen install path: ```bash - xattr -cr "/Applications/DevOpster.app" + APP_PATH="$HOME/Applications/DevOpster.app" + xattr -cr "$APP_PATH" ``` ### Windows SmartScreen diff --git a/docs/index.html b/docs/index.html index 8e66770..20a2efe 100644 --- a/docs/index.html +++ b/docs/index.html @@ -841,9 +841,9 @@

macOS

↓ Download for macOS
    -
  1. Step 1: Download the latest .dmg and drag DevOpster.app to /Applications.
  2. -
  3. Step 2: For stable releases, open normally. If Gatekeeper blocks a prerelease, run this once: -
    xattr -cr "/Applications/DevOpster.app"
    +
  4. Step 1: Download the latest .dmg and drag DevOpster.app to /Applications (recommended) or any folder you prefer, for example ~/Applications.
  5. +
  6. Step 2: For stable releases, open normally. If Gatekeeper blocks a prerelease, run this once using your install path: +
    APP_PATH="$HOME/Applications/DevOpster.app"; xattr -cr "$APP_PATH"
  7. Step 3: Open DevOpster.app again. Signed and notarized builds should launch without warnings.
From 9bf96cfd558b96a80751ec8ce608095886afdbbf Mon Sep 17 00:00:00 2001 From: Timna Brown Date: Sat, 25 Apr 2026 23:48:36 -0400 Subject: [PATCH 6/6] feat(macos): add one-click app installer script and release integration --- .github/workflows/desktop-app.yml | 14 +++++ docs/index.html | 9 ++-- scripts/install-macos-app.sh | 85 +++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 3 deletions(-) create mode 100755 scripts/install-macos-app.sh diff --git a/.github/workflows/desktop-app.yml b/.github/workflows/desktop-app.yml index dfe3660..9efcb2e 100644 --- a/.github/workflows/desktop-app.yml +++ b/.github/workflows/desktop-app.yml @@ -230,6 +230,10 @@ jobs: -o -name "*.deb" -o -name "*.AppImage" -o -name "*.rpm" \) \ -exec cp {} dist/ \; || true fi + if [ "${{ runner.os }}" = "macOS" ]; then + cp scripts/install-macos-app.sh dist/DevOpster-macos-install.sh + chmod +x dist/DevOpster-macos-install.sh + fi ls -la dist || true - name: Upload artifacts @@ -392,6 +396,8 @@ jobs: \( -name "*.dmg" -o -name "*.app.tar.gz" \) \ -exec cp {} dist/ \; || true fi + cp scripts/install-macos-app.sh dist/DevOpster-macos-install.sh + chmod +x dist/DevOpster-macos-install.sh ls -la dist || true - name: Upload artifacts @@ -455,6 +461,7 @@ jobs: |---|---|---| | **macOS** | `*.dmg` | Recommended. Drag `DevOpster.app` to `/Applications` or `~/Applications` | | **macOS** | `*.app.tar.gz` | Advanced/manual extraction | + | **macOS** | `DevOpster-macos-install.sh` | One-click installer script (path-aware + quarantine clear) | | **Windows** | `*-setup.exe` | Recommended NSIS installer | | **Windows** | `*.msi` | MSI deployment/install option | | **Linux** | `*.AppImage` | `chmod +x DevOpster*.AppImage && ./DevOpster*.AppImage` | @@ -464,6 +471,13 @@ jobs: ### macOS trust and verification - Stable releases are signed and notarized when Apple credentials are configured in CI. + - You can run the one-click installer from the mounted DMG or extracted assets: + + ```bash + chmod +x ./DevOpster-macos-install.sh + ./DevOpster-macos-install.sh + ``` + - If you are testing an unsigned prerelease build and Gatekeeper blocks launch, run with your chosen install path: ```bash diff --git a/docs/index.html b/docs/index.html index 20a2efe..b961913 100644 --- a/docs/index.html +++ b/docs/index.html @@ -842,10 +842,13 @@

macOS

  1. Step 1: Download the latest .dmg and drag DevOpster.app to /Applications (recommended) or any folder you prefer, for example ~/Applications.
  2. -
  3. Step 2: For stable releases, open normally. If Gatekeeper blocks a prerelease, run this once using your install path: +
  4. Step 2: Optional one-click installer from release assets: +
    chmod +x ./DevOpster-macos-install.sh && ./DevOpster-macos-install.sh
    +
  5. +
  6. Step 3: For stable releases, open normally. If Gatekeeper blocks a prerelease, run this once using your install path:
    APP_PATH="$HOME/Applications/DevOpster.app"; xattr -cr "$APP_PATH"
  7. -
  8. Step 3: Open DevOpster.app again. Signed and notarized builds should launch without warnings.
  9. +
  10. Step 4: Open DevOpster.app again. Signed and notarized builds should launch without warnings.
@@ -1041,7 +1044,7 @@

Owner / Organization

function pickAssets(assets, platform) { var rules = { - macos: [/.dmg$/i, /\.app\.tar\.gz$/i, /macos.*\.zip$/i, /darwin.*\.zip$/i], + macos: [/.dmg$/i, /\.app\.tar\.gz$/i, /macos-install\.sh$/i, /macos.*\.zip$/i, /darwin.*\.zip$/i], windows: [/-setup\.exe$/i, /\.msi$/i, /\.exe$/i, /windows.*\.zip$/i, /win.*\.zip$/i, /\.zip$/i], linux: [/\.AppImage$/i, /\.deb$/i, /\.rpm$/i, /linux.*\.tar\.gz$/i, /\.tar\.gz$/i] }; diff --git a/scripts/install-macos-app.sh b/scripts/install-macos-app.sh new file mode 100755 index 0000000..27cabab --- /dev/null +++ b/scripts/install-macos-app.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +set -euo pipefail + +APP_NAME="DevOpster.app" + +print_usage() { + cat <<'EOF' +Usage: + ./install-macos-app.sh [SOURCE_APP_PATH] [INSTALL_DIR] + +Examples: + ./install-macos-app.sh + ./install-macos-app.sh "/Volumes/devopster-cli/DevOpster.app" + ./install-macos-app.sh "/Volumes/devopster-cli/DevOpster.app" "$HOME/Applications" + +Behavior: + - If SOURCE_APP_PATH is omitted, the script tries to auto-detect DevOpster.app + from the current directory and mounted volumes. + - If INSTALL_DIR is omitted, it uses /Applications when writable, otherwise + $HOME/Applications. +EOF +} + +detect_source_app() { + local candidate + + if [[ -d "./${APP_NAME}" ]]; then + printf '%s\n' "./${APP_NAME}" + return 0 + fi + + for candidate in /Volumes/*/"${APP_NAME}"; do + if [[ -d "${candidate}" ]]; then + printf '%s\n' "${candidate}" + return 0 + fi + done + + return 1 +} + +SOURCE_APP_PATH="${1:-}" +INSTALL_DIR="${2:-}" + +if [[ -z "${SOURCE_APP_PATH}" ]]; then + if ! SOURCE_APP_PATH="$(detect_source_app)"; then + echo "Could not find ${APP_NAME} automatically." + print_usage + exit 1 + fi +fi + +if [[ ! -d "${SOURCE_APP_PATH}" ]]; then + echo "Source app not found: ${SOURCE_APP_PATH}" + print_usage + exit 1 +fi + +if [[ -z "${INSTALL_DIR}" ]]; then + if [[ -w "/Applications" ]]; then + INSTALL_DIR="/Applications" + else + INSTALL_DIR="$HOME/Applications" + fi +fi + +mkdir -p "${INSTALL_DIR}" +TARGET_APP_PATH="${INSTALL_DIR}/${APP_NAME}" + +echo "Installing ${APP_NAME} from: ${SOURCE_APP_PATH}" +echo "Target location: ${TARGET_APP_PATH}" + +if [[ -d "${TARGET_APP_PATH}" ]]; then + echo "Replacing existing app at ${TARGET_APP_PATH}" + rm -rf "${TARGET_APP_PATH}" +fi + +# ditto preserves app bundle metadata and permissions better than cp -R. +ditto "${SOURCE_APP_PATH}" "${TARGET_APP_PATH}" + +# Clear quarantine so unsigned prerelease builds can launch. +xattr -cr "${TARGET_APP_PATH}" || true + +echo "Install complete." +echo "You can launch it with: open \"${TARGET_APP_PATH}\""