From 8fefe6e16746c096c9aa95bce5bc9c9bb488626b Mon Sep 17 00:00:00 2001 From: vansangpfiev Date: Wed, 14 May 2025 20:46:01 +0700 Subject: [PATCH] chore: tauri codesign and CI/CD (#4961) * chore: build tauri * chore: codesign tauri app * chore: test update from electron to tauri * chore: test update from electron to tauri * chore: update csp config and cors * chore: nightly to 1317 * fix: correct pre_install_path * chore: jan-nightly to 1320 * chore: self sign tauri * chore: CI/CD for Windows, Linux commit 4897b2bcf7f044080fce81bd725515e62fc4eb29 Author: vansangpfiev Date: Fri Apr 25 15:32:37 2025 +0700 chore: cleanup tauri config commit 66c5676ec146b25c89cccb570ede7c070dbc5853 Author: vansangpfiev Date: Fri Apr 25 14:37:04 2025 +0700 fix: store path commit bc6560c576873e55f84c4b21764bedbdd9dbd5a8 Author: vansangpfiev Date: Thu Apr 24 09:39:50 2025 +0700 chore: Linux CI commit b036275dc9f1df7614aaca3b358b9c6493082512 Author: vansangpfiev Date: Wed Apr 23 16:41:22 2025 +0700 chore: updater windows commit e91b543dbdd82bd4a44db7550ffb993897b56081 Merge: dea80a83 4a54a378 Author: vansangpfiev Date: Wed Apr 23 16:39:24 2025 +0700 Merge branch 'chore/tauri-cicd' of https://github.com/menloresearch/jan into chore/tauri-cicd-windows commit dea80a83966113b108137c385a3c28920d2adda4 Author: Minh141120 Date: Wed Apr 23 11:47:04 2025 +0700 chore: update azuresigntool install method commit 2ec2234082be57e53887192153fa982a134ea535 Author: Minh141120 Date: Wed Apr 23 11:01:31 2025 +0700 chore: add verbose option build tauri and targets app and dmg for macos build commit 42c7592cc89641130545551d4d864268cde3d5b0 Author: Minh141120 Date: Wed Apr 23 10:35:27 2025 +0700 chore: update targets build commit 4c8ba44ff60cdef8b639fa189f5729dc69c5aff6 Author: Minh141120 Date: Wed Apr 23 09:53:21 2025 +0700 refactor: remove debug step and upload electron build artifact commit 158c08b465e18823e0f2b9a30fd5ecd589d08934 Author: Minh141120 Date: Wed Apr 23 09:21:08 2025 +0700 chore: add script codesign on windows commit 4545b2bcd852029472298e530176494992dd0950 Author: vansangpfiev Date: Tue Apr 22 13:39:49 2025 +0700 chore: update csp setting commit f64a1e1ca958e3c1c685485a06d45956ddcf14a0 Author: Minh141120 Date: Tue Apr 22 10:15:14 2025 +0700 chore: update azuresigntool installation commit 1f4b9d18b332d5205685a6fe68f5dfaf973d273c Author: Minh141120 Date: Tue Apr 22 09:49:42 2025 +0700 chore: update signcommand commit 911a3ab3540f872f6fe906c8e2135440d39f108c Author: Minh141120 Date: Mon Apr 21 19:19:23 2025 +0700 chore: update codesign tauri windows commit fba15c4c2de43b4cb87308ef998cdd8dc88b1ce6 Author: Minh141120 Date: Mon Apr 21 19:04:29 2025 +0700 chore: update path azuresigntool commit 8b8c950b56f5aa42baf76aba064fc99b50758150 Author: Minh141120 Date: Mon Apr 21 18:38:56 2025 +0700 chore: update azuresigntool path commit bd67a2b7908b5f3a126c634a840e0b941373a3c6 Author: Minh141120 Date: Mon Apr 21 17:47:33 2025 +0700 chore: update azuresigntool url commit f70effca7c09cd2fe9b5866b4f194b64a13294b9 Author: Minh141120 Date: Mon Apr 21 17:33:32 2025 +0700 chore: update azuretoolsign download commit 667910772f30369b9afa554ad06e4378f93d0b1a Author: Minh141120 Date: Mon Apr 21 16:56:25 2025 +0700 chore: update path azuresigntool commit f1610bfd80dfa996db4a777bb58475f2e6d02cc6 Author: Minh141120 Date: Mon Apr 21 16:52:36 2025 +0700 chore: update azuresigntool path commit 0873d56fb88fb66c884eff31d3f63aa99858f038 Author: Minh141120 Date: Mon Apr 21 16:19:46 2025 +0700 chore: add debug step commit 88e0b1a697ed478375429686eb1c03ae71a3b447 Author: Minh141120 Date: Mon Apr 21 15:58:39 2025 +0700 ci(windows): download AzureSignTool to src-tauri for Tauri code signing commit 47f94e86589826c3941a3d602298f188d6480980 Author: Minh141120 Date: Mon Apr 21 15:21:20 2025 +0700 fix: AzureSignTool signcommand Path commit dc014a7905fd0b49b5972e24b4d5773c5dc29ea5 Author: Minh141120 Date: Mon Apr 21 15:00:02 2025 +0700 chore: add debug step azuresigntool tauri windows commit ee7b6163a8419604dfba7dc2f967026be4884da4 Author: Minh141120 Date: Mon Apr 21 14:33:33 2025 +0700 chore: tauri windows codesign commit 6607090857120531d8a096f45ff556c3f2553e53 Author: vansangpfiev Date: Thu Apr 17 10:29:50 2025 +0700 chore: add windows download script commit 4b1a5cc29c77eecca75978a1ab3126d2c710e738 Author: Nguyen Ngoc Minh Date: Mon Apr 21 13:44:34 2025 +0700 chore: tauri codesign windows * chore: workflows for tauri * chore: test tauri manual build * chore: hide windows install detail * chore: upload artifacts * feat: run mcp with bundled bun and uv * chore: clean up * chore: update cicd * chore: remove deprecated workflows * chore: update allowed origins * chore: pull binaries windows, linux (#4963) * fix: get bun and uv from execution path * fix: macos * fix: typo * fix: remove old Jan binaries on windows * chore: build bun and uv universal * fix: appimage bundle bun issue * chore: libfuse2 for linux CI * feat: tauri cicd preview (#4975) * feat: tauri cicd preview * chore: add suffix preview for tauri build * chore: update condition for s3 upload nightly channel * chore: add debug step for tauri macos preview * chore: update aws s3 macos tauri build * refactor: remove debug code * chore: update artifact name tauri macos build * chore: add tauri build step for electron beta and stable * chore: update preview * chore: bump llama.cpp engine to b5351 * chore: bump engine version * fix: cors windows --------- Co-authored-by: vansangpfiev Co-authored-by: Service Account Co-authored-by: Louis Co-authored-by: hiento09 Co-authored-by: Nguyen Ngoc Minh <91668012+Minh141120@users.noreply.github.com> --- .github/scripts/electron-checksum.py | 28 + .github/scripts/icon-beta.png | Bin 0 -> 49895 bytes .github/scripts/icon-nightly.png | Bin 0 -> 50474 bytes .github/scripts/rename-tauri-app.sh | 48 + .github/workflows/jan-electron-build-beta.yml | 105 +- .../workflows/jan-electron-build-nightly.yml | 108 +- .github/workflows/jan-electron-build.yml | 53 +- .github/workflows/jan-tauri-build-beta.yml | 156 +++ .github/workflows/jan-tauri-build-nightly.yml | 186 +++ .github/workflows/jan-tauri-build.yml | 145 +++ .../nightly-integrate-cortex-cpp.yml | 127 -- ... => template-electron-build-linux-x64.yml} | 4 +- ....yml => template-electron-build-macos.yml} | 2 +- ...> template-electron-build-windows-x64.yml} | 2 +- .../workflows/template-get-update-version.yml | 7 +- ...ate-noti-discord-and-update-url-readme.yml | 8 +- ...template-tauri-build-linux-x64-preview.yml | 262 +++++ .../template-tauri-build-linux-x64.yml | 296 +++++ .../template-tauri-build-macos-preview.yml | 282 +++++ .../workflows/template-tauri-build-macos.yml | 310 +++++ ...mplate-tauri-build-windows-x64-preview.yml | 259 +++++ .../template-tauri-build-windows-x64.yml | 286 +++++ Makefile | 3 + .../rolldown.config.mjs | 4 +- .../inference-cortex-extension/download.bat | 12 +- .../inference-cortex-extension/download.sh | 2 +- .../rolldown.config.mjs | 2 +- package.json | 4 + src-tauri/Cargo.toml | 14 +- src-tauri/binaries/download.bat | 19 +- src-tauri/binaries/download.sh | 2 +- src-tauri/latest.json.template | 23 + src-tauri/sign.ps1 | 12 + src-tauri/src/core/cmd.rs | 14 +- src-tauri/src/core/setup.rs | 31 +- src-tauri/tauri.bundle.windows.nsis.template | 1031 +++++++++++++++++ src-tauri/tauri.conf.json | 63 +- web/screens/Hub/ModelPage/index.tsx | 4 +- .../Settings/Engines/RemoteEngineSettings.tsx | 3 +- 39 files changed, 3685 insertions(+), 232 deletions(-) create mode 100644 .github/scripts/electron-checksum.py create mode 100644 .github/scripts/icon-beta.png create mode 100644 .github/scripts/icon-nightly.png create mode 100644 .github/scripts/rename-tauri-app.sh create mode 100644 .github/workflows/jan-tauri-build-beta.yml create mode 100644 .github/workflows/jan-tauri-build-nightly.yml create mode 100644 .github/workflows/jan-tauri-build.yml delete mode 100644 .github/workflows/nightly-integrate-cortex-cpp.yml rename .github/workflows/{template-build-linux-x64.yml => template-electron-build-linux-x64.yml} (98%) rename .github/workflows/{template-build-macos.yml => template-electron-build-macos.yml} (99%) rename .github/workflows/{template-build-windows-x64.yml => template-electron-build-windows-x64.yml} (99%) create mode 100644 .github/workflows/template-tauri-build-linux-x64-preview.yml create mode 100644 .github/workflows/template-tauri-build-linux-x64.yml create mode 100644 .github/workflows/template-tauri-build-macos-preview.yml create mode 100644 .github/workflows/template-tauri-build-macos.yml create mode 100644 .github/workflows/template-tauri-build-windows-x64-preview.yml create mode 100644 .github/workflows/template-tauri-build-windows-x64.yml create mode 100644 src-tauri/latest.json.template create mode 100644 src-tauri/sign.ps1 create mode 100644 src-tauri/tauri.bundle.windows.nsis.template diff --git a/.github/scripts/electron-checksum.py b/.github/scripts/electron-checksum.py new file mode 100644 index 000000000..fba4ff609 --- /dev/null +++ b/.github/scripts/electron-checksum.py @@ -0,0 +1,28 @@ +import hashlib +import base64 +import sys + +def hash_file(file_path): + # Create a SHA-512 hash object + sha512 = hashlib.sha512() + + # Read and update the hash object with the content of the file + with open(file_path, 'rb') as f: + while True: + data = f.read(1024 * 1024) # Read in 1 MB chunks + if not data: + break + sha512.update(data) + + # Obtain the hash result and encode it in base64 + hash_base64 = base64.b64encode(sha512.digest()).decode('utf-8') + return hash_base64 + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: python3 script.py ") + sys.exit(1) + + file_path = sys.argv[1] + hash_base64_output = hash_file(file_path) + print(hash_base64_output) diff --git a/.github/scripts/icon-beta.png b/.github/scripts/icon-beta.png new file mode 100644 index 0000000000000000000000000000000000000000..4b715494d78b5c0720ca44daebb7150e1a2979c6 GIT binary patch literal 49895 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 literal 0 HcmV?d00001 diff --git a/.github/scripts/icon-nightly.png b/.github/scripts/icon-nightly.png new file mode 100644 index 0000000000000000000000000000000000000000..23f53294714ec9ff900251ea4d4da4aaac9a9ef2 GIT binary patch literal 50474 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 PQ2@P1hFaAR?IQjM0@H$q literal 0 HcmV?d00001 diff --git a/.github/scripts/rename-tauri-app.sh b/.github/scripts/rename-tauri-app.sh new file mode 100644 index 000000000..12a1fadb7 --- /dev/null +++ b/.github/scripts/rename-tauri-app.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# Check if the correct number of arguments is provided +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +INPUT_JSON_FILE="$1" + +CHANNEL="$2" + +if [ "$CHANNEL" == "nightly" ]; then + UPDATER="latest" +else + UPDATER="beta" +fi + +# Check if the input file exists +if [ ! -f "$INPUT_JSON_FILE" ]; then + echo "Input file not found: $INPUT_JSON_FILE" + exit 1 +fi + +# Use jq to transform the content +jq --arg channel "$CHANNEL" --arg updater "$UPDATER" ' + .productName = "Jan-\($channel)" | + .identifier = "jan-\($channel).ai.app" +' "$INPUT_JSON_FILE" > ./tauri.conf.json.tmp + +cat ./tauri.conf.json.tmp + +rm $INPUT_JSON_FILE +mv ./tauri.conf.json.tmp $INPUT_JSON_FILE + +# Update the layout file +# LAYOUT_FILE_PATH="web/app/layout.tsx" + +# if [ ! -f "$LAYOUT_FILE_PATH" ]; then +# echo "File does not exist: $LAYOUT_FILE_PATH" +# exit 1 +# fi + +# Perform the replacements +# sed -i -e "s#Jan#Jan-$CHANNEL#g" "$LAYOUT_FILE_PATH" + +# Notify completion +# echo "File has been updated: $LAYOUT_FILE_PATH" diff --git a/.github/workflows/jan-electron-build-beta.yml b/.github/workflows/jan-electron-build-beta.yml index 61ff717ac..c16b08244 100644 --- a/.github/workflows/jan-electron-build-beta.yml +++ b/.github/workflows/jan-electron-build-beta.yml @@ -8,9 +8,33 @@ jobs: # Job create Update app version based on latest release tag with build number and save to output get-update-version: uses: ./.github/workflows/template-get-update-version.yml + create-draft-release: + runs-on: ubuntu-latest + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') + outputs: + upload_url: ${{ steps.create_release.outputs.upload_url }} + version: ${{ steps.get_version.outputs.version }} + permissions: + contents: write + steps: + - name: Extract tag name without v prefix + id: get_version + run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV && echo "::set-output name=version::${GITHUB_REF#refs/tags/v}" + env: + GITHUB_REF: ${{ github.ref }} + - name: Create Draft Release + id: create_release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.ref_name }} + token: ${{ secrets.GITHUB_TOKEN }} + name: "${{ env.VERSION }}" + draft: true + prerelease: false + generate_release_notes: true - build-macos: - uses: ./.github/workflows/template-build-macos.yml + build-electron-macos: + uses: ./.github/workflows/template-electron-build-macos.yml secrets: inherit needs: [get-update-version] with: @@ -21,8 +45,8 @@ jobs: nightly: false cortex_api_port: "39271" - build-windows-x64: - uses: ./.github/workflows/template-build-windows-x64.yml + build-electron-windows-x64: + uses: ./.github/workflows/template-electron-build-windows-x64.yml secrets: inherit needs: [get-update-version] with: @@ -33,8 +57,8 @@ jobs: nightly: false cortex_api_port: "39271" - build-linux-x64: - uses: ./.github/workflows/template-build-linux-x64.yml + build-electron-linux-x64: + uses: ./.github/workflows/template-electron-build-linux-x64.yml secrets: inherit needs: [get-update-version] with: @@ -45,8 +69,51 @@ jobs: nightly: false cortex_api_port: "39271" + build-tauri-macos: + uses: ./.github/workflows/template-tauri-build-macos-preview.yml + secrets: inherit + needs: [get-update-version, create-draft-release] + with: + ref: ${{ github.ref }} + public_provider: github + new_version: ${{ needs.get-update-version.outputs.new_version }} + channel: beta + cortex_api_port: "39271" + upload_url: ${{ needs.create-draft-release.outputs.upload_url }} + + build-tauri-windows-x64: + uses: ./.github/workflows/template-tauri-build-windows-x64-preview.yml + secrets: inherit + needs: [get-update-version, create-draft-release] + with: + ref: ${{ github.ref }} + public_provider: github + new_version: ${{ needs.get-update-version.outputs.new_version }} + channel: beta + cortex_api_port: "39271" + upload_url: ${{ needs.create-draft-release.outputs.upload_url }} + + build-tauri-linux-x64: + uses: ./.github/workflows/template-tauri-build-linux-x64-preview.yml + secrets: inherit + needs: [get-update-version, create-draft-release] + with: + ref: ${{ github.ref }} + public_provider: github + new_version: ${{ needs.get-update-version.outputs.new_version }} + channel: beta + cortex_api_port: "39271" + upload_url: ${{ needs.create-draft-release.outputs.upload_url }} + sync-temp-to-latest: - needs: [build-macos, build-windows-x64, build-linux-x64] + needs: [ + build-electron-windows-x64, + build-electron-linux-x64, + build-electron-macos, + build-tauri-windows-x64, + build-tauri-linux-x64, + build-tauri-macos + ] runs-on: ubuntu-latest permissions: contents: write @@ -64,7 +131,16 @@ jobs: AWS_EC2_METADATA_DISABLED: "true" noti-discord-and-update-url-readme: - needs: [build-macos, get-update-version, build-windows-x64, build-linux-x64, sync-temp-to-latest] + needs: [ + get-update-version, + sync-temp-to-latest, + build-electron-windows-x64, + build-electron-linux-x64, + build-electron-macos, + build-tauri-windows-x64, + build-tauri-linux-x64, + build-tauri-macos + ] runs-on: ubuntu-latest steps: - name: Set version to environment variable @@ -78,9 +154,14 @@ jobs: with: args: | Jan-beta App version {{ VERSION }}, has been released, use the following links to download the app with faster speed or visit the Github release page for more information: - - Windows: https://delta.jan.ai/beta/jan-beta-win-x64-{{ VERSION }}.exe - - macOS Universal: https://delta.jan.ai/beta/jan-beta-mac-universal-{{ VERSION }}.dmg - - Linux Deb: https://delta.jan.ai/beta/jan-beta-linux-amd64-{{ VERSION }}.deb - - Linux AppImage: https://delta.jan.ai/beta/jan-beta-linux-x86_64-{{ VERSION }}.AppImage + - Windows Electron: https://delta.jan.ai/beta/jan-beta-win-x64-{{ VERSION }}.exe + - macOS Electron Universal: https://delta.jan.ai/beta/jan-beta-mac-universal-{{ VERSION }}.dmg + - Linux Electron Deb: https://delta.jan.ai/beta/jan-beta-linux-amd64-{{ VERSION }}.deb + - Linux Electron AppImage: https://delta.jan.ai/beta/jan-beta-linux-x86_64-{{ VERSION }}.AppImage + - Windows Tauri Preview: https://delta.jan.ai/beta/Jan-beta_{{ VERSION }}_x64-setup-preview.exe + - macOS Taupri Preview Universal: https://delta.jan.ai/beta/Jan-beta_{{ VERSION }}_universal-preview.dmg + - Linux Tauri Preview Deb: https://delta.jan.ai/beta/Jan-beta_{{ VERSION }}_amd64-preview.deb + - Linux Tauri Preview AppImage: https://delta.jan.ai/beta/Jan-beta_{{ VERSION }}_amd64-preview.AppImage + env: DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_JAN_BETA }} \ No newline at end of file diff --git a/.github/workflows/jan-electron-build-nightly.yml b/.github/workflows/jan-electron-build-nightly.yml index af5bab195..00da06d37 100644 --- a/.github/workflows/jan-electron-build-nightly.yml +++ b/.github/workflows/jan-electron-build-nightly.yml @@ -14,6 +14,9 @@ on: default: none pull_request_review: types: [submitted] + pull_request: + branches: + - chore/tauri-cicd jobs: set-public-provider: @@ -47,8 +50,41 @@ jobs: get-update-version: uses: ./.github/workflows/template-get-update-version.yml - build-macos: - uses: ./.github/workflows/template-build-macos.yml + build-tauri-macos: + uses: ./.github/workflows/template-tauri-build-macos-preview.yml + secrets: inherit + needs: [get-update-version, set-public-provider] + with: + ref: ${{ needs.set-public-provider.outputs.ref }} + public_provider: ${{ needs.set-public-provider.outputs.public_provider }} + new_version: ${{ needs.get-update-version.outputs.new_version }} + channel: nightly + cortex_api_port: "39261" + + build-tauri-windows-x64: + uses: ./.github/workflows/template-tauri-build-windows-x64-preview.yml + secrets: inherit + needs: [get-update-version, set-public-provider] + with: + ref: ${{ needs.set-public-provider.outputs.ref }} + public_provider: ${{ needs.set-public-provider.outputs.public_provider }} + new_version: ${{ needs.get-update-version.outputs.new_version }} + channel: nightly + cortex_api_port: "39261" + + build-tauri-linux-x64: + uses: ./.github/workflows/template-tauri-build-linux-x64-preview.yml + secrets: inherit + needs: [get-update-version, set-public-provider] + with: + ref: ${{ needs.set-public-provider.outputs.ref }} + public_provider: ${{ needs.set-public-provider.outputs.public_provider }} + new_version: ${{ needs.get-update-version.outputs.new_version }} + channel: nightly + cortex_api_port: "39261" + + build-electron-macos: + uses: ./.github/workflows/template-electron-build-macos.yml needs: [get-update-version, set-public-provider] secrets: inherit with: @@ -59,8 +95,8 @@ jobs: beta: false cortex_api_port: "39261" - build-windows-x64: - uses: ./.github/workflows/template-build-windows-x64.yml + build-electron-windows-x64: + uses: ./.github/workflows/template-electron-build-windows-x64.yml secrets: inherit needs: [get-update-version, set-public-provider] with: @@ -70,8 +106,8 @@ jobs: nightly: true beta: false cortex_api_port: "39261" - build-linux-x64: - uses: ./.github/workflows/template-build-linux-x64.yml + build-electron-linux-x64: + uses: ./.github/workflows/template-electron-build-linux-x64.yml secrets: inherit needs: [get-update-version, set-public-provider] with: @@ -83,8 +119,16 @@ jobs: cortex_api_port: "39261" sync-temp-to-latest: - needs: [set-public-provider, build-windows-x64, build-linux-x64, build-macos] runs-on: ubuntu-latest + needs: [ + set-public-provider, + build-electron-windows-x64, + build-electron-linux-x64, + build-electron-macos, + build-tauri-windows-x64, + build-tauri-linux-x64, + build-tauri-macos + ] steps: - name: Sync temp to latest if: ${{ needs.set-public-provider.outputs.public_provider == 'aws-s3' }} @@ -97,7 +141,17 @@ jobs: AWS_EC2_METADATA_DISABLED: "true" noti-discord-nightly-and-update-url-readme: - needs: [build-macos, build-windows-x64, build-linux-x64, get-update-version, set-public-provider, sync-temp-to-latest] + needs: [ + build-electron-macos, + build-electron-windows-x64, + build-electron-linux-x64, + build-tauri-macos, + build-tauri-windows-x64, + build-tauri-linux-x64, + get-update-version, + set-public-provider, + sync-temp-to-latest + ] secrets: inherit if: github.event_name == 'schedule' uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml @@ -108,7 +162,17 @@ jobs: new_version: ${{ needs.get-update-version.outputs.new_version }} noti-discord-pre-release-and-update-url-readme: - needs: [build-macos, build-windows-x64, build-linux-x64, get-update-version, set-public-provider, sync-temp-to-latest] + needs: [ + build-electron-macos, + build-electron-windows-x64, + build-electron-linux-x64, + build-tauri-macos, + build-tauri-windows-x64, + build-tauri-linux-x64, + get-update-version, + set-public-provider, + sync-temp-to-latest + ] secrets: inherit if: github.event_name == 'push' uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml @@ -119,7 +183,17 @@ jobs: new_version: ${{ needs.get-update-version.outputs.new_version }} noti-discord-manual-and-update-url-readme: - needs: [build-macos, build-windows-x64, build-linux-x64, get-update-version, set-public-provider, sync-temp-to-latest] + needs: [ + build-electron-macos, + build-electron-windows-x64, + build-electron-linux-x64, + build-tauri-macos, + build-tauri-windows-x64, + build-tauri-linux-x64, + get-update-version, + set-public-provider, + sync-temp-to-latest + ] secrets: inherit if: github.event_name == 'workflow_dispatch' && github.event.inputs.public_provider == 'aws-s3' uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml @@ -131,7 +205,17 @@ jobs: comment-pr-build-url: - needs: [build-macos, build-windows-x64, build-linux-x64, get-update-version, set-public-provider, sync-temp-to-latest] + needs: [ + build-electron-macos, + build-electron-windows-x64, + build-electron-linux-x64, + build-tauri-macos, + build-tauri-windows-x64, + build-tauri-linux-x64, + get-update-version, + set-public-provider, + sync-temp-to-latest + ] runs-on: ubuntu-latest if: github.event_name == 'pull_request_review' steps: @@ -147,4 +231,4 @@ jobs: PR_URL=${{ github.event.pull_request.html_url }} RUN_ID=${{ github.run_id }} COMMENT="This is the build for this pull request. You can download it from the Artifacts section here: [Build URL](https://github.com/${{ github.repository }}/actions/runs/${RUN_ID})." - gh pr comment $PR_URL --body "$COMMENT" + gh pr comment $PR_URL --body "$COMMENT" \ No newline at end of file diff --git a/.github/workflows/jan-electron-build.yml b/.github/workflows/jan-electron-build.yml index 7d69a5c12..c41376f49 100644 --- a/.github/workflows/jan-electron-build.yml +++ b/.github/workflows/jan-electron-build.yml @@ -33,8 +33,8 @@ jobs: draft: true prerelease: false - build-macos: - uses: ./.github/workflows/template-build-macos.yml + build-electron-macos: + uses: ./.github/workflows/template-electron-build-macos.yml secrets: inherit needs: [get-update-version] with: @@ -44,8 +44,8 @@ jobs: nightly: false new_version: ${{ needs.get-update-version.outputs.new_version }} - build-windows-x64: - uses: ./.github/workflows/template-build-windows-x64.yml + build-electron-windows-x64: + uses: ./.github/workflows/template-electron-build-windows-x64.yml secrets: inherit needs: [get-update-version] with: @@ -55,8 +55,8 @@ jobs: nightly: false new_version: ${{ needs.get-update-version.outputs.new_version }} - build-linux-x64: - uses: ./.github/workflows/template-build-linux-x64.yml + build-electron-linux-x64: + uses: ./.github/workflows/template-electron-build-linux-x64.yml secrets: inherit needs: [get-update-version] with: @@ -65,9 +65,48 @@ jobs: beta: false nightly: false new_version: ${{ needs.get-update-version.outputs.new_version }} + build-tauri-macos: + uses: ./.github/workflows/template-tauri-build-macos-preview.yml + secrets: inherit + needs: [get-update-version, create-draft-release] + with: + ref: ${{ github.ref }} + public_provider: github + channel: stable + new_version: ${{ needs.get-update-version.outputs.new_version }} + upload_url: ${{ needs.create-draft-release.outputs.upload_url }} + + build-tauri-windows-x64: + uses: ./.github/workflows/template-tauri-build-windows-x64-preview.yml + secrets: inherit + needs: [get-update-version, create-draft-release] + with: + ref: ${{ github.ref }} + public_provider: github + channel: stable + new_version: ${{ needs.get-update-version.outputs.new_version }} + upload_url: ${{ needs.create-draft-release.outputs.upload_url }} + + build-tauri-linux-x64: + uses: ./.github/workflows/template-tauri-build-linux-x64-preview.yml + secrets: inherit + needs: [get-update-version, create-draft-release] + with: + ref: ${{ github.ref }} + public_provider: github + channel: stable + new_version: ${{ needs.get-update-version.outputs.new_version }} + upload_url: ${{ needs.create-draft-release.outputs.upload_url }} update_release_draft: - needs: [build-macos, build-windows-x64, build-linux-x64] + needs: [ + build-electron-windows-x64, + build-electron-linux-x64, + build-electron-macos, + build-tauri-windows-x64, + build-tauri-linux-x64, + build-tauri-macos + ] permissions: # write permission is required to create a github release contents: write diff --git a/.github/workflows/jan-tauri-build-beta.yml b/.github/workflows/jan-tauri-build-beta.yml new file mode 100644 index 000000000..476293d71 --- /dev/null +++ b/.github/workflows/jan-tauri-build-beta.yml @@ -0,0 +1,156 @@ +name: Tauri Builder - Beta Build + +on: + push: + tags: ["v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+-beta"] + +jobs: + # Job create Update app version based on latest release tag with build number and save to output + get-update-version: + uses: ./.github/workflows/template-get-update-version.yml + create-draft-release: + runs-on: ubuntu-latest + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') + outputs: + upload_url: ${{ steps.create_release.outputs.upload_url }} + version: ${{ steps.get_version.outputs.version }} + permissions: + contents: write + steps: + - name: Extract tag name without v prefix + id: get_version + run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV && echo "::set-output name=version::${GITHUB_REF#refs/tags/v}" + env: + GITHUB_REF: ${{ github.ref }} + - name: Create Draft Release + id: create_release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.ref_name }} + token: ${{ secrets.GITHUB_TOKEN }} + name: "${{ env.VERSION }}" + draft: true + prerelease: false + generate_release_notes: true + + build-macos: + uses: ./.github/workflows/template-tauri-build-macos.yml + secrets: inherit + needs: [get-update-version, create-draft-release] + with: + ref: ${{ github.ref }} + public_provider: github + new_version: ${{ needs.get-update-version.outputs.new_version }} + channel: beta + cortex_api_port: "39271" + upload_url: ${{ needs.create-draft-release.outputs.upload_url }} + + build-windows-x64: + uses: ./.github/workflows/template-tauri-build-windows-x64.yml + secrets: inherit + needs: [get-update-version, create-draft-release] + with: + ref: ${{ github.ref }} + public_provider: github + new_version: ${{ needs.get-update-version.outputs.new_version }} + channel: beta + cortex_api_port: "39271" + upload_url: ${{ needs.create-draft-release.outputs.upload_url }} + + build-linux-x64: + uses: ./.github/workflows/template-tauri-build-linux-x64.yml + secrets: inherit + needs: [get-update-version, create-draft-release] + with: + ref: ${{ github.ref }} + public_provider: github + new_version: ${{ needs.get-update-version.outputs.new_version }} + channel: beta + cortex_api_port: "39271" + upload_url: ${{ needs.create-draft-release.outputs.upload_url }} + + sync-temp-to-latest: + needs: [create-draft-release, get-update-version, build-macos, build-windows-x64, build-linux-x64] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Getting the repo + uses: actions/checkout@v3 + + - name: create latest.json file + run: | + + VERSION=${{ needs.get-update-version.outputs.new_version }} + PUB_DATE=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ") + LINUX_SIGNATURE="${{ needs.build-linux-x64.outputs.APPIMAGE_SIG }}" + LINUX_URL="https://delta.jan.ai/beta/${{ needs.build-linux-x64.outputs.APPIMAGE_FILE_NAME }}" + WINDOWS_SIGNATURE="${{ needs.build-windows-x64.outputs.WIN_SIG }}" + WINDOWS_URL="https://delta.jan.ai/beta/${{ needs.build-windows-x64.outputs.FILE_NAME }}" + DARWIN_SIGNATURE="${{ needs.build-macos.outputs.MAC_UNIVERSAL_SIG }}" + DARWIN_URL="https://delta.jan.ai/beta/Jan-beta_${{ needs.get-update-version.outputs.new_version }}.app.tar.gz" + + jq --arg version "$VERSION" \ + --arg pub_date "$PUB_DATE" \ + --arg linux_signature "$LINUX_SIGNATURE" \ + --arg linux_url "$LINUX_URL" \ + --arg windows_signature "$WINDOWS_SIGNATURE" \ + --arg windows_url "$WINDOWS_URL" \ + --arg darwin_arm_signature "$DARWIN_SIGNATURE" \ + --arg darwin_arm_url "$DARWIN_URL" \ + --arg darwin_amd_signature "$DARWIN_SIGNATURE" \ + --arg darwin_amd_url "$DARWIN_URL" \ + '.version = $version + | .pub_date = $pub_date + | .platforms["linux-x86_64"].signature = $linux_signature + | .platforms["linux-x86_64"].url = $linux_url + | .platforms["windows-x86_64"].signature = $windows_signature + | .platforms["windows-x86_64"].url = $windows_url + | .platforms["darwin-aarch64"].signature = $darwin_arm_signature + | .platforms["darwin-aarch64"].url = $darwin_arm_url + | .platforms["darwin-x86_64"].signature = $darwin_amd_signature + | .platforms["darwin-x86_64"].url = $darwin_amd_url' \ + src-tauri/latest.json.template > latest.json + cat latest.json + - name: Sync temp to latest + run: | + # sync temp-beta to beta by copy files that are different or new + aws s3 cp ./latest.json s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-beta/latest.json + aws s3 sync "s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-beta/" "s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/beta/" + env: + AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.DELTA_AWS_REGION }} + AWS_EC2_METADATA_DISABLED: "true" + + - name: Upload release assert if public provider is github + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ needs.create-draft-release.outputs.upload_url }} + asset_path: ./latest.json + asset_name: latest.json + asset_content_type: text/json + + noti-discord-and-update-url-readme: + needs: [build-macos, get-update-version, build-windows-x64, build-linux-x64, sync-temp-to-latest] + runs-on: ubuntu-latest + steps: + - name: Set version to environment variable + run: | + VERSION=${{ needs.get-update-version.outputs.new_version }} + VERSION="${VERSION#v}" + echo "VERSION=$VERSION" >> $GITHUB_ENV + + - name: Notify Discord + uses: Ilshidur/action-discord@master + with: + args: | + Jan-beta App version {{ VERSION }}, has been released, use the following links to download the app with faster speed or visit the Github release page for more information: + - Windows: https://delta.jan.ai/beta/Jan-beta_{{ VERSION }}_x64-setup.exe + - macOS Universal: https://delta.jan.ai/beta/Jan-beta_{{ VERSION }}_universal.dmg + - Linux Deb: https://delta.jan.ai/beta/Jan-beta_{{ VERSION }}_amd64.deb + - Linux AppImage: https://delta.jan.ai/beta/Jan-beta_{{ VERSION }}_amd64.AppImage + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_JAN_BETA }} \ No newline at end of file diff --git a/.github/workflows/jan-tauri-build-nightly.yml b/.github/workflows/jan-tauri-build-nightly.yml new file mode 100644 index 000000000..ba21e89f1 --- /dev/null +++ b/.github/workflows/jan-tauri-build-nightly.yml @@ -0,0 +1,186 @@ +name: Tauri Builder - Nightly / Manual + +on: + schedule: + - cron: '0 20 * * 1,2,3' # At 8 PM UTC on Monday, Tuesday, and Wednesday which is 3 AM UTC+7 Tuesday, Wednesday, and Thursday + workflow_dispatch: + inputs: + public_provider: + type: choice + description: 'Public Provider' + options: + - none + - aws-s3 + default: none + pull_request_review: + types: [submitted] + +jobs: + set-public-provider: + runs-on: ubuntu-latest + outputs: + public_provider: ${{ steps.set-public-provider.outputs.public_provider }} + ref: ${{ steps.set-public-provider.outputs.ref }} + steps: + - name: Set public provider + id: set-public-provider + run: | + if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + echo "::set-output name=public_provider::${{ github.event.inputs.public_provider }}" + echo "::set-output name=ref::${{ github.ref }}" + else + if [ "${{ github.event_name }}" == "schedule" ]; then + echo "::set-output name=public_provider::aws-s3" + echo "::set-output name=ref::refs/heads/dev" + elif [ "${{ github.event_name }}" == "push" ]; then + echo "::set-output name=public_provider::aws-s3" + echo "::set-output name=ref::${{ github.ref }}" + elif [ "${{ github.event_name }}" == "pull_request_review" ]; then + echo "::set-output name=public_provider::none" + echo "::set-output name=ref::${{ github.ref }}" + else + echo "::set-output name=public_provider::none" + echo "::set-output name=ref::${{ github.ref }}" + fi + fi + # Job create Update app version based on latest release tag with build number and save to output + get-update-version: + uses: ./.github/workflows/template-get-update-version.yml + + build-macos: + uses: ./.github/workflows/template-tauri-build-macos.yml + needs: [get-update-version, set-public-provider] + secrets: inherit + with: + ref: ${{ needs.set-public-provider.outputs.ref }} + public_provider: ${{ needs.set-public-provider.outputs.public_provider }} + new_version: ${{ needs.get-update-version.outputs.new_version }} + channel: nightly + cortex_api_port: "39261" + + build-windows-x64: + uses: ./.github/workflows/template-tauri-build-windows-x64.yml + secrets: inherit + needs: [get-update-version, set-public-provider] + with: + ref: ${{ needs.set-public-provider.outputs.ref }} + public_provider: ${{ needs.set-public-provider.outputs.public_provider }} + new_version: ${{ needs.get-update-version.outputs.new_version }} + channel: nightly + cortex_api_port: "39261" + build-linux-x64: + uses: ./.github/workflows/template-tauri-build-linux-x64.yml + secrets: inherit + needs: [get-update-version, set-public-provider] + with: + ref: ${{ needs.set-public-provider.outputs.ref }} + public_provider: ${{ needs.set-public-provider.outputs.public_provider }} + new_version: ${{ needs.get-update-version.outputs.new_version }} + channel: nightly + cortex_api_port: "39261" + + sync-temp-to-latest: + needs: [get-update-version, set-public-provider, build-windows-x64, build-linux-x64, build-macos] + runs-on: ubuntu-latest + steps: + - name: Getting the repo + uses: actions/checkout@v3 + - name: Install jq + uses: dcarbone/install-jq-action@v2.0.1 + - name: create latest.json file + run: | + + VERSION=${{ needs.get-update-version.outputs.new_version }} + PUB_DATE=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ") + LINUX_SIGNATURE="${{ needs.build-linux-x64.outputs.APPIMAGE_SIG }}" + LINUX_URL="https://delta.jan.ai/nightly/${{ needs.build-linux-x64.outputs.APPIMAGE_FILE_NAME }}" + WINDOWS_SIGNATURE="${{ needs.build-windows-x64.outputs.WIN_SIG }}" + WINDOWS_URL="https://delta.jan.ai/nightly/${{ needs.build-windows-x64.outputs.FILE_NAME }}" + DARWIN_SIGNATURE="${{ needs.build-macos.outputs.MAC_UNIVERSAL_SIG }}" + DARWIN_URL="https://delta.jan.ai/nightly/Jan-nightly_${{ needs.get-update-version.outputs.new_version }}.app.tar.gz" + + jq --arg version "$VERSION" \ + --arg pub_date "$PUB_DATE" \ + --arg linux_signature "$LINUX_SIGNATURE" \ + --arg linux_url "$LINUX_URL" \ + --arg windows_signature "$WINDOWS_SIGNATURE" \ + --arg windows_url "$WINDOWS_URL" \ + --arg darwin_arm_signature "$DARWIN_SIGNATURE" \ + --arg darwin_arm_url "$DARWIN_URL" \ + --arg darwin_amd_signature "$DARWIN_SIGNATURE" \ + --arg darwin_amd_url "$DARWIN_URL" \ + '.version = $version + | .pub_date = $pub_date + | .platforms["linux-x86_64"].signature = $linux_signature + | .platforms["linux-x86_64"].url = $linux_url + | .platforms["windows-x86_64"].signature = $windows_signature + | .platforms["windows-x86_64"].url = $windows_url + | .platforms["darwin-aarch64"].signature = $darwin_arm_signature + | .platforms["darwin-aarch64"].url = $darwin_arm_url + | .platforms["darwin-x86_64"].signature = $darwin_amd_signature + | .platforms["darwin-x86_64"].url = $darwin_amd_url' \ + src-tauri/latest.json.template > latest.json + cat latest.json + - name: Sync temp to latest + if: ${{ needs.set-public-provider.outputs.public_provider == 'aws-s3' }} + run: | + aws s3 cp ./latest.json s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-nightly/latest.json + aws s3 sync s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-nightly/ s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/nightly/ + env: + AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.DELTA_AWS_REGION }} + AWS_EC2_METADATA_DISABLED: "true" + + noti-discord-nightly-and-update-url-readme: + needs: [build-macos, build-windows-x64, build-linux-x64, get-update-version, set-public-provider, sync-temp-to-latest] + secrets: inherit + if: github.event_name == 'schedule' + uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml + with: + ref: refs/heads/dev + build_reason: Nightly + push_to_branch: dev + new_version: ${{ needs.get-update-version.outputs.new_version }} + + noti-discord-pre-release-and-update-url-readme: + needs: [build-macos, build-windows-x64, build-linux-x64, get-update-version, set-public-provider, sync-temp-to-latest] + secrets: inherit + if: github.event_name == 'push' + uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml + with: + ref: refs/heads/dev + build_reason: Pre-release + push_to_branch: dev + new_version: ${{ needs.get-update-version.outputs.new_version }} + + noti-discord-manual-and-update-url-readme: + needs: [build-macos, build-windows-x64, build-linux-x64, get-update-version, set-public-provider, sync-temp-to-latest] + secrets: inherit + if: github.event_name == 'workflow_dispatch' && github.event.inputs.public_provider == 'aws-s3' + uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml + with: + ref: refs/heads/dev + build_reason: Manual + push_to_branch: dev + new_version: ${{ needs.get-update-version.outputs.new_version }} + + + comment-pr-build-url: + needs: [build-macos, build-windows-x64, build-linux-x64, get-update-version, set-public-provider, sync-temp-to-latest] + runs-on: ubuntu-latest + if: github.event_name == 'pull_request_review' + steps: + - name: Set up GitHub CLI + run: | + curl -sSL https://github.com/cli/cli/releases/download/v2.33.0/gh_2.33.0_linux_amd64.tar.gz | tar xz + sudo cp gh_2.33.0_linux_amd64/bin/gh /usr/local/bin/ + + - name: Comment build URL on PR + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PR_URL=${{ github.event.pull_request.html_url }} + RUN_ID=${{ github.run_id }} + COMMENT="This is the build for this pull request. You can download it from the Artifacts section here: [Build URL](https://github.com/${{ github.repository }}/actions/runs/${RUN_ID})." + gh pr comment $PR_URL --body "$COMMENT" diff --git a/.github/workflows/jan-tauri-build.yml b/.github/workflows/jan-tauri-build.yml new file mode 100644 index 000000000..1dc22f2e4 --- /dev/null +++ b/.github/workflows/jan-tauri-build.yml @@ -0,0 +1,145 @@ +name: Tauri Builder - Tag + +on: + push: + tags: ["v[0-9]+.[0-9]+.[0-9]+"] + +jobs: + # Job create Update app version based on latest release tag with build number and save to output + get-update-version: + uses: ./.github/workflows/template-get-update-version.yml + + create-draft-release: + runs-on: ubuntu-latest + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') + outputs: + upload_url: ${{ steps.create_release.outputs.upload_url }} + version: ${{ steps.get_version.outputs.version }} + permissions: + contents: write + steps: + - name: Extract tag name without v prefix + id: get_version + run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV && echo "::set-output name=version::${GITHUB_REF#refs/tags/v}" + env: + GITHUB_REF: ${{ github.ref }} + - name: Create Draft Release + id: create_release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.ref_name }} + token: ${{ secrets.GITHUB_TOKEN }} + name: "${{ env.VERSION }}" + draft: true + prerelease: false + + build-macos: + uses: ./.github/workflows/template-tauri-build-macos.yml + secrets: inherit + needs: [get-update-version, create-draft-release] + with: + ref: ${{ github.ref }} + public_provider: github + channel: stable + new_version: ${{ needs.get-update-version.outputs.new_version }} + upload_url: ${{ needs.create-draft-release.outputs.upload_url }} + + build-windows-x64: + uses: ./.github/workflows/template-tauri-build-windows-x64.yml + secrets: inherit + needs: [get-update-version, create-draft-release] + with: + ref: ${{ github.ref }} + public_provider: github + channel: stable + new_version: ${{ needs.get-update-version.outputs.new_version }} + upload_url: ${{ needs.create-draft-release.outputs.upload_url }} + + build-linux-x64: + uses: ./.github/workflows/template-tauri-build-linux-x64.yml + secrets: inherit + needs: [get-update-version, create-draft-release] + with: + ref: ${{ github.ref }} + public_provider: github + channel: stable + new_version: ${{ needs.get-update-version.outputs.new_version }} + upload_url: ${{ needs.create-draft-release.outputs.upload_url }} + + sync-temp-to-latest: + needs: [create-draft-release, get-update-version, build-macos, build-windows-x64, build-linux-x64] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Getting the repo + uses: actions/checkout@v3 + + - name: create latest.json file + run: | + + VERSION=${{ needs.get-update-version.outputs.new_version }} + PUB_DATE=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ") + LINUX_SIGNATURE="${{ needs.build-linux-x64.outputs.APPIMAGE_SIG }}" + LINUX_URL="https://github.com/menloresearch/jan/releases/download/v${{ needs.get-update-version.outputs.new_version }}/${{ needs.build-linux-x64.outputs.APPIMAGE_FILE_NAME }}" + WINDOWS_SIGNATURE="${{ needs.build-windows-x64.outputs.WIN_SIG }}" + WINDOWS_URL="https://github.com/menloresearch/jan/releases/download/v${{ needs.get-update-version.outputs.new_version }}/${{ needs.build-windows-x64.outputs.FILE_NAME }}" + DARWIN_SIGNATURE="${{ needs.build-macos.outputs.MAC_UNIVERSAL_SIG }}" + DARWIN_URL="https://github.com/menloresearch/jan/releases/download/v${{ needs.get-update-version.outputs.new_version }}/${{ needs.build-macos.outputs.TAR_NAME }}" + + jq --arg version "$VERSION" \ + --arg pub_date "$PUB_DATE" \ + --arg linux_signature "$LINUX_SIGNATURE" \ + --arg linux_url "$LINUX_URL" \ + --arg windows_signature "$WINDOWS_SIGNATURE" \ + --arg windows_url "$WINDOWS_URL" \ + --arg darwin_arm_signature "$DARWIN_SIGNATURE" \ + --arg darwin_arm_url "$DARWIN_URL" \ + --arg darwin_amd_signature "$DARWIN_SIGNATURE" \ + --arg darwin_amd_url "$DARWIN_URL" \ + '.version = $version + | .pub_date = $pub_date + | .platforms["linux-x86_64"].signature = $linux_signature + | .platforms["linux-x86_64"].url = $linux_url + | .platforms["windows-x86_64"].signature = $windows_signature + | .platforms["windows-x86_64"].url = $windows_url + | .platforms["darwin-aarch64"].signature = $darwin_arm_signature + | .platforms["darwin-aarch64"].url = $darwin_arm_url + | .platforms["darwin-x86_64"].signature = $darwin_amd_signature + | .platforms["darwin-x86_64"].url = $darwin_amd_url' \ + src-tauri/latest.json.template > latest.json + cat latest.json + + - name: Upload release assert if public provider is github + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ needs.create-draft-release.outputs.upload_url }} + asset_path: ./latest.json + asset_name: latest.json + asset_content_type: text/json + + update_release_draft: + needs: [build-macos, build-windows-x64, build-linux-x64] + permissions: + # write permission is required to create a github release + contents: write + # write permission is required for autolabeler + # otherwise, read permission is required at least + pull-requests: write + runs-on: ubuntu-latest + steps: + # (Optional) GitHub Enterprise requires GHE_HOST variable set + #- name: Set GHE_HOST + # run: | + # echo "GHE_HOST=${GITHUB_SERVER_URL##https:\/\/}" >> $GITHUB_ENV + + # Drafts your next Release notes as Pull Requests are merged into "master" + - uses: release-drafter/release-drafter@v5 + # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml + # with: + # config-name: my-config.yml + # disable-autolabeler: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/nightly-integrate-cortex-cpp.yml b/.github/workflows/nightly-integrate-cortex-cpp.yml deleted file mode 100644 index 066fbd28e..000000000 --- a/.github/workflows/nightly-integrate-cortex-cpp.yml +++ /dev/null @@ -1,127 +0,0 @@ -name: Nightly Update cortex cpp - -on: - schedule: - - cron: '30 19 * * 1-5' # At 01:30 on every day-of-week from Monday through Friday UTC +7 - workflow_dispatch: - -jobs: - update-submodule: - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - actions: write - - outputs: - pr_number: ${{ steps.check-update.outputs.pr_number }} - pr_created: ${{ steps.check-update.outputs.pr_created }} - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - with: - submodules: recursive - ref: dev - fetch-depth: 0 - token: ${{ secrets.PAT_SERVICE_ACCOUNT }} - - - name: Configure Git - run: | - git config --global user.name 'github-actions[bot]' - git config --global user.email 'github-actions[bot]@users.noreply.github.com' - - - name: Update submodule to latest release - id: check-update - env: - GITHUB_TOKEN: ${{ secrets.PAT_SERVICE_ACCOUNT }} - run: | - curl -s https://api.github.com/repos/menloresearch/cortex/releases > /tmp/github_api_releases.json - latest_prerelease_name=$(cat /tmp/github_api_releases.json | jq -r '.[] | select(.prerelease) | .name' | head -n 1) - - get_asset_count() { - local version_name=$1 - cat /tmp/github_api_releases.json | jq -r --arg version_name "$version_name" '.[] | select(.name == $version_name) | .assets | length' - } - - cortex_cpp_version_file_path="extensions/inference-nitro-extension/bin/version.txt" - current_version_name=$(cat "$cortex_cpp_version_file_path" | head -n 1) - - current_version_asset_count=$(get_asset_count "$current_version_name") - latest_prerelease_asset_count=$(get_asset_count "$latest_prerelease_name") - - if [ "$current_version_name" = "$latest_prerelease_name" ]; then - echo "cortex cpp remote repo doesn't have update today, skip update cortex.cpp for today nightly build" - echo "::set-output name=pr_created::false" - exit 0 - fi - - if [ "$current_version_asset_count" != "$latest_prerelease_asset_count" ]; then - echo "Latest prerelease version has different number of assets, somethink went wrong, skip update cortex.cpp for today nightly build" - echo "::set-output name=pr_created::false" - exit 1 - fi - - echo $latest_prerelease_name > $cortex_cpp_version_file_path - echo "Updated version from $current_version_name to $latest_prerelease_name." - echo "::set-output name=pr_created::true" - - git add -f $cortex_cpp_version_file_path - git commit -m "Update cortex cpp nightly to version $latest_prerelease_name" - branch_name="update-nightly-$(date +'%Y-%m-%d-%H-%M')" - git checkout -b $branch_name - git push origin $branch_name - - pr_title="Update cortex cpp nightly to version $latest_prerelease_name" - pr_body="This PR updates the Update cortex cpp nightly to version $latest_prerelease_name" - - gh pr create --title "$pr_title" --body "$pr_body" --head $branch_name --base dev --reviewer Van-QA - - pr_number=$(gh pr list --head $branch_name --json number --jq '.[0].number') - echo "::set-output name=pr_number::$pr_number" - - check-and-merge-pr: - needs: update-submodule - if: needs.update-submodule.outputs.pr_created == 'true' - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - with: - submodules: recursive - fetch-depth: 0 - token: ${{ secrets.PAT_SERVICE_ACCOUNT }} - - - name: Wait for CI to pass - env: - GITHUB_TOKEN: ${{ secrets.PAT_SERVICE_ACCOUNT }} - run: | - pr_number=${{ needs.update-submodule.outputs.pr_number }} - while true; do - ci_completed=$(gh pr checks $pr_number --json completedAt --jq '.[].completedAt') - if echo "$ci_completed" | grep -q "0001-01-01T00:00:00Z"; then - echo "CI is still running, waiting..." - sleep 60 - else - echo "CI has completed, checking states..." - ci_states=$(gh pr checks $pr_number --json state --jq '.[].state') - if echo "$ci_states" | grep -vqE "SUCCESS|SKIPPED"; then - echo "CI failed, exiting..." - exit 1 - else - echo "CI passed, merging PR..." - break - fi - fi - done - - - name: Merge the PR - env: - GITHUB_TOKEN: ${{ secrets.PAT_SERVICE_ACCOUNT }} - run: | - pr_number=${{ needs.update-submodule.outputs.pr_number }} - gh pr merge $pr_number --merge --admin diff --git a/.github/workflows/template-build-linux-x64.yml b/.github/workflows/template-electron-build-linux-x64.yml similarity index 98% rename from .github/workflows/template-build-linux-x64.yml rename to .github/workflows/template-electron-build-linux-x64.yml index 58b566931..e13c8371e 100644 --- a/.github/workflows/template-build-linux-x64.yml +++ b/.github/workflows/template-electron-build-linux-x64.yml @@ -176,12 +176,12 @@ jobs: if: inputs.public_provider != 'github' uses: actions/upload-artifact@v4 with: - name: jan-linux-amd64-${{ inputs.new_version }}-deb + name: jan-electron-linux-amd64-${{ inputs.new_version }}-deb path: ./electron/dist/*.deb - name: Upload Artifact .AppImage file if: inputs.public_provider != 'github' uses: actions/upload-artifact@v4 with: - name: jan-linux-amd64-${{ inputs.new_version }}-AppImage + name: jan-electron-linux-amd64-${{ inputs.new_version }}-AppImage path: ./electron/dist/*.AppImage \ No newline at end of file diff --git a/.github/workflows/template-build-macos.yml b/.github/workflows/template-electron-build-macos.yml similarity index 99% rename from .github/workflows/template-build-macos.yml rename to .github/workflows/template-electron-build-macos.yml index a5e5cc724..f0c69d5f1 100644 --- a/.github/workflows/template-build-macos.yml +++ b/.github/workflows/template-electron-build-macos.yml @@ -229,5 +229,5 @@ jobs: if: inputs.public_provider != 'github' uses: actions/upload-artifact@v4 with: - name: jan-mac-universal-${{ inputs.new_version }} + name: jan-electron-mac-universal-${{ inputs.new_version }} path: ./electron/dist/*.dmg \ No newline at end of file diff --git a/.github/workflows/template-build-windows-x64.yml b/.github/workflows/template-electron-build-windows-x64.yml similarity index 99% rename from .github/workflows/template-build-windows-x64.yml rename to .github/workflows/template-electron-build-windows-x64.yml index 9be028e15..7ba296504 100644 --- a/.github/workflows/template-build-windows-x64.yml +++ b/.github/workflows/template-electron-build-windows-x64.yml @@ -225,5 +225,5 @@ jobs: if: inputs.public_provider != 'github' uses: actions/upload-artifact@v4 with: - name: jan-win-x64-${{ inputs.new_version }} + name: jan-electron-win-x64-${{ inputs.new_version }} path: ./electron/dist/*.exe \ No newline at end of file diff --git a/.github/workflows/template-get-update-version.yml b/.github/workflows/template-get-update-version.yml index 97340be81..70f5eace9 100644 --- a/.github/workflows/template-get-update-version.yml +++ b/.github/workflows/template-get-update-version.yml @@ -44,9 +44,12 @@ jobs: exit 1 } - if ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') }}; then + if ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') }}; then echo "Tag detected, set output follow tag" - echo "::set-output name=new_version::${{ steps.tag.outputs.tag }}" + sanitized_tag="${{ steps.tag.outputs.tag }}" + # Remove the 'v' prefix if it exists + sanitized_tag="${sanitized_tag#v}" + echo "::set-output name=new_version::$sanitized_tag" else # Get the latest release tag from GitHub API LATEST_TAG=$(get_latest_tag) diff --git a/.github/workflows/template-noti-discord-and-update-url-readme.yml b/.github/workflows/template-noti-discord-and-update-url-readme.yml index 282e0aa76..eaaee7e50 100644 --- a/.github/workflows/template-noti-discord-and-update-url-readme.yml +++ b/.github/workflows/template-noti-discord-and-update-url-readme.yml @@ -47,10 +47,10 @@ jobs: with: args: | Jan App ${{ inputs.build_reason }} build artifact version {{ VERSION }}: - - Windows: https://delta.jan.ai/nightly/jan-nightly-win-x64-{{ VERSION }}.exe - - macOS Universal: https://delta.jan.ai/nightly/jan-nightly-mac-universal-{{ VERSION }}.dmg - - Linux Deb: https://delta.jan.ai/nightly/jan-nightly-linux-amd64-{{ VERSION }}.deb - - Linux AppImage: https://delta.jan.ai/nightly/jan-nightly-linux-x86_64-{{ VERSION }}.AppImage + - Windows: https://delta.jan.ai/nightly/Jan-nightly_{{ VERSION }}_x64-setup.exe + - macOS Universal: https://delta.jan.ai/nightly/Jan-nightly_{{ VERSION }}_universal.dmg + - Linux Deb: https://delta.jan.ai/nightly/Jan-nightly_{{ VERSION }}_amd64.deb + - Linux AppImage: https://delta.jan.ai/nightly/Jan-nightly_{{ VERSION }}_amd64.AppImage - Github action run: https://github.com/menloresearch/jan/actions/runs/{{ GITHUB_RUN_ID }} env: DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} diff --git a/.github/workflows/template-tauri-build-linux-x64-preview.yml b/.github/workflows/template-tauri-build-linux-x64-preview.yml new file mode 100644 index 000000000..8192a60e1 --- /dev/null +++ b/.github/workflows/template-tauri-build-linux-x64-preview.yml @@ -0,0 +1,262 @@ +name: tauri-build-linux-x64 +on: + workflow_call: + inputs: + ref: + required: true + type: string + default: 'refs/heads/main' + public_provider: + required: true + type: string + default: none + description: 'none: build only, github: build and publish to github, aws s3: build and publish to aws s3' + new_version: + required: true + type: string + default: '' + cortex_api_port: + required: false + type: string + default: "" + upload_url: + required: false + type: string + default: '' + channel: + required: true + type: string + default: 'nightly' + description: 'The channel to use for this job' + secrets: + DELTA_AWS_S3_BUCKET_NAME: + required: false + DELTA_AWS_ACCESS_KEY_ID: + required: false + DELTA_AWS_SECRET_ACCESS_KEY: + required: false + TAURI_SIGNING_PRIVATE_KEY: + required: false + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: + required: false + TAURI_SIGNING_PUBLIC_KEY: + required: false + outputs: + DEB_SIG: + value: ${{ jobs.build-linux-x64.outputs.DEB_SIG }} + APPIMAGE_SIG: + value: ${{ jobs.build-linux-x64.outputs.APPIMAGE_SIG }} + APPIMAGE_FILE_NAME: + value: ${{ jobs.build-linux-x64.outputs.APPIMAGE_FILE_NAME }} +jobs: + build-linux-x64: + runs-on: ubuntu-22.04 + outputs: + DEB_SIG: ${{ steps.packageinfo.outputs.DEB_SIG }} + APPIMAGE_SIG: ${{ steps.packageinfo.outputs.APPIMAGE_SIG }} + APPIMAGE_FILE_NAME: ${{ steps.packageinfo.outputs.APPIMAGE_FILE_NAME }} + environment: production + permissions: + contents: write + steps: + - name: Getting the repo + uses: actions/checkout@v3 + with: + ref: ${{ inputs.ref }} + + - name: Free Disk Space Before Build + run: | + echo "Disk space before cleanup:" + df -h + sudo rm -rf /usr/local/.ghcup + sudo rm -rf /opt/hostedtoolcache/CodeQL + sudo rm -rf /usr/local/lib/android/sdk/ndk + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf /usr/local/share/boost + sudo apt-get clean + echo "Disk space after cleanup:" + df -h + + - name: Replace Icons for Beta Build + if: inputs.channel != 'stable' + shell: bash + run: | + cp .github/scripts/icon-${{ inputs.channel }}.png src-tauri/icons/icon.png + + - name: Installing node + uses: actions/setup-node@v1 + with: + node-version: 20 + + - name: Install jq + uses: dcarbone/install-jq-action@v2.0.1 + + - name: Install ctoml + run: | + cargo install ctoml + + - name: Install Tauri dependecies + run: | + sudo apt update + sudo apt install -y libglib2.0-dev libatk1.0-dev libpango1.0-dev libgtk-3-dev libsoup-3.0-dev libwebkit2gtk-4.1-dev librsvg2-dev libfuse2 + + - name: Update app version base public_provider + run: | + echo "Version: ${{ inputs.new_version }}" + # Update tauri.conf.json + jq --arg version "${{ inputs.new_version }}" '.version = $version | .bundle.createUpdaterArtifacts = true | .bundle.resources = ["resources/themes/**/*", "resources/pre-install/**/*"] | .bundle.externalBin = ["binaries/cortex-server", "resources/bin/uv"]' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json + mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json + if [ "${{ inputs.channel }}" != "stable" ]; then + jq '.bundle.linux.deb.files = {"usr/bin/bun": "resources/bin/bun", "usr/lib/Jan-${{ inputs.channel }}/binaries/engines": "binaries/engines"}' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json + else + jq '.bundle.linux.deb.files = {"usr/bin/bun": "resources/bin/bun", "usr/lib/Jan/binaries/engines": "binaries/engines"}' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json + fi + mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json + jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json + mv /tmp/package.json web/package.json + + ctoml ./src-tauri/Cargo.toml package.version "${{ inputs.new_version }}" + cat ./src-tauri/Cargo.toml + + # Change app name for beta and nightly builds + if [ "${{ inputs.channel }}" != "stable" ]; then + jq '.plugins.updater.endpoints = ["https://delta.jan.ai/${{ inputs.channel }}/latest.json"]' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json + mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json + + chmod +x .github/scripts/rename-tauri-app.sh + .github/scripts/rename-tauri-app.sh ./src-tauri/tauri.conf.json ${{ inputs.channel }} + + cat ./src-tauri/tauri.conf.json + + # Update Cargo.toml + ctoml ./src-tauri/Cargo.toml package.name "Jan-${{ inputs.channel }}" + echo "------------------" + cat ./src-tauri/Cargo.toml + + chmod +x .github/scripts/rename-workspace.sh + .github/scripts/rename-workspace.sh ./package.json ${{ inputs.channel }} + cat ./package.json + fi + - name: Build app + run: | + make build-tauri + # Copy engines and bun to appimage + wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage -O ./appimagetool + chmod +x ./appimagetool + if [ "${{ inputs.channel }}" != "stable" ]; then + cp ./src-tauri/resources/bin/bun ./src-tauri/target/release/bundle/appimage/Jan-${{ inputs.channel }}.AppDir/usr/bin/bun + cp -rf ./src-tauri/binaries/engines ./src-tauri/target/release/bundle/appimage/Jan-${{ inputs.channel }}.AppDir/usr/lib/Jan-${{ inputs.channel }}/engines + ./appimagetool ./src-tauri/target/release/bundle/appimage/Jan-${{ inputs.channel }}.AppDir $(ls ./src-tauri/target/release/bundle/appimage/ | grep AppImage) + else + cp ./src-tauri/resources/bin/bun ./src-tauri/target/release/bundle/appimage/Jan.AppDir/usr/bin/bun + cp -rf ./src-tauri/binaries/engines ./src-tauri/target/release/bundle/appimage/Jan.AppDir/usr/lib/Jan/engines + ./appimagetool ./src-tauri/target/release/bundle/appimage/Jan.AppDir $(ls ./src-tauri/target/release/bundle/appimage/ | grep AppImage) + fi + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} + POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }} + # CORTEX_API_PORT: ${{ inputs.cortex_api_port }} + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + TAURI_SIGNING_PUBLIC_KEY: ${{ secrets.TAURI_SIGNING_PUBLIC_KEY }} + + # Publish app + + ## Artifacts, for dev and test + - name: Upload Artifact + if: inputs.public_provider != 'github' + uses: actions/upload-artifact@v4 + with: + name: jan-${{ inputs.channel }}-tauri-linux-amd64-${{ inputs.new_version }}-deb + path: ./src-tauri/target/release/bundle/deb/*.deb + + - name: Upload Artifact + if: inputs.public_provider != 'github' + uses: actions/upload-artifact@v4 + with: + name: jan-tauri-linux-amd64-${{ inputs.new_version }}-AppImage + path: ./src-tauri/target/release/bundle/appimage/*.AppImage + + ## create zip file and latest-linux.yml for linux electron auto updater + - name: Create zip file and latest-linux.yml for linux electron auto updater + id: packageinfo + run: | + cd ./src-tauri/target/release/bundle + + if [ "${{ inputs.channel }}" != "stable" ]; then + DEB_FILE_NAME=Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.deb + APPIMAGE_FILE_NAME=Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.AppImage + DEB_SIG=$(cat deb/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.deb.sig) + APPIMAGE_SIG=$(cat appimage/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.AppImage.sig) + else + DEB_FILE_NAME=Jan_${{ inputs.new_version }}_amd64.deb + APPIMAGE_FILE_NAME=Jan_${{ inputs.new_version }}_amd64.AppImage + DEB_SIG=$(cat deb/Jan_${{ inputs.new_version }}_amd64.deb.sig) + APPIMAGE_SIG=$(cat appimage/Jan_${{ inputs.new_version }}_amd64.AppImage.sig) + fi + + echo "DEB_SIG=$DEB_SIG" >> $GITHUB_OUTPUT + echo "APPIMAGE_SIG=$APPIMAGE_SIG" >> $GITHUB_OUTPUT + echo "DEB_FILE_NAME=$DEB_FILE_NAME" >> $GITHUB_OUTPUT + echo "APPIMAGE_FILE_NAME=$APPIMAGE_FILE_NAME" >> $GITHUB_OUTPUT + + ## Upload to s3 for nightly and beta + - name: upload to aws s3 if public provider is aws + if: inputs.public_provider == 'aws-s3' || inputs.channel == 'beta' || inputs.channel == 'nightly' + run: | + cd ./src-tauri/target/release/bundle + + # Upload for tauri updater + aws s3 cp ./deb/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.deb s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64-preview.deb + aws s3 cp ./appimage/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.AppImage s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64-preview.AppImage + env: + AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.DELTA_AWS_REGION }} + AWS_EC2_METADATA_DISABLED: "true" + + ## Upload to github release for stable release + - name: Upload release assert if public provider is github + if: inputs.channel == 'stable' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/release/bundle/latest-linux.yml + asset_name: latest-linux.yml + asset_content_type: text/yaml + + - name: Upload release assert if public provider is github + if: inputs.channel == 'beta' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/release/bundle/beta-linux.yml + asset_name: beta-linux.yml + asset_content_type: text/yaml + - name: Upload release assert if public provider is github + if: inputs.public_provider == 'github' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/release/bundle/appimage/${{ steps.packageinfo.outputs.APPIMAGE_FILE_NAME }} + asset_name: ${{ steps.packageinfo.outputs.APPIMAGE_FILE_NAME }} + asset_content_type: application/octet-stream + + - name: Upload release assert if public provider is github + if: inputs.public_provider == 'github' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/release/bundle/deb/${{ steps.packageinfo.outputs.DEB_FILE_NAME }} + asset_name: ${{ steps.packageinfo.outputs.DEB_FILE_NAME }} + asset_content_type: application/octet-stream diff --git a/.github/workflows/template-tauri-build-linux-x64.yml b/.github/workflows/template-tauri-build-linux-x64.yml new file mode 100644 index 000000000..3baa742ce --- /dev/null +++ b/.github/workflows/template-tauri-build-linux-x64.yml @@ -0,0 +1,296 @@ +name: tauri-build-linux-x64 +on: + workflow_call: + inputs: + ref: + required: true + type: string + default: 'refs/heads/main' + public_provider: + required: true + type: string + default: none + description: 'none: build only, github: build and publish to github, aws s3: build and publish to aws s3' + new_version: + required: true + type: string + default: '' + cortex_api_port: + required: false + type: string + default: "" + upload_url: + required: false + type: string + default: '' + channel: + required: true + type: string + default: 'nightly' + description: 'The channel to use for this job' + secrets: + DELTA_AWS_S3_BUCKET_NAME: + required: false + DELTA_AWS_ACCESS_KEY_ID: + required: false + DELTA_AWS_SECRET_ACCESS_KEY: + required: false + TAURI_SIGNING_PRIVATE_KEY: + required: false + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: + required: false + TAURI_SIGNING_PUBLIC_KEY: + required: false + outputs: + DEB_SIG: + value: ${{ jobs.build-linux-x64.outputs.DEB_SIG }} + APPIMAGE_SIG: + value: ${{ jobs.build-linux-x64.outputs.APPIMAGE_SIG }} + APPIMAGE_FILE_NAME: + value: ${{ jobs.build-linux-x64.outputs.APPIMAGE_FILE_NAME }} +jobs: + build-linux-x64: + runs-on: ubuntu-22.04 + outputs: + DEB_SIG: ${{ steps.packageinfo.outputs.DEB_SIG }} + APPIMAGE_SIG: ${{ steps.packageinfo.outputs.APPIMAGE_SIG }} + APPIMAGE_FILE_NAME: ${{ steps.packageinfo.outputs.APPIMAGE_FILE_NAME }} + environment: production + permissions: + contents: write + steps: + - name: Getting the repo + uses: actions/checkout@v3 + with: + ref: ${{ inputs.ref }} + + - name: Free Disk Space Before Build + run: | + echo "Disk space before cleanup:" + df -h + sudo rm -rf /usr/local/.ghcup + sudo rm -rf /opt/hostedtoolcache/CodeQL + sudo rm -rf /usr/local/lib/android/sdk/ndk + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf /usr/local/share/boost + sudo apt-get clean + echo "Disk space after cleanup:" + df -h + + - name: Replace Icons for Beta Build + if: inputs.channel != 'stable' + shell: bash + run: | + cp .github/scripts/icon-${{ inputs.channel }}.png src-tauri/icons/icon.png + + - name: Installing node + uses: actions/setup-node@v1 + with: + node-version: 20 + + - name: Install jq + uses: dcarbone/install-jq-action@v2.0.1 + + - name: Install ctoml + run: | + cargo install ctoml + + - name: Install Tauri dependecies + run: | + sudo apt update + sudo apt install -y libglib2.0-dev libatk1.0-dev libpango1.0-dev libgtk-3-dev libsoup-3.0-dev libwebkit2gtk-4.1-dev librsvg2-dev libfuse2 + + - name: Update app version base public_provider + run: | + echo "Version: ${{ inputs.new_version }}" + # Update tauri.conf.json + jq --arg version "${{ inputs.new_version }}" '.version = $version | .bundle.createUpdaterArtifacts = true | .bundle.resources = ["resources/themes/**/*", "resources/pre-install/**/*"] | .bundle.externalBin = ["binaries/cortex-server", "resources/bin/uv"]' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json + mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json + if [ "${{ inputs.channel }}" != "stable" ]; then + jq '.bundle.linux.deb.files = {"usr/bin/bun": "resources/bin/bun", "usr/lib/Jan-${{ inputs.channel }}/binaries/engines": "binaries/engines"}' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json + else + jq '.bundle.linux.deb.files = {"usr/bin/bun": "resources/bin/bun", "usr/lib/Jan/binaries/engines": "binaries/engines"}' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json + fi + mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json + jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json + mv /tmp/package.json web/package.json + + ctoml ./src-tauri/Cargo.toml package.version "${{ inputs.new_version }}" + cat ./src-tauri/Cargo.toml + + # Change app name for beta and nightly builds + if [ "${{ inputs.channel }}" != "stable" ]; then + jq '.plugins.updater.endpoints = ["https://delta.jan.ai/${{ inputs.channel }}/latest.json"]' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json + mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json + + chmod +x .github/scripts/rename-tauri-app.sh + .github/scripts/rename-tauri-app.sh ./src-tauri/tauri.conf.json ${{ inputs.channel }} + + cat ./src-tauri/tauri.conf.json + + # Update Cargo.toml + ctoml ./src-tauri/Cargo.toml package.name "Jan-${{ inputs.channel }}" + echo "------------------" + cat ./src-tauri/Cargo.toml + + chmod +x .github/scripts/rename-workspace.sh + .github/scripts/rename-workspace.sh ./package.json ${{ inputs.channel }} + cat ./package.json + fi + - name: Build app + run: | + make build-tauri + # Copy engines and bun to appimage + wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage -O ./appimagetool + chmod +x ./appimagetool + if [ "${{ inputs.channel }}" != "stable" ]; then + cp ./src-tauri/resources/bin/bun ./src-tauri/target/release/bundle/appimage/Jan-${{ inputs.channel }}.AppDir/usr/bin/bun + cp -rf ./src-tauri/binaries/engines ./src-tauri/target/release/bundle/appimage/Jan-${{ inputs.channel }}.AppDir/usr/lib/Jan-${{ inputs.channel }}/engines + ./appimagetool ./src-tauri/target/release/bundle/appimage/Jan-${{ inputs.channel }}.AppDir $(ls ./src-tauri/target/release/bundle/appimage/ | grep AppImage) + else + cp ./src-tauri/resources/bin/bun ./src-tauri/target/release/bundle/appimage/Jan.AppDir/usr/bin/bun + cp -rf ./src-tauri/binaries/engines ./src-tauri/target/release/bundle/appimage/Jan.AppDir/usr/lib/Jan/engines + ./appimagetool ./src-tauri/target/release/bundle/appimage/Jan.AppDir $(ls ./src-tauri/target/release/bundle/appimage/ | grep AppImage) + fi + + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} + POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }} + # CORTEX_API_PORT: ${{ inputs.cortex_api_port }} + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + TAURI_SIGNING_PUBLIC_KEY: ${{ secrets.TAURI_SIGNING_PUBLIC_KEY }} + + # Publish app + + ## Artifacts, for dev and test + - name: Upload Artifact + if: inputs.public_provider != 'github' + uses: actions/upload-artifact@v4 + with: + name: jan-linux-amd64-${{ inputs.new_version }}-deb + path: ./src-tauri/target/release/bundle/deb/*.deb + + - name: Upload Artifact + if: inputs.public_provider != 'github' + uses: actions/upload-artifact@v4 + with: + name: jan-linux-amd64-${{ inputs.new_version }}-AppImage + path: ./src-tauri/target/release/bundle/appimage/*.AppImage + + ## create zip file and latest-linux.yml for linux electron auto updater + - name: Create zip file and latest-linux.yml for linux electron auto updater + id: packageinfo + run: | + cd ./src-tauri/target/release/bundle + + if [ "${{ inputs.channel }}" != "stable" ]; then + DEB_FILE_NAME=Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.deb + APPIMAGE_FILE_NAME=Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.AppImage + DEB_SIG=$(cat deb/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.deb.sig) + APPIMAGE_SIG=$(cat appimage/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.AppImage.sig) + else + DEB_FILE_NAME=Jan_${{ inputs.new_version }}_amd64.deb + APPIMAGE_FILE_NAME=Jan_${{ inputs.new_version }}_amd64.AppImage + DEB_SIG=$(cat deb/Jan_${{ inputs.new_version }}_amd64.deb.sig) + APPIMAGE_SIG=$(cat appimage/Jan_${{ inputs.new_version }}_amd64.AppImage.sig) + fi + + DEB_FILE_SIZE=$(stat -c%s deb/$DEB_FILE_NAME) + APPIMAGE_FILE_SIZE=$(stat -c%s appimage/$APPIMAGE_FILE_NAME) + echo "deb file size: $DEB_FILE_SIZE" + echo "appimage file size: $APPIMAGE_FILE_SIZE" + + DEB_SH512_CHECKSUM=$(python3 ../../../../.github/scripts/electron-checksum.py deb/$DEB_FILE_NAME) + APPIMAGE_SH512_CHECKSUM=$(python3 ../../../../.github/scripts/electron-checksum.py appimage/$APPIMAGE_FILE_NAME) + echo "deb sh512 checksum: $DEB_SH512_CHECKSUM" + echo "appimage sh512 checksum: $APPIMAGE_SH512_CHECKSUM" + + CURRENT_TIME=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ") + echo "releaseDate: $CURRENT_TIME" + + # Create latest-linux.yml file + echo "version: ${{ inputs.new_version }}" > latest-linux.yml + echo "files:" >> latest-linux.yml + echo " - url: $DEB_FILE_NAME" >> latest-linux.yml + echo " sha512: $DEB_SH512_CHECKSUM" >> latest-linux.yml + echo " size: $DEB_FILE_SIZE" >> latest-linux.yml + echo " - url: $APPIMAGE_FILE_NAME" >> latest-linux.yml + echo " sha512: $APPIMAGE_SH512_CHECKSUM" >> latest-linux.yml + echo " size: $APPIMAGE_FILE_SIZE" >> latest-linux.yml + echo "path: $APPIMAGE_FILE_NAME" >> latest-linux.yml + echo "sha512: $APPIMAGE_SH512_CHECKSUM" >> latest-linux.yml + echo "releaseDate: $CURRENT_TIME" >> latest-linux.yml + + cat latest-linux.yml + cp latest-linux.yml beta-linux.yml + + echo "DEB_SIG=$DEB_SIG" >> $GITHUB_OUTPUT + echo "APPIMAGE_SIG=$APPIMAGE_SIG" >> $GITHUB_OUTPUT + echo "DEB_FILE_NAME=$DEB_FILE_NAME" >> $GITHUB_OUTPUT + echo "APPIMAGE_FILE_NAME=$APPIMAGE_FILE_NAME" >> $GITHUB_OUTPUT + + ## Upload to s3 for nightly and beta + - name: upload to aws s3 if public provider is aws + if: inputs.public_provider == 'aws-s3' || inputs.channel == 'beta' + run: | + cd ./src-tauri/target/release/bundle + + # Upload for electron updater + aws s3 cp ./latest-linux.yml s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/latest-linux.yml + aws s3 cp ./beta-linux.yml s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/beta-linux.yml + + # Upload for tauri updater + aws s3 cp ./appimage/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.AppImage s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.AppImage + aws s3 cp ./deb/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.deb s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.deb + env: + AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.DELTA_AWS_REGION }} + AWS_EC2_METADATA_DISABLED: "true" + + ## Upload to github release for stable release + - name: Upload release assert if public provider is github + if: inputs.channel == 'stable' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/release/bundle/latest-linux.yml + asset_name: latest-linux.yml + asset_content_type: text/yaml + + - name: Upload release assert if public provider is github + if: inputs.channel == 'beta' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/release/bundle/beta-linux.yml + asset_name: beta-linux.yml + asset_content_type: text/yaml + - name: Upload release assert if public provider is github + if: inputs.public_provider == 'github' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/release/bundle/appimage/${{ steps.packageinfo.outputs.APPIMAGE_FILE_NAME }} + asset_name: ${{ steps.packageinfo.outputs.APPIMAGE_FILE_NAME }} + asset_content_type: application/octet-stream + + - name: Upload release assert if public provider is github + if: inputs.public_provider == 'github' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/release/bundle/deb/${{ steps.packageinfo.outputs.DEB_FILE_NAME }} + asset_name: ${{ steps.packageinfo.outputs.DEB_FILE_NAME }} + asset_content_type: application/octet-stream diff --git a/.github/workflows/template-tauri-build-macos-preview.yml b/.github/workflows/template-tauri-build-macos-preview.yml new file mode 100644 index 000000000..67195f7f2 --- /dev/null +++ b/.github/workflows/template-tauri-build-macos-preview.yml @@ -0,0 +1,282 @@ +name: tauri-build-macos +on: + workflow_call: + inputs: + ref: + required: true + type: string + default: 'refs/heads/main' + public_provider: + required: true + type: string + default: none + description: 'none: build only, github: build and publish to github, aws s3: build and publish to aws s3' + new_version: + required: true + type: string + default: '' + cortex_api_port: + required: false + type: string + default: "" + upload_url: + required: false + type: string + default: '' + channel: + required: true + type: string + default: 'nightly' + description: 'The channel to use for this job' + secrets: + DELTA_AWS_S3_BUCKET_NAME: + required: false + DELTA_AWS_ACCESS_KEY_ID: + required: false + DELTA_AWS_SECRET_ACCESS_KEY: + required: false + CODE_SIGN_P12_BASE64: + required: false + CODE_SIGN_P12_PASSWORD: + required: false + APPLE_ID: + required: false + APPLE_APP_SPECIFIC_PASSWORD: + required: false + DEVELOPER_ID: + required: false + TAURI_SIGNING_PRIVATE_KEY: + required: false + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: + required: false + TAURI_SIGNING_PUBLIC_KEY: + required: false + outputs: + MAC_UNIVERSAL_SIG: + value: ${{ jobs.build-macos.outputs.MAC_UNIVERSAL_SIG }} + TAR_NAME: + value: ${{ jobs.build-macos.outputs.TAR_NAME }} + +jobs: + build-macos: + runs-on: macos-latest + outputs: + MAC_UNIVERSAL_SIG: ${{ steps.metadata.outputs.MAC_UNIVERSAL_SIG }} + TAR_NAME: ${{ steps.metadata.outputs.TAR_NAME }} + environment: production + permissions: + contents: write + steps: + - name: Getting the repo + uses: actions/checkout@v3 + with: + ref: ${{ inputs.ref }} + - name: Replace Icons for Beta Build + if: inputs.channel != 'stable' + shell: bash + run: | + cp .github/scripts/icon-${{ inputs.channel }}.png src-tauri/icons/icon.png + + - name: Installing node + uses: actions/setup-node@v1 + with: + node-version: 20 + + - name: Install jq + uses: dcarbone/install-jq-action@v2.0.1 + + - name: Install ctoml + run: | + cargo install ctoml + + - name: Create bun and uv universal + run: | + mkdir -p ./src-tauri/resources/bin/ + cd ./src-tauri/resources/bin/ + curl -L -o bun-darwin-x64.zip https://github.com/oven-sh/bun/releases/download/bun-v1.2.10/bun-darwin-x64.zip + curl -L -o bun-darwin-aarch64.zip https://github.com/oven-sh/bun/releases/download/bun-v1.2.10/bun-darwin-aarch64.zip + unzip bun-darwin-x64.zip + unzip bun-darwin-aarch64.zip + lipo -create -output bun-universal-apple-darwin bun-darwin-x64/bun bun-darwin-aarch64/bun + cp -f bun-darwin-aarch64/bun bun-aarch64-apple-darwin + cp -f bun-darwin-x64/bun bun-x86_64-apple-darwin + cp -f bun-universal-apple-darwin bun + + curl -L -o uv-x86_64.tar.gz https://github.com/astral-sh/uv/releases/download/0.6.17/uv-x86_64-apple-darwin.tar.gz + curl -L -o uv-arm64.tar.gz https://github.com/astral-sh/uv/releases/download/0.6.17/uv-aarch64-apple-darwin.tar.gz + tar -xzf uv-x86_64.tar.gz + tar -xzf uv-arm64.tar.gz + mv uv-x86_64-apple-darwin uv-x86_64 + mv uv-aarch64-apple-darwin uv-aarch64 + lipo -create -output uv-universal-apple-darwin uv-x86_64/uv uv-aarch64/uv + cp -f uv-x86_64/uv uv-x86_64-apple-darwin + cp -f uv-aarch64/uv uv-aarch64-apple-darwin + cp -f uv-universal-apple-darwin uv + ls -la + + - name: Update app version based on latest release tag with build number + run: | + echo "Version: ${{ inputs.new_version }}" + # Update tauri.conf.json + jq --arg version "${{ inputs.new_version }}" '.version = $version | .bundle.createUpdaterArtifacts = true' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json + mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json + jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json + mv /tmp/package.json web/package.json + + ctoml ./src-tauri/Cargo.toml package.version "${{ inputs.new_version }}" + cat ./src-tauri/Cargo.toml + + # Change app name for beta and nightly builds + if [ "${{ inputs.channel }}" != "stable" ]; then + jq '.plugins.updater.endpoints = ["https://delta.jan.ai/${{ inputs.channel }}/latest.json"]' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json + mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json + + chmod +x .github/scripts/rename-tauri-app.sh + .github/scripts/rename-tauri-app.sh ./src-tauri/tauri.conf.json ${{ inputs.channel }} + + cat ./src-tauri/tauri.conf.json + + # Update Cargo.toml + ctoml ./src-tauri/Cargo.toml package.name "Jan-${{ inputs.channel }}" + echo "------------------" + cat ./src-tauri/Cargo.toml + + chmod +x .github/scripts/rename-workspace.sh + .github/scripts/rename-workspace.sh ./package.json ${{ inputs.channel }} + cat ./package.json + fi + - name: Get key for notarize + run: base64 -d <<< "$NOTARIZE_P8_BASE64" > /tmp/notary-key.p8 + shell: bash + env: + NOTARIZE_P8_BASE64: ${{ secrets.NOTARIZE_P8_BASE64 }} + + - uses: apple-actions/import-codesign-certs@v2 + continue-on-error: true + with: + p12-file-base64: ${{ secrets.CODE_SIGN_P12_BASE64 }} + p12-password: ${{ secrets.CODE_SIGN_P12_PASSWORD }} + + - name: Build app + run: | + rustup target add x86_64-apple-darwin + make build-tauri + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + APP_PATH: '.' + POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} + POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }} + # CORTEX_API_PORT: ${{ inputs.cortex_api_port }} + APPLE_CERTIFICATE: ${{ secrets.CODE_SIGN_P12_BASE64 }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.CODE_SIGN_P12_PASSWORD }} + APPLE_API_ISSUER: ${{ secrets.NOTARY_ISSUER }} + APPLE_API_KEY: ${{ secrets.NOTARY_KEY_ID }} + APPLE_API_KEY_PATH: /tmp/notary-key.p8 + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + TAURI_SIGNING_PUBLIC_KEY: ${{ secrets.TAURI_SIGNING_PUBLIC_KEY }} + + # Publish app + + ## Artifacts, for dev and test + - name: Upload Artifact + if: inputs.public_provider != 'github' + uses: actions/upload-artifact@v4 + with: + name: jan-${{ inputs.channel }}-tauri-mac-universal-${{ inputs.new_version }}.dmg + path: | + ./src-tauri/target/universal-apple-darwin/release/bundle/dmg/*.dmg + + ## create zip file and latest-mac.yml for mac electron auto updater + - name: create zip file and latest-mac.yml for mac electron auto updater + run: | + cd ./src-tauri/target/universal-apple-darwin/release/bundle/macos + if [ "${{ inputs.channel }}" != "stable" ]; then + zip -r jan-${{ inputs.channel }}-mac-universal-${{ inputs.new_version }}.zip Jan-${{ inputs.channel }}.app + FILE_NAME=jan-${{ inputs.channel }}-mac-universal-${{ inputs.new_version }}.zip + DMG_NAME=Jan-${{ inputs.channel }}_${{ inputs.new_version }}_universal.dmg + MAC_UNIVERSAL_SIG=$(cat Jan-${{ inputs.channel }}.app.tar.gz.sig) + TAR_NAME=Jan-${{ inputs.channel }}.app.tar.gz + else + zip -r jan-mac-universal-${{ inputs.new_version }}.zip Jan.app + FILE_NAME=jan-mac-universal-${{ inputs.new_version }}.zip + MAC_UNIVERSAL_SIG=$(cat Jan.app.tar.gz.sig) + DMG_NAME=Jan_${{ inputs.new_version }}_universal.dmg + TAR_NAME=Jan.app.tar.gz + fi + + echo "::set-output name=MAC_UNIVERSAL_SIG::$MAC_UNIVERSAL_SIG" + echo "::set-output name=FILE_NAME::$FILE_NAME" + echo "::set-output name=DMG_NAME::$DMG_NAME" + echo "::set-output name=TAR_NAME::$TAR_NAME" + id: metadata + ## Upload to s3 for nightly and beta + - name: upload to aws s3 if public provider is aws + if: inputs.public_provider == 'aws-s3' || inputs.channel == 'beta' || inputs.channel == 'nightly' + run: | + cd ./src-tauri/target/universal-apple-darwin/release/bundle + + # Upload for tauri updater + aws s3 cp ./dmg/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_universal.dmg s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_universal-preview.dmg + aws s3 cp ./macos/Jan-${{ inputs.channel }}.app.tar.gz s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/Jan-${{ inputs.channel }}_${{ inputs.new_version }}-preview.app.tar.gz + env: + AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.DELTA_AWS_REGION }} + AWS_EC2_METADATA_DISABLED: "true" + + ## Upload to github release for stable release + - name: Upload release assert if public provider is github + if: inputs.channel == 'stable' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/universal-apple-darwin/release/bundle/macos/latest-mac.yml + asset_name: latest-mac.yml + asset_content_type: text/yaml + + - name: Upload release assert if public provider is github + if: inputs.channel == 'beta' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/universal-apple-darwin/release/bundle/macos/beta-mac.yml + asset_name: beta-mac.yml + asset_content_type: text/yaml + + - name: Upload release assert if public provider is github + if: inputs.public_provider == 'github' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/universal-apple-darwin/release/bundle/macos/${{ steps.metadata.outputs.FILE_NAME }} + asset_name: ${{ steps.metadata.outputs.FILE_NAME }} + asset_content_type: application/gzip + + - name: Upload release assert if public provider is github + if: inputs.public_provider == 'github' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/universal-apple-darwin/release/bundle/dmg/${{ steps.metadata.outputs.DMG_NAME }} + asset_name: ${{ steps.metadata.outputs.DMG_NAME }} + asset_content_type: application/octet-stream + + - name: Upload release assert if public provider is github + if: inputs.public_provider == 'github' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/universal-apple-darwin/release/bundle/macos/${{ steps.metadata.outputs.TAR_NAME }} + asset_name: ${{ steps.metadata.outputs.TAR_NAME }} + asset_content_type: application/gzip \ No newline at end of file diff --git a/.github/workflows/template-tauri-build-macos.yml b/.github/workflows/template-tauri-build-macos.yml new file mode 100644 index 000000000..a68f4487d --- /dev/null +++ b/.github/workflows/template-tauri-build-macos.yml @@ -0,0 +1,310 @@ +name: tauri-build-macos +on: + workflow_call: + inputs: + ref: + required: true + type: string + default: 'refs/heads/main' + public_provider: + required: true + type: string + default: none + description: 'none: build only, github: build and publish to github, aws s3: build and publish to aws s3' + new_version: + required: true + type: string + default: '' + cortex_api_port: + required: false + type: string + default: "" + upload_url: + required: false + type: string + default: '' + channel: + required: true + type: string + default: 'nightly' + description: 'The channel to use for this job' + secrets: + DELTA_AWS_S3_BUCKET_NAME: + required: false + DELTA_AWS_ACCESS_KEY_ID: + required: false + DELTA_AWS_SECRET_ACCESS_KEY: + required: false + CODE_SIGN_P12_BASE64: + required: false + CODE_SIGN_P12_PASSWORD: + required: false + APPLE_ID: + required: false + APPLE_APP_SPECIFIC_PASSWORD: + required: false + DEVELOPER_ID: + required: false + TAURI_SIGNING_PRIVATE_KEY: + required: false + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: + required: false + TAURI_SIGNING_PUBLIC_KEY: + required: false + outputs: + MAC_UNIVERSAL_SIG: + value: ${{ jobs.build-macos.outputs.MAC_UNIVERSAL_SIG }} + TAR_NAME: + value: ${{ jobs.build-macos.outputs.TAR_NAME }} + +jobs: + build-macos: + runs-on: macos-latest + outputs: + MAC_UNIVERSAL_SIG: ${{ steps.metadata.outputs.MAC_UNIVERSAL_SIG }} + TAR_NAME: ${{ steps.metadata.outputs.TAR_NAME }} + environment: production + permissions: + contents: write + steps: + - name: Getting the repo + uses: actions/checkout@v3 + with: + ref: ${{ inputs.ref }} + - name: Replace Icons for Beta Build + if: inputs.channel != 'stable' + shell: bash + run: | + cp .github/scripts/icon-${{ inputs.channel }}.png src-tauri/icons/icon.png + + - name: Installing node + uses: actions/setup-node@v1 + with: + node-version: 20 + + - name: Install jq + uses: dcarbone/install-jq-action@v2.0.1 + + - name: Install ctoml + run: | + cargo install ctoml + + - name: Create bun and uv universal + run: | + mkdir -p ./src-tauri/resources/bin/ + cd ./src-tauri/resources/bin/ + curl -L -o bun-darwin-x64.zip https://github.com/oven-sh/bun/releases/download/bun-v1.2.10/bun-darwin-x64.zip + curl -L -o bun-darwin-aarch64.zip https://github.com/oven-sh/bun/releases/download/bun-v1.2.10/bun-darwin-aarch64.zip + unzip bun-darwin-x64.zip + unzip bun-darwin-aarch64.zip + lipo -create -output bun-universal-apple-darwin bun-darwin-x64/bun bun-darwin-aarch64/bun + cp -f bun-darwin-aarch64/bun bun-aarch64-apple-darwin + cp -f bun-darwin-x64/bun bun-x86_64-apple-darwin + cp -f bun-universal-apple-darwin bun + + curl -L -o uv-x86_64.tar.gz https://github.com/astral-sh/uv/releases/download/0.6.17/uv-x86_64-apple-darwin.tar.gz + curl -L -o uv-arm64.tar.gz https://github.com/astral-sh/uv/releases/download/0.6.17/uv-aarch64-apple-darwin.tar.gz + tar -xzf uv-x86_64.tar.gz + tar -xzf uv-arm64.tar.gz + mv uv-x86_64-apple-darwin uv-x86_64 + mv uv-aarch64-apple-darwin uv-aarch64 + lipo -create -output uv-universal-apple-darwin uv-x86_64/uv uv-aarch64/uv + cp -f uv-x86_64/uv uv-x86_64-apple-darwin + cp -f uv-aarch64/uv uv-aarch64-apple-darwin + cp -f uv-universal-apple-darwin uv + ls -la + + - name: Update app version based on latest release tag with build number + run: | + echo "Version: ${{ inputs.new_version }}" + # Update tauri.conf.json + jq --arg version "${{ inputs.new_version }}" '.version = $version | .bundle.createUpdaterArtifacts = true' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json + mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json + jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json + mv /tmp/package.json web/package.json + + ctoml ./src-tauri/Cargo.toml package.version "${{ inputs.new_version }}" + cat ./src-tauri/Cargo.toml + + # Change app name for beta and nightly builds + if [ "${{ inputs.channel }}" != "stable" ]; then + jq '.plugins.updater.endpoints = ["https://delta.jan.ai/${{ inputs.channel }}/latest.json"]' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json + mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json + + chmod +x .github/scripts/rename-tauri-app.sh + .github/scripts/rename-tauri-app.sh ./src-tauri/tauri.conf.json ${{ inputs.channel }} + + cat ./src-tauri/tauri.conf.json + + # Update Cargo.toml + ctoml ./src-tauri/Cargo.toml package.name "Jan-${{ inputs.channel }}" + echo "------------------" + cat ./src-tauri/Cargo.toml + + chmod +x .github/scripts/rename-workspace.sh + .github/scripts/rename-workspace.sh ./package.json ${{ inputs.channel }} + cat ./package.json + fi + - name: Get key for notarize + run: base64 -d <<< "$NOTARIZE_P8_BASE64" > /tmp/notary-key.p8 + shell: bash + env: + NOTARIZE_P8_BASE64: ${{ secrets.NOTARIZE_P8_BASE64 }} + + - uses: apple-actions/import-codesign-certs@v2 + continue-on-error: true + with: + p12-file-base64: ${{ secrets.CODE_SIGN_P12_BASE64 }} + p12-password: ${{ secrets.CODE_SIGN_P12_PASSWORD }} + + - name: Build app + run: | + rustup target add x86_64-apple-darwin + make build-tauri + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + APP_PATH: '.' + POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} + POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }} + # CORTEX_API_PORT: ${{ inputs.cortex_api_port }} + APPLE_CERTIFICATE: ${{ secrets.CODE_SIGN_P12_BASE64 }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.CODE_SIGN_P12_PASSWORD }} + APPLE_API_ISSUER: ${{ secrets.NOTARY_ISSUER }} + APPLE_API_KEY: ${{ secrets.NOTARY_KEY_ID }} + APPLE_API_KEY_PATH: /tmp/notary-key.p8 + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + TAURI_SIGNING_PUBLIC_KEY: ${{ secrets.TAURI_SIGNING_PUBLIC_KEY }} + + # Publish app + + ## Artifacts, for dev and test + - name: Upload Artifact + if: inputs.public_provider != 'github' + uses: actions/upload-artifact@v4 + with: + name: jan-${{ inputs.channel }}-mac-universal-${{ inputs.new_version }}.dmg + path: | + ./src-tauri/target/universal-apple-darwin/release/bundle/dmg/*.dmg + + + ## create zip file and latest-mac.yml for mac electron auto updater + - name: create zip file and latest-mac.yml for mac electron auto updater + run: | + cd ./src-tauri/target/universal-apple-darwin/release/bundle/macos + if [ "${{ inputs.channel }}" != "stable" ]; then + zip -r jan-${{ inputs.channel }}-mac-universal-${{ inputs.new_version }}.zip Jan-${{ inputs.channel }}.app + FILE_NAME=jan-${{ inputs.channel }}-mac-universal-${{ inputs.new_version }}.zip + DMG_NAME=Jan-${{ inputs.channel }}_${{ inputs.new_version }}_universal.dmg + MAC_UNIVERSAL_SIG=$(cat Jan-${{ inputs.channel }}.app.tar.gz.sig) + TAR_NAME=Jan-${{ inputs.channel }}.app.tar.gz + else + zip -r jan-mac-universal-${{ inputs.new_version }}.zip Jan.app + FILE_NAME=jan-mac-universal-${{ inputs.new_version }}.zip + MAC_UNIVERSAL_SIG=$(cat Jan.app.tar.gz.sig) + DMG_NAME=Jan_${{ inputs.new_version }}_universal.dmg + TAR_NAME=Jan.app.tar.gz + fi + + FILE_SIZE=$(stat -f%z $FILE_NAME) + echo "size: $FILE_SIZE" + + SH512_CHECKSUM=$(python3 ../../../../../../.github/scripts/electron-checksum.py $FILE_NAME) + echo "sha512: $SH512_CHECKSUM" + CURRENT_TIME=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ") + echo "releaseDate: $CURRENT_TIME" + + # Create latest-mac.yml file + echo "version: ${{ inputs.new_version }}" > latest-mac.yml + echo "files:" >> latest-mac.yml + echo " - url: $FILE_NAME" >> latest-mac.yml + echo " sha512: $SH512_CHECKSUM" >> latest-mac.yml + echo " size: $FILE_NAME" >> latest-mac.yml + echo "path: $FILE_NAME" >> latest-mac.yml + echo "sha512: $SH512_CHECKSUM" >> latest-mac.yml + echo "releaseDate: $CURRENT_TIME" >> latest-mac.yml + + cat latest-mac.yml + cp latest-mac.yml beta-mac.yml + + echo "::set-output name=MAC_UNIVERSAL_SIG::$MAC_UNIVERSAL_SIG" + echo "::set-output name=FILE_NAME::$FILE_NAME" + echo "::set-output name=DMG_NAME::$DMG_NAME" + echo "::set-output name=TAR_NAME::$TAR_NAME" + id: metadata + + ## Upload to s3 for nightly and beta + - name: upload to aws s3 if public provider is aws + if: inputs.public_provider == 'aws-s3' || inputs.channel == 'beta' + run: | + cd ./src-tauri/target/universal-apple-darwin/release/bundle + + # Upload for electron updater + aws s3 cp ./macos/latest-mac.yml s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/latest-mac.yml + aws s3 cp ./macos/beta-mac.yml s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/beta-mac.yml + aws s3 cp ./macos/jan-${{ inputs.channel }}-mac-universal-${{ inputs.new_version }}.zip s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/jan-${{ inputs.channel }}-mac-universal-${{ inputs.new_version }}.zip + + # Upload for tauri updater + aws s3 cp ./dmg/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_universal.dmg s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_universal.dmg + aws s3 cp ./macos/Jan-${{ inputs.channel }}.app.tar.gz s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}//Jan-${{ inputs.channel }}_${{ inputs.new_version }}.app.tar.gz + env: + AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.DELTA_AWS_REGION }} + AWS_EC2_METADATA_DISABLED: "true" + + ## Upload to github release for stable release + - name: Upload release assert if public provider is github + if: inputs.channel == 'stable' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/universal-apple-darwin/release/bundle/macos/latest-mac.yml + asset_name: latest-mac.yml + asset_content_type: text/yaml + + - name: Upload release assert if public provider is github + if: inputs.channel == 'beta' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/universal-apple-darwin/release/bundle/macos/beta-mac.yml + asset_name: beta-mac.yml + asset_content_type: text/yaml + + - name: Upload release assert if public provider is github + if: inputs.public_provider == 'github' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/universal-apple-darwin/release/bundle/macos/${{ steps.metadata.outputs.FILE_NAME }} + asset_name: ${{ steps.metadata.outputs.FILE_NAME }} + asset_content_type: application/gzip + + - name: Upload release assert if public provider is github + if: inputs.public_provider == 'github' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/universal-apple-darwin/release/bundle/dmg/${{ steps.metadata.outputs.DMG_NAME }} + asset_name: ${{ steps.metadata.outputs.DMG_NAME }} + asset_content_type: application/octet-stream + + - name: Upload release assert if public provider is github + if: inputs.public_provider == 'github' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/universal-apple-darwin/release/bundle/macos/${{ steps.metadata.outputs.TAR_NAME }} + asset_name: ${{ steps.metadata.outputs.TAR_NAME }} + asset_content_type: application/gzip \ No newline at end of file diff --git a/.github/workflows/template-tauri-build-windows-x64-preview.yml b/.github/workflows/template-tauri-build-windows-x64-preview.yml new file mode 100644 index 000000000..01a0d07fb --- /dev/null +++ b/.github/workflows/template-tauri-build-windows-x64-preview.yml @@ -0,0 +1,259 @@ +name: tauri-build-windows-x64 +on: + workflow_call: + inputs: + ref: + required: true + type: string + default: "refs/heads/main" + public_provider: + required: true + type: string + default: none + description: "none: build only, github: build and publish to github, aws s3: build and publish to aws s3" + new_version: + required: true + type: string + default: '' + cortex_api_port: + required: false + type: string + default: "" + upload_url: + required: false + type: string + default: '' + channel: + required: true + type: string + default: 'nightly' + description: 'The channel to use for this job' + secrets: + DELTA_AWS_S3_BUCKET_NAME: + required: false + DELTA_AWS_ACCESS_KEY_ID: + required: false + DELTA_AWS_SECRET_ACCESS_KEY: + required: false + AZURE_KEY_VAULT_URI: + required: false + AZURE_CLIENT_ID: + required: false + AZURE_TENANT_ID: + required: false + AZURE_CLIENT_SECRET: + required: false + AZURE_CERT_NAME: + required: false + TAURI_SIGNING_PRIVATE_KEY: + required: false + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: + required: false + TAURI_SIGNING_PUBLIC_KEY: + required: false + outputs: + WIN_SIG: + value: ${{ jobs.build-windows-x64.outputs.WIN_SIG }} + FILE_NAME: + value: ${{ jobs.build-windows-x64.outputs.FILE_NAME }} + +jobs: + build-windows-x64: + runs-on: windows-latest + outputs: + WIN_SIG: ${{ steps.metadata.outputs.WIN_SIG }} + FILE_NAME: ${{ steps.metadata.outputs.FILE_NAME }} + permissions: + contents: write + steps: + - name: Getting the repo + uses: actions/checkout@v3 + with: + ref: ${{ inputs.ref }} + + - name: Replace Icons for Beta Build + if: inputs.channel != 'stable' + shell: bash + run: | + cp .github/scripts/icon-${{ inputs.channel }}.png src-tauri/icons/icon.png + + - name: Installing node + uses: actions/setup-node@v1 + with: + node-version: 20 + + - name: Install jq + uses: dcarbone/install-jq-action@v2.0.1 + + - name: Install ctoml + run: | + cargo install ctoml + + - name: Update app version base on tag + id: version_update + shell: bash + run: | + echo "Version: ${{ inputs.new_version }}" + # Update tauri.conf.json + jq --arg version "${{ inputs.new_version }}" '.version = $version | .bundle.createUpdaterArtifacts = true' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json + mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json + jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json + mv /tmp/package.json web/package.json + + ctoml ./src-tauri/Cargo.toml package.version "${{ inputs.new_version }}" + cat ./src-tauri/Cargo.toml + + generate_build_version() { + ### Examble + ### input 0.5.6 output will be 0.5.6 and 0.5.6.0 + ### input 0.5.6-rc2-beta output will be 0.5.6 and 0.5.6.2 + ### input 0.5.6-1213 output will be 0.5.6 and and 0.5.6.1213 + local new_version="$1" + local base_version + local t_value + + # Check if it has a "-" + if [[ "$new_version" == *-* ]]; then + base_version="${new_version%%-*}" # part before - + suffix="${new_version#*-}" # part after - + + # Check if it is rcX-beta + if [[ "$suffix" =~ ^rc([0-9]+)-beta$ ]]; then + t_value="${BASH_REMATCH[1]}" + else + t_value="$suffix" + fi + else + base_version="$new_version" + t_value="0" + fi + + # Export two values + new_base_version="$base_version" + new_build_version="${base_version}.${t_value}" + } + generate_build_version ${{ inputs.new_version }} + sed -i "s/jan_version/$new_base_version/g" ./src-tauri/tauri.bundle.windows.nsis.template + sed -i "s/jan_build/$new_build_version/g" ./src-tauri/tauri.bundle.windows.nsis.template + echo "------------------" + cat ./src-tauri/tauri.bundle.windows.nsis.template + + # Change app name for beta and nightly builds + if [ "${{ inputs.channel }}" != "stable" ]; then + jq '.plugins.updater.endpoints = ["https://delta.jan.ai/${{ inputs.channel }}/latest.json"]' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json + mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json + + chmod +x .github/scripts/rename-tauri-app.sh + .github/scripts/rename-tauri-app.sh ./src-tauri/tauri.conf.json ${{ inputs.channel }} + + cat ./src-tauri/tauri.conf.json + + # Update Cargo.toml + ctoml ./src-tauri/Cargo.toml package.name "Jan-${{ inputs.channel }}" + echo "------------------" + cat ./src-tauri/Cargo.toml + + chmod +x .github/scripts/rename-workspace.sh + .github/scripts/rename-workspace.sh ./package.json ${{ inputs.channel }} + cat ./package.json + + sed -i "s/jan_productname/Jan-${{ inputs.channel }}/g" ./src-tauri/tauri.bundle.windows.nsis.template + sed -i "s/jan_mainbinaryname/jan-${{ inputs.channel }}/g" ./src-tauri/tauri.bundle.windows.nsis.template + fi + + - name: Install AzureSignTool + run: | + dotnet tool install --global --version 6.0.0 AzureSignTool + + - name: Build app + shell: bash + run: | + make build-tauri + env: + AZURE_KEY_VAULT_URI: ${{ secrets.AZURE_KEY_VAULT_URI }} + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} + AZURE_CERT_NAME: ${{ secrets.AZURE_CERT_NAME }} + AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: auto + AWS_EC2_METADATA_DISABLED: "true" + AWS_MAX_ATTEMPTS: "5" + POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} + POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }} + # CORTEX_API_PORT: ${{ inputs.cortex_api_port }} + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + TAURI_SIGNING_PUBLIC_KEY: ${{ secrets.TAURI_SIGNING_PUBLIC_KEY }} + + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: jan-${{ inputs.channel }}-tauri-windows-${{ inputs.new_version }} + path: | + ./src-tauri/target/release/bundle/nsis/*.exe + + ## create zip file and latest.yml for windows electron auto updater + - name: create zip file and latest.yml for windows electron auto updater + shell: bash + run: | + cd ./src-tauri/target/release/bundle/nsis + if [ "${{ inputs.channel }}" != "stable" ]; then + FILE_NAME=Jan-${{ inputs.channel }}_${{ inputs.new_version }}_x64-setup.exe + WIN_SIG=$(cat Jan-${{ inputs.channel }}_${{ inputs.new_version }}_x64-setup.exe.sig) + else + FILE_NAME=Jan_${{ inputs.new_version }}_x64-setup.exe + WIN_SIG=$(cat Jan_${{ inputs.new_version }}_x64-setup.exe.sig) + fi + + echo "::set-output name=WIN_SIG::$WIN_SIG" + echo "::set-output name=FILE_NAME::$FILE_NAME" + id: metadata + + ## Upload to s3 for nightly and beta + - name: upload to aws s3 if public provider is aws + shell: bash + if: inputs.public_provider == 'aws-s3' || inputs.channel == 'beta' || inputs.channel == 'nightly' + run: | + cd ./src-tauri/target/release/bundle/nsis + aws s3 cp ./Jan-${{ inputs.channel }}_${{ inputs.new_version }}_x64-setup.exe s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_x64-setup-preview.exe + env: + AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.DELTA_AWS_REGION }} + AWS_EC2_METADATA_DISABLED: "true" + + ## Upload to github release for stable release + - name: Upload release assert if public provider is github + if: inputs.channel == 'stable' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/release/bundle/nsis/latest.yml + asset_name: latest.yml + asset_content_type: text/yaml + + - name: Upload release assert if public provider is github + if: inputs.channel == 'beta' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/release/bundle/nsis/beta.yml + asset_name: beta.yml + asset_content_type: text/yaml + + - name: Upload release assert if public provider is github + if: inputs.public_provider == 'github' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/release/bundle/nsis/${{ steps.metadata.outputs.FILE_NAME }} + asset_name: ${{ steps.metadata.outputs.FILE_NAME }} + asset_content_type: application/octet-stream \ No newline at end of file diff --git a/.github/workflows/template-tauri-build-windows-x64.yml b/.github/workflows/template-tauri-build-windows-x64.yml new file mode 100644 index 000000000..dcccf3001 --- /dev/null +++ b/.github/workflows/template-tauri-build-windows-x64.yml @@ -0,0 +1,286 @@ +name: tauri-build-windows-x64 +on: + workflow_call: + inputs: + ref: + required: true + type: string + default: "refs/heads/main" + public_provider: + required: true + type: string + default: none + description: "none: build only, github: build and publish to github, aws s3: build and publish to aws s3" + new_version: + required: true + type: string + default: '' + cortex_api_port: + required: false + type: string + default: "" + upload_url: + required: false + type: string + default: '' + channel: + required: true + type: string + default: 'nightly' + description: 'The channel to use for this job' + secrets: + DELTA_AWS_S3_BUCKET_NAME: + required: false + DELTA_AWS_ACCESS_KEY_ID: + required: false + DELTA_AWS_SECRET_ACCESS_KEY: + required: false + AZURE_KEY_VAULT_URI: + required: false + AZURE_CLIENT_ID: + required: false + AZURE_TENANT_ID: + required: false + AZURE_CLIENT_SECRET: + required: false + AZURE_CERT_NAME: + required: false + TAURI_SIGNING_PRIVATE_KEY: + required: false + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: + required: false + TAURI_SIGNING_PUBLIC_KEY: + required: false + outputs: + WIN_SIG: + value: ${{ jobs.build-windows-x64.outputs.WIN_SIG }} + FILE_NAME: + value: ${{ jobs.build-windows-x64.outputs.FILE_NAME }} + +jobs: + build-windows-x64: + runs-on: windows-latest + outputs: + WIN_SIG: ${{ steps.metadata.outputs.WIN_SIG }} + FILE_NAME: ${{ steps.metadata.outputs.FILE_NAME }} + permissions: + contents: write + steps: + - name: Getting the repo + uses: actions/checkout@v3 + with: + ref: ${{ inputs.ref }} + + - name: Replace Icons for Beta Build + if: inputs.channel != 'stable' + shell: bash + run: | + cp .github/scripts/icon-${{ inputs.channel }}.png src-tauri/icons/icon.png + + - name: Installing node + uses: actions/setup-node@v1 + with: + node-version: 20 + + - name: Install jq + uses: dcarbone/install-jq-action@v2.0.1 + + - name: Install ctoml + run: | + cargo install ctoml + + - name: Update app version base on tag + id: version_update + shell: bash + run: | + echo "Version: ${{ inputs.new_version }}" + # Update tauri.conf.json + jq --arg version "${{ inputs.new_version }}" '.version = $version | .bundle.createUpdaterArtifacts = true' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json + mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json + jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json + mv /tmp/package.json web/package.json + + ctoml ./src-tauri/Cargo.toml package.version "${{ inputs.new_version }}" + cat ./src-tauri/Cargo.toml + + generate_build_version() { + ### Examble + ### input 0.5.6 output will be 0.5.6 and 0.5.6.0 + ### input 0.5.6-rc2-beta output will be 0.5.6 and 0.5.6.2 + ### input 0.5.6-1213 output will be 0.5.6 and and 0.5.6.1213 + local new_version="$1" + local base_version + local t_value + + # Check if it has a "-" + if [[ "$new_version" == *-* ]]; then + base_version="${new_version%%-*}" # part before - + suffix="${new_version#*-}" # part after - + + # Check if it is rcX-beta + if [[ "$suffix" =~ ^rc([0-9]+)-beta$ ]]; then + t_value="${BASH_REMATCH[1]}" + else + t_value="$suffix" + fi + else + base_version="$new_version" + t_value="0" + fi + + # Export two values + new_base_version="$base_version" + new_build_version="${base_version}.${t_value}" + } + generate_build_version ${{ inputs.new_version }} + sed -i "s/jan_version/$new_base_version/g" ./src-tauri/tauri.bundle.windows.nsis.template + sed -i "s/jan_build/$new_build_version/g" ./src-tauri/tauri.bundle.windows.nsis.template + echo "------------------" + cat ./src-tauri/tauri.bundle.windows.nsis.template + + # Change app name for beta and nightly builds + if [ "${{ inputs.channel }}" != "stable" ]; then + jq '.plugins.updater.endpoints = ["https://delta.jan.ai/${{ inputs.channel }}/latest.json"]' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json + mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json + + chmod +x .github/scripts/rename-tauri-app.sh + .github/scripts/rename-tauri-app.sh ./src-tauri/tauri.conf.json ${{ inputs.channel }} + + cat ./src-tauri/tauri.conf.json + + # Update Cargo.toml + ctoml ./src-tauri/Cargo.toml package.name "Jan-${{ inputs.channel }}" + echo "------------------" + cat ./src-tauri/Cargo.toml + + chmod +x .github/scripts/rename-workspace.sh + .github/scripts/rename-workspace.sh ./package.json ${{ inputs.channel }} + cat ./package.json + + sed -i "s/jan_productname/Jan-${{ inputs.channel }}/g" ./src-tauri/tauri.bundle.windows.nsis.template + sed -i "s/jan_mainbinaryname/jan-${{ inputs.channel }}/g" ./src-tauri/tauri.bundle.windows.nsis.template + fi + + - name: Install AzureSignTool + run: | + dotnet tool install --global --version 6.0.0 AzureSignTool + + - name: Build app + shell: bash + run: | + make build-tauri + env: + AZURE_KEY_VAULT_URI: ${{ secrets.AZURE_KEY_VAULT_URI }} + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} + AZURE_CERT_NAME: ${{ secrets.AZURE_CERT_NAME }} + AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: auto + AWS_EC2_METADATA_DISABLED: "true" + AWS_MAX_ATTEMPTS: "5" + POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} + POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }} + # CORTEX_API_PORT: ${{ inputs.cortex_api_port }} + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + TAURI_SIGNING_PUBLIC_KEY: ${{ secrets.TAURI_SIGNING_PUBLIC_KEY }} + + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: jan-windows-${{ inputs.new_version }} + path: | + ./src-tauri/target/release/bundle/nsis/*.exe + + ## create zip file and latest.yml for windows electron auto updater + - name: create zip file and latest.yml for windows electron auto updater + shell: bash + run: | + cd ./src-tauri/target/release/bundle/nsis + if [ "${{ inputs.channel }}" != "stable" ]; then + FILE_NAME=Jan-${{ inputs.channel }}_${{ inputs.new_version }}_x64-setup.exe + WIN_SIG=$(cat Jan-${{ inputs.channel }}_${{ inputs.new_version }}_x64-setup.exe.sig) + else + FILE_NAME=Jan_${{ inputs.new_version }}_x64-setup.exe + WIN_SIG=$(cat Jan_${{ inputs.new_version }}_x64-setup.exe.sig) + fi + + FILE_SIZE=$(stat -c %s $FILE_NAME) + echo "size: $FILE_SIZE" + + SH512_CHECKSUM=$(python3 ../../../../../.github/scripts/electron-checksum.py $FILE_NAME) + echo "sha512: $SH512_CHECKSUM" + CURRENT_TIME=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ") + echo "releaseDate: $CURRENT_TIME" + + # Create latest.yml file + echo "version: ${{ inputs.new_version }}" > latest.yml + echo "files:" >> latest.yml + echo " - url: $FILE_NAME" >> latest.yml + echo " sha512: $SH512_CHECKSUM" >> latest.yml + echo " size: $FILE_NAME" >> latest.yml + echo "path: $FILE_NAME" >> latest.yml + echo "sha512: $SH512_CHECKSUM" >> latest.yml + echo "releaseDate: $CURRENT_TIME" >> latest.yml + + cat latest.yml + cp latest.yml beta.yml + + echo "::set-output name=WIN_SIG::$WIN_SIG" + echo "::set-output name=FILE_NAME::$FILE_NAME" + id: metadata + + ## Upload to s3 for nightly and beta + - name: upload to aws s3 if public provider is aws + shell: bash + if: inputs.public_provider == 'aws-s3' || inputs.channel == 'beta' + run: | + cd ./src-tauri/target/release/bundle/nsis + + # Upload for electron updater + aws s3 cp ./latest.yml s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/latest.yml + aws s3 cp ./beta.yml s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/beta.yml + + # Upload for tauri updater + aws s3 cp ./${{ steps.metadata.outputs.FILE_NAME }} s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/${{ steps.metadata.outputs.FILE_NAME }} + env: + AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.DELTA_AWS_REGION }} + AWS_EC2_METADATA_DISABLED: "true" + + ## Upload to github release for stable release + - name: Upload release assert if public provider is github + if: inputs.channel == 'stable' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/release/bundle/nsis/latest.yml + asset_name: latest.yml + asset_content_type: text/yaml + + - name: Upload release assert if public provider is github + if: inputs.channel == 'beta' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/release/bundle/nsis/beta.yml + asset_name: beta.yml + asset_content_type: text/yaml + + - name: Upload release assert if public provider is github + if: inputs.public_provider == 'github' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/release/bundle/nsis/${{ steps.metadata.outputs.FILE_NAME }} + asset_name: ${{ steps.metadata.outputs.FILE_NAME }} + asset_content_type: application/octet-stream \ No newline at end of file diff --git a/Makefile b/Makefile index 176140a64..2803641fa 100644 --- a/Makefile +++ b/Makefile @@ -120,6 +120,9 @@ build-and-publish: check-file-counts build: check-file-counts yarn build +build-tauri: check-file-counts + yarn build-tauri + clean: ifeq ($(OS),Windows_NT) -powershell -Command "Get-ChildItem -Path . -Include node_modules, .next, dist, build, out, .turbo, .yarn -Recurse -Directory | Remove-Item -Recurse -Force" diff --git a/extensions/engine-management-extension/rolldown.config.mjs b/extensions/engine-management-extension/rolldown.config.mjs index 98bbf1d7f..a385f1efd 100644 --- a/extensions/engine-management-extension/rolldown.config.mjs +++ b/extensions/engine-management-extension/rolldown.config.mjs @@ -15,7 +15,7 @@ export default defineConfig([ `http://127.0.0.1:${process.env.CORTEX_API_PORT ?? '39291'}` ), PLATFORM: JSON.stringify(process.platform), - CORTEX_ENGINE_VERSION: JSON.stringify('b5350'), + CORTEX_ENGINE_VERSION: JSON.stringify('b5371'), DEFAULT_REMOTE_ENGINES: JSON.stringify(engines), DEFAULT_REMOTE_MODELS: JSON.stringify(models), DEFAULT_REQUEST_PAYLOAD_TRANSFORM: JSON.stringify( @@ -38,7 +38,7 @@ export default defineConfig([ file: 'dist/node/index.cjs.js', }, define: { - CORTEX_ENGINE_VERSION: JSON.stringify('b5350'), + CORTEX_ENGINE_VERSION: JSON.stringify('b5371'), }, }, ]) diff --git a/extensions/inference-cortex-extension/download.bat b/extensions/inference-cortex-extension/download.bat index 775602b81..ec6e68560 100644 --- a/extensions/inference-cortex-extension/download.bat +++ b/extensions/inference-cortex-extension/download.bat @@ -2,7 +2,7 @@ set BIN_PATH=./bin set SHARED_PATH=./../../electron/shared set /p CORTEX_VERSION=<./bin/version.txt -set ENGINE_VERSION=b5350 +set ENGINE_VERSION=b5371 @REM Download llama.cpp binaries set DOWNLOAD_URL=https://github.com/menloresearch/llama.cpp/releases/download/%ENGINE_VERSION%/llama-%ENGINE_VERSION%-bin-win @@ -15,11 +15,11 @@ call .\node_modules\.bin\download %DOWNLOAD_URL%-avx2-cuda-cu12.0-x64.tar.gz -e call .\node_modules\.bin\download %DOWNLOAD_URL%-avx2-cuda-cu11.7-x64.tar.gz -e --strip 2 -o %SHARED_PATH%/engines/llama.cpp/win-avx2-cuda-cu11.7-x64/%ENGINE_VERSION% call .\node_modules\.bin\download %DOWNLOAD_URL%-noavx-cuda-cu12.0-x64.tar.gz -e --strip 2 -o %SHARED_PATH%/engines/llama.cpp/win-noavx-cuda-cu12.0-x64/%ENGINE_VERSION% call .\node_modules\.bin\download %DOWNLOAD_URL%-noavx-cuda-cu11.7-x64.tar.gz -e --strip 2 -o %SHARED_PATH%/engines/llama.cpp/win-noavx-cuda-cu11.7-x64/%ENGINE_VERSION% -@REM call .\node_modules\.bin\download %DOWNLOAD_GGML_URL%-noavx-x64.zip -e --strip 1 -o %SHARED_PATH%/engines/llama.cpp/win-noavx-x64/%ENGINE_VERSION% -@REM call .\node_modules\.bin\download %DOWNLOAD_GGML_URL%-avx-x64.zip -e --strip 1 -o %SHARED_PATH%/engines/llama.cpp/win-avx-x64/%ENGINE_VERSION% -@REM call .\node_modules\.bin\download %DOWNLOAD_GGML_URL%-avx2-x64.zip -e --strip 1 -o %SHARED_PATH%/engines/llama.cpp/win-avx2-x64/%ENGINE_VERSION% -@REM call .\node_modules\.bin\download %DOWNLOAD_GGML_URL%-avx512-x64.zip -e --strip 1 -o %SHARED_PATH%/engines/llama.cpp/win-avx512-x64/%ENGINE_VERSION% -@REM call .\node_modules\.bin\download %DOWNLOAD_GGML_URL%-vulkan-x64.zip -e --strip 1 -o %SHARED_PATH%/engines/llama.cpp/win-vulkan-x64/%ENGINE_VERSION% +call .\node_modules\.bin\download %DOWNLOAD_URL%-noavx-x64.tar.gz -e --strip 2 -o %SHARED_PATH%/engines/llama.cpp/win-noavx-x64/%ENGINE_VERSION% +call .\node_modules\.bin\download %DOWNLOAD_URL%-avx-x64.tar.gz -e --strip 2 -o %SHARED_PATH%/engines/llama.cpp/win-avx-x64/%ENGINE_VERSION% +call .\node_modules\.bin\download %DOWNLOAD_URL%-avx2-x64.tar.gz -e --strip 2 -o %SHARED_PATH%/engines/llama.cpp/win-avx2-x64/%ENGINE_VERSION% +call .\node_modules\.bin\download %DOWNLOAD_URL%-avx512-x64.tar.gz -e --strip 2 -o %SHARED_PATH%/engines/llama.cpp/win-avx512-x64/%ENGINE_VERSION% +call .\node_modules\.bin\download %DOWNLOAD_GGML_URL%-vulkan-x64.zip -e --strip 1 -o %SHARED_PATH%/engines/llama.cpp/win-vulkan-x64/%ENGINE_VERSION% call .\node_modules\.bin\download %CUDA_DOWNLOAD_URL%/cudart-llama-bin-win-cu12.0-x64.tar.gz -e --strip 1 -o %BIN_PATH% call .\node_modules\.bin\download %CUDA_DOWNLOAD_URL%/cudart-llama-bin-win-cu11.7-x64.tar.gz -e --strip 1 -o %BIN_PATH% diff --git a/extensions/inference-cortex-extension/download.sh b/extensions/inference-cortex-extension/download.sh index 47ee14d65..6d3ea4639 100755 --- a/extensions/inference-cortex-extension/download.sh +++ b/extensions/inference-cortex-extension/download.sh @@ -2,7 +2,7 @@ # Read CORTEX_VERSION CORTEX_VERSION=$(cat ./bin/version.txt) -ENGINE_VERSION=b5350 +ENGINE_VERSION=b5371 CORTEX_RELEASE_URL="https://github.com/menloresearch/cortex.cpp/releases/download" ENGINE_DOWNLOAD_URL=https://github.com/menloresearch/llama.cpp/releases/download/${ENGINE_VERSION}/llama-${ENGINE_VERSION}-bin CUDA_DOWNLOAD_URL=https://github.com/menloresearch/llama.cpp/releases/download/${ENGINE_VERSION} diff --git a/extensions/inference-cortex-extension/rolldown.config.mjs b/extensions/inference-cortex-extension/rolldown.config.mjs index ae6e42331..6c0df4933 100644 --- a/extensions/inference-cortex-extension/rolldown.config.mjs +++ b/extensions/inference-cortex-extension/rolldown.config.mjs @@ -19,7 +19,7 @@ export default defineConfig([ CORTEX_SOCKET_URL: JSON.stringify( `ws://127.0.0.1:${process.env.CORTEX_API_PORT ?? '39291'}` ), - CORTEX_ENGINE_VERSION: JSON.stringify('b5350'), + CORTEX_ENGINE_VERSION: JSON.stringify('b5371'), }, }, { diff --git a/package.json b/package.json index de8248786..dcbf93831 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,9 @@ "install:cortex": "run-script-os", "download:bin": "node ./scripts/download-bin.mjs", "dev:tauri": "yarn build:icon && yarn copy:assets:tauri && tauri dev", + "build:tauri:linux:win32": "yarn download:bin && yarn install:cortex && yarn build:icon && yarn copy:assets:tauri && yarn tauri build --verbose", + "build:tauri:darwin": "yarn install:cortex && yarn build:icon && yarn copy:assets:tauri && yarn tauri build --verbose --target universal-apple-darwin", + "build:tauri": "run-script-os", "build:icon": "tauri icon ./src-tauri/icons/icon.png", "build:server": "cd server && yarn build", "build:core": "cd core && yarn build && yarn pack", @@ -37,6 +40,7 @@ "build:extensions": "rimraf ./pre-install/*.tgz || true && yarn workspace @janhq/core build && cd extensions && yarn install && yarn workspaces foreach -Apt run build:publish", "build:test": "yarn copy:assets && yarn workspace @janhq/web build && cpx \"web/out/**\" \"electron/renderer/\" && yarn workspace jan build:test", "build": "yarn build:web && yarn build:electron", + "build-tauri": "yarn build:web && yarn build:tauri", "build:publish": "yarn copy:assets && yarn build:web && yarn workspace jan build:publish", "dev:joi": "yarn workspace @janhq/joi install && yarn workspace @janhq/joi dev", "build:joi": "yarn workspace @janhq/joi build", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 4cbdde105..5ed527678 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,15 +1,13 @@ [package] -name = "app" -version = "0.1.0" -description = "A Tauri App" -authors = ["you"] -license = "" -repository = "" +name = "Jan" +version = "0.5.16" +description = "Use offline LLMs with your own data. Run open source models like Llama2 or Falcon on your internal computers/servers." +authors = ["Jan "] +license = "MIT" +repository = "https://github.com/menloresearch/jan" edition = "2021" rust-version = "1.77.2" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [lib] name = "app_lib" crate-type = ["staticlib", "cdylib", "rlib"] diff --git a/src-tauri/binaries/download.bat b/src-tauri/binaries/download.bat index 7172a1ac4..7eaee17c6 100644 --- a/src-tauri/binaries/download.bat +++ b/src-tauri/binaries/download.bat @@ -1,25 +1,24 @@ @echo off set CORTEX_VERSION=1.0.13-rc6 -set ENGINE_VERSION=b5350 +set ENGINE_VERSION=b5371 set ENGINE_DOWNLOAD_URL=https://github.com/menloresearch/llama.cpp/releases/download/%ENGINE_VERSION%/llama-%ENGINE_VERSION%-bin-win set ENGINE_DOWNLOAD_GGML_URL=https://github.com/ggml-org/llama.cpp/releases/download/%ENGINE_VERSION%/llama-%ENGINE_VERSION%-bin-win set CUDA_DOWNLOAD_URL=https://github.com/menloresearch/cortex.llamacpp/releases/download/v%ENGINE_VERSION% -set SUBFOLDERS=windows-amd64-noavx-cuda-12-0 windows-amd64-noavx-cuda-11-7 windows-amd64-avx2-cuda-12-0 windows-amd64-avx2-cuda-11-7 windows-amd64-noavx windows-amd64-avx windows-amd64-avx2 windows-amd64-avx512 windows-amd64-vulkan +@REM set SUBFOLDERS=windows-amd64-noavx-cuda-12-0 windows-amd64-noavx-cuda-11-7 windows-amd64-avx2-cuda-12-0 windows-amd64-avx2-cuda-11-7 windows-amd64-noavx windows-amd64-avx windows-amd64-avx2 windows-amd64-avx512 windows-amd64-vulkan set BIN_PATH="./" set DOWNLOAD_TOOL=..\..\extensions\inference-cortex-extension\node_modules\.bin\download -@REM Download cortex.llamacpp binaries - +@REM Download llama.cpp binaries call %DOWNLOAD_TOOL% -e --strip 1 -o %BIN_PATH% https://github.com/menloresearch/cortex.cpp/releases/download/v%CORTEX_VERSION%/cortex-%CORTEX_VERSION%-windows-amd64.tar.gz call %DOWNLOAD_TOOL% %ENGINE_DOWNLOAD_URL%-avx2-cuda-cu12.0-x64.tar.gz -e --strip 2 -o./engines/llama.cpp/win-avx2-cuda-cu12.0-x64/%ENGINE_VERSION% call %DOWNLOAD_TOOL% %ENGINE_DOWNLOAD_URL%-avx2-cuda-cu11.7-x64.tar.gz -e --strip 2 -o./engines/llama.cpp/win-avx2-cuda-cu11.7-x64/%ENGINE_VERSION% -call %DOWNLOAD_TOOL% %ENGINE_DOWNLOAD_URL%-noavx-cuda-cu12.0-x64.tar.gz -e --strip 2 -o./engines/llama.cpp/win-noavx-cuda-cu12.0-x64/%ENGINE_VERSION% -call %DOWNLOAD_TOOL% %ENGINE_DOWNLOAD_URL%-noavx-cuda-cu11.7-x64.tar.gz -e --strip 2 -o./engines/llama.cpp/win-noavx-cuda-cu11.7-x64/%ENGINE_VERSION% -call %DOWNLOAD_TOOL% %ENGINE_DOWNLOAD_GGML_URL%-noavx-x64.zip -e --strip 1 -o./engines/llama.cpp/win-noavx-x64/%ENGINE_VERSION% -call %DOWNLOAD_TOOL% %ENGINE_DOWNLOAD_GGML_URL%-avx-x64.zip -e --strip 1 -o./engines/llama.cpp/win-avx-x64/%ENGINE_VERSION% -call %DOWNLOAD_TOOL% %ENGINE_DOWNLOAD_GGML_URL%-avx2-x64.zip -e --strip 1 -o./engines/llama.cpp/win-avx2-x64/%ENGINE_VERSION% -call %DOWNLOAD_TOOL% %ENGINE_DOWNLOAD_GGML_URL%-avx512-x64.zip -e --strip 1 -o./engines/llama.cpp/win-avx512-x64/%ENGINE_VERSION% +@REM call %DOWNLOAD_TOOL% %ENGINE_DOWNLOAD_URL%-noavx-cuda-cu12.0-x64.tar.gz -e --strip 2 -o./engines/llama.cpp/win-noavx-cuda-cu12.0-x64/%ENGINE_VERSION% +@REM call %DOWNLOAD_TOOL% %ENGINE_DOWNLOAD_URL%-noavx-cuda-cu11.7-x64.tar.gz -e --strip 2 -o./engines/llama.cpp/win-noavx-cuda-cu11.7-x64/%ENGINE_VERSION% +call %DOWNLOAD_TOOL% %ENGINE_DOWNLOAD_URL%-noavx-x64.tar.gz -e --strip 2 -o./engines/llama.cpp/win-noavx-x64/%ENGINE_VERSION% +call %DOWNLOAD_TOOL% %ENGINE_DOWNLOAD_URL%-avx-x64.tar.gz -e --strip 2 -o./engines/llama.cpp/win-avx-x64/%ENGINE_VERSION% +call %DOWNLOAD_TOOL% %ENGINE_DOWNLOAD_URL%-avx2-x64.tar.gz -e --strip 2 -o./engines/llama.cpp/win-avx2-x64/%ENGINE_VERSION% +call %DOWNLOAD_TOOL% %ENGINE_DOWNLOAD_URL%-avx512-x64.tar.gz -e --strip 2 -o./engines/llama.cpp/win-avx512-x64/%ENGINE_VERSION% call %DOWNLOAD_TOOL% %ENGINE_DOWNLOAD_GGML_URL%-vulkan-x64.zip -e --strip 1 -o./engines/llama.cpp/win-vulkan-x64/%ENGINE_VERSION% call %DOWNLOAD_TOOL% %CUDA_DOWNLOAD_URL%/cudart-llama-bin-win-cu12.0-x64.tar.gz -e --strip 1 -o %BIN_PATH% call %DOWNLOAD_TOOL% %CUDA_DOWNLOAD_URL%/cudart-llama-bin-win-cu11.7-x64.tar.gz -e --strip 1 -o %BIN_PATH% diff --git a/src-tauri/binaries/download.sh b/src-tauri/binaries/download.sh index bd63273e4..e1ad30db9 100755 --- a/src-tauri/binaries/download.sh +++ b/src-tauri/binaries/download.sh @@ -15,7 +15,7 @@ download() { # Read CORTEX_VERSION CORTEX_VERSION=1.0.13-rc6 -ENGINE_VERSION=b5350 +ENGINE_VERSION=b5371 CORTEX_RELEASE_URL="https://github.com/menloresearch/cortex.cpp/releases/download" ENGINE_DOWNLOAD_URL=https://github.com/menloresearch/llama.cpp/releases/download/${ENGINE_VERSION}/llama-${ENGINE_VERSION}-bin CUDA_DOWNLOAD_URL=https://github.com/menloresearch/llama.cpp/releases/download/${ENGINE_VERSION} diff --git a/src-tauri/latest.json.template b/src-tauri/latest.json.template new file mode 100644 index 000000000..50a570c9e --- /dev/null +++ b/src-tauri/latest.json.template @@ -0,0 +1,23 @@ +{ + "version": "", + "notes": "", + "pub_date": "", + "platforms": { + "linux-x86_64": { + "signature": "", + "url": "" + }, + "windows-x86_64": { + "signature": "", + "url": "" + }, + "darwin-aarch64": { + "signature": "", + "url": "" + }, + "darwin-x86_64": { + "signature": "", + "url": "" + } + } +} \ No newline at end of file diff --git a/src-tauri/sign.ps1 b/src-tauri/sign.ps1 new file mode 100644 index 000000000..a54d525fe --- /dev/null +++ b/src-tauri/sign.ps1 @@ -0,0 +1,12 @@ +param ( + [string]$Target +) + +AzureSignTool.exe sign ` + -tr http://timestamp.digicert.com ` + -kvu $env:AZURE_KEY_VAULT_URI ` + -kvi $env:AZURE_CLIENT_ID ` + -kvt $env:AZURE_TENANT_ID ` + -kvs $env:AZURE_CLIENT_SECRET ` + -kvc $env:AZURE_CERT_NAME ` + -v $Target \ No newline at end of file diff --git a/src-tauri/src/core/cmd.rs b/src-tauri/src/core/cmd.rs index 3d7d921ee..a9f90ca80 100644 --- a/src-tauri/src/core/cmd.rs +++ b/src-tauri/src/core/cmd.rs @@ -101,6 +101,7 @@ pub fn get_jan_data_folder_path(app_handle: tauri::AppHandle) -> } let app_configurations = get_app_configurations(app_handle); + log::info!("data_folder: {}", app_configurations.data_folder); PathBuf::from(app_configurations.data_folder) } @@ -158,7 +159,18 @@ pub fn get_configuration_file_path(app_handle: tauri::AppHandle) PathBuf::from(home_dir) }); - app_path.join(CONFIGURATION_FILE_NAME) + let package_name = env!("CARGO_PKG_NAME"); + log::info!("Package name: {}", package_name); + let old_data_dir = app_path + .clone() + .parent() + .unwrap_or(&app_path.join("../")) + .join(package_name); + if old_data_dir.exists() { + return old_data_dir.join(CONFIGURATION_FILE_NAME); + } else { + return app_path.join(CONFIGURATION_FILE_NAME); + } } #[tauri::command] diff --git a/src-tauri/src/core/setup.rs b/src-tauri/src/core/setup.rs index b83cc8973..d70af1c70 100644 --- a/src-tauri/src/core/setup.rs +++ b/src-tauri/src/core/setup.rs @@ -19,7 +19,9 @@ use super::{ }; pub fn install_extensions(app: tauri::AppHandle, force: bool) -> Result<(), String> { - let store = app.store("store.json").expect("Store not initialized"); + let mut store_path = get_jan_data_folder_path(app.clone()); + store_path.push("store.json"); + let store = app.store(store_path).expect("Store not initialized"); let stored_version = store .get("version") .and_then(|v| v.as_str().map(String::from)) @@ -35,7 +37,12 @@ pub fn install_extensions(app: tauri::AppHandle, force: bool) -> Result<(), Stri return Ok(()); } let extensions_path = get_jan_extensions_path(app.clone()); - let pre_install_path = PathBuf::from("./resources/pre-install"); + let pre_install_path = app + .path() + .resource_dir() + .unwrap() + .join("resources") + .join("pre-install"); // Attempt to remove extensions folder if extensions_path.exists() { @@ -198,29 +205,19 @@ pub fn setup_sidecar(app: &App) -> Result<(), String> { // Setup sidecar let app_state = app.state::(); + let app_data_dir = get_jan_data_folder_path(app.handle().clone()); let mut sidecar_command = app.shell().sidecar("cortex-server").unwrap().args([ "--start-server", "--port", "39291", "--config_file_path", - app.app_handle() - .path() - .app_data_dir() - .unwrap() - .join(".janrc") - .to_str() - .unwrap(), + app_data_dir.join(".janrc").to_str().unwrap(), "--data_folder_path", - app.app_handle() - .path() - .app_data_dir() - .unwrap() - .to_str() - .unwrap(), + app_data_dir.to_str().unwrap(), "--cors", "ON", "--allowed_origins", - "http://localhost:3000", + "http://localhost:3000,tauri://localhost,http://tauri.localhost", "config", "--api_keys", app_state.inner().app_token.as_deref().unwrap_or(""), @@ -286,7 +283,7 @@ fn copy_dir_all(src: PathBuf, dst: PathBuf) -> Result<(), String> { pub fn setup_engine_binaries(app: &App) -> Result<(), String> { // Copy engine binaries to app_data - let app_data_dir = app.handle().path().app_data_dir().unwrap(); + let app_data_dir = get_jan_data_folder_path(app.handle().clone()); let binaries_dir = app.handle().path().resource_dir().unwrap().join("binaries"); let themes_dir = app .handle() diff --git a/src-tauri/tauri.bundle.windows.nsis.template b/src-tauri/tauri.bundle.windows.nsis.template new file mode 100644 index 000000000..bb3463e12 --- /dev/null +++ b/src-tauri/tauri.bundle.windows.nsis.template @@ -0,0 +1,1031 @@ +Unicode true +ManifestDPIAware true +; Add in `dpiAwareness` `PerMonitorV2` to manifest for Windows 10 1607+ (note this should not affect lower versions since they should be able to ignore this and pick up `dpiAware` `true` set by `ManifestDPIAware true`) +; Currently undocumented on NSIS's website but is in the Docs folder of source tree, see +; https://github.com/kichik/nsis/blob/5fc0b87b819a9eec006df4967d08e522ddd651c9/Docs/src/attributes.but#L286-L300 +; https://github.com/tauri-apps/tauri/pull/10106 +ManifestDPIAwareness PerMonitorV2 + +!if "lzma" == "none" + SetCompress off +!else + ; Set the compression algorithm. We default to LZMA. + SetCompressor /SOLID "lzma" +!endif + +!include MUI2.nsh +!include FileFunc.nsh +!include x64.nsh +!include WordFunc.nsh +!include "utils.nsh" +!include "FileAssociation.nsh" +!include "Win\COM.nsh" +!include "Win\Propkey.nsh" +!include "StrFunc.nsh" +${StrCase} +${StrLoc} + + +!define WEBVIEW2APPGUID "{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" + +!define MANUFACTURER "ai" +!define PRODUCTNAME "jan_productname" +!define VERSION "jan_version" +!define VERSIONWITHBUILD "jan_build" +!define HOMEPAGE "" +!define INSTALLMODE "currentUser" +!define LICENSE "" +!define INSTALLERICON "D:\a\jan\jan\src-tauri\icons\icon.ico" +!define SIDEBARIMAGE "" +!define HEADERIMAGE "" +!define MAINBINARYNAME "jan_mainbinaryname" +!define MAINBINARYSRCPATH "D:\a\jan\jan\src-tauri\target\release\jan_mainbinaryname.exe" +!define BUNDLEID "jan_mainbinaryname.ai.app" +!define COPYRIGHT "" +!define OUTFILE "nsis-output.exe" +!define ARCH "x64" +!define ADDITIONALPLUGINSPATH "D:\a\jan\jan\src-tauri\target\release\nsis\x64\Plugins\x86-unicode\additional" +!define ALLOWDOWNGRADES "true" +!define DISPLAYLANGUAGESELECTOR "false" +!define INSTALLWEBVIEW2MODE "downloadBootstrapper" +!define WEBVIEW2INSTALLERARGS "/silent" +!define WEBVIEW2BOOTSTRAPPERPATH "" +!define WEBVIEW2INSTALLERPATH "" +!define MINIMUMWEBVIEW2VERSION "" +!define UNINSTKEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCTNAME}" +!define MANUKEY "Software\${MANUFACTURER}" +!define MANUPRODUCTKEY "${MANUKEY}\${PRODUCTNAME}" +!define UNINSTALLERSIGNCOMMAND "$\"powershell$\" $\"-ExecutionPolicy$\" $\"Bypass$\" $\"-File$\" $\"./sign.ps1$\" $\"%1$\"" +!define ESTIMATEDSIZE "793795" +!define STARTMENUFOLDER "" + +Var PassiveMode +Var UpdateMode +Var NoShortcutMode +Var WixMode +Var OldMainBinaryName + +Name "${PRODUCTNAME}" +BrandingText "${COPYRIGHT}" +OutFile "${OUTFILE}" + +ShowInstDetails nevershow +ShowUninstDetails nevershow + +; We don't actually use this value as default install path, +; it's just for nsis to append the product name folder in the directory selector +; https://nsis.sourceforge.io/Reference/InstallDir +!define PLACEHOLDER_INSTALL_DIR "placeholder\${PRODUCTNAME}" +InstallDir "${PLACEHOLDER_INSTALL_DIR}" + +VIProductVersion "${VERSIONWITHBUILD}" +VIAddVersionKey "ProductName" "${PRODUCTNAME}" +VIAddVersionKey "FileDescription" "${PRODUCTNAME}" +VIAddVersionKey "LegalCopyright" "${COPYRIGHT}" +VIAddVersionKey "FileVersion" "${VERSION}" +VIAddVersionKey "ProductVersion" "${VERSION}" + +# additional plugins +!addplugindir "${ADDITIONALPLUGINSPATH}" + +; Uninstaller signing command +!if "${UNINSTALLERSIGNCOMMAND}" != "" + !uninstfinalize '${UNINSTALLERSIGNCOMMAND}' +!endif + +; Handle install mode, `perUser`, `perMachine` or `both` +!if "${INSTALLMODE}" == "perMachine" + RequestExecutionLevel highest +!endif + +!if "${INSTALLMODE}" == "currentUser" + RequestExecutionLevel user +!endif + +!if "${INSTALLMODE}" == "both" + !define MULTIUSER_MUI + !define MULTIUSER_INSTALLMODE_INSTDIR "${PRODUCTNAME}" + !define MULTIUSER_INSTALLMODE_COMMANDLINE + !if "${ARCH}" == "x64" + !define MULTIUSER_USE_PROGRAMFILES64 + !else if "${ARCH}" == "arm64" + !define MULTIUSER_USE_PROGRAMFILES64 + !endif + !define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY "${UNINSTKEY}" + !define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME "CurrentUser" + !define MULTIUSER_INSTALLMODEPAGE_SHOWUSERNAME + !define MULTIUSER_INSTALLMODE_FUNCTION RestorePreviousInstallLocation + !define MULTIUSER_EXECUTIONLEVEL Highest + !include MultiUser.nsh +!endif + +; Installer icon +!if "${INSTALLERICON}" != "" + !define MUI_ICON "${INSTALLERICON}" +!endif + +; Installer sidebar image +!if "${SIDEBARIMAGE}" != "" + !define MUI_WELCOMEFINISHPAGE_BITMAP "${SIDEBARIMAGE}" +!endif + +; Installer header image +!if "${HEADERIMAGE}" != "" + !define MUI_HEADERIMAGE + !define MUI_HEADERIMAGE_BITMAP "${HEADERIMAGE}" +!endif + +; Define registry key to store installer language +!define MUI_LANGDLL_REGISTRY_ROOT "HKCU" +!define MUI_LANGDLL_REGISTRY_KEY "${MANUPRODUCTKEY}" +!define MUI_LANGDLL_REGISTRY_VALUENAME "Installer Language" + +; Installer pages, must be ordered as they appear +; 1. Welcome Page +!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive +!insertmacro MUI_PAGE_WELCOME + +; 2. License Page (if defined) +!if "${LICENSE}" != "" + !define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive + !insertmacro MUI_PAGE_LICENSE "${LICENSE}" +!endif + +; 3. Install mode (if it is set to `both`) +!if "${INSTALLMODE}" == "both" + !define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive + !insertmacro MULTIUSER_PAGE_INSTALLMODE +!endif + +; 4. Custom page to ask user if he wants to reinstall/uninstall +; only if a previous installation was detected +Var ReinstallPageCheck +Page custom PageReinstall PageLeaveReinstall +Function PageReinstall + ; Uninstall previous WiX installation if exists. + ; + ; A WiX installer stores the installation info in registry + ; using a UUID and so we have to loop through all keys under + ; `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall` + ; and check if `DisplayName` and `Publisher` keys match ${PRODUCTNAME} and ${MANUFACTURER} + ; + ; This has a potential issue that there maybe another installation that matches + ; our ${PRODUCTNAME} and ${MANUFACTURER} but wasn't installed by our WiX installer, + ; however, this should be fine since the user will have to confirm the uninstallation + ; and they can chose to abort it if doesn't make sense. + StrCpy $0 0 + wix_loop: + EnumRegKey $1 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" $0 + StrCmp $1 "" wix_loop_done ; Exit loop if there is no more keys to loop on + IntOp $0 $0 + 1 + ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1" "DisplayName" + ReadRegStr $R1 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1" "Publisher" + StrCmp "$R0$R1" "${PRODUCTNAME}${MANUFACTURER}" 0 wix_loop + ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1" "UninstallString" + ${StrCase} $R1 $R0 "L" + ${StrLoc} $R0 $R1 "msiexec" ">" + StrCmp $R0 0 0 wix_loop_done + StrCpy $WixMode 1 + StrCpy $R6 "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1" + Goto compare_version + wix_loop_done: + + ; Check if there is an existing installation, if not, abort the reinstall page + ReadRegStr $R0 SHCTX "${UNINSTKEY}" "" + ReadRegStr $R1 SHCTX "${UNINSTKEY}" "UninstallString" + ${IfThen} "$R0$R1" == "" ${|} Abort ${|} + + ; Compare this installar version with the existing installation + ; and modify the messages presented to the user accordingly + compare_version: + StrCpy $R4 "$(older)" + ${If} $WixMode = 1 + ReadRegStr $R0 HKLM "$R6" "DisplayVersion" + ${Else} + ReadRegStr $R0 SHCTX "${UNINSTKEY}" "DisplayVersion" + ${EndIf} + ${IfThen} $R0 == "" ${|} StrCpy $R4 "$(unknown)" ${|} + + nsis_tauri_utils::SemverCompare "${VERSION}" $R0 + Pop $R0 + ; Reinstalling the same version + ${If} $R0 = 0 + StrCpy $R1 "$(alreadyInstalledLong)" + StrCpy $R2 "$(addOrReinstall)" + StrCpy $R3 "$(uninstallApp)" + !insertmacro MUI_HEADER_TEXT "$(alreadyInstalled)" "$(chooseMaintenanceOption)" + ; Upgrading + ${ElseIf} $R0 = 1 + StrCpy $R1 "$(olderOrUnknownVersionInstalled)" + StrCpy $R2 "$(uninstallBeforeInstalling)" + StrCpy $R3 "$(dontUninstall)" + !insertmacro MUI_HEADER_TEXT "$(alreadyInstalled)" "$(choowHowToInstall)" + ; Downgrading + ${ElseIf} $R0 = -1 + StrCpy $R1 "$(newerVersionInstalled)" + StrCpy $R2 "$(uninstallBeforeInstalling)" + !if "${ALLOWDOWNGRADES}" == "true" + StrCpy $R3 "$(dontUninstall)" + !else + StrCpy $R3 "$(dontUninstallDowngrade)" + !endif + !insertmacro MUI_HEADER_TEXT "$(alreadyInstalled)" "$(choowHowToInstall)" + ${Else} + Abort + ${EndIf} + + ; Skip showing the page if passive + ; + ; Note that we don't call this earlier at the begining + ; of this function because we need to populate some variables + ; related to current installed version if detected and whether + ; we are downgrading or not. + ${If} $PassiveMode = 1 + Call PageLeaveReinstall + ${Else} + nsDialogs::Create 1018 + Pop $R4 + ${IfThen} $(^RTL) = 1 ${|} nsDialogs::SetRTL $(^RTL) ${|} + + ${NSD_CreateLabel} 0 0 100% 24u $R1 + Pop $R1 + + ${NSD_CreateRadioButton} 30u 50u -30u 8u $R2 + Pop $R2 + ${NSD_OnClick} $R2 PageReinstallUpdateSelection + + ${NSD_CreateRadioButton} 30u 70u -30u 8u $R3 + Pop $R3 + ; Disable this radio button if downgrading and downgrades are disabled + !if "${ALLOWDOWNGRADES}" == "false" + ${IfThen} $R0 = -1 ${|} EnableWindow $R3 0 ${|} + !endif + ${NSD_OnClick} $R3 PageReinstallUpdateSelection + + ; Check the first radio button if this the first time + ; we enter this page or if the second button wasn't + ; selected the last time we were on this page + ${If} $ReinstallPageCheck <> 2 + SendMessage $R2 ${BM_SETCHECK} ${BST_CHECKED} 0 + ${Else} + SendMessage $R3 ${BM_SETCHECK} ${BST_CHECKED} 0 + ${EndIf} + + ${NSD_SetFocus} $R2 + nsDialogs::Show + ${EndIf} +FunctionEnd +Function PageReinstallUpdateSelection + ${NSD_GetState} $R2 $R1 + ${If} $R1 == ${BST_CHECKED} + StrCpy $ReinstallPageCheck 1 + ${Else} + StrCpy $ReinstallPageCheck 2 + ${EndIf} +FunctionEnd +Function PageLeaveReinstall + ${NSD_GetState} $R2 $R1 + + ; If migrating from Wix, always uninstall + ${If} $WixMode = 1 + Goto reinst_uninstall + ${EndIf} + + ; In update mode, always proceeds without uninstalling + ${If} $UpdateMode = 1 + Goto reinst_done + ${EndIf} + + ; $R0 holds whether same(0)/upgrading(1)/downgrading(-1) version + ; $R1 holds the radio buttons state: + ; 1 => first choice was selected + ; 0 => second choice was selected + ${If} $R0 = 0 ; Same version, proceed + ${If} $R1 = 1 ; User chose to add/reinstall + Goto reinst_done + ${Else} ; User chose to uninstall + Goto reinst_uninstall + ${EndIf} + ${ElseIf} $R0 = 1 ; Upgrading + ${If} $R1 = 1 ; User chose to uninstall + Goto reinst_uninstall + ${Else} + Goto reinst_done ; User chose NOT to uninstall + ${EndIf} + ${ElseIf} $R0 = -1 ; Downgrading + ${If} $R1 = 1 ; User chose to uninstall + Goto reinst_uninstall + ${Else} + Goto reinst_done ; User chose NOT to uninstall + ${EndIf} + ${EndIf} + + reinst_uninstall: + HideWindow + ClearErrors + + ${If} $WixMode = 1 + ReadRegStr $R1 HKLM "$R6" "UninstallString" + ExecWait '$R1' $0 + ${Else} + ReadRegStr $4 SHCTX "${MANUPRODUCTKEY}" "" + ReadRegStr $R1 SHCTX "${UNINSTKEY}" "UninstallString" + ${IfThen} $UpdateMode = 1 ${|} StrCpy $R1 "$R1 /UPDATE" ${|} ; append /UPDATE + ${IfThen} $PassiveMode = 1 ${|} StrCpy $R1 "$R1 /P" ${|} ; append /P + StrCpy $R1 "$R1 _?=$4" ; append uninstall directory + ExecWait '$R1' $0 + ${EndIf} + + BringToFront + + ${IfThen} ${Errors} ${|} StrCpy $0 2 ${|} ; ExecWait failed, set fake exit code + + ${If} $0 <> 0 + ${OrIf} ${FileExists} "$INSTDIR\${MAINBINARYNAME}.exe" + ; User cancelled wix uninstaller? return to select un/reinstall page + ${If} $WixMode = 1 + ${AndIf} $0 = 1602 + Abort + ${EndIf} + + ; User cancelled NSIS uninstaller? return to select un/reinstall page + ${If} $0 = 1 + Abort + ${EndIf} + + ; Other erros? show generic error message and return to select un/reinstall page + MessageBox MB_ICONEXCLAMATION "$(unableToUninstall)" + Abort + ${EndIf} + reinst_done: +FunctionEnd + +; 5. Choose install directory page +!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive +!insertmacro MUI_PAGE_DIRECTORY + +; 6. Start menu shortcut page +Var AppStartMenuFolder +!if "${STARTMENUFOLDER}" != "" + !define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive + !define MUI_STARTMENUPAGE_DEFAULTFOLDER "${STARTMENUFOLDER}" +!else + !define MUI_PAGE_CUSTOMFUNCTION_PRE Skip +!endif +!insertmacro MUI_PAGE_STARTMENU Application $AppStartMenuFolder + +; 7. Installation page +!insertmacro MUI_PAGE_INSTFILES + +; 8. Finish page +; +; Don't auto jump to finish page after installation page, +; because the installation page has useful info that can be used debug any issues with the installer. +!define MUI_FINISHPAGE_NOAUTOCLOSE +; Use show readme button in the finish page as a button create a desktop shortcut +!define MUI_FINISHPAGE_SHOWREADME +!define MUI_FINISHPAGE_SHOWREADME_TEXT "$(createDesktop)" +!define MUI_FINISHPAGE_SHOWREADME_FUNCTION CreateOrUpdateDesktopShortcut +; Show run app after installation. +!define MUI_FINISHPAGE_RUN +!define MUI_FINISHPAGE_RUN_FUNCTION RunMainBinary +!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive +!insertmacro MUI_PAGE_FINISH + +Function RunMainBinary + nsis_tauri_utils::RunAsUser "$INSTDIR\${MAINBINARYNAME}.exe" "" +FunctionEnd + +; Uninstaller Pages +; 1. Confirm uninstall page +Var DeleteAppDataCheckbox +Var DeleteAppDataCheckboxState +!define /ifndef WS_EX_LAYOUTRTL 0x00400000 +!define MUI_PAGE_CUSTOMFUNCTION_SHOW un.ConfirmShow +Function un.ConfirmShow ; Add add a `Delete app data` check box + ; $1 inner dialog HWND + ; $2 window DPI + ; $3 style + ; $4 x + ; $5 y + ; $6 width + ; $7 height + FindWindow $1 "#32770" "" $HWNDPARENT ; Find inner dialog + System::Call "user32::GetDpiForWindow(p r1) i .r2" + ${If} $(^RTL) = 1 + StrCpy $3 "${__NSD_CheckBox_EXSTYLE} | ${WS_EX_LAYOUTRTL}" + IntOp $4 50 * $2 + ${Else} + StrCpy $3 "${__NSD_CheckBox_EXSTYLE}" + IntOp $4 0 * $2 + ${EndIf} + IntOp $5 100 * $2 + IntOp $6 400 * $2 + IntOp $7 25 * $2 + IntOp $4 $4 / 96 + IntOp $5 $5 / 96 + IntOp $6 $6 / 96 + IntOp $7 $7 / 96 + System::Call 'user32::CreateWindowEx(i r3, w "${__NSD_CheckBox_CLASS}", w "$(deleteAppData)", i ${__NSD_CheckBox_STYLE}, i r4, i r5, i r6, i r7, p r1, i0, i0, i0) i .s' + Pop $DeleteAppDataCheckbox + SendMessage $HWNDPARENT ${WM_GETFONT} 0 0 $1 + SendMessage $DeleteAppDataCheckbox ${WM_SETFONT} $1 1 +FunctionEnd +!define MUI_PAGE_CUSTOMFUNCTION_LEAVE un.ConfirmLeave +Function un.ConfirmLeave + SendMessage $DeleteAppDataCheckbox ${BM_GETCHECK} 0 0 $DeleteAppDataCheckboxState +FunctionEnd +!define MUI_PAGE_CUSTOMFUNCTION_PRE un.SkipIfPassive +!insertmacro MUI_UNPAGE_CONFIRM + +; 2. Uninstalling Page +!insertmacro MUI_UNPAGE_INSTFILES + +;Languages +!insertmacro MUI_LANGUAGE "English" +!insertmacro MUI_RESERVEFILE_LANGDLL + !include "D:\a\jan\jan\src-tauri\target\release\nsis\x64\English.nsh" + +Function .onInit + ${GetOptions} $CMDLINE "/P" $PassiveMode + ${IfNot} ${Errors} + StrCpy $PassiveMode 1 + ${EndIf} + ; always run in passive mode + StrCpy $PassiveMode 1 + + ${GetOptions} $CMDLINE "/NS" $NoShortcutMode + ${IfNot} ${Errors} + StrCpy $NoShortcutMode 1 + ${EndIf} + + ${GetOptions} $CMDLINE "/UPDATE" $UpdateMode + ${IfNot} ${Errors} + StrCpy $UpdateMode 1 + ${EndIf} + + !if "${DISPLAYLANGUAGESELECTOR}" == "true" + !insertmacro MUI_LANGDLL_DISPLAY + !endif + + !insertmacro SetContext + + ${If} $INSTDIR == "${PLACEHOLDER_INSTALL_DIR}" + ; Set default install location + !if "${INSTALLMODE}" == "perMachine" + ${If} ${RunningX64} + !if "${ARCH}" == "x64" + StrCpy $INSTDIR "$PROGRAMFILES64\${PRODUCTNAME}" + !else if "${ARCH}" == "arm64" + StrCpy $INSTDIR "$PROGRAMFILES64\${PRODUCTNAME}" + !else + StrCpy $INSTDIR "$PROGRAMFILES\${PRODUCTNAME}" + !endif + ${Else} + StrCpy $INSTDIR "$PROGRAMFILES\${PRODUCTNAME}" + ${EndIf} + !else if "${INSTALLMODE}" == "currentUser" + StrCpy $INSTDIR "$LOCALAPPDATA\Programs\${PRODUCTNAME}" + !endif + + Call RestorePreviousInstallLocation + ${EndIf} + + ; Remove old Jan if it exists + ${If} ${FileExists} "$INSTDIR\LICENSE.electron.txt" + DeleteRegKey HKLM "Software\${PRODUCTNAME}" + RMDir /r "$INSTDIR" + Delete "$INSTDIR\*.*" + ${EndIf} + + !if "${INSTALLMODE}" == "both" + !insertmacro MULTIUSER_INIT + !endif +FunctionEnd + + +Section EarlyChecks + ; Abort silent installer if downgrades is disabled + !if "${ALLOWDOWNGRADES}" == "false" + ${If} ${Silent} + ; If downgrading + ${If} $R0 = -1 + System::Call 'kernel32::AttachConsole(i -1)i.r0' + ${If} $0 <> 0 + System::Call 'kernel32::GetStdHandle(i -11)i.r0' + System::call 'kernel32::SetConsoleTextAttribute(i r0, i 0x0004)' ; set red color + FileWrite $0 "$(silentDowngrades)" + ${EndIf} + Abort + ${EndIf} + ${EndIf} + !endif + +SectionEnd + +Section WebView2 + ; Check if Webview2 is already installed and skip this section + ${If} ${RunningX64} + ReadRegStr $4 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\${WEBVIEW2APPGUID}" "pv" + ${Else} + ReadRegStr $4 HKLM "SOFTWARE\Microsoft\EdgeUpdate\Clients\${WEBVIEW2APPGUID}" "pv" + ${EndIf} + ${If} $4 == "" + ReadRegStr $4 HKCU "SOFTWARE\Microsoft\EdgeUpdate\Clients\${WEBVIEW2APPGUID}" "pv" + ${EndIf} + + ${If} $4 == "" + ; Webview2 installation + ; + ; Skip if updating + ${If} $UpdateMode <> 1 + !if "${INSTALLWEBVIEW2MODE}" == "downloadBootstrapper" + Delete "$TEMP\MicrosoftEdgeWebview2Setup.exe" + DetailPrint "$(webview2Downloading)" + NSISdl::download "https://go.microsoft.com/fwlink/p/?LinkId=2124703" "$TEMP\MicrosoftEdgeWebview2Setup.exe" + Pop $0 + ${If} $0 == "success" + DetailPrint "$(webview2DownloadSuccess)" + ${Else} + DetailPrint "$(webview2DownloadError)" + Abort "$(webview2AbortError)" + ${EndIf} + StrCpy $6 "$TEMP\MicrosoftEdgeWebview2Setup.exe" + Goto install_webview2 + !endif + + !if "${INSTALLWEBVIEW2MODE}" == "embedBootstrapper" + Delete "$TEMP\MicrosoftEdgeWebview2Setup.exe" + File "/oname=$TEMP\MicrosoftEdgeWebview2Setup.exe" "${WEBVIEW2BOOTSTRAPPERPATH}" + DetailPrint "$(installingWebview2)" + StrCpy $6 "$TEMP\MicrosoftEdgeWebview2Setup.exe" + Goto install_webview2 + !endif + + !if "${INSTALLWEBVIEW2MODE}" == "offlineInstaller" + Delete "$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe" + File "/oname=$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe" "${WEBVIEW2INSTALLERPATH}" + DetailPrint "$(installingWebview2)" + StrCpy $6 "$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe" + Goto install_webview2 + !endif + + Goto webview2_done + + install_webview2: + DetailPrint "$(installingWebview2)" + ; $6 holds the path to the webview2 installer + ExecWait "$6 ${WEBVIEW2INSTALLERARGS} /install" $1 + ${If} $1 = 0 + DetailPrint "$(webview2InstallSuccess)" + ${Else} + DetailPrint "$(webview2InstallError)" + Abort "$(webview2AbortError)" + ${EndIf} + webview2_done: + ${EndIf} + ${Else} + !if "${MINIMUMWEBVIEW2VERSION}" != "" + ${VersionCompare} "${MINIMUMWEBVIEW2VERSION}" "$4" $R0 + ${If} $R0 = 1 + update_webview: + DetailPrint "$(installingWebview2)" + ${If} ${RunningX64} + ReadRegStr $R1 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate" "path" + ${Else} + ReadRegStr $R1 HKLM "SOFTWARE\Microsoft\EdgeUpdate" "path" + ${EndIf} + ${If} $R1 == "" + ReadRegStr $R1 HKCU "SOFTWARE\Microsoft\EdgeUpdate" "path" + ${EndIf} + ${If} $R1 != "" + ; Chromium updater docs: https://source.chromium.org/chromium/chromium/src/+/main:docs/updater/user_manual.md + ; Modified from "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft EdgeWebView\ModifyPath" + ExecWait `"$R1" /install appguid=${WEBVIEW2APPGUID}&needsadmin=true` $1 + ${If} $1 = 0 + DetailPrint "$(webview2InstallSuccess)" + ${Else} + MessageBox MB_ICONEXCLAMATION|MB_ABORTRETRYIGNORE "$(webview2InstallError)" IDIGNORE ignore IDRETRY update_webview + Quit + ignore: + ${EndIf} + ${EndIf} + ${EndIf} + !endif + ${EndIf} +SectionEnd + +Section Install + SetDetailsPrint none + SetOutPath $INSTDIR + + !ifmacrodef NSIS_HOOK_PREINSTALL + !insertmacro NSIS_HOOK_PREINSTALL + !endif + + !insertmacro CheckIfAppIsRunning + + ; Copy main executable + File "${MAINBINARYSRCPATH}" + + ; Copy resources + CreateDirectory "$INSTDIR\resources\themes\joi-light" + CreateDirectory "$INSTDIR\resources\pre-install" + CreateDirectory "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx\v0.1.55" + CreateDirectory "$INSTDIR\resources\themes\night-blue" + CreateDirectory "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx512\v0.1.55" + CreateDirectory "$INSTDIR\resources\themes\joi-dark" + CreateDirectory "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-vulkan\v0.1.55" + CreateDirectory "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-noavx\v0.1.55" + CreateDirectory "$INSTDIR\resources\themes\dark-dimmed" + CreateDirectory "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx2\v0.1.55" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-avx\v0.1.55\engine.dll" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-avx\v0.1.55\engine.dll" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-avx\v0.1.55\msvcp140.dll" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-avx\v0.1.55\msvcp140.dll" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-avx\v0.1.55\vcomp140.dll" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-avx\v0.1.55\vcomp140.dll" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-avx\v0.1.55\vcruntime140.dll" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-avx\v0.1.55\vcruntime140.dll" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-avx\v0.1.55\vcruntime140_1.dll" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-avx\v0.1.55\vcruntime140_1.dll" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-avx\v0.1.55\version.txt" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-avx\v0.1.55\version.txt" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-avx2\v0.1.55\engine.dll" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-avx2\v0.1.55\engine.dll" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-avx2\v0.1.55\msvcp140.dll" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-avx2\v0.1.55\msvcp140.dll" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-avx2\v0.1.55\vcomp140.dll" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-avx2\v0.1.55\vcomp140.dll" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-avx2\v0.1.55\vcruntime140.dll" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-avx2\v0.1.55\vcruntime140.dll" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-avx2\v0.1.55\vcruntime140_1.dll" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-avx2\v0.1.55\vcruntime140_1.dll" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-avx2\v0.1.55\version.txt" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-avx2\v0.1.55\version.txt" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-avx512\v0.1.55\engine.dll" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-avx512\v0.1.55\engine.dll" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-avx512\v0.1.55\msvcp140.dll" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-avx512\v0.1.55\msvcp140.dll" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-avx512\v0.1.55\vcomp140.dll" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-avx512\v0.1.55\vcomp140.dll" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-avx512\v0.1.55\vcruntime140.dll" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-avx512\v0.1.55\vcruntime140.dll" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-avx512\v0.1.55\vcruntime140_1.dll" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-avx512\v0.1.55\vcruntime140_1.dll" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-avx512\v0.1.55\version.txt" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-avx512\v0.1.55\version.txt" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-noavx\v0.1.55\engine.dll" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-noavx\v0.1.55\engine.dll" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-noavx\v0.1.55\msvcp140.dll" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-noavx\v0.1.55\msvcp140.dll" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-noavx\v0.1.55\vcomp140.dll" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-noavx\v0.1.55\vcomp140.dll" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-noavx\v0.1.55\vcruntime140.dll" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-noavx\v0.1.55\vcruntime140.dll" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-noavx\v0.1.55\vcruntime140_1.dll" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-noavx\v0.1.55\vcruntime140_1.dll" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-noavx\v0.1.55\version.txt" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-noavx\v0.1.55\version.txt" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-vulkan\v0.1.55\engine.dll" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-vulkan\v0.1.55\engine.dll" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-vulkan\v0.1.55\msvcp140.dll" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-vulkan\v0.1.55\msvcp140.dll" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-vulkan\v0.1.55\vcomp140.dll" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-vulkan\v0.1.55\vcomp140.dll" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-vulkan\v0.1.55\vcruntime140.dll" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-vulkan\v0.1.55\vcruntime140.dll" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-vulkan\v0.1.55\vcruntime140_1.dll" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-vulkan\v0.1.55\vcruntime140_1.dll" + File /a "/oname=binaries\engines\cortex.llamacpp\windows-amd64-vulkan\v0.1.55\version.txt" "D:\a\jan\jan\src-tauri\binaries\engines\cortex.llamacpp\windows-amd64-vulkan\v0.1.55\version.txt" + File /a "/oname=resources\pre-install\janhq-assistant-extension-1.0.2.tgz" "D:\a\jan\jan\src-tauri\resources\pre-install\janhq-assistant-extension-1.0.2.tgz" + File /a "/oname=resources\pre-install\janhq-conversational-extension-1.0.0.tgz" "D:\a\jan\jan\src-tauri\resources\pre-install\janhq-conversational-extension-1.0.0.tgz" + File /a "/oname=resources\pre-install\janhq-engine-management-extension-1.0.3.tgz" "D:\a\jan\jan\src-tauri\resources\pre-install\janhq-engine-management-extension-1.0.3.tgz" + File /a "/oname=resources\pre-install\janhq-hardware-management-extension-1.0.0.tgz" "D:\a\jan\jan\src-tauri\resources\pre-install\janhq-hardware-management-extension-1.0.0.tgz" + File /a "/oname=resources\pre-install\janhq-inference-cortex-extension-1.0.25.tgz" "D:\a\jan\jan\src-tauri\resources\pre-install\janhq-inference-cortex-extension-1.0.25.tgz" + File /a "/oname=resources\pre-install\janhq-model-extension-1.0.36.tgz" "D:\a\jan\jan\src-tauri\resources\pre-install\janhq-model-extension-1.0.36.tgz" + File /a "/oname=resources\themes\dark-dimmed\theme.json" "D:\a\jan\jan\src-tauri\resources\themes\dark-dimmed\theme.json" + File /a "/oname=resources\themes\joi-dark\theme.json" "D:\a\jan\jan\src-tauri\resources\themes\joi-dark\theme.json" + File /a "/oname=resources\themes\joi-light\theme.json" "D:\a\jan\jan\src-tauri\resources\themes\joi-light\theme.json" + File /a "/oname=resources\themes\night-blue\theme.json" "D:\a\jan\jan\src-tauri\resources\themes\night-blue\theme.json" + + ; Copy external binaries + File /a "/oname=cortex-server.exe" "D:\a\jan\jan\src-tauri\binaries\cortex-server-x86_64-pc-windows-msvc.exe" + + ; Create file associations + + ; Register deep links + + ; Create uninstaller + WriteUninstaller "$INSTDIR\uninstall.exe" + + ; Save $INSTDIR in registry for future installations + WriteRegStr SHCTX "${MANUPRODUCTKEY}" "" $INSTDIR + + !if "${INSTALLMODE}" == "both" + ; Save install mode to be selected by default for the next installation such as updating + ; or when uninstalling + WriteRegStr SHCTX "${UNINSTKEY}" $MultiUser.InstallMode 1 + !endif + + ; Remove old main binary if it doesn't match new main binary name + ReadRegStr $OldMainBinaryName SHCTX "${UNINSTKEY}" "MainBinaryName" + ${If} $OldMainBinaryName != "" + ${AndIf} $OldMainBinaryName != "${MAINBINARYNAME}.exe" + Delete "$INSTDIR\$OldMainBinaryName" + ${EndIf} + + ; Save current MAINBINARYNAME for future updates + WriteRegStr SHCTX "${UNINSTKEY}" "MainBinaryName" "${MAINBINARYNAME}.exe" + + ; Registry information for add/remove programs + WriteRegStr SHCTX "${UNINSTKEY}" "DisplayName" "${PRODUCTNAME}" + WriteRegStr SHCTX "${UNINSTKEY}" "DisplayIcon" "$\"$INSTDIR\${MAINBINARYNAME}.exe$\"" + WriteRegStr SHCTX "${UNINSTKEY}" "DisplayVersion" "${VERSION}" + WriteRegStr SHCTX "${UNINSTKEY}" "Publisher" "${MANUFACTURER}" + WriteRegStr SHCTX "${UNINSTKEY}" "InstallLocation" "$\"$INSTDIR$\"" + WriteRegStr SHCTX "${UNINSTKEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" + WriteRegDWORD SHCTX "${UNINSTKEY}" "NoModify" "1" + WriteRegDWORD SHCTX "${UNINSTKEY}" "NoRepair" "1" + + ${GetSize} "$INSTDIR" "/M=uninstall.exe /S=0K /G=0" $0 $1 $2 + IntOp $0 $0 + ${ESTIMATEDSIZE} + IntFmt $0 "0x%08X" $0 + WriteRegDWORD SHCTX "${UNINSTKEY}" "EstimatedSize" "$0" + + !if "${HOMEPAGE}" != "" + WriteRegStr SHCTX "${UNINSTKEY}" "URLInfoAbout" "${HOMEPAGE}" + WriteRegStr SHCTX "${UNINSTKEY}" "URLUpdateInfo" "${HOMEPAGE}" + WriteRegStr SHCTX "${UNINSTKEY}" "HelpLink" "${HOMEPAGE}" + !endif + + ; Create start menu shortcut + !insertmacro MUI_STARTMENU_WRITE_BEGIN Application + Call CreateOrUpdateStartMenuShortcut + !insertmacro MUI_STARTMENU_WRITE_END + + ; Create desktop shortcut for silent and passive installers + ; because finish page will be skipped + ${If} $PassiveMode = 1 + ${OrIf} ${Silent} + Call CreateOrUpdateDesktopShortcut + ${EndIf} + + !ifmacrodef NSIS_HOOK_POSTINSTALL + !insertmacro NSIS_HOOK_POSTINSTALL + !endif + + ; Auto close this page for passive mode + ${If} $PassiveMode = 1 + SetAutoClose true + ${EndIf} +SectionEnd + +Function .onInstSuccess + ; Check for `/R` flag only in silent and passive installers because + ; GUI installer has a toggle for the user to (re)start the app + ${If} $PassiveMode = 1 + ${OrIf} ${Silent} + ; ${GetOptions} $CMDLINE "/R" $R0 + ; ${IfNot} ${Errors} + ${GetOptions} $CMDLINE "/ARGS" $R0 + nsis_tauri_utils::RunAsUser "$INSTDIR\${MAINBINARYNAME}.exe" "$R0" + ; ${EndIf} + ${EndIf} +FunctionEnd + +Function un.onInit + !insertmacro SetContext + + !if "${INSTALLMODE}" == "both" + !insertmacro MULTIUSER_UNINIT + !endif + + !insertmacro MUI_UNGETLANGUAGE + + ${GetOptions} $CMDLINE "/P" $PassiveMode + ${IfNot} ${Errors} + StrCpy $PassiveMode 1 + ${EndIf} + + ${GetOptions} $CMDLINE "/UPDATE" $UpdateMode + ${IfNot} ${Errors} + StrCpy $UpdateMode 1 + ${EndIf} +FunctionEnd + +Section Uninstall + SetDetailsPrint none + + !ifmacrodef NSIS_HOOK_PREUNINSTALL + !insertmacro NSIS_HOOK_PREUNINSTALL + !endif + + !insertmacro CheckIfAppIsRunning + + ; Delete the app directory and its content from disk + ; Copy main executable + Delete "$INSTDIR\${MAINBINARYNAME}.exe" + + ; Delete resources + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx\v0.1.55\engine.dll" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx\v0.1.55\msvcp140.dll" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx\v0.1.55\vcomp140.dll" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx\v0.1.55\vcruntime140.dll" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx\v0.1.55\vcruntime140_1.dll" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx\v0.1.55\version.txt" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx2\v0.1.55\engine.dll" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx2\v0.1.55\msvcp140.dll" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx2\v0.1.55\vcomp140.dll" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx2\v0.1.55\vcruntime140.dll" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx2\v0.1.55\vcruntime140_1.dll" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx2\v0.1.55\version.txt" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx512\v0.1.55\engine.dll" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx512\v0.1.55\msvcp140.dll" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx512\v0.1.55\vcomp140.dll" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx512\v0.1.55\vcruntime140.dll" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx512\v0.1.55\vcruntime140_1.dll" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx512\v0.1.55\version.txt" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-noavx\v0.1.55\engine.dll" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-noavx\v0.1.55\msvcp140.dll" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-noavx\v0.1.55\vcomp140.dll" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-noavx\v0.1.55\vcruntime140.dll" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-noavx\v0.1.55\vcruntime140_1.dll" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-noavx\v0.1.55\version.txt" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-vulkan\v0.1.55\engine.dll" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-vulkan\v0.1.55\msvcp140.dll" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-vulkan\v0.1.55\vcomp140.dll" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-vulkan\v0.1.55\vcruntime140.dll" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-vulkan\v0.1.55\vcruntime140_1.dll" + Delete "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-vulkan\v0.1.55\version.txt" + Delete "$INSTDIR\resources\pre-install\janhq-assistant-extension-1.0.2.tgz" + Delete "$INSTDIR\resources\pre-install\janhq-conversational-extension-1.0.0.tgz" + Delete "$INSTDIR\resources\pre-install\janhq-engine-management-extension-1.0.3.tgz" + Delete "$INSTDIR\resources\pre-install\janhq-hardware-management-extension-1.0.0.tgz" + Delete "$INSTDIR\resources\pre-install\janhq-inference-cortex-extension-1.0.25.tgz" + Delete "$INSTDIR\resources\pre-install\janhq-model-extension-1.0.36.tgz" + Delete "$INSTDIR\resources\themes\dark-dimmed\theme.json" + Delete "$INSTDIR\resources\themes\joi-dark\theme.json" + Delete "$INSTDIR\resources\themes\joi-light\theme.json" + Delete "$INSTDIR\resources\themes\night-blue\theme.json" + + ; Delete external binaries + Delete "$INSTDIR\cortex-server.exe" + + ; Delete app associations + + ; Delete deep links + + + ; Delete uninstaller + Delete "$INSTDIR\uninstall.exe" + + RMDir /REBOOTOK "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx\v0.1.55" + RMDir /REBOOTOK "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx2\v0.1.55" + RMDir /REBOOTOK "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx512\v0.1.55" + RMDir /REBOOTOK "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-noavx\v0.1.55" + RMDir /REBOOTOK "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-vulkan\v0.1.55" + RMDir /REBOOTOK "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx" + RMDir /REBOOTOK "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx2" + RMDir /REBOOTOK "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-avx512" + RMDir /REBOOTOK "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-noavx" + RMDir /REBOOTOK "$INSTDIR\binaries\engines\cortex.llamacpp\windows-amd64-vulkan" + RMDir /REBOOTOK "$INSTDIR\binaries\engines\cortex.llamacpp" + RMDir /REBOOTOK "$INSTDIR\resources\themes\dark-dimmed" + RMDir /REBOOTOK "$INSTDIR\resources\themes\joi-dark" + RMDir /REBOOTOK "$INSTDIR\resources\themes\joi-light" + RMDir /REBOOTOK "$INSTDIR\resources\themes\night-blue" + RMDir /REBOOTOK "$INSTDIR\binaries\engines" + RMDir /REBOOTOK "$INSTDIR\resources\pre-install" + RMDir /REBOOTOK "$INSTDIR\resources\themes" + RMDir /REBOOTOK "$INSTDIR\binaries" + RMDir /REBOOTOK "$INSTDIR\resources" + RMDir "$INSTDIR" + + ; Remove shortcuts if not updating + ${If} $UpdateMode <> 1 + !insertmacro DeleteAppUserModelId + + ; Remove start menu shortcut + !insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder + !insertmacro IsShortcutTarget "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + Pop $0 + ${If} $0 = 1 + !insertmacro UnpinShortcut "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" + Delete "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" + RMDir "$SMPROGRAMS\$AppStartMenuFolder" + ${EndIf} + !insertmacro IsShortcutTarget "$SMPROGRAMS\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + Pop $0 + ${If} $0 = 1 + !insertmacro UnpinShortcut "$SMPROGRAMS\${PRODUCTNAME}.lnk" + Delete "$SMPROGRAMS\${PRODUCTNAME}.lnk" + ${EndIf} + + ; Remove desktop shortcuts + !insertmacro IsShortcutTarget "$DESKTOP\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + Pop $0 + ${If} $0 = 1 + !insertmacro UnpinShortcut "$DESKTOP\${PRODUCTNAME}.lnk" + Delete "$DESKTOP\${PRODUCTNAME}.lnk" + ${EndIf} + ${EndIf} + + ; Remove registry information for add/remove programs + !if "${INSTALLMODE}" == "both" + DeleteRegKey SHCTX "${UNINSTKEY}" + !else if "${INSTALLMODE}" == "perMachine" + DeleteRegKey HKLM "${UNINSTKEY}" + !else + DeleteRegKey HKCU "${UNINSTKEY}" + !endif + + ; Removes the Autostart entry for ${PRODUCTNAME} from the HKCU Run key if it exists. + ; This ensures the program does not launch automatically after uninstallation if it exists. + ; If it doesn't exist, it does nothing. + ; We do this when not updating (to preserve the registry value on updates) + ${If} $UpdateMode <> 1 + DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "${PRODUCTNAME}" + ${EndIf} + + ; Delete app data if the checkbox is selected + ; and if not updating + ${If} $DeleteAppDataCheckboxState = 1 + ${AndIf} $UpdateMode <> 1 + ; Clear the install location $INSTDIR from registry + DeleteRegKey SHCTX "${MANUPRODUCTKEY}" + DeleteRegKey /ifempty SHCTX "${MANUKEY}" + + ; Clear the install language from registry + DeleteRegValue HKCU "${MANUPRODUCTKEY}" "Installer Language" + DeleteRegKey /ifempty HKCU "${MANUPRODUCTKEY}" + DeleteRegKey /ifempty HKCU "${MANUKEY}" + + SetShellVarContext current + RmDir /r "$APPDATA\${BUNDLEID}" + RmDir /r "$LOCALAPPDATA\${BUNDLEID}" + ${EndIf} + + !ifmacrodef NSIS_HOOK_POSTUNINSTALL + !insertmacro NSIS_HOOK_POSTUNINSTALL + !endif + + ; Auto close if passive mode or updating + ${If} $PassiveMode = 1 + ${OrIf} $UpdateMode = 1 + SetAutoClose true + ${EndIf} +SectionEnd + +Function RestorePreviousInstallLocation + ReadRegStr $4 SHCTX "${MANUPRODUCTKEY}" "" + StrCmp $4 "" +2 0 + StrCpy $INSTDIR $4 +FunctionEnd + +Function Skip + Abort +FunctionEnd + +Function SkipIfPassive + ${IfThen} $PassiveMode = 1 ${|} Abort ${|} +FunctionEnd +Function un.SkipIfPassive + ${IfThen} $PassiveMode = 1 ${|} Abort ${|} +FunctionEnd + +Function CreateOrUpdateStartMenuShortcut + ; We used to use product name as MAINBINARYNAME + ; migrate old shortcuts to target the new MAINBINARYNAME + StrCpy $R0 0 + + !insertmacro IsShortcutTarget "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" "$INSTDIR\$OldMainBinaryName" + Pop $0 + ${If} $0 = 1 + !insertmacro SetShortcutTarget "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + StrCpy $R0 1 + ${EndIf} + + !insertmacro IsShortcutTarget "$SMPROGRAMS\${PRODUCTNAME}.lnk" "$INSTDIR\$OldMainBinaryName" + Pop $0 + ${If} $0 = 1 + !insertmacro SetShortcutTarget "$SMPROGRAMS\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + StrCpy $R0 1 + ${EndIf} + + ${If} $R0 = 1 + Return + ${EndIf} + + ; Skip creating shortcut if in update mode or no shortcut mode + ; but always create if migrating from wix + ${If} $WixMode = 0 + ${If} $UpdateMode = 1 + ${OrIf} $NoShortcutMode = 1 + Return + ${EndIf} + ${EndIf} + + !if "${STARTMENUFOLDER}" != "" + CreateDirectory "$SMPROGRAMS\$AppStartMenuFolder" + CreateShortcut "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + !insertmacro SetLnkAppUserModelId "$SMPROGRAMS\$AppStartMenuFolder\${PRODUCTNAME}.lnk" + !else + CreateShortcut "$SMPROGRAMS\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + !insertmacro SetLnkAppUserModelId "$SMPROGRAMS\${PRODUCTNAME}.lnk" + !endif +FunctionEnd + +Function CreateOrUpdateDesktopShortcut + ; We used to use product name as MAINBINARYNAME + ; migrate old shortcuts to target the new MAINBINARYNAME + !insertmacro IsShortcutTarget "$DESKTOP\${PRODUCTNAME}.lnk" "$INSTDIR\$OldMainBinaryName" + Pop $0 + ${If} $0 = 1 + !insertmacro SetShortcutTarget "$DESKTOP\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + Return + ${EndIf} + + ; Skip creating shortcut if in update mode or no shortcut mode + ; but always create if migrating from wix + ${If} $WixMode = 0 + ${If} $UpdateMode = 1 + ${OrIf} $NoShortcutMode = 1 + Return + ${EndIf} + ${EndIf} + + CreateShortcut "$DESKTOP\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe" + !insertmacro SetLnkAppUserModelId "$DESKTOP\${PRODUCTNAME}.lnk" +FunctionEnd diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 3e74e9fc7..35e7b1fa5 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,8 +1,8 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Jan", - "version": "0.1.0", - "identifier": "jan.ai", + "version": "0.5.16", + "identifier": "jan.ai.app", "build": { "frontendDist": "../web/out", "devUrl": "http://localhost:3000", @@ -22,7 +22,12 @@ "transparent": true, "titleBarStyle": "Overlay", "windowEffects": { - "effects": ["fullScreenUI", "mica", "blur", "acrylic"], + "effects": [ + "fullScreenUI", + "mica", + "blur", + "acrylic" + ], "state": "active" } } @@ -30,33 +35,46 @@ "security": { "csp": { "default-src": "'self' customprotocol: asset: http://localhost:* http://127.0.0.1:* ws://localhost:* ws://127.0.0.1:*", - "connect-src": "ipc: http://ipc.localhost", - "font-src": ["https://fonts.gstatic.com blob: data:"], + "connect-src": "ipc: http://ipc.localhost http://127.0.0.1:* ws://localhost:* ws://127.0.0.1:* https://registry.npmjs.org", + "font-src": [ + "https://fonts.gstatic.com blob: data:" + ], "img-src": "'self' asset: http://asset.localhost blob: data:", "style-src": "'unsafe-inline' 'self' https://fonts.googleapis.com", - "script-src": "'self' asset: $APPDATA/**.*" + "script-src": "'self' asset: $APPDATA/**.* http://asset.localhost" }, "assetProtocol": { "enable": true, "scope": { "requireLiteralLeadingDot": false, - "allow": ["**/*"] + "allow": [ + "**/*" + ] } } } }, "plugins": { "updater": { - "pubkey": "", + "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDJFNDEzMEVCMUEzNUFENDQKUldSRXJUVWE2ekJCTGc1Mm1BVXgrWmtES3huUlBFR0lCdG5qbWFvMzgyNDhGN3VTTko5Q1NtTW0K", "endpoints": [ "https://github.com/menloresearch/jan/releases/latest/download/latest.json" - ] + ], + "windows": { + "installMode": "passive" + } } }, "bundle": { "active": true, - "targets": "all", - "createUpdaterArtifacts": true, + "targets": [ + "nsis", + "app", + "dmg", + "deb", + "appimage" + ], + "createUpdaterArtifacts": false, "icon": [ "icons/32x32.png", "icons/128x128.png", @@ -69,6 +87,25 @@ "resources/themes/**/*", "resources/pre-install/**/*" ], - "externalBin": ["binaries/cortex-server", "resources/bin/bun", "resources/bin/uv"] + "externalBin": [ + "binaries/cortex-server", + "resources/bin/bun", + "resources/bin/uv" + ], + "linux": { + "appimage": { + "bundleMediaFramework": false, + "files": {} + }, + "deb": { + "files": { + "usr/bin/bun": "resources/bin/bun", + "usr/lib/Jan/binaries/engines": "binaries/engines" + } + } + }, + "windows": { + "signCommand": "powershell -ExecutionPolicy Bypass -File ./sign.ps1 %1" + } } -} +} \ No newline at end of file diff --git a/web/screens/Hub/ModelPage/index.tsx b/web/screens/Hub/ModelPage/index.tsx index dcd0c833b..904a73cda 100644 --- a/web/screens/Hub/ModelPage/index.tsx +++ b/web/screens/Hub/ModelPage/index.tsx @@ -144,10 +144,10 @@ const ModelPage = ({ model, onGoBack }: Props) => { {model.type !== 'cloud' && ( <> - + Format - + Size diff --git a/web/screens/Settings/Engines/RemoteEngineSettings.tsx b/web/screens/Settings/Engines/RemoteEngineSettings.tsx index e773b1957..fa3f7a668 100644 --- a/web/screens/Settings/Engines/RemoteEngineSettings.tsx +++ b/web/screens/Settings/Engines/RemoteEngineSettings.tsx @@ -168,8 +168,7 @@ const RemoteEngineSettings = ({

{!customEngineLogo ? ( - Enter your authentication key to activate this - engine.{' '} + Enter your authentication key to activate this engine.{' '} ) : (