mirror of
https://github.com/aaddrick/claude-desktop-debian.git
synced 2026-05-17 00:26:21 +03:00
- Fix compute_checksum() stdout contamination: log messages were captured into variables alongside hash values; redirect to stderr - Use EXIT trap for temp file cleanup instead of repeating rm/output in every early-exit path - Remove redundant log messages in Patch 4 (replaceChecksums already logs its own status) Co-Authored-By: Claude <claude@anthropic.com>
423 lines
17 KiB
YAML
423 lines
17 KiB
YAML
name: Check Claude Desktop Version
|
|
on:
|
|
schedule:
|
|
- cron: "0 1 * * *"
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: write
|
|
|
|
concurrency:
|
|
group: main-branch-auto-update
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
check-version:
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
token: ${{ secrets.GH_PAT }}
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
python-version: "3.12"
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y p7zip-full wget zstd
|
|
pip install playwright requests
|
|
playwright install chromium
|
|
|
|
- name: Resolve download URLs
|
|
id: resolve_urls
|
|
run: |
|
|
echo "Resolving Claude Desktop download URLs..."
|
|
|
|
# Run the resolver script - stdout has KEY=VALUE pairs, stderr has status messages
|
|
python scripts/resolve-download-url.py all --format both > resolved_urls.txt || true
|
|
|
|
echo "Resolved URLs:"
|
|
cat resolved_urls.txt
|
|
|
|
# Parse the output
|
|
AMD64_URL=$(grep "^AMD64_URL=" resolved_urls.txt | cut -d= -f2-)
|
|
ARM64_URL=$(grep "^ARM64_URL=" resolved_urls.txt | cut -d= -f2-)
|
|
AMD64_VERSION=$(grep "^AMD64_VERSION=" resolved_urls.txt | cut -d= -f2-)
|
|
ARM64_VERSION=$(grep "^ARM64_VERSION=" resolved_urls.txt | cut -d= -f2-)
|
|
|
|
echo "AMD64 URL: $AMD64_URL"
|
|
echo "ARM64 URL: $ARM64_URL"
|
|
echo "AMD64 Version: $AMD64_VERSION"
|
|
echo "ARM64 Version: $ARM64_VERSION"
|
|
|
|
# Use AMD64 version as the canonical version (they should match)
|
|
CLAUDE_VERSION="${AMD64_VERSION:-$ARM64_VERSION}"
|
|
|
|
if [ -z "$AMD64_URL" ]; then
|
|
echo "::error::Failed to resolve AMD64 download URL"
|
|
exit 1
|
|
fi
|
|
|
|
echo "amd64_url=$AMD64_URL" >> $GITHUB_OUTPUT
|
|
echo "arm64_url=$ARM64_URL" >> $GITHUB_OUTPUT
|
|
echo "claude_version=$CLAUDE_VERSION" >> $GITHUB_OUTPUT
|
|
|
|
- name: Get current URLs from build.sh
|
|
id: current_urls
|
|
run: |
|
|
# Extract current URLs from build.sh
|
|
# The build.sh case statement uses x86_64/aarch64 patterns with claude_download_url on the next line
|
|
CURRENT_AMD64_URL=$(grep -E "x86_64\)" -A1 build.sh | grep -oP "claude_download_url='\\K[^']+")
|
|
CURRENT_ARM64_URL=$(grep -E "aarch64\)" -A1 build.sh | grep -oP "claude_download_url='\\K[^']+")
|
|
|
|
echo "Current AMD64 URL: $CURRENT_AMD64_URL"
|
|
echo "Current ARM64 URL: $CURRENT_ARM64_URL"
|
|
|
|
echo "current_amd64_url=$CURRENT_AMD64_URL" >> $GITHUB_OUTPUT
|
|
echo "current_arm64_url=$CURRENT_ARM64_URL" >> $GITHUB_OUTPUT
|
|
|
|
- name: Check if update needed
|
|
id: check_update
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
NEW_AMD64_URL="${{ steps.resolve_urls.outputs.amd64_url }}"
|
|
NEW_ARM64_URL="${{ steps.resolve_urls.outputs.arm64_url }}"
|
|
CURRENT_AMD64_URL="${{ steps.current_urls.outputs.current_amd64_url }}"
|
|
CURRENT_ARM64_URL="${{ steps.current_urls.outputs.current_arm64_url }}"
|
|
CLAUDE_VERSION="${{ steps.resolve_urls.outputs.claude_version }}"
|
|
STORED_CLAUDE_VERSION="${{ vars.CLAUDE_DESKTOP_VERSION }}"
|
|
REPO_VERSION="${{ vars.REPO_VERSION }}"
|
|
|
|
echo "Current stored Claude version: $STORED_CLAUDE_VERSION"
|
|
echo "Detected Claude version: $CLAUDE_VERSION"
|
|
echo "Repository version: $REPO_VERSION"
|
|
|
|
UPDATE_NEEDED=false
|
|
|
|
# Check if AMD64 URL changed
|
|
if [ "$NEW_AMD64_URL" != "$CURRENT_AMD64_URL" ]; then
|
|
echo "AMD64 URL has changed"
|
|
UPDATE_NEEDED=true
|
|
fi
|
|
|
|
# Check if ARM64 URL changed (only if we got a new one)
|
|
if [ -n "$NEW_ARM64_URL" ] && [ "$NEW_ARM64_URL" != "$CURRENT_ARM64_URL" ]; then
|
|
echo "ARM64 URL has changed"
|
|
UPDATE_NEEDED=true
|
|
fi
|
|
|
|
# Check if version changed
|
|
if [ -n "$CLAUDE_VERSION" ] && [ "$CLAUDE_VERSION" != "$STORED_CLAUDE_VERSION" ]; then
|
|
echo "Claude version has changed"
|
|
UPDATE_NEEDED=true
|
|
fi
|
|
|
|
if [ "$UPDATE_NEEDED" = "true" ]; then
|
|
echo "Update needed!"
|
|
echo "update_needed=true" >> $GITHUB_OUTPUT
|
|
|
|
# Construct the new tag
|
|
NEW_TAG="v${REPO_VERSION}+claude${CLAUDE_VERSION}"
|
|
echo "new_tag=$NEW_TAG" >> $GITHUB_OUTPUT
|
|
echo "New tag will be: $NEW_TAG"
|
|
else
|
|
echo "No updates needed"
|
|
echo "update_needed=false" >> $GITHUB_OUTPUT
|
|
fi
|
|
|
|
- name: Update build.sh with new URLs
|
|
if: steps.check_update.outputs.update_needed == 'true'
|
|
run: |
|
|
NEW_AMD64_URL="${{ steps.resolve_urls.outputs.amd64_url }}"
|
|
NEW_ARM64_URL="${{ steps.resolve_urls.outputs.arm64_url }}"
|
|
CURRENT_AMD64_URL="${{ steps.current_urls.outputs.current_amd64_url }}"
|
|
CURRENT_ARM64_URL="${{ steps.current_urls.outputs.current_arm64_url }}"
|
|
|
|
echo "Updating build.sh with new URLs..."
|
|
|
|
# Update AMD64 URL
|
|
if [ -n "$NEW_AMD64_URL" ] && [ "$NEW_AMD64_URL" != "$CURRENT_AMD64_URL" ]; then
|
|
echo "Updating AMD64 URL..."
|
|
# Escape special characters for sed
|
|
ESCAPED_CURRENT=$(printf '%s\n' "$CURRENT_AMD64_URL" | sed 's/[[\.*^$()+?{|]/\\&/g')
|
|
ESCAPED_NEW=$(printf '%s\n' "$NEW_AMD64_URL" | sed 's/[&/\]/\\&/g')
|
|
sed -i "s|$ESCAPED_CURRENT|$ESCAPED_NEW|g" build.sh
|
|
fi
|
|
|
|
# Update ARM64 URL (if we have a new one)
|
|
if [ -n "$NEW_ARM64_URL" ] && [ "$NEW_ARM64_URL" != "$CURRENT_ARM64_URL" ]; then
|
|
echo "Updating ARM64 URL..."
|
|
ESCAPED_CURRENT=$(printf '%s\n' "$CURRENT_ARM64_URL" | sed 's/[[\.*^$()+?{|]/\\&/g')
|
|
ESCAPED_NEW=$(printf '%s\n' "$NEW_ARM64_URL" | sed 's/[&/\]/\\&/g')
|
|
sed -i "s|$ESCAPED_CURRENT|$ESCAPED_NEW|g" build.sh
|
|
fi
|
|
|
|
echo "Updated build.sh URLs:"
|
|
grep "claude_download_url=" build.sh
|
|
|
|
- name: Compute SRI hashes for Nix
|
|
if: steps.check_update.outputs.update_needed == 'true'
|
|
id: nix_hashes
|
|
run: |
|
|
AMD64_URL="${{ steps.resolve_urls.outputs.amd64_url }}"
|
|
ARM64_URL="${{ steps.resolve_urls.outputs.arm64_url }}"
|
|
|
|
# Download to temp files first so a partial download doesn't
|
|
# produce a valid-looking but wrong SRI hash.
|
|
# Keep the AMD64 installer for VM checksum extraction later.
|
|
amd64_tmp=$(mktemp)
|
|
curl -fsSL -o "$amd64_tmp" "$AMD64_URL" || { echo "AMD64 download failed"; exit 1; }
|
|
AMD64_HEX=$(sha256sum "$amd64_tmp" | awk '{print $1}')
|
|
AMD64_HASH=$(echo "$AMD64_HEX" | xxd -r -p | base64 -w0)
|
|
echo "amd64_installer=$amd64_tmp" >> $GITHUB_OUTPUT
|
|
echo "amd64_sri=sha256-$AMD64_HASH" >> $GITHUB_OUTPUT
|
|
echo "amd64_sha256=$AMD64_HEX" >> $GITHUB_OUTPUT
|
|
|
|
if [ -n "$ARM64_URL" ]; then
|
|
arm64_tmp=$(mktemp)
|
|
curl -fsSL -o "$arm64_tmp" "$ARM64_URL" || { echo "ARM64 download failed"; exit 1; }
|
|
ARM64_HEX=$(sha256sum "$arm64_tmp" | awk '{print $1}')
|
|
ARM64_HASH=$(echo "$ARM64_HEX" | xxd -r -p | base64 -w0)
|
|
rm "$arm64_tmp"
|
|
echo "arm64_sri=sha256-$ARM64_HASH" >> $GITHUB_OUTPUT
|
|
echo "arm64_sha256=$ARM64_HEX" >> $GITHUB_OUTPUT
|
|
fi
|
|
|
|
- name: Update build.sh SHA-256 checksums
|
|
if: steps.check_update.outputs.update_needed == 'true'
|
|
run: |
|
|
AMD64_SHA256="${{ steps.nix_hashes.outputs.amd64_sha256 }}"
|
|
ARM64_SHA256="${{ steps.nix_hashes.outputs.arm64_sha256 }}"
|
|
|
|
echo "Updating build.sh SHA-256 checksums..."
|
|
|
|
# Update AMD64 hash (in x86_64 case block)
|
|
if [ -n "$AMD64_SHA256" ]; then
|
|
sed -i "/x86_64)/,/;;/{
|
|
s/claude_exe_sha256='[^']*'/claude_exe_sha256='$AMD64_SHA256'/
|
|
}" build.sh
|
|
fi
|
|
|
|
# Update ARM64 hash (in aarch64 case block)
|
|
if [ -n "$ARM64_SHA256" ]; then
|
|
sed -i "/aarch64)/,/;;/{
|
|
s/claude_exe_sha256='[^']*'/claude_exe_sha256='$ARM64_SHA256'/
|
|
}" build.sh
|
|
fi
|
|
|
|
echo "Updated build.sh checksums:"
|
|
grep "claude_exe_sha256=" build.sh
|
|
|
|
- name: Extract VM bundle SHA from installer
|
|
if: steps.check_update.outputs.update_needed == 'true'
|
|
id: vm_bundle
|
|
run: |
|
|
INSTALLER="${{ steps.nix_hashes.outputs.amd64_installer }}"
|
|
if [ -z "$INSTALLER" ] || [ ! -f "$INSTALLER" ]; then
|
|
echo "No installer available, skipping VM checksums"
|
|
echo "bundle_sha=" >> $GITHUB_OUTPUT
|
|
exit 0
|
|
fi
|
|
|
|
WORK=$(mktemp -d)
|
|
# Clean up temp files on any exit path
|
|
skip_vm() { echo "bundle_sha=" >> $GITHUB_OUTPUT; }
|
|
trap 'rm -rf "$WORK"; rm -f "$INSTALLER"' EXIT
|
|
|
|
echo "Extracting installer to find bundle SHA..."
|
|
|
|
# Extract exe → nupkg → app.asar → index.js
|
|
7z x -y -o"$WORK/exe" "$INSTALLER" > /dev/null 2>&1
|
|
NUPKG=$(find "$WORK/exe" -name "AnthropicClaude-*.nupkg" | head -1)
|
|
if [ -z "$NUPKG" ]; then
|
|
echo "No nupkg found, skipping VM checksums"
|
|
skip_vm; exit 0
|
|
fi
|
|
|
|
7z x -y -o"$WORK/nupkg" "$NUPKG" > /dev/null 2>&1
|
|
ASAR="$WORK/nupkg/lib/net45/resources/app.asar"
|
|
if [ ! -f "$ASAR" ]; then
|
|
echo "No app.asar found, skipping VM checksums"
|
|
skip_vm; exit 0
|
|
fi
|
|
|
|
npx --yes @electron/asar extract "$ASAR" "$WORK/app" 2>/dev/null
|
|
INDEX_JS="$WORK/app/.vite/build/index.js"
|
|
if [ ! -f "$INDEX_JS" ]; then
|
|
echo "No index.js found, skipping VM checksums"
|
|
skip_vm; exit 0
|
|
fi
|
|
|
|
# Extract bundle SHA (40-char hex after sha: or sha:")
|
|
BUNDLE_SHA=$(grep -oP 'sha\s*:\s*"([a-f0-9]{40})"' "$INDEX_JS" \
|
|
| grep -oP '[a-f0-9]{40}' | head -1)
|
|
|
|
if [ -z "$BUNDLE_SHA" ]; then
|
|
echo "Could not extract bundle SHA, skipping VM checksums"
|
|
skip_vm; exit 0
|
|
fi
|
|
|
|
echo "Bundle SHA: $BUNDLE_SHA"
|
|
|
|
# Validate CDN has linux files for this SHA
|
|
HTTP_CODE=$(curl -sI -o /dev/null -w "%{http_code}" \
|
|
"https://downloads.claude.ai/vms/linux/x64/$BUNDLE_SHA/vmlinuz.zst")
|
|
if [ "$HTTP_CODE" != "200" ]; then
|
|
echo "Linux VM files not published yet (HTTP $HTTP_CODE), skipping"
|
|
skip_vm; exit 0
|
|
fi
|
|
|
|
echo "Linux VM files confirmed on CDN"
|
|
echo "bundle_sha=$BUNDLE_SHA" >> $GITHUB_OUTPUT
|
|
|
|
- name: Compute VM bundle checksums
|
|
if: steps.check_update.outputs.update_needed == 'true' && steps.vm_bundle.outputs.bundle_sha != ''
|
|
id: vm_checksums
|
|
run: |
|
|
SHA="${{ steps.vm_bundle.outputs.bundle_sha }}"
|
|
BASE="https://downloads.claude.ai/vms/linux"
|
|
|
|
compute_checksum() {
|
|
local url="$1" label="$2"
|
|
local hash
|
|
hash=$(set -o pipefail
|
|
curl -fsSL --max-time 600 "$url" \
|
|
| zstd -d \
|
|
| sha256sum \
|
|
| awk '{print $1}'
|
|
) || { echo " FAILED: $label" >&2; return; }
|
|
|
|
if [[ ! $hash =~ ^[a-f0-9]{64}$ ]]; then
|
|
echo " INVALID hash for $label: $hash" >&2
|
|
return
|
|
fi
|
|
echo " $label: $hash" >&2
|
|
echo "$hash"
|
|
}
|
|
|
|
echo "Computing x64 checksums..."
|
|
X64_ROOTFS=$(compute_checksum "$BASE/x64/$SHA/rootfs.vhdx.zst" "x64/rootfs.vhdx")
|
|
X64_VMLINUZ=$(compute_checksum "$BASE/x64/$SHA/vmlinuz.zst" "x64/vmlinuz")
|
|
X64_INITRD=$(compute_checksum "$BASE/x64/$SHA/initrd.zst" "x64/initrd")
|
|
|
|
echo "Computing arm64 checksums..."
|
|
ARM64_ROOTFS=$(compute_checksum "$BASE/arm64/$SHA/rootfs.vhdx.zst" "arm64/rootfs.vhdx")
|
|
ARM64_VMLINUZ=$(compute_checksum "$BASE/arm64/$SHA/vmlinuz.zst" "arm64/vmlinuz")
|
|
ARM64_INITRD=$(compute_checksum "$BASE/arm64/$SHA/initrd.zst" "arm64/initrd")
|
|
|
|
echo "x64_rootfs=$X64_ROOTFS" >> $GITHUB_OUTPUT
|
|
echo "x64_vmlinuz=$X64_VMLINUZ" >> $GITHUB_OUTPUT
|
|
echo "x64_initrd=$X64_INITRD" >> $GITHUB_OUTPUT
|
|
echo "arm64_rootfs=$ARM64_ROOTFS" >> $GITHUB_OUTPUT
|
|
echo "arm64_vmlinuz=$ARM64_VMLINUZ" >> $GITHUB_OUTPUT
|
|
echo "arm64_initrd=$ARM64_INITRD" >> $GITHUB_OUTPUT
|
|
|
|
- name: Update build.sh VM checksums
|
|
if: steps.check_update.outputs.update_needed == 'true' && steps.vm_bundle.outputs.bundle_sha != ''
|
|
run: |
|
|
update_vm_checksum() {
|
|
local var="$1" value="$2"
|
|
if [ -z "$value" ]; then
|
|
echo " Skipping $var (empty)"
|
|
return
|
|
fi
|
|
sed -i "s/${var}='[^']*'/${var}='${value}'/" build.sh
|
|
}
|
|
|
|
echo "Updating build.sh VM checksums..."
|
|
update_vm_checksum vm_checksum_x64_rootfs_vhdx "${{ steps.vm_checksums.outputs.x64_rootfs }}"
|
|
update_vm_checksum vm_checksum_x64_vmlinuz "${{ steps.vm_checksums.outputs.x64_vmlinuz }}"
|
|
update_vm_checksum vm_checksum_x64_initrd "${{ steps.vm_checksums.outputs.x64_initrd }}"
|
|
update_vm_checksum vm_checksum_arm64_rootfs_vhdx "${{ steps.vm_checksums.outputs.arm64_rootfs }}"
|
|
update_vm_checksum vm_checksum_arm64_vmlinuz "${{ steps.vm_checksums.outputs.arm64_vmlinuz }}"
|
|
update_vm_checksum vm_checksum_arm64_initrd "${{ steps.vm_checksums.outputs.arm64_initrd }}"
|
|
|
|
echo "Verifying updates:"
|
|
grep "vm_checksum_" build.sh
|
|
|
|
- name: Update Nix package
|
|
if: steps.check_update.outputs.update_needed == 'true'
|
|
run: |
|
|
CLAUDE_VERSION="${{ steps.resolve_urls.outputs.claude_version }}"
|
|
NEW_AMD64_URL="${{ steps.resolve_urls.outputs.amd64_url }}"
|
|
NEW_ARM64_URL="${{ steps.resolve_urls.outputs.arm64_url }}"
|
|
AMD64_SRI="${{ steps.nix_hashes.outputs.amd64_sri }}"
|
|
ARM64_SRI="${{ steps.nix_hashes.outputs.arm64_sri }}"
|
|
|
|
NIX_FILE="nix/claude-desktop.nix"
|
|
|
|
# Update version
|
|
sed -i "s/version = \"[^\"]*\"/version = \"$CLAUDE_VERSION\"/" "$NIX_FILE"
|
|
|
|
# Update amd64 URL and hash
|
|
AMD64_PATH=$(echo "$NEW_AMD64_URL" | sed 's|.*releases/|releases/|')
|
|
sed -i "s|releases/win32/x64/[^\"]*|$AMD64_PATH|" "$NIX_FILE"
|
|
# Range assumes each arch block (x86_64-linux/aarch64-linux) contains
|
|
# exactly one `hash = "..."` line before its closing `};`.
|
|
sed -i "/x86_64-linux/,/};/{s|hash = \"[^\"]*\"|hash = \"$AMD64_SRI\"|}" "$NIX_FILE"
|
|
|
|
# Update arm64 URL and hash
|
|
if [ -n "$NEW_ARM64_URL" ]; then
|
|
ARM64_PATH=$(echo "$NEW_ARM64_URL" | sed 's|.*releases/|releases/|')
|
|
sed -i "s|releases/win32/arm64/[^\"]*|$ARM64_PATH|" "$NIX_FILE"
|
|
sed -i "/aarch64-linux/,/};/{s|hash = \"[^\"]*\"|hash = \"$ARM64_SRI\"|}" "$NIX_FILE"
|
|
fi
|
|
|
|
echo "Updated $NIX_FILE:"
|
|
head -30 "$NIX_FILE"
|
|
|
|
- name: Commit and push changes
|
|
if: steps.check_update.outputs.update_needed == 'true'
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GH_PAT }}
|
|
run: |
|
|
CLAUDE_VERSION="${{ steps.resolve_urls.outputs.claude_version }}"
|
|
NEW_TAG="${{ steps.check_update.outputs.new_tag }}"
|
|
|
|
# Check if we have a PAT
|
|
if [ -z "$GH_TOKEN" ]; then
|
|
echo "Error: GH_PAT secret is not configured"
|
|
exit 1
|
|
fi
|
|
|
|
# Configure git
|
|
git config user.name "github-actions[bot]"
|
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
|
|
# Check if there are changes to commit
|
|
if git diff --quiet build.sh nix/claude-desktop.nix; then
|
|
echo "No changes to build.sh or nix/claude-desktop.nix"
|
|
else
|
|
git add build.sh nix/claude-desktop.nix
|
|
git commit -m "$(cat <<COMMIT_MSG
|
|
Update Claude Desktop download URLs to version $CLAUDE_VERSION
|
|
|
|
Updated download URLs resolved from official redirect endpoints.
|
|
|
|
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
|
COMMIT_MSG
|
|
)"
|
|
git push
|
|
echo "Changes committed and pushed"
|
|
fi
|
|
|
|
# Update the version variable
|
|
gh variable set CLAUDE_DESKTOP_VERSION --body "$CLAUDE_VERSION"
|
|
|
|
echo "Creating and pushing new tag: $NEW_TAG"
|
|
|
|
# Create annotated tag
|
|
git tag -a "$NEW_TAG" -m "Update to Claude Desktop $CLAUDE_VERSION"
|
|
|
|
# Push the tag
|
|
git push origin "$NEW_TAG"
|
|
|
|
echo "Successfully created tag $NEW_TAG"
|