Release #29
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: "Release version" | |
| required: true | |
| type: string | |
| permissions: | |
| contents: write | |
| # TODO: Uncomment when enabling SignPath code signing | |
| # actions: read | |
| # id-token: write | |
| env: | |
| CARGO_TERM_COLOR: always | |
| MAKEFLAGS: -j4 | |
| jobs: | |
| build-linux: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Set version | |
| env: | |
| VERSION: ${{ inputs.version }} | |
| run: | | |
| echo "VERSION=$VERSION" >> "$GITHUB_ENV" | |
| sed -i "s/^version = .*/version = \"$VERSION\"/" Cargo.toml | |
| - name: Install system dependencies | |
| # asciidoctor renders the man page from docs/cli/octa.1.adoc; bundled | |
| # with the Linux tarball so `install.sh` can drop it into | |
| # share/man/man1/. Same package on macOS via brew (asciidoctor) and | |
| # on Arch via the standard repo, but Linux release is the only path | |
| # that needs it in CI. | |
| # | |
| # The remaining packages are linuxdeploy / linuxdeploy-plugin-gtk | |
| # runtime deps used in the "Build AppImage" step: patchelf rewrites | |
| # rpaths in the bundled binary; gtk/gdk-pixbuf/rsvg *-bin packages | |
| # supply the loader-cache tools the plugin invokes. | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libgtk-3-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev libfontconfig1-dev libfreetype6-dev asciidoctor patchelf librsvg2-common libgdk-pixbuf2.0-bin libgtk-3-bin | |
| - uses: dtolnay/rust-toolchain@stable | |
| # No `cargo test` here. PR-time CI (ci.yml::test) already ran fmt + clippy | |
| # + tests against this exact commit before merge; re-running on tag push | |
| # would double the wall-time of every release without catching anything new. | |
| - name: Build release binary | |
| run: cargo build --release | |
| - name: Render man page | |
| run: asciidoctor -b manpage docs/cli/octa.1.adoc -o octa.1 | |
| - name: Package Linux release | |
| run: | | |
| DIR="octa-${VERSION}-linux-x86_64" | |
| mkdir -p "$DIR/assets" | |
| cp target/release/octa "$DIR/" | |
| cp install.sh "$DIR/" | |
| cp uninstall.sh "$DIR/" | |
| cp octa.desktop "$DIR/" | |
| cp assets/octa.svg "$DIR/assets/" | |
| cp assets/octa.png "$DIR/assets/" | |
| cp pkg/README-linux.md "$DIR/README.md" | |
| cp LICENSE "$DIR/" 2>/dev/null || true | |
| cp octa.1 "$DIR/" | |
| tar czf "${DIR}.tar.gz" "$DIR/" | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: linux-release | |
| path: octa-${{ env.VERSION }}-linux-x86_64.tar.gz | |
| - name: Download AppImage tooling | |
| # linuxdeploy ships only as a rolling "continuous" release. Pinning by | |
| # tag isn't an option; if a future linuxdeploy release breaks the build, | |
| # swap to a specific dated tag from | |
| # https://github.com/linuxdeploy/linuxdeploy/releases. | |
| run: | | |
| cd "$RUNNER_TEMP" | |
| wget -q -O linuxdeploy https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage | |
| wget -q -O linuxdeploy-plugin-gtk https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh | |
| chmod +x linuxdeploy linuxdeploy-plugin-gtk | |
| echo "$RUNNER_TEMP" >> "$GITHUB_PATH" | |
| - name: Build AppImage | |
| env: | |
| OUTPUT: Octa-${{ env.VERSION }}-x86_64.AppImage | |
| # linuxdeploy-plugin-gtk can't auto-detect a GTK version when the | |
| # ELF doesn't link libgtk directly (our binary only pulls libgtk | |
| # transitively through eframe at runtime); set it explicitly to | |
| # match the libgtk-3-dev we install above. | |
| DEPLOY_GTK_VERSION: "3" | |
| # APPIMAGE_EXTRACT_AND_RUN sidesteps FUSE — ubuntu-latest (24.04) no | |
| # longer ships libfuse2 by default, and running AppImages without it | |
| # otherwise fails with "dlopen(): error loading libfuse.so.2". | |
| run: | | |
| rm -rf AppDir | |
| APPIMAGE_EXTRACT_AND_RUN=1 linuxdeploy \ | |
| --appdir AppDir \ | |
| --executable target/release/octa \ | |
| --desktop-file octa.desktop \ | |
| --icon-file assets/octa.png \ | |
| --plugin gtk \ | |
| --output appimage | |
| # linuxdeploy derives the filename from the desktop Name field | |
| # ("Octa-x86_64.AppImage"); rename to embed the version. | |
| mv Octa-*.AppImage "$OUTPUT" | |
| - name: Upload AppImage artifact | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: linux-appimage | |
| path: Octa-${{ env.VERSION }}-x86_64.AppImage | |
| build-windows: | |
| runs-on: windows-latest | |
| # TODO: Uncomment when enabling SignPath code signing | |
| # outputs: | |
| # unsigned-artifact-id: ${{ steps.upload-unsigned.outputs.artifact-id }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Set version | |
| env: | |
| VERSION: ${{ inputs.version }} | |
| shell: pwsh | |
| run: | | |
| echo "VERSION=$env:VERSION" >> $env:GITHUB_ENV | |
| (Get-Content Cargo.toml) -replace '^version = .*', "version = `"$env:VERSION`"" | Set-Content Cargo.toml | |
| - name: Generate ICO from PNG | |
| run: magick assets/octa.png -define icon:auto-resize=256,128,64,48,32,16 assets/octa.ico | |
| - uses: dtolnay/rust-toolchain@stable | |
| # See linux comment: PR-time CI already covered tests on this commit. | |
| - name: Build release binary | |
| run: cargo build --release | |
| - name: Package Windows release | |
| shell: pwsh | |
| run: | | |
| $dir = "octa-$env:VERSION-windows-x86_64" | |
| New-Item -ItemType Directory -Path "$dir\assets" -Force | |
| Copy-Item "target\release\octa.exe" "$dir\" | |
| Copy-Item "install.bat" "$dir\" | |
| Copy-Item "assets\octa.svg" "$dir\assets\" | |
| Copy-Item "assets\octa.png" "$dir\assets\" | |
| if (Test-Path "assets\octa.ico") { Copy-Item "assets\octa.ico" "$dir\assets\" } | |
| Copy-Item "pkg\README-windows.md" "$dir\README.md" | |
| if (Test-Path "LICENSE") { Copy-Item "LICENSE" "$dir\" } | |
| Compress-Archive -Path "$dir\*" -DestinationPath "octa-$env:VERSION-windows-x86_64.zip" | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: windows-release | |
| path: octa-${{ env.VERSION }}-windows-x86_64.zip | |
| build-macos: | |
| runs-on: macos-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Set version | |
| env: | |
| VERSION: ${{ inputs.version }} | |
| run: | | |
| echo "VERSION=$VERSION" >> "$GITHUB_ENV" | |
| sed -i '' "s/^version = .*/version = \"$VERSION\"/" Cargo.toml | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: aarch64-apple-darwin | |
| # See linux comment: PR-time CI already covered tests on this commit. | |
| - name: Build release binary | |
| run: cargo build --release --target aarch64-apple-darwin | |
| - name: Generate ICNS from PNG | |
| run: | | |
| ICONSET="assets/octa.iconset" | |
| mkdir -p "$ICONSET" | |
| for size in 16 32 64 128 256 512; do | |
| sips -z $size $size assets/octa.png --out "$ICONSET/icon_${size}x${size}.png" | |
| double=$((size * 2)) | |
| sips -z $double $double assets/octa.png --out "$ICONSET/icon_${size}x${size}@2x.png" | |
| done | |
| iconutil -c icns "$ICONSET" -o assets/octa.icns | |
| - name: Build .app bundle | |
| run: | | |
| APP="Octa.app" | |
| rm -rf "$APP" | |
| mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources" | |
| cp target/aarch64-apple-darwin/release/octa "$APP/Contents/MacOS/octa" | |
| chmod +x "$APP/Contents/MacOS/octa" | |
| cp assets/octa.icns "$APP/Contents/Resources/octa.icns" | |
| cat > "$APP/Contents/Info.plist" <<EOF | |
| <?xml version="1.0" encoding="UTF-8"?> | |
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |
| <plist version="1.0"> | |
| <dict> | |
| <key>CFBundleDevelopmentRegion</key><string>en</string> | |
| <key>CFBundleDisplayName</key><string>Octa</string> | |
| <key>CFBundleExecutable</key><string>octa</string> | |
| <key>CFBundleIconFile</key><string>octa</string> | |
| <key>CFBundleIdentifier</key><string>dev.octa.Octa</string> | |
| <key>CFBundleInfoDictionaryVersion</key><string>6.0</string> | |
| <key>CFBundleName</key><string>Octa</string> | |
| <key>CFBundlePackageType</key><string>APPL</string> | |
| <key>CFBundleShortVersionString</key><string>${VERSION}</string> | |
| <key>CFBundleVersion</key><string>${VERSION}</string> | |
| <key>LSMinimumSystemVersion</key><string>11.0</string> | |
| <key>NSHighResolutionCapable</key><true/> | |
| <key>NSPrincipalClass</key><string>NSApplication</string> | |
| </dict> | |
| </plist> | |
| EOF | |
| - name: Ad-hoc codesign the bundle | |
| run: | | |
| # Sign the inner binary first, then seal the bundle. Ad-hoc (`-`) is | |
| # not Developer ID — Gatekeeper still warns — but it converts the | |
| # "Octa.app is damaged" dead-end into the standard "unidentified | |
| # developer" dialog, where right-click → Open works. | |
| codesign --force --sign - --timestamp=none \ | |
| --identifier dev.octa.Octa \ | |
| Octa.app/Contents/MacOS/octa | |
| codesign --force --sign - --timestamp=none Octa.app | |
| codesign --verify --verbose Octa.app | |
| - name: Package macOS release | |
| run: | | |
| DIR="octa-${VERSION}-macos-aarch64" | |
| mkdir -p "$DIR" | |
| cp -R Octa.app "$DIR/" | |
| cp pkg/README-macos.md "$DIR/README.md" | |
| cp LICENSE "$DIR/" 2>/dev/null || true | |
| tar czf "${DIR}.tar.gz" "$DIR/" | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: macos-release | |
| path: octa-${{ env.VERSION }}-macos-aarch64.tar.gz | |
| publish: | |
| needs: [build-linux, build-windows, build-macos] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: actions/download-artifact@v7 | |
| with: | |
| path: artifacts | |
| - name: Create release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| VERSION: ${{ inputs.version }} | |
| run: | | |
| NOTES_ARGS="--generate-notes" | |
| if [ -s release_notes.md ]; then | |
| NOTES_ARGS="--notes-file release_notes.md" | |
| fi | |
| gh release create "$VERSION" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --title "Octa $VERSION" \ | |
| $NOTES_ARGS \ | |
| artifacts/linux-release/*.tar.gz \ | |
| artifacts/linux-appimage/*.AppImage \ | |
| artifacts/windows-release/*.zip \ | |
| artifacts/macos-release/*.tar.gz | |
| aur-publish: | |
| needs: [publish] | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: aur | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v5 | |
| - name: Download tarballs and compute sha256 | |
| id: sha | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| VERSION: ${{ inputs.version }} | |
| run: | | |
| # Source tarball (GitHub auto-generated archive) | |
| curl -fL -o source.tar.gz \ | |
| "https://github.com/$GITHUB_REPOSITORY/archive/${VERSION}.tar.gz" | |
| SOURCE_SHA256=$(sha256sum source.tar.gz | awk '{print $1}') | |
| echo "source=$SOURCE_SHA256" >> "$GITHUB_OUTPUT" | |
| echo "Source sha256: $SOURCE_SHA256" | |
| # Binary tarball (release artifact) | |
| gh release download "$VERSION" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --pattern "octa-${VERSION}-linux-x86_64.tar.gz" \ | |
| --output binary.tar.gz | |
| BINARY_SHA256=$(sha256sum binary.tar.gz | awk '{print $1}') | |
| echo "binary=$BINARY_SHA256" >> "$GITHUB_OUTPUT" | |
| echo "Binary sha256: $BINARY_SHA256" | |
| - name: Prepare PKGBUILDs | |
| env: | |
| VERSION: ${{ inputs.version }} | |
| SOURCE_SHA256: ${{ steps.sha.outputs.source }} | |
| BINARY_SHA256: ${{ steps.sha.outputs.binary }} | |
| run: | | |
| # octa (source) | |
| sed -i "s/X\.X\.X/$VERSION/g" .github/aur/PKGBUILD .github/aur/.SRCINFO | |
| sed -i "s/sha256sums=('SKIP')/sha256sums=('$SOURCE_SHA256')/" .github/aur/PKGBUILD | |
| sed -i "s/sha256sums = SKIP/sha256sums = $SOURCE_SHA256/" .github/aur/.SRCINFO | |
| # octa-bin (binary) | |
| sed -i "s/X\.X\.X/$VERSION/g" .github/aur-bin/PKGBUILD .github/aur-bin/.SRCINFO | |
| sed -i "s/sha256sums=('SKIP')/sha256sums=('$BINARY_SHA256')/" .github/aur-bin/PKGBUILD | |
| sed -i "s/sha256sums = SKIP/sha256sums = $BINARY_SHA256/" .github/aur-bin/.SRCINFO | |
| - name: Configure SSH for AUR | |
| run: | | |
| mkdir -p ~/.ssh | |
| echo "${{ secrets.AUR_SSH_PRIVATE_KEY }}" > ~/.ssh/aur | |
| chmod 600 ~/.ssh/aur | |
| cat >> ~/.ssh/config <<'EOF' | |
| Host aur.archlinux.org | |
| IdentityFile ~/.ssh/aur | |
| User aur | |
| EOF | |
| ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts | |
| - name: Push octa to AUR | |
| env: | |
| VERSION: ${{ inputs.version }} | |
| run: | | |
| git -c init.defaultBranch=master clone \ | |
| ssh://aur@aur.archlinux.org/octa.git /tmp/aur-octa | |
| cp .github/aur/PKGBUILD .github/aur/.SRCINFO /tmp/aur-octa/ | |
| cd /tmp/aur-octa | |
| echo "--- PKGBUILD ---" | |
| cat PKGBUILD | |
| echo "--- .SRCINFO ---" | |
| cat .SRCINFO | |
| STATUS="$(git status --porcelain)" | |
| echo "$STATUS" | grep -q 'PKGBUILD' | |
| echo "$STATUS" | grep -q '.SRCINFO' | |
| git config user.name "GitHub Actions" | |
| git config user.email "actions@github.com" | |
| git add PKGBUILD .SRCINFO | |
| git commit -m "release $VERSION" | |
| git push | |
| - name: Push octa-bin to AUR | |
| env: | |
| VERSION: ${{ inputs.version }} | |
| run: | | |
| git -c init.defaultBranch=master clone \ | |
| ssh://aur@aur.archlinux.org/octa-bin.git /tmp/aur-octa-bin | |
| cp .github/aur-bin/PKGBUILD .github/aur-bin/.SRCINFO /tmp/aur-octa-bin/ | |
| cd /tmp/aur-octa-bin | |
| echo "--- PKGBUILD ---" | |
| cat PKGBUILD | |
| echo "--- .SRCINFO ---" | |
| cat .SRCINFO | |
| STATUS="$(git status --porcelain)" | |
| echo "$STATUS" | grep -q 'PKGBUILD' | |
| echo "$STATUS" | grep -q '.SRCINFO' | |
| git config user.name "GitHub Actions" | |
| git config user.email "actions@github.com" | |
| git add PKGBUILD .SRCINFO | |
| git commit -m "release $VERSION" | |
| git push |