mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-07-29 10:55:00 +00:00
416 lines
12 KiB
Bash
Executable file
416 lines
12 KiB
Bash
Executable file
#!/bin/bash
|
|
|
|
set -e
|
|
|
|
VERSIONS_URL=""
|
|
DOWNLOAD_DIR="$HOME/.craft-agent/downloads"
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[0;33m'
|
|
BLUE='\033[0;34m'
|
|
BOLD='\033[1m'
|
|
NC='\033[0m' # No Color
|
|
|
|
info() { printf "%b\n" "${BLUE}>${NC} $1"; }
|
|
success() { printf "%b\n" "${GREEN}>${NC} $1"; }
|
|
warn() { printf "%b\n" "${YELLOW}!${NC} $1"; }
|
|
error() { printf "%b\n" "${RED}x${NC} $1"; exit 1; }
|
|
|
|
error "Desktop installer is disabled until an owned update server is configured."
|
|
|
|
# Detect OS
|
|
OS="$(uname -s)"
|
|
case "$OS" in
|
|
Darwin) OS_TYPE="darwin" ;;
|
|
Linux) OS_TYPE="linux" ;;
|
|
*) error "Unsupported operating system: $OS" ;;
|
|
esac
|
|
|
|
# Check for required dependencies
|
|
DOWNLOADER=""
|
|
if command -v curl >/dev/null 2>&1; then
|
|
DOWNLOADER="curl"
|
|
elif command -v wget >/dev/null 2>&1; then
|
|
DOWNLOADER="wget"
|
|
else
|
|
error "Either curl or wget is required but neither is installed"
|
|
fi
|
|
|
|
# Check if yq is available (optional, for YAML parsing)
|
|
HAS_YQ=false
|
|
if command -v yq >/dev/null 2>&1; then
|
|
HAS_YQ=true
|
|
fi
|
|
|
|
# Download function that works with both curl and wget
|
|
# Usage: download_file <url> [output_file] [show_progress]
|
|
download_file() {
|
|
local url="$1"
|
|
local output="$2"
|
|
local show_progress="${3:-false}"
|
|
|
|
if [ "$DOWNLOADER" = "curl" ]; then
|
|
if [ -n "$output" ]; then
|
|
if [ "$show_progress" = "true" ]; then
|
|
curl -fL --progress-bar -o "$output" "$url"
|
|
else
|
|
curl -fsSL -o "$output" "$url"
|
|
fi
|
|
else
|
|
curl -fsSL "$url"
|
|
fi
|
|
elif [ "$DOWNLOADER" = "wget" ]; then
|
|
if [ -n "$output" ]; then
|
|
if [ "$show_progress" = "true" ]; then
|
|
wget --show-progress -q -O "$output" "$url"
|
|
else
|
|
wget -q -O "$output" "$url"
|
|
fi
|
|
else
|
|
wget -q -O - "$url"
|
|
fi
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Extract sha512 from YAML for a specific architecture
|
|
# YAML format: files array with url, sha512, arch fields
|
|
get_sha512_from_yaml() {
|
|
local yaml="$1"
|
|
local target_arch="$2"
|
|
|
|
# Find the line with the target arch and extract sha512 from preceding lines
|
|
local in_target_block=false
|
|
local sha512=""
|
|
|
|
while IFS= read -r line; do
|
|
# Check if we're entering a new file entry
|
|
if [[ $line =~ ^[[:space:]]*-[[:space:]]*url: ]]; then
|
|
in_target_block=false
|
|
sha512=""
|
|
fi
|
|
# Extract sha512
|
|
if [[ $line =~ sha512:[[:space:]]*(.+) ]]; then
|
|
sha512="${BASH_REMATCH[1]}"
|
|
fi
|
|
# Check arch
|
|
if [[ $line =~ arch:[[:space:]]*(.+) ]]; then
|
|
local arch="${BASH_REMATCH[1]}"
|
|
if [ "$arch" = "$target_arch" ] && [ -n "$sha512" ]; then
|
|
echo "$sha512"
|
|
return 0
|
|
fi
|
|
fi
|
|
done <<< "$yaml"
|
|
|
|
return 1
|
|
}
|
|
|
|
# Extract filename from YAML for a specific architecture
|
|
get_filename_from_yaml() {
|
|
local yaml="$1"
|
|
local target_arch="$2"
|
|
|
|
local url=""
|
|
|
|
while IFS= read -r line; do
|
|
# Check if we're entering a new file entry
|
|
if [[ $line =~ ^[[:space:]]*-[[:space:]]*url:[[:space:]]*(.+) ]]; then
|
|
url="${BASH_REMATCH[1]}"
|
|
fi
|
|
# Check arch
|
|
if [[ $line =~ arch:[[:space:]]*(.+) ]]; then
|
|
local arch="${BASH_REMATCH[1]}"
|
|
if [ "$arch" = "$target_arch" ] && [ -n "$url" ]; then
|
|
echo "$url"
|
|
return 0
|
|
fi
|
|
fi
|
|
done <<< "$yaml"
|
|
|
|
return 1
|
|
}
|
|
|
|
# Detect architecture
|
|
case "$(uname -m)" in
|
|
x86_64|amd64) arch="x64" ;;
|
|
arm64|aarch64) arch="arm64" ;;
|
|
*) error "Unsupported architecture: $(uname -m)" ;;
|
|
esac
|
|
|
|
# Set platform-specific variables
|
|
if [ "$OS_TYPE" = "darwin" ]; then
|
|
platform="darwin-${arch}"
|
|
APP_NAME="Qwen Code Desktop.app"
|
|
INSTALL_DIR="/Applications"
|
|
ext="zip"
|
|
yml_file="latest-mac.yml"
|
|
else
|
|
# Linux only supports x64 currently
|
|
if [ "$arch" != "x64" ]; then
|
|
error "Linux currently only supports x64 architecture. Your architecture: $arch"
|
|
fi
|
|
platform="linux-${arch}"
|
|
APP_NAME="Qwen-Code-Desktop-x64.AppImage"
|
|
INSTALL_DIR="$HOME/.local/bin"
|
|
ext="AppImage"
|
|
yml_file="latest-linux.yml"
|
|
fi
|
|
|
|
echo ""
|
|
info "Detected platform: $platform"
|
|
|
|
mkdir -p "$DOWNLOAD_DIR"
|
|
mkdir -p "$INSTALL_DIR"
|
|
|
|
# Fetch YAML manifest directly from /electron/latest/ (no version endpoint needed)
|
|
info "Fetching release info..."
|
|
manifest_yaml=$(download_file "$VERSIONS_URL/latest/$yml_file")
|
|
|
|
if [ -z "$manifest_yaml" ]; then
|
|
error "Failed to fetch release info from $yml_file"
|
|
fi
|
|
|
|
# Extract version from YAML manifest
|
|
if [ "$HAS_YQ" = true ]; then
|
|
version=$(echo "$manifest_yaml" | yq -r '.version // empty')
|
|
else
|
|
version=$(echo "$manifest_yaml" | grep -m1 '^version:' | sed 's/^version:[[:space:]]*//')
|
|
fi
|
|
|
|
if [ -z "$version" ]; then
|
|
error "Failed to extract version from manifest"
|
|
fi
|
|
|
|
info "Latest version: $version"
|
|
|
|
# Extract sha512 and filename for our architecture
|
|
if [ "$HAS_YQ" = true ]; then
|
|
checksum=$(echo "$manifest_yaml" | yq -r ".files[] | select(.arch == \"$arch\") | .sha512")
|
|
filename=$(echo "$manifest_yaml" | yq -r ".files[] | select(.arch == \"$arch\") | .url")
|
|
else
|
|
checksum=$(get_sha512_from_yaml "$manifest_yaml" "$arch")
|
|
filename=$(get_filename_from_yaml "$manifest_yaml" "$arch")
|
|
fi
|
|
|
|
# Validate checksum format (SHA512 base64 = 88 characters)
|
|
if [ -z "$checksum" ] || [ ${#checksum} -lt 80 ]; then
|
|
error "Architecture $arch not found in $yml_file"
|
|
fi
|
|
|
|
# Use default filename if not found
|
|
if [ -z "$filename" ]; then
|
|
filename="Qwen-Code-Desktop-${arch}.${ext}"
|
|
fi
|
|
|
|
info "Expected sha512: ${checksum:0:20}..."
|
|
|
|
# Download installer
|
|
installer_url="$VERSIONS_URL/latest/$filename"
|
|
installer_path="$DOWNLOAD_DIR/$filename"
|
|
|
|
info "Downloading $filename..."
|
|
echo ""
|
|
if ! download_file "$installer_url" "$installer_path" true; then
|
|
rm -f "$installer_path"
|
|
error "Download failed"
|
|
fi
|
|
echo ""
|
|
|
|
# Verify checksum (sha512, base64 encoded)
|
|
info "Verifying checksum..."
|
|
if [ "$OS_TYPE" = "darwin" ]; then
|
|
# macOS: shasum outputs hex, convert to base64
|
|
actual=$(shasum -a 512 "$installer_path" | cut -d' ' -f1 | xxd -r -p | base64)
|
|
else
|
|
# Linux: sha512sum outputs hex, convert to base64
|
|
actual=$(sha512sum "$installer_path" | cut -d' ' -f1 | xxd -r -p | base64 | tr -d '\n')
|
|
fi
|
|
|
|
if [ "$actual" != "$checksum" ]; then
|
|
rm -f "$installer_path"
|
|
error "Checksum verification failed\n Expected: $checksum\n Actual: $actual"
|
|
fi
|
|
|
|
success "Checksum verified!"
|
|
|
|
# Platform-specific installation
|
|
if [ "$OS_TYPE" = "darwin" ]; then
|
|
# macOS installation (from ZIP)
|
|
zip_path="$installer_path"
|
|
|
|
# Quit the app if it's running (use bundle ID for reliability)
|
|
APP_BUNDLE_ID="com.alibaba.qwen-code"
|
|
is_app_running() {
|
|
[ "$(osascript -e "application id \"$APP_BUNDLE_ID\" is running" 2>/dev/null || echo false)" = "true" ]
|
|
}
|
|
|
|
if is_app_running; then
|
|
info "Quitting Qwen Code Desktop..."
|
|
osascript -e "tell application id \"$APP_BUNDLE_ID\" to quit" 2>/dev/null || true
|
|
# Wait for app to quit (max 5 seconds) - POSIX compatible loop
|
|
i=0
|
|
while [ $i -lt 10 ]; do
|
|
if ! is_app_running; then
|
|
break
|
|
fi
|
|
sleep 0.5
|
|
i=$((i + 1))
|
|
done
|
|
# Force kill if still running
|
|
if is_app_running; then
|
|
warn "App didn't quit gracefully. Force quitting (unsaved data may be lost)..."
|
|
pkill -9 -f "Qwen Code Desktop.app" 2>/dev/null || true
|
|
pkill -9 -f "Qwen Code.app" 2>/dev/null || true
|
|
# Wait longer for macOS to release file handles
|
|
sleep 3
|
|
fi
|
|
fi
|
|
|
|
# Remove existing installation if present
|
|
if [ -d "$INSTALL_DIR/$APP_NAME" ]; then
|
|
info "Removing previous installation..."
|
|
rm -rf "$INSTALL_DIR/$APP_NAME"
|
|
fi
|
|
if [ -d "$INSTALL_DIR/Qwen Code.app" ]; then
|
|
info "Removing previous Qwen Code installation..."
|
|
rm -rf "$INSTALL_DIR/Qwen Code.app"
|
|
fi
|
|
|
|
# Extract ZIP to temp directory
|
|
info "Extracting..."
|
|
temp_dir=$(mktemp -d)
|
|
if ! unzip -q "$zip_path" -d "$temp_dir"; then
|
|
rm -rf "$temp_dir"
|
|
rm -f "$zip_path"
|
|
error "Failed to extract ZIP"
|
|
fi
|
|
|
|
# Find the .app in the extracted contents
|
|
app_source=$(find "$temp_dir" -maxdepth 1 -name "*.app" -type d | head -1)
|
|
|
|
if [ -z "$app_source" ]; then
|
|
rm -rf "$temp_dir"
|
|
rm -f "$zip_path"
|
|
error "No .app found in ZIP"
|
|
fi
|
|
|
|
# Copy app to /Applications
|
|
info "Installing to $INSTALL_DIR..."
|
|
cp -R "$app_source" "$INSTALL_DIR/$APP_NAME"
|
|
|
|
# Clean up
|
|
info "Cleaning up..."
|
|
rm -rf "$temp_dir"
|
|
rm -f "$zip_path"
|
|
|
|
# Remove quarantine attribute if present
|
|
xattr -rd com.apple.quarantine "$INSTALL_DIR/$APP_NAME" 2>/dev/null || true
|
|
|
|
echo ""
|
|
echo "─────────────────────────────────────────────────────────────────────────"
|
|
echo ""
|
|
success "Installation complete!"
|
|
echo ""
|
|
printf "%b\n" " Qwen Code Desktop has been installed to ${BOLD}$INSTALL_DIR/$APP_NAME${NC}"
|
|
echo ""
|
|
printf "%b\n" " You can launch it from ${BOLD}Applications${NC} or by running:"
|
|
printf "%b\n" " ${BOLD}open -a 'Qwen Code Desktop'${NC}"
|
|
echo ""
|
|
|
|
else
|
|
# Linux installation
|
|
appimage_path="$installer_path"
|
|
|
|
# New paths
|
|
APP_DIR="$HOME/.craft-agent/app"
|
|
WRAPPER_PATH="$INSTALL_DIR/qwen-code"
|
|
APPIMAGE_INSTALL_PATH="$APP_DIR/Qwen-Code-Desktop-x64.AppImage"
|
|
|
|
# Kill the app if it's running
|
|
if pgrep -f "Qwen-Code.*AppImage" >/dev/null 2>&1; then
|
|
info "Stopping Qwen Code Desktop..."
|
|
pkill -f "Qwen-Code.*AppImage" 2>/dev/null || true
|
|
sleep 2
|
|
fi
|
|
|
|
# Create directories
|
|
mkdir -p "$APP_DIR"
|
|
mkdir -p "$INSTALL_DIR"
|
|
|
|
# Remove existing AppImage
|
|
[ -f "$APPIMAGE_INSTALL_PATH" ] && rm -f "$APPIMAGE_INSTALL_PATH"
|
|
[ -f "$APP_DIR/Qwen-Code-x64.AppImage" ] && rm -f "$APP_DIR/Qwen-Code-x64.AppImage"
|
|
|
|
# Install AppImage
|
|
info "Installing AppImage to $APP_DIR..."
|
|
mv "$appimage_path" "$APPIMAGE_INSTALL_PATH"
|
|
chmod +x "$APPIMAGE_INSTALL_PATH"
|
|
|
|
# Create wrapper script
|
|
info "Creating launcher at $WRAPPER_PATH..."
|
|
cat > "$WRAPPER_PATH" << 'WRAPPER_EOF'
|
|
#!/bin/bash
|
|
# Qwen Code Desktop launcher - handles Linux-specific AppImage issues
|
|
|
|
APPIMAGE_PATH="$HOME/.craft-agent/app/Qwen-Code-Desktop-x64.AppImage"
|
|
ELECTRON_CACHE="$HOME/.config/@qwen-code"
|
|
ELECTRON_CACHE_ALT="$HOME/.cache/@qwen-code"
|
|
|
|
# Verify AppImage exists
|
|
if [ ! -f "$APPIMAGE_PATH" ]; then
|
|
echo "Error: Qwen Code Desktop not found at $APPIMAGE_PATH"
|
|
echo "Reinstall: disabled until an owned update server is configured"
|
|
exit 1
|
|
fi
|
|
|
|
# Ensure DISPLAY is set (required for X11)
|
|
if [ -z "$DISPLAY" ]; then
|
|
export DISPLAY=:0.0
|
|
fi
|
|
|
|
# Clear stale cache referencing AppImage mount paths
|
|
# AppImage creates a new /tmp/.mount_Qwen-XXXX each launch, so any cached path is stale
|
|
for cache_dir in "$ELECTRON_CACHE" "$ELECTRON_CACHE_ALT"; do
|
|
if [ -d "$cache_dir" ] && grep -rq '/tmp/\.mount_Qwen' "$cache_dir" 2>/dev/null; then
|
|
rm -rf "$cache_dir"
|
|
fi
|
|
done
|
|
|
|
# Set APPIMAGE for auto-update
|
|
export APPIMAGE="$APPIMAGE_PATH"
|
|
|
|
# Launch with --no-sandbox (AppImage extracts to /tmp, losing SUID on chrome-sandbox)
|
|
exec "$APPIMAGE_PATH" --no-sandbox "$@"
|
|
WRAPPER_EOF
|
|
|
|
chmod +x "$WRAPPER_PATH"
|
|
|
|
# Migrate old installation
|
|
OLD_APPIMAGE="$INSTALL_DIR/Qwen-Code-x64.AppImage"
|
|
[ -f "$OLD_APPIMAGE" ] && rm -f "$OLD_APPIMAGE"
|
|
|
|
echo ""
|
|
echo "─────────────────────────────────────────────────────────────────────────"
|
|
echo ""
|
|
success "Installation complete!"
|
|
echo ""
|
|
printf "%b\n" " AppImage: ${BOLD}$APPIMAGE_INSTALL_PATH${NC}"
|
|
printf "%b\n" " Launcher: ${BOLD}$WRAPPER_PATH${NC}"
|
|
echo ""
|
|
printf "%b\n" " Run with: ${BOLD}qwen-code${NC}"
|
|
echo ""
|
|
printf "%b\n" " Add to PATH if needed:"
|
|
printf "%b\n" " ${BOLD}echo 'export PATH=\"\$HOME/.local/bin:\$PATH\"' >> ~/.bashrc${NC}"
|
|
echo ""
|
|
|
|
# FUSE check
|
|
if ! command -v fusermount >/dev/null 2>&1; then
|
|
warn "FUSE required but not detected."
|
|
printf "%b\n" " Install: ${BOLD}sudo apt install fuse libfuse2${NC} (Debian/Ubuntu)"
|
|
printf "%b\n" " ${BOLD}sudo dnf install fuse fuse-libs${NC} (Fedora)"
|
|
fi
|
|
fi
|