feat: add multi-platform packaging and auto-update infrastructure

- Add PyInstaller configuration (clide.spec) for cross-platform builds
- Add build scripts for macOS (DMG), Linux (AppImage), and Windows (Inno Setup)
- Add Gitea Actions CI/CD workflow for automated releases
- Add auto-update service with Gitea API integration
- Add 'clide update' CLI command for checking and installing updates

Packaging outputs:
- macOS: Signed/notarized .app bundle in DMG
- Linux: Portable AppImage (glibc 2.17+)
- Windows: Inno Setup installer

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jeroen Schweitzer
2026-02-01 22:51:36 +01:00
co-authored by Claude Opus 4.5
parent 8b1a84e7e2
commit aff03a2e9b
11 changed files with 1032 additions and 10 deletions
+85
View File
@@ -0,0 +1,85 @@
#!/bin/bash
# Build script for Linux AppImage distribution
# Creates a portable AppImage that runs on most Linux distributions
#
# Prerequisites:
# - wget (for downloading appimagetool)
# - FUSE (for running appimagetool)
#
# Usage: ./scripts/build-linux.sh [VERSION]
set -e
VERSION="${1:-1.0.0}"
ARCH=$(uname -m)
echo "========================================"
echo "Building Clide $VERSION for Linux ($ARCH)"
echo "========================================"
# Ensure we're in the project root
cd "$(dirname "$0")/.."
# Build with PyInstaller
echo "Building with PyInstaller..."
pyinstaller clide.spec --clean --noconfirm
# Create AppDir structure
echo "Creating AppDir structure..."
rm -rf dist/Clide.AppDir
mkdir -p dist/Clide.AppDir/usr/bin
mkdir -p dist/Clide.AppDir/usr/share/applications
mkdir -p dist/Clide.AppDir/usr/share/icons/hicolor/256x256/apps
# Copy binary
cp dist/clide dist/Clide.AppDir/usr/bin/
# Copy desktop file
cp packaging/clide.desktop dist/Clide.AppDir/
cp packaging/clide.desktop dist/Clide.AppDir/usr/share/applications/
# Create placeholder icon (TODO: Replace with actual icon)
# For now, create a simple placeholder
if [ ! -f packaging/clide.png ]; then
echo "Note: No icon found at packaging/clide.png, AppImage will have no icon"
else
cp packaging/clide.png dist/Clide.AppDir/clide.png
cp packaging/clide.png dist/Clide.AppDir/usr/share/icons/hicolor/256x256/apps/clide.png
fi
# Create AppRun launcher
cat > dist/Clide.AppDir/AppRun << 'EOF'
#!/bin/bash
# AppRun - Entry point for AppImage
SELF=$(readlink -f "$0")
HERE=${SELF%/*}
export PATH="${HERE}/usr/bin:${PATH}"
export TERM="${TERM:-xterm-256color}"
exec "${HERE}/usr/bin/clide" "$@"
EOF
chmod +x dist/Clide.AppDir/AppRun
# Download appimagetool if needed
APPIMAGETOOL="appimagetool-${ARCH}.AppImage"
if [ ! -f "$APPIMAGETOOL" ]; then
echo "Downloading appimagetool..."
wget -q "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-${ARCH}.AppImage" \
-O "$APPIMAGETOOL"
chmod +x "$APPIMAGETOOL"
fi
# Create AppImage
echo "Creating AppImage..."
rm -f "dist/Clide-$VERSION-linux-$ARCH.AppImage"
ARCH=$ARCH ./"$APPIMAGETOOL" dist/Clide.AppDir "dist/Clide-$VERSION-linux-$ARCH.AppImage"
# Clean up AppDir
rm -rf dist/Clide.AppDir
echo ""
echo "========================================"
echo "Build complete!"
echo "Output: dist/Clide-$VERSION-linux-$ARCH.AppImage"
echo ""
echo "To run: chmod +x dist/Clide-$VERSION-linux-$ARCH.AppImage && ./dist/Clide-$VERSION-linux-$ARCH.AppImage"
echo "========================================"
+101
View File
@@ -0,0 +1,101 @@
#!/bin/bash
# Build script for macOS distribution
# Creates a signed and notarized DMG installer
#
# Prerequisites:
# - Xcode Command Line Tools: xcode-select --install
# - create-dmg: brew install create-dmg
# - Apple Developer ID certificate installed in Keychain
# - Keychain profile for notarytool: xcrun notarytool store-credentials
#
# Usage: ./scripts/build-macos.sh [VERSION]
set -e
VERSION="${1:-1.0.0}"
DEVELOPER_ID="${DEVELOPER_ID:-Developer ID Application: Your Name (TEAMID)}"
KEYCHAIN_PROFILE="${KEYCHAIN_PROFILE:-AC_PASSWORD}"
echo "========================================"
echo "Building Clide $VERSION for macOS"
echo "========================================"
# Ensure we're in the project root
cd "$(dirname "$0")/.."
# Check for required tools
if ! command -v create-dmg &> /dev/null; then
echo "Error: create-dmg not found. Install with: brew install create-dmg"
exit 1
fi
# Build with PyInstaller
echo "Building with PyInstaller..."
pyinstaller clide.spec --clean --noconfirm
# Check if signing is configured
if [[ "$DEVELOPER_ID" == *"Your Name"* ]]; then
echo ""
echo "Warning: DEVELOPER_ID not configured. Skipping code signing."
echo "To enable signing, set DEVELOPER_ID environment variable."
echo ""
SKIP_SIGNING=true
else
SKIP_SIGNING=false
fi
if [[ "$SKIP_SIGNING" == "false" ]]; then
# Code sign the app
echo "Code signing..."
codesign --deep --force --verify --verbose \
--sign "$DEVELOPER_ID" \
--options runtime \
--entitlements packaging/entitlements.plist \
--timestamp \
"dist/Clide.app"
# Create ZIP for notarization
echo "Creating ZIP for notarization..."
ditto -c -k --sequesterRsrc --keepParent \
"dist/Clide.app" "dist/Clide-$VERSION.zip"
# Notarize
echo "Submitting for notarization (this may take a few minutes)..."
xcrun notarytool submit "dist/Clide-$VERSION.zip" \
--keychain-profile "$KEYCHAIN_PROFILE" \
--wait
# Staple the notarization ticket
echo "Stapling notarization ticket..."
xcrun stapler staple "dist/Clide.app"
# Clean up ZIP
rm "dist/Clide-$VERSION.zip"
fi
# Create DMG
echo "Creating DMG..."
# Remove existing DMG if present
rm -f "dist/Clide-$VERSION-macos.dmg"
create-dmg \
--volname "Clide $VERSION" \
--window-pos 200 120 \
--window-size 600 400 \
--icon-size 100 \
--icon "Clide.app" 150 190 \
--hide-extension "Clide.app" \
--app-drop-link 450 185 \
"dist/Clide-$VERSION-macos.dmg" \
"dist/Clide.app" || true # create-dmg returns non-zero even on success sometimes
echo ""
echo "========================================"
echo "Build complete!"
echo "Output: dist/Clide-$VERSION-macos.dmg"
if [[ "$SKIP_SIGNING" == "true" ]]; then
echo ""
echo "Note: App was NOT signed or notarized."
echo "Users will see Gatekeeper warnings."
fi
echo "========================================"
+102
View File
@@ -0,0 +1,102 @@
# Build script for Windows installer distribution
# Creates an installer using Inno Setup
#
# Prerequisites:
# - Inno Setup 6: choco install innosetup -y
# Or download from: https://jrsoftware.org/isinfo.php
#
# Usage: .\scripts\build-windows.ps1 [-Version "1.0.0"]
param(
[string]$Version = "1.0.0"
)
$ErrorActionPreference = "Stop"
Write-Host "========================================"
Write-Host "Building Clide $Version for Windows"
Write-Host "========================================"
# Ensure we're in the project root
$ProjectRoot = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
Set-Location $ProjectRoot
# Build with PyInstaller
Write-Host "Building with PyInstaller..."
pyinstaller clide.spec --clean --noconfirm
# Check for Inno Setup
$InnoSetup = "C:\Program Files (x86)\Inno Setup 6\ISCC.exe"
if (-not (Test-Path $InnoSetup)) {
Write-Host ""
Write-Host "Warning: Inno Setup not found at $InnoSetup"
Write-Host "Install with: choco install innosetup -y"
Write-Host "Or download from: https://jrsoftware.org/isinfo.php"
Write-Host ""
Write-Host "Skipping installer creation. Binary available at: dist\clide.exe"
exit 0
}
# Create Inno Setup script
Write-Host "Creating Inno Setup script..."
$InnoScript = @"
; Clide Installer Script
; Generated by build-windows.ps1
[Setup]
AppName=Clide
AppVersion=$Version
AppPublisher=Clide
AppPublisherURL=https://github.com/your-repo/clide
AppSupportURL=https://github.com/your-repo/clide/issues
DefaultDirName={autopf}\Clide
DefaultGroupName=Clide
AllowNoIcons=yes
OutputDir=dist
OutputBaseFilename=Clide-$Version-windows-setup
Compression=lzma2
SolidCompression=yes
WizardStyle=modern
PrivilegesRequired=lowest
PrivilegesRequiredOverridesAllowed=dialog
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
[Files]
Source: "dist\clide.exe"; DestDir: "{app}"; Flags: ignoreversion
[Icons]
Name: "{group}\Clide"; Filename: "{app}\clide.exe"
Name: "{group}\{cm:UninstallProgram,Clide}"; Filename: "{uninstallexe}"
Name: "{autodesktop}\Clide"; Filename: "{app}\clide.exe"; Tasks: desktopicon
[Run]
Filename: "{app}\clide.exe"; Description: "{cm:LaunchProgram,Clide}"; Flags: nowait postinstall skipifsilent shellexec
[Code]
function InitializeSetup(): Boolean;
begin
Result := True;
// Add any initialization checks here
end;
"@
# Write Inno Setup script
$InnoScriptPath = Join-Path $ProjectRoot "build\clide.iss"
$InnoScript | Out-File -FilePath $InnoScriptPath -Encoding UTF8
# Build installer
Write-Host "Building installer with Inno Setup..."
& $InnoSetup $InnoScriptPath
Write-Host ""
Write-Host "========================================"
Write-Host "Build complete!"
Write-Host "Output: dist\Clide-$Version-windows-setup.exe"
Write-Host ""
Write-Host "Note: For best experience, users should run Clide in Windows Terminal."
Write-Host "========================================"