name: AUR Release permissions: contents: read actions: read on: workflow_dispatch: inputs: tag: description: 'Tag version to release to AUR (e.g., v0.5.1)' required: true type: string pkgrel: description: 'Package release number (default: 1)' required: false type: string default: '1' workflow_call: inputs: tag: description: 'Tag version to release to AUR' required: true type: string pkgrel: description: 'Package release number (default: 1)' required: false type: string default: '1' jobs: aur-release: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 with: ref: ${{ inputs.tag }} fetch-depth: 0 - name: Verify tag exists run: | if ! git rev-parse --verify "${{ inputs.tag }}" >/dev/null 2>&1; then echo "❌ Tag '${{ inputs.tag }}' does not exist" echo "Available tags:" git tag -l | tail -10 exit 1 fi echo "✅ Tag '${{ inputs.tag }}' exists" - name: Get release information id: release_info uses: actions/github-script@v7 with: script: | const fs = require('fs'); const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8')); const tag = "${{ inputs.tag }}"; const version = tag.replace(/^v/, ''); const authorName = packageJson.author.name; const authorEmail = packageJson.author.email; const appImageUrl = `https://github.com/lone-cloud/gerbil/releases/download/${tag}/Gerbil-${version}.AppImage`; core.setOutput('version', version); core.setOutput('tag', tag); core.setOutput('author_name', authorName); core.setOutput('author_email', authorEmail); core.setOutput('appimage_url', appImageUrl); console.log(`Release info:`); console.log(`Version: ${version}`); console.log(`Tag: ${tag}`); console.log(`Author: ${authorName} <${authorEmail}>`); console.log(`AppImage URL: ${appImageUrl}`); - name: Verify AppImage availability run: | echo "Verifying AppImage is accessible..." APPIMAGE_URL="${{ steps.release_info.outputs.appimage_url }}" max_attempts=10 attempt=1 while [ $attempt -le $max_attempts ]; do echo "Attempt $attempt: Checking if AppImage is available..." if curl -I -f "$APPIMAGE_URL" > /dev/null 2>&1; then echo "✅ AppImage is accessible" break else echo "❌ AppImage not yet accessible, waiting 30 seconds..." sleep 30 attempt=$((attempt + 1)) fi done if [ $attempt -gt $max_attempts ]; then echo "❌ AppImage not accessible after $max_attempts attempts" exit 1 fi - name: Download artifacts and calculate SHA256s id: sha_calc run: | # Download AppImage and desktop file with retry logic, then calculate SHA256s max_attempts=3 # Download AppImage with retry for attempt in $(seq 1 $max_attempts); do echo "Attempt $attempt: Downloading AppImage..." if curl -L -o "gerbil-${{ steps.release_info.outputs.version }}.AppImage" "${{ steps.release_info.outputs.appimage_url }}"; then echo "✅ AppImage downloaded successfully" break else echo "❌ AppImage download failed, waiting 10 seconds..." sleep 10 if [ $attempt -eq $max_attempts ]; then echo "❌ Failed to download AppImage after $max_attempts attempts" exit 1 fi fi done # Use local files from the repository (already checked out) echo "✅ Using local desktop file from assets/" echo "✅ Using local LICENSE file from repository root" # Verify file sizes appimage_size=$(stat -c%s "gerbil-${{ steps.release_info.outputs.version }}.AppImage") echo "AppImage size: $appimage_size bytes" if [ $appimage_size -lt 50000000 ]; then # Less than 50MB is suspicious echo "❌ AppImage seems too small ($appimage_size bytes), possible download issue" exit 1 fi SHA256_APPIMAGE=$(sha256sum "gerbil-${{ steps.release_info.outputs.version }}.AppImage" | cut -d' ' -f1) SHA256_DESKTOP=$(sha256sum "assets/gerbil.desktop" | cut -d' ' -f1) SHA256_LICENSE=$(sha256sum "LICENSE" | cut -d' ' -f1) echo "sha256_appimage=$SHA256_APPIMAGE" >> $GITHUB_OUTPUT echo "sha256_desktop=$SHA256_DESKTOP" >> $GITHUB_OUTPUT echo "sha256_license=$SHA256_LICENSE" >> $GITHUB_OUTPUT echo "AppImage SHA256: $SHA256_APPIMAGE" echo "Desktop SHA256: $SHA256_DESKTOP" echo "License SHA256: $SHA256_LICENSE" - name: Generate PKGBUILD run: | cat > PKGBUILD << 'EOF' # Maintainer: ${{ steps.release_info.outputs.author_name }} <${{ steps.release_info.outputs.author_email }}> pkgname=gerbil pkgver=${{ steps.release_info.outputs.version }} pkgrel=${{ inputs.pkgrel || '1' }} pkgdesc="Run Large Language Models locally" arch=('x86_64') url="https://github.com/lone-cloud/gerbil" license=('AGPL-3.0-or-later') depends=('gtk3' 'nss') optdepends=('alsa-lib: Audio support for sound effects' 'libxss: Screen saver detection support') provides=('gerbil') conflicts=('gerbil-git') source=("gerbil-${pkgver}.AppImage::${{ steps.release_info.outputs.appimage_url }}") sha256sums=('${{ steps.sha_calc.outputs.sha256_appimage }}') prepare() { chmod +x "gerbil-${pkgver}.AppImage" "./gerbil-${pkgver}.AppImage" --appimage-extract } package() { # Install the application install -dm755 "${pkgdir}/opt/gerbil" cp -r squashfs-root/* "${pkgdir}/opt/gerbil/" # Fix permissions on extracted files chmod -R 755 "${pkgdir}/opt/gerbil/" # Rename the main executable to lowercase mv "${pkgdir}/opt/gerbil/Gerbil" "${pkgdir}/opt/gerbil/gerbil" # Create executable wrapper install -dm755 "${pkgdir}/usr/bin" cat > "${pkgdir}/usr/bin/gerbil" << 'WRAPPER' #!/bin/bash exec "/opt/gerbil/gerbil" "$@" WRAPPER chmod +x "${pkgdir}/usr/bin/gerbil" # Install desktop file and license install -dm755 "${pkgdir}/usr/share/applications" install -dm755 "${pkgdir}/usr/share/licenses/gerbil" install -m644 "${startdir}/gerbil.desktop" "${pkgdir}/usr/share/applications/" install -m644 "${startdir}/LICENSE" "${pkgdir}/usr/share/licenses/gerbil/" # Install icon to hicolor theme directory and pixmaps as fallback install -dm755 "${pkgdir}/usr/share/icons/hicolor/512x512/apps" install -dm755 "${pkgdir}/usr/share/pixmaps" if [ -f "${pkgdir}/opt/gerbil/usr/share/icons/hicolor/512x512/apps/Gerbil.png" ]; then cp "${pkgdir}/opt/gerbil/usr/share/icons/hicolor/512x512/apps/Gerbil.png" "${pkgdir}/usr/share/icons/hicolor/512x512/apps/gerbil.png" cp "${pkgdir}/opt/gerbil/usr/share/icons/hicolor/512x512/apps/Gerbil.png" "${pkgdir}/usr/share/pixmaps/gerbil.png" else echo "Warning: Could not find Gerbil.png in expected locations" find "${pkgdir}/opt/gerbil" -name "*erbil*.png" -type f | head -1 | while read icon_file; do if [ -n "$icon_file" ]; then echo "Found icon at: $icon_file" cp "$icon_file" "${pkgdir}/usr/share/icons/hicolor/512x512/apps/gerbil.png" cp "$icon_file" "${pkgdir}/usr/share/pixmaps/gerbil.png" fi done fi # Install shell completions install -dm755 "${pkgdir}/usr/share/bash-completion/completions" install -dm755 "${pkgdir}/usr/share/zsh/site-functions" # Bash completion cat > "${pkgdir}/usr/share/bash-completion/completions/gerbil" << 'BASH_COMP' _gerbil() { local cur prev opts COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" opts="--version --cli" case "${prev}" in --cli) # Don't complete after --cli, let user type kobold args return 0 ;; *) COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 ;; esac } complete -F _gerbil gerbil BASH_COMP # Zsh completion cat > "${pkgdir}/usr/share/zsh/site-functions/_gerbil" << 'ZSH_COMP' #compdef gerbil _gerbil() { local context state line _arguments -C \ '1: :->command' \ '*: :->args' && return 0 case $state in command) local commands=( '--version:Show version information' '--cli:Run in CLI mode (pass remaining args to kobold binary)' ) _describe 'commands' commands ;; args) case ${words[2]} in --cli) # Don't complete after --cli, let user type kobold args ;; esac ;; esac } _gerbil "$@" ZSH_COMP } EOF - name: Create .SRCINFO run: | # Create .SRCINFO file (required for AUR) cat > .SRCINFO << 'EOF' pkgbase = gerbil pkgdesc = Run Large Language Models locally pkgver = ${{ steps.release_info.outputs.version }} pkgrel = ${{ inputs.pkgrel || '1' }} url = https://github.com/lone-cloud/gerbil arch = x86_64 license = AGPL-3.0-or-later depends = gtk3 depends = nss optdepends = alsa-lib: Audio support for sound effects optdepends = libxss: Screen saver detection support provides = gerbil conflicts = gerbil-git source = gerbil-${{ steps.release_info.outputs.version }}.AppImage::${{ steps.release_info.outputs.appimage_url }} sha256sums = ${{ steps.sha_calc.outputs.sha256_appimage }} pkgname = gerbil EOF - name: Prepare local files for AUR package run: | # Copy desktop file so it's available during PKGBUILD execution cp "assets/gerbil.desktop" ./ echo "✅ Local files prepared for AUR package" - name: Publish to AUR uses: KSXGitHub/github-actions-deploy-aur@v2.7.2 with: pkgname: gerbil pkgbuild: ./PKGBUILD commit_username: ${{ steps.release_info.outputs.author_name }} commit_email: ${{ steps.release_info.outputs.author_email }} ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} commit_message: "Update to ${{ steps.release_info.outputs.version }}-${{ inputs.pkgrel || '1' }}" ssh_keyscan_types: rsa,ecdsa,ed25519 - name: Create AUR release summary run: | echo "## AUR Release Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "📦 **Package**: gerbil" >> $GITHUB_STEP_SUMMARY echo "🔖 **Version**: ${{ steps.release_info.outputs.version }}" >> $GITHUB_STEP_SUMMARY echo "🔗 **AUR Page**: https://aur.archlinux.org/packages/gerbil" >> $GITHUB_STEP_SUMMARY echo "✅ **Status**: Successfully published to AUR" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Installation" >> $GITHUB_STEP_SUMMARY echo '```bash' >> $GITHUB_STEP_SUMMARY echo "# Using yay" >> $GITHUB_STEP_SUMMARY echo "yay -S gerbil" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "# Using paru" >> $GITHUB_STEP_SUMMARY echo "paru -S gerbil" >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY