Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions .github/workflows/desktop-packaging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,93 @@ jobs:
working-directory: .
steps:
- uses: actions/checkout@v4
- name: Determine next release version
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
version="$(gh release list --repo "$GITHUB_REPOSITORY" --limit 1000 --json tagName --jq '.[].tagName' | grep -E '^v[0-9]+\\.[0-9]+\\.[0-9]+

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore valid YAML in the packaging workflow

The version command is truncated before its regex, quote, and command substitution are closed, so the following duplicated workflow steps are absorbed into the run scalar until the unindented pipe at line 105 makes the document invalid YAML. GitHub cannot load or dispatch Desktop packaging in this state, preventing any Windows/Linux release artifacts from being built.

Useful? React with 👍 / 👎.

- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: apps/desktop/package-lock.json
- uses: dtolnay/rust-toolchain@stable
- uses: swatinem/rust-cache@v2

- name: Install Linux system dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev librsvg2-dev libayatana-appindicator3-dev patchelf

- name: Download Linux llama.cpp runtime
if: runner.os == 'Linux'
shell: bash
run: |
set -euo pipefail
python3 - <<'PY'
import json, os, pathlib, urllib.request
headers = {"User-Agent": "deepLocal-CI", "Accept": "application/vnd.github+json"}
request = urllib.request.Request("https://api.github.com/repos/ggml-org/llama.cpp/releases?per_page=10", headers=headers)
with urllib.request.urlopen(request) as response:
releases = json.load(response)
assets = [a for r in releases if not r["draft"] for a in r["assets"]]
matches = [a for a in assets if "ubuntu-x64" in a["name"].lower() and a["name"].endswith(".tar.gz")]
if not matches:
raise SystemExit("No compatible Linux llama.cpp release asset found")
url = matches[0]["browser_download_url"]
urllib.request.urlretrieve(url, "/tmp/llama-runtime.tar.gz")
PY
mkdir -p .tools/llama.cpp
tar -xzf /tmp/llama-runtime.tar.gz -C .tools/llama.cpp
server="$(find .tools/llama.cpp -type f -name llama-server -print -quit)"
test -n "$server"
chmod +x "$server"
echo "DEEPLOCAL_LLAMA_SERVER=$GITHUB_WORKSPACE/$server" >> "$GITHUB_ENV"

- name: Download Windows llama.cpp runtime
if: runner.os == 'Windows'
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$releases = Invoke-RestMethod -Uri 'https://api.github.com/repos/ggml-org/llama.cpp/releases?per_page=10' -Headers @{ 'User-Agent' = 'deepLocal-CI' }
$asset = $releases | Where-Object { -not $_.draft } | ForEach-Object { $_.assets } |
Where-Object { $_.name -match 'win-cpu-x64.*\.zip$' } | Select-Object -First 1
if (-not $asset) { throw 'No compatible Windows llama.cpp release asset found.' }
Invoke-WebRequest -Uri $asset.browser_download_url -OutFile "$env:RUNNER_TEMP\llama-runtime.zip"
New-Item -ItemType Directory -Force '.tools\llama.cpp' | Out-Null
Expand-Archive "$env:RUNNER_TEMP\llama-runtime.zip" '.tools\llama.cpp' -Force
$server = Get-ChildItem '.tools\llama.cpp' -Filter 'llama-server.exe' -Recurse | Select-Object -First 1
if (-not $server) { throw 'llama-server.exe was not found in the downloaded archive.' }
"DEEPLOCAL_LLAMA_SERVER=$($server.FullName)" | Out-File -FilePath $env:GITHUB_ENV -Append

- name: Package Linux
if: runner.os == 'Linux'
shell: bash
run: bash scripts/package-linux-app.sh

- name: Package Windows
if: runner.os == 'Windows'
shell: pwsh
run: ./scripts/package-windows-app.ps1

- name: Upload installers
uses: actions/upload-artifact@v4
with:
name: deeplocal-${{ runner.os }}-${{ runner.arch }}
path: |
dist/*.deb
dist/*.AppImage
dist/*.exe
release-version.txt
if-no-files-found: error
retention-days: 14
| sort -V | tail -n 1 || true)"
if [[ -z "$version" ]]; then version=v0.1.0; else version="$(awk -F. '{printf "v%d.%d.%d", substr($1,2),$2,$3+1}' <<< "$version")"; fi
echo "DEEPLOCAL_RELEASE_VERSION=$version" >> "$GITHUB_ENV"
echo "$version" > release-version.txt
- uses: actions/setup-node@v4
with:
node-version: "22"
Expand Down
64 changes: 34 additions & 30 deletions .github/workflows/publish-desktop-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,41 +16,20 @@ jobs:
name: Publish Windows and Linux installers
runs-on: ubuntu-latest
steps:
- name: Resolve matching build and release
- name: Select latest successful packaging run
id: resolve
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
shell: bash
run: |
set -euo pipefail
gh api --paginate "repos/$GH_REPO/actions/workflows/desktop-packaging.yml/runs?event=workflow_dispatch&status=success&per_page=100" --jq '.workflow_runs[] | [.id, .head_sha] | @tsv' > "$RUNNER_TEMP/build-runs.tsv"
gh release list --limit 1000 --json tagName,isDraft --jq '.[] | select(.isDraft == false) | .tagName' > "$RUNNER_TEMP/release-tags.txt"
while IFS=$'\t' read -r run_id run_sha; do
[[ -n "$run_id" ]] || continue
while IFS= read -r tag; do
[[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([.-][A-Za-z0-9.-]+)?$ ]] || continue
ref_json="$(gh api "repos/$GH_REPO/git/ref/tags/$tag")"
tag_sha="$(jq -r '.object.sha' <<< "$ref_json")"
if [[ "$(jq -r '.object.type' <<< "$ref_json")" == "tag" ]]; then
tag_sha="$(gh api "repos/$GH_REPO/git/tags/$tag_sha" --jq '.object.sha')"
fi
if [[ "$run_sha" == "$tag_sha" ]]; then
echo "run_id=$run_id" >> "$GITHUB_OUTPUT"
echo "release_tag=$tag" >> "$GITHUB_OUTPUT"
echo "Selected build $run_id for release $tag ($run_sha)" >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
done < "$RUNNER_TEMP/release-tags.txt"
done < "$RUNNER_TEMP/build-runs.tsv"
echo "::error::No successful packaging run matches an existing published release tag."
echo "Successful packaging runs:" >&2
head -n 10 "$RUNNER_TEMP/build-runs.tsv" >&2
echo "Published release tags:" >&2
head -n 20 "$RUNNER_TEMP/release-tags.txt" >&2
echo "Create a release tag at the packaging run commit, or run Desktop packaging on the commit of an existing release tag." >&2
echo "The publishing workflow will not silently attach binaries built from a different commit." >&2
exit 1
gh api "repos/$GH_REPO/actions/workflows/desktop-packaging.yml/runs?event=workflow_dispatch&status=success&per_page=100" > "$RUNNER_TEMP/runs.json"
run_id="$(jq -r '.workflow_runs[0].id // empty' "$RUNNER_TEMP/runs.json")"
run_sha="$(jq -r '.workflow_runs[0].head_sha // empty' "$RUNNER_TEMP/runs.json")"
[[ -n "$run_id" && -n "$run_sha" ]] || { echo "No successful packaging run found." >&2; exit 1; }
echo "run_id=$run_id" >> "$GITHUB_OUTPUT"
echo "run_sha=$run_sha" >> "$GITHUB_OUTPUT"

- name: Download packaging artifacts
env:
Expand All @@ -63,6 +42,31 @@ jobs:
mkdir -p "$RUNNER_TEMP/installers"
gh run download "$RUN_ID" --repo "$GH_REPO" --dir "$RUNNER_TEMP/installers"

- name: Resolve packaged version
id: version
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RUN_SHA: ${{ steps.resolve.outputs.run_sha }}
shell: bash
run: |
set -euo pipefail
mapfile -t manifests < <(find "$RUNNER_TEMP/installers" -name release-version.txt -type f)
[[ "${#manifests[@]}" == 2 ]] || { echo "Expected two version manifests." >&2; exit 1; }
version="$(cat "${manifests[0]}")"
[[ "$version" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || exit 1
[[ "$(cat "${manifests[1]}")" == "$version" ]] || { echo "Package versions differ." >&2; exit 1; }
if gh release view "$version" >/dev/null 2>&1; then
echo "Release $version already exists. Refusing to overwrite it." >&2
exit 1
fi
if gh api "repos/$GH_REPO/git/ref/tags/$version" >/dev/null 2>&1; then
echo "Tag $version already exists. Refusing to overwrite it." >&2
exit 1
fi
echo "release_tag=$version" >> "$GITHUB_OUTPUT"
echo "Publishing $version from commit $RUN_SHA" >> "$GITHUB_STEP_SUMMARY"

- name: Validate installer files
shell: bash
run: |
Expand All @@ -86,12 +90,12 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RELEASE_TAG: ${{ steps.resolve.outputs.release_tag }}
RELEASE_TAG: ${{ steps.version.outputs.release_tag }}
shell: bash
run: |
set -euo pipefail
shopt -s nullglob
files=("$RUNNER_TEMP"/upload/*)
(("${#files[@]}" > 0)) || exit 1
gh release upload "$RELEASE_TAG" "${files[@]}" --repo "$GH_REPO"
gh release create "$RELEASE_TAG" "${files[@]}" --repo "$GH_REPO" --target "${{ steps.resolve.outputs.run_sha }}" --title "deepLocal $RELEASE_TAG" --notes "Windows and Linux desktop installers. macOS packages can be added separately."

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep the release unpublished until latest.json is uploaded

Whenever this workflow runs before the separate macOS uploader, this command publishes the new release without latest.json; because the desktop updater endpoint is releases/latest/download/latest.json, installed macOS clients receive a 404 until that later upload succeeds, or indefinitely if it never does. gh release create --help documents --draft as “Save the release as a draft instead of publishing it,” so the release should remain unpublished or include the updater metadata before becoming the latest release.

Useful? React with 👍 / 👎.

gh release view "$RELEASE_TAG" --repo "$GH_REPO" --json url --jq '.url' >> "$GITHUB_STEP_SUMMARY"
1 change: 1 addition & 0 deletions scripts/package-linux-app.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const fs = require('fs');
const [source, target] = process.argv.slice(2);
const config = JSON.parse(fs.readFileSync(source, 'utf8'));
config.bundle.targets = ['deb', 'appimage'];
if (process.env.DEEPLOCAL_RELEASE_VERSION) config.version = process.env.DEEPLOCAL_RELEASE_VERSION.replace(/^v/, '');
if (!process.env.TAURI_SIGNING_PRIVATE_KEY) {
config.bundle.createUpdaterArtifacts = false;
delete config.plugins?.updater;
Expand Down
1 change: 1 addition & 0 deletions scripts/package-windows-app.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ $configPath = Join-Path ([IO.Path]::GetTempPath()) ("deeplocal-tauri-" + [guid]:
try {
$config = Get-Content (Join-Path $desktop 'src-tauri\tauri.conf.json') -Raw | ConvertFrom-Json
$config.bundle.targets = @('nsis')
if ($env:DEEPLOCAL_RELEASE_VERSION) { $config.version = $env:DEEPLOCAL_RELEASE_VERSION.TrimStart('v') }
if (-not $env:TAURI_SIGNING_PRIVATE_KEY) {
$config.bundle.createUpdaterArtifacts = $false
$config.plugins.PSObject.Properties.Remove('updater')
Expand Down
21 changes: 20 additions & 1 deletion scripts/upload-new-app.sh
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,24 @@ cat > "$LATEST_JSON" <<EOF
}
EOF

gh release create "$RELEASE_VERSION" \
if gh release view "$RELEASE_VERSION" >/dev/null 2>&1; then
RELEASE_COMMIT="$(git rev-list -n 1 "$RELEASE_VERSION" 2>/dev/null || true)"
if [[ -z "$RELEASE_COMMIT" ]]; then
git fetch origin "refs/tags/$RELEASE_VERSION:refs/tags/$RELEASE_VERSION"
RELEASE_COMMIT="$(git rev-list -n 1 "$RELEASE_VERSION")"
fi
if [[ "$(git rev-parse HEAD)" != "$RELEASE_COMMIT" ]]; then
echo "The local commit does not match the release tag. Check out the release tag before uploading macOS assets." >&2
exit 1
fi
gh release upload "$RELEASE_VERSION" \
dist/deepLocal-macos.zip \
dist/deepLocal-macos.dmg \
"$UPDATER_ARCHIVE" \
"$UPDATER_SIGNATURE" \
"$LATEST_JSON"
else
gh release create "$RELEASE_VERSION" \
dist/deepLocal-macos.zip \
dist/deepLocal-macos.dmg \
"$UPDATER_ARCHIVE" \
Expand All @@ -162,6 +179,8 @@ SHA256:
- deepLocal-macos.zip: $(shasum -a 256 dist/deepLocal-macos.zip | awk '{print $1}')
- deepLocal-macos.dmg: $(shasum -a 256 dist/deepLocal-macos.dmg | awk '{print $1}')"

fi

echo ""
echo "=== Release ==="
gh release view "$RELEASE_VERSION"
Loading