diff --git a/.github/packaging/macos/build.sh b/.github/packaging/macos/build.sh new file mode 100755 index 0000000000..3849ffdaba --- /dev/null +++ b/.github/packaging/macos/build.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# Build a universal Incus client PKG installer. +set -eu + +VERSION="${VERSION:?VERSION must be set}" +here="$(dirname "$0")" +out="installers" +root="pkgroot" +res="pkgresources" + +mkdir -p "${out}" "${root}/usr/local/bin" "${res}" +cp COPYING "${res}/LICENSE.txt" + +CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o incus.amd64 ./cmd/incus +CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o incus.arm64 ./cmd/incus +lipo -create -output "${root}/usr/local/bin/incus" incus.amd64 incus.arm64 +chmod 0755 "${root}/usr/local/bin/incus" + +pkgbuild --root "${root}" \ + --identifier org.linuxcontainers.incus \ + --version "${VERSION}" \ + --install-location / \ + incus-component.pkg + +productbuild --distribution "${here}/distribution.xml" \ + --package-path . \ + --resources "${res}" \ + "${out}/incus.macos.pkg" + +rm -rf incus.amd64 incus.arm64 incus-component.pkg "${root}" "${res}" diff --git a/.github/packaging/macos/distribution.xml b/.github/packaging/macos/distribution.xml new file mode 100644 index 0000000000..468f5016ed --- /dev/null +++ b/.github/packaging/macos/distribution.xml @@ -0,0 +1,16 @@ + + + Incus + + + + + + + + + + + + incus-component.pkg + diff --git a/.github/packaging/windows/build.sh b/.github/packaging/windows/build.sh new file mode 100755 index 0000000000..609225328c --- /dev/null +++ b/.github/packaging/windows/build.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# Build per-architecture Incus client MSI installers. +set -eu + +VERSION="${VERSION:?VERSION must be set}" +here="$(dirname "$0")" +out="installers" + +mkdir -p "${out}" +bash "${here}/make-license-rtf.sh" COPYING "${out}/license.rtf" + +build_one() { + goarch="$1" + wixarch="$2" + name="$3" + + CGO_ENABLED=0 GOOS=windows GOARCH="${goarch}" go build -o "${out}/incus.exe" ./cmd/incus + wix build -arch "${wixarch}" \ + -ext WixToolset.UI.wixext \ + -d Version="${VERSION}" \ + -d BinPath="${out}/incus.exe" \ + -d LicenseRtf="${out}/license.rtf" \ + -o "${out}/incus.windows.${name}.msi" \ + "${here}/incus.wxs" +} + +build_one amd64 x64 x86_64 +build_one arm64 arm64 aarch64 + +rm -f "${out}/incus.exe" "${out}/license.rtf" diff --git a/.github/packaging/windows/incus.wxs b/.github/packaging/windows/incus.wxs new file mode 100644 index 0000000000..5d05d3097d --- /dev/null +++ b/.github/packaging/windows/incus.wxs @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.github/packaging/windows/make-license-rtf.sh b/.github/packaging/windows/make-license-rtf.sh new file mode 100755 index 0000000000..5441f87648 --- /dev/null +++ b/.github/packaging/windows/make-license-rtf.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# Wrap a plain-text license into a minimal RTF for the WiX license dialog. +set -eu + +src="$1" +dst="$2" + +{ + printf '{\\rtf1\\ansi\\deff0{\\fonttbl{\\f0 Courier New;}}\\fs16\n' + sed -e 's/\\/\\\\/g' -e 's/{/\\{/g' -e 's/}/\\}/g' -e 's/$/\\par/' "$src" + printf '}\n' +} > "$dst" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c07e316ad7..edd4f27f69 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,9 +11,96 @@ permissions: attestations: write jobs: + version: + name: Format version + runs-on: ubuntu-latest + outputs: + display: ${{ steps.version.outputs.display }} + full: ${{ steps.version.outputs.full }} + steps: + - name: Format version + id: version + run: | + raw="${GITHUB_REF_NAME#v}" + IFS='.' read -r major minor patch <<< "$raw" + echo "full=${major}.${minor}.${patch}" >> $GITHUB_OUTPUT + if [ "${patch}" = "0" ]; then + echo "display=${major}.${minor}" >> $GITHUB_OUTPUT + else + echo "display=${major}.${minor}.${patch}" >> $GITHUB_OUTPUT + fi + + build-msi: + name: Build Windows installer + runs-on: windows-latest + needs: version + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Install Go + uses: actions/setup-go@v6 + with: + go-version: stable + + # Pinned to v5, the last release before WiX v6/v7 moved to the Open + # Source Maintenance Fee license requiring EULA acceptance. + - name: Install WiX + shell: pwsh + run: | + dotnet tool install --global wix --version 5.0.2 + echo "$env:USERPROFILE\.dotnet\tools" | Out-File -FilePath $env:GITHUB_PATH -Append + + - name: Add WiX extensions + shell: pwsh + run: wix extension add -g WixToolset.UI.wixext/5.0.2 + + - name: Build installers + shell: bash + env: + VERSION: ${{ needs.version.outputs.full }} + run: bash .github/packaging/windows/build.sh + + - name: Upload installers + uses: actions/upload-artifact@v4 + with: + name: installers-windows + path: installers/*.msi + if-no-files-found: error + + build-pkg: + name: Build macOS installer + runs-on: macos-latest + needs: version + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Install Go + uses: actions/setup-go@v6 + with: + go-version: stable + + - name: Build installer + env: + VERSION: ${{ needs.version.outputs.full }} + run: bash .github/packaging/macos/build.sh + + - name: Upload installer + uses: actions/upload-artifact@v4 + with: + name: installers-macos + path: installers/*.pkg + if-no-files-found: error + goreleaser: name: Release runs-on: ubuntu-latest + needs: [version, build-msi, build-pkg] steps: - name: Checkout @@ -29,16 +116,12 @@ jobs: - name: Install syft uses: anchore/sbom-action/download-syft@v0 - - name: Format version - id: version - run: | - raw="${GITHUB_REF_NAME#v}" - IFS='.' read -r major minor patch <<< "$raw" - if [ "${patch}" = "0" ]; then - echo "value=${major}.${minor}" >> $GITHUB_OUTPUT - else - echo "value=${major}.${minor}.${patch}" >> $GITHUB_OUTPUT - fi + - name: Download installers + uses: actions/download-artifact@v4 + with: + pattern: installers-* + path: installers + merge-multiple: true - name: Run GoReleaser uses: goreleaser/goreleaser-action@v7 @@ -48,9 +131,16 @@ jobs: args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - INCUS_VERSION: ${{ steps.version.outputs.value }} + INCUS_VERSION: ${{ needs.version.outputs.display }} - name: Handle attestation uses: actions/attest@v4 with: subject-checksums: ./dist/checksums.txt + + - name: Handle installer attestation + uses: actions/attest@v4 + with: + subject-path: | + installers/*.msi + installers/*.pkg diff --git a/.gitignore b/.gitignore index c49fbf1525..42de1f4060 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ po/*.mo po/*.po~ incus-*.tar.xz +installers .vagrant *~ tags diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 5114bb02d9..bc874f71dc 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -162,6 +162,9 @@ release: owner: lxc name: incus name_template: "Incus {{ .Env.INCUS_VERSION }}" + extra_files: + - glob: ./installers/*.msi + - glob: ./installers/*.pkg sboms: - id: archive