Skip to content

v0.6.0-alpha.1

v0.6.0-alpha.1 #207

Workflow file for this run

name: Publish release
on:
release:
types: [prereleased]
jobs:
create-release:
if: startsWith(github.ref, 'refs/tags/v') == true
permissions:
contents: write
runs-on: ubuntu-22.04
outputs:
package_version: ${{ steps.get-version.outputs.package_version }}
original_package_version: ${{ steps.get-version.outputs.original_package_version }}
release_id: ${{ steps.get-release.outputs.id }}
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16
- run: npm install semver
- name: Get Version
uses: actions/github-script@v6
id: get-version
with:
script: |
const semver = require("semver")
const refName = `${process.env.GITHUB_REF_NAME}`
let version = refName.split("v")[1]
core.info(`Original Version: ${version}`)
core.setOutput("original_package_version", version)
const parsed = semver.parse(version);
const supportedPreleases = [
{ tag: "alpha", number: 1 },
{ tag: "beta", number: 2 },
{ tag: "rc", number: 3 },
];
const maybePrelease = semver.prerelease(version);
const maybeSupported = supportedPreleases.find(
(p) => p.tag === maybePrelease?.[0]
);
// If we have a prelease and it is in the supported range, then we can use it
if (maybePrelease && maybeSupported) {
version = `${parsed.major}.${parsed.minor}.${parsed.patch}-${
maybeSupported.number
}${maybePrelease[1] ?? 0}`;
}
if(maybePrelease && !maybeSupported) {
core.setFailed(`Unsupported prerelease: ${version}`)
}
core.info(`Version: ${version}`)
core.setOutput("package_version", version)
- name: Get Release
uses: actions/github-script@v6
id: get-release
with:
script: |
// Find the prerelease release in our repo that triggered this workflow
const refName = `${process.env.GITHUB_REF_NAME}`
const res = await github.rest.repos.listReleases({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 10,
})
const release = res.data.find((r) => r.tag_name === refName && r.prerelease)
if(!release) { core.setFailed("Unable to find prerelease for this workflow") }
core.setOutput("id", release.id)
build-app:
needs: create-release
if: startsWith(github.ref, 'refs/tags/v') == true
permissions:
contents: write
strategy:
fail-fast: false
matrix:
settings:
- host: macos-latest
target: x86_64-apple-darwin
os: darwin
arch: amd64
cli_only: false
- host: macos-latest
target: aarch64-apple-darwin
os: darwin
arch: arm64
cli_only: false
# The WIX version we use for the installer (latest 3.something) doesn't support arm builds - if we need to support arm windows,
# we'd need to switch the installer toolchain to WIX 4.xx, not sure how that works out with tauri
# - host: windows-latest
# target: aarch64-pc-windows-msvc
# arch: arm64
# cli-only: false
- host: windows-latest
target: x86_64-pc-windows-msvc
arch: amd64
cli_only: false
- host: ubuntu-22.04
target: x86_64-unknown-linux-gnu
os: linux
arch: amd64
cli_only: false
- host: ubuntu-22.04
target: aarch64-unknown-linux-gnu
os: linux
arch: arm64
cli_only: true
name: ${{ matrix.settings.target }}
runs-on: ${{ matrix.settings.host }}
env:
GO111MODULE: on
GOFLAGS: -mod=vendor
steps:
- name: Set git to use LF
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- name: Checkout repository
uses: actions/checkout@v3
- name: Apply Version
if: matrix.settings.cli_only == false
run: yarn version --new-version ${{ needs.create-release.outputs.package_version }} --no-git-tag-version
working-directory: "./desktop"
- name: Setup System Dependencies
if: matrix.settings.host == 'ubuntu-22.04' && matrix.settings.cli_only == false
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libayatana-appindicator3-dev librsvg2-dev
- name: Rust setup
uses: dtolnay/rust-toolchain@stable
if: matrix.settings.cli_only == false
with:
targets: ${{ matrix.settings.target }}
- name: Rust cache
uses: swatinem/rust-cache@v2
if: matrix.settings.cli_only == false
with:
workspaces: "./desktop/src-tauri -> target"
- name: Go setup
uses: actions/setup-go@v2
with:
go-version: 1.21.8
- name: Build Sidecar CLI
if: matrix.settings.host != 'windows-latest'
run: |
BIN_NAME=devpod-cli-${{ matrix.settings.target }}
GOOS=${{ matrix.settings.os }} GOARCH=${{ matrix.settings.arch }} CGO_ENABLED=0 go build -ldflags "-s -w -X github.com/loft-sh/devpod/pkg/version.version="v${{ needs.create-release.outputs.original_package_version }}" -X github.com/loft-sh/devpod/pkg/telemetry.telemetryPrivateKey=${{ secrets.DEVPOD_TELEMETRY_PRIVATE_KEY }}" -o "test/$BIN_NAME"
cp "test/$BIN_NAME" "desktop/src-tauri/bin/$BIN_NAME"
ls desktop/src-tauri/bin
- name: Build Sidecar CLI
if: matrix.settings.host == 'windows-latest'
shell: cmd
run: |
set GOOS=windows
set GOARCH=${{ matrix.settings.arch }}
set BIN_NAME=devpod-cli-${{ matrix.settings.target }}.exe
go build -ldflags "-s -w -X github.com/loft-sh/devpod/pkg/version.version="v${{ needs.create-release.outputs.original_package_version }}" -X github.com/loft-sh/devpod/pkg/telemetry.telemetryPrivateKey=${{ secrets.DEVPOD_TELEMETRY_PRIVATE_KEY }}" -o "test\%BIN_NAME%"
xcopy /F /Y "test\%BIN_NAME%" desktop\src-tauri\bin\*
- name: Sync node version and setup cache
uses: actions/setup-node@v3
if: matrix.settings.cli_only == false
with:
node-version: "lts/*"
cache: "yarn"
cache-dependency-path: "./desktop/yarn.lock"
- name: Install frontend dependencies
if: matrix.settings.cli_only == false
run: yarn install
working-directory: "./desktop"
- name: Install additional ubuntu dependencies
if: matrix.settings.host == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev librsvg2-dev patchelf
- name: Build Desktop App
if: matrix.settings.host == 'ubuntu-22.04' && matrix.settings.cli_only == false
uses: tauri-apps/[email protected]
with:
releaseId: ${{ needs.create-release.outputs.release_id }}
projectPath: "./desktop"
args: "--config src-tauri/tauri-linux.conf.json --target ${{ matrix.settings.target }} --bundles appimage,updater"
includeUpdaterJson: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
# AppImage Signing:
SIGN: ${{ secrets.APP_IMAGE_SIGN }}
SIGN_KEY: ${{ secrets.APP_IMAGE_SIGN_KEY }}
APPIMAGETOOL_SIGN_PASSPHRASE: ${{ secrets.APP_IMAGE_SIGN_PASSPHRASE }}
- name: Build Desktop App
if: matrix.settings.host == 'macos-latest' && matrix.settings.cli_only == false
uses: tauri-apps/[email protected]
with:
releaseId: ${{ needs.create-release.outputs.release_id }}
projectPath: "./desktop"
args: "--target ${{ matrix.settings.target }}"
includeUpdaterJson: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
# MacOS Signing:
ENABLE_CODE_SIGNING: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
- name: Build linux tar.gz
if: matrix.settings.host == 'ubuntu-22.04' && matrix.settings.cli_only == false
id: build-desktop-targz
run: |
cd ./desktop/src-tauri/target/${{ matrix.settings.target }}/release/bundle/appimage/DevPod.AppDir || exit 1
tar --exclude=usr/bin/xdg-open --exclude=usr/lib --exclude=usr/share/doc --exclude=usr/share/glib-2.0 -zcvf DevPod-desktop.tar.gz usr
mv DevPod-desktop.tar.gz ../../DevPod-${{needs.create-release.outputs.package_version}}.tar.gz
- name: Build Desktop App
if: matrix.settings.host == 'windows-latest' && matrix.settings.cli_only == false
id: build-desktop-app
uses: tauri-apps/[email protected]
with:
projectPath: "./desktop"
args: " --target ${{ matrix.settings.target }}"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
- name: Sign Windows App
if: matrix.settings.host == 'windows-latest'
shell: powershell
env:
CODESIGNTOOL_USERNAME: ${{ secrets.CODESIGNTOOL_USERNAME }}
CODESIGNTOOL_PASSWORD: ${{ secrets.CODESIGNTOOL_PASSWORD }}
CODESIGNTOOL_TOTP_SECRET: ${{ secrets.CODESIGNTOOL_TOTP_SECRET }}
CODESIGNTOOL_CREDENTIAL_ID: ${{ secrets.CODESIGNTOOL_CREDENTIAL_ID }}
CODESIGNTOOL_DOWNLOAD_URL: ${{ vars.CODESIGNTOOL_DOWNLOAD_URL }}
run: |
$username = "$Env:CODESIGNTOOL_USERNAME"
$password = "$Env:CODESIGNTOOL_PASSWORD"
$totp_secret = "$Env:CODESIGNTOOL_TOTP_SECRET"
$credential_id = "$Env:CODESIGNTOOL_CREDENTIAL_ID"
$download_url = "$Env:CODESIGNTOOL_DOWNLOAD_URL"
$msi_input_file_path = "desktop\src-tauri\target\${{ matrix.settings.target }}\release\bundle\msi\DevPod_${{ needs.create-release.outputs.package_version }}_x64_en-US.msi"
$nsis_input_file_path = "desktop\src-tauri\target\${{ matrix.settings.target }}\release\bundle\nsis\DevPod_${{ needs.create-release.outputs.package_version }}_x64-setup.exe"
Write-Output "Starting to download CodeSignTool from $download_url"
Invoke-WebRequest -Uri $download_url -OutFile codesigntool.zip
$destination_path = "codesigntool"
Write-Output "Unzipping to $destination_path"
Expand-Archive "codesigntool.zip" -DestinationPath $destination_path
Set-Location -Path $destination_path
$msi_input_file_path = Resolve-Path "..\$msi_input_file_path" | select -ExpandProperty Path
$nsis_input_file_path = Resolve-Path "..\$nsis_input_file_path" | select -ExpandProperty Path
cmd.exe /c ".\CodeSignTool.bat" sign -username="$username" -password="$password" -totp_secret="$totp_secret" -credential_id="$credential_id" -input_file_path="$msi_input_file_path" -override
cmd.exe /c ".\CodeSignTool.bat" sign -username="$username" -password="$password" -totp_secret="$totp_secret" -credential_id="$credential_id" -input_file_path="$nsis_input_file_path" -override
- name: Upload Release Asset
if: matrix.settings.host == 'windows-latest'
uses: actions/github-script@v6
with:
script: |
const fs = require("fs")
const version = "${{ needs.create-release.outputs.package_version }}"
// prepare MSI vars
const msiName = `DevPod_${version}_x64_en-US.msi`
const msiPath = `desktop/src-tauri/target/${{ matrix.settings.target }}/release/bundle/msi/${msiName}`
const msiZipName = `${msiName}.zip`
const msiZipPath = `desktop/src-tauri/target/${{ matrix.settings.target }}/release/bundle/msi/${msiZipName}`
const msiZipSigName = `${msiName}.zip.sig`
const msiZipSigPath = `desktop/src-tauri/target/${{ matrix.settings.target }}/release/bundle/msi/${msiZipSigName}`
// prepare NSIS vars
// the installer itself is suffixed with `.exe` but updater artifacts end with `.nsis.*`
const nsisName = `DevPod_${version}_x64-setup`
const nsisPath = `desktop/src-tauri/target/${{ matrix.settings.target }}/release/bundle/nsis/${nsisName}.exe`
// Let's skip uploading the updater artifacts until we've figured out auto updating for both nsis and msi
// const nsisZipName = `${nsisName}.nsis.zip`
// const nsisZipPath = `desktop/src-tauri/target/${{ matrix.settings.target }}/release/bundle/nsis/${nsisZipName}`
// const nsisZipSigName = `${nsisName}.nsis.zip.sig`
// const nsisZipSigPath = `desktop/src-tauri/target/${{ matrix.settings.target }}/release/bundle/nsis/${nsisZipSigName}`
const cliName = "devpod-windows-${{ matrix.settings.arch }}.exe"
const cliPath = "desktop/src-tauri/bin/devpod-cli-${{ matrix.settings.target }}.exe"
const releaseId = "${{ needs.create-release.outputs.release_id }}"
const releaseAssets = [
{ name: cliName, path: cliPath },
{ name: msiName, path: msiPath },
{ name: msiZipName, path: msiZipPath },
{ name: msiZipSigName, path: msiZipSigPath },
{ name: `${nsisName}.exe`, path: nsisPath },
// { name: nsisZipName, path: nsisZipPath },
// { name: nsisZipSigName, path: nsisZipSigPath },
]
for (const asset of releaseAssets) {
console.log("Attempting to upload release asset: ", asset)
await github.rest.repos.uploadReleaseAsset({
headers: {
"content-type": "application/zip",
"content-length": fs.statSync(asset.path).size
},
name: asset.name,
data: fs.readFileSync(asset.path),
owner: context.repo.owner,
repo: context.repo.repo,
release_id: releaseId
})
}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload CLI Asset
if: matrix.settings.host != 'windows-latest'
uses: actions/github-script@v6
with:
script: |
const fs = require("fs")
const releaseId = "${{ needs.create-release.outputs.release_id }}"
const assetName = "devpod-${{ matrix.settings.os }}-${{ matrix.settings.arch }}"
const assetPath = "desktop/src-tauri/bin/devpod-cli-${{ matrix.settings.target }}"
console.log("Attempting to upload release asset: ", assetName)
await github.rest.repos.uploadReleaseAsset({
headers: {
"content-type": "application/zip",
"content-length": fs.statSync(assetPath).size
},
name: assetName,
data: fs.readFileSync(assetPath),
owner: context.repo.owner,
repo: context.repo.repo,
release_id: releaseId
})
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Tar.gz Asset
if: matrix.settings.host == 'ubuntu-22.04' && matrix.settings.cli_only == false
uses: actions/github-script@v6
with:
script: |
const fs = require("fs")
const releaseId = "${{ needs.create-release.outputs.release_id }}"
const assetName = "DevPod-${{needs.create-release.outputs.package_version}}.tar.gz"
const assetPath = `desktop/src-tauri/target/${{ matrix.settings.target }}/release/bundle/${assetName}`
console.log("Attempting to upload release asset: ", assetName)
await github.rest.repos.uploadReleaseAsset({
headers: {
"content-type": "application/zip",
"content-length": fs.statSync(assetPath).size
},
name: assetName,
data: fs.readFileSync(assetPath),
owner: context.repo.owner,
repo: context.repo.repo,
release_id: releaseId
})
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish-updates:
needs: [build-app, create-release]
if: startsWith(github.ref, 'refs/tags/v') == true
permissions:
contents: write
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Release pro provider
run: |
set -e
VERSION="v${{ needs.create-release.outputs.original_package_version }}"
OUT_DIR="release"
ASSETS="devpod-darwin-amd64 devpod-darwin-arm64 devpod-linux-amd64 devpod-linux-arm64 devpod-windows-amd64.exe"
echo "Prepare output directory $OUT_DIR..."
if [ ! -d "$OUT_DIR" ]; then
mkdir "$OUT_DIR"
else
rm -rf $OUT_DIR
fi
printf "Done\n\n"
echo "Download release assets into $OUT_DIR..."
for asset in $ASSETS; do
printf "\t$asset\n"
gh release download $VERSION --pattern="$asset" --dir="$OUT_DIR"
printf "\tDone\n"
done
printf "Done\n\n"
echo "Generate provider.yaml..."
go run ./hack/pro/main.go $VERSION > ./release/provider.yaml
printf "Done\n\n"
echo "Upload provider.yaml..."
gh release upload $VERSION ./release/provider.yaml --clobber
printf "Done\n\n"
env:
GH_TOKEN: ${{ github.token }}
- name: Update `latest.json`
uses: actions/github-script@v6
with:
retries: 2
retry-exempt-status-codes: 400,401,403
script: |
// At this point, we should have `linux-x86_64`, `darwin-aarch64` and `darwin-x86_64`.
// We need to add the missing platform/arch combinations by hand
const fs = require("fs")
async function fetchAsset(assetID) {
const releaseAsset = await github.rest.repos.getReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
asset_id: assetID,
headers: { accept: "application/octet-stream" }
})
const res = await fetch(releaseAsset.url, { headers: { accept: "application/octet-stream" } })
if (!res.ok) { core.setFailed(`${await res.text()}`) }
return res
}
const releaseId = "${{ needs.create-release.outputs.release_id }}"
const releaseArgs = { owner: context.repo.owner, repo: context.repo.repo, release_id: releaseId }
const release = await github.rest.repos.getRelease({ ...releaseArgs })
const latestAsset = release.data.assets.find(a => a.name === "latest.json")
core.info(`Downloading ${latestAsset.name} (ID: ${latestAsset.id})`)
const latestRes = await fetchAsset(latestAsset.id)
const latest = await latestRes.json()
const version = latest.version
const infos = [
{ target: "linux-x86_64", sigFile: ".AppImage.tar.gz.sig", packageType: ".tar.gz", originalAssetName: `DevPod_${version}_amd64.AppImage`, desiredAssetName: "DevPod_linux_amd64.AppImage" },
{ target: "darwin-aarch64", sigFile: "aarch64.app.tar.gz.sig", packageType: ".tar.gz", originalAssetName: `DevPod_${version}_aarch64.dmg`, desiredAssetName: "DevPod_macos_aarch64.dmg", originalUpdaterAssetName: "DevPod_aarch64.app.tar.gz", desiredUpdaterAssetName: "DevPod_macos_aarch64.app.tar.gz" },
{ target: "darwin-x86_64", sigFile: "x64.app.tar.gz.sig", packageType: ".tar.gz", originalAssetName: `DevPod_${version}_x64.dmg`, desiredAssetName: "DevPod_macos_x64.dmg", originalUpdaterAssetName: "DevPod_x64.app.tar.gz", desiredUpdaterAssetName: "DevPod_macos_x64.app.tar.gz" },
{ target: "windows-x86_64", sigFile: ".msi.zip.sig", packageType: ".zip", originalAssetName: `DevPod_${version}_x64_en-US.msi`, desiredAssetName: "DevPod_windows_x64_en-US.msi" },
{ originalAssetName: `DevPod-${version}.tar.gz`, desiredAssetName: "DevPod_linux_x86_64.tar.gz" },
]
for (const info of infos) {
// Update latest.json for platform
if (info.target) {
core.info(`Generating update info for ${info.desiredAssetName}`)
const sigAsset = release.data.assets.find(a => a.name.endsWith(info.sigFile))
if (!sigAsset) {
core.warning(`Unable to find sig asset: ${info.sigFile}`)
continue
}
core.info(`Downloading ${sigAsset.name} (ID: ${sigAsset.id})`)
const sig = await fetchAsset(sigAsset.id)
let assetName = `${info.desiredAssetName}${info.packageType}`
if (info.desiredUpdaterAssetName) {
assetName = info.desiredUpdaterAssetName
}
latest.platforms[info.target] = {
signature: await sig.text(),
url: `https://github.com/loft-sh/devpod/releases/download/${process.env.GITHUB_REF_NAME}/${assetName}`,
}
// once we're done with the sig file, delete it
await github.rest.repos.deleteReleaseAsset({
...releaseArgs,
asset_id: sigAsset.id
})
}
const a = release.data.assets.find(a => a.name === info.originalAssetName)
if (!a) {
core.warning(`Unable to find asset: ${info.originalAssetName}`)
continue
}
const assetID = a.id
// Update the asset name
await github.rest.repos.updateReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
asset_id: assetID,
name: info.desiredAssetName
})
if (info.packageType) {
let name = `${info.originalAssetName}${info.packageType}`
if (info.originalUpdaterAssetName) {
name = info.originalUpdaterAssetName
}
const b = release.data.assets.find(a => a.name === name)
if (!b) {
core.warning(`Unable to find update asset: ${name}`)
continue
}
let desiredName = `${info.desiredAssetName}${info.packageType}`
if (info.desiredUpdaterAssetName) {
desiredName = info.desiredUpdaterAssetName
}
const assetID = b.id
// Update the asset name
await github.rest.repos.updateReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
asset_id: assetID,
name: desiredName
})
}
}
const latestJSON = JSON.stringify(latest)
const latestDestPath = "desktop/latest.json"
core.info(`Writing latest.json to disk (${latestDestPath}): ${latestJSON}`)
fs.writeFileSync(latestDestPath, latestJSON)
// Attempting to upload a previously released asset results in an error so we need to clean up before
if (latestAsset) {
await github.rest.repos.deleteReleaseAsset({
...releaseArgs,
asset_id: latestAsset.id
})
}
await github.rest.repos.uploadReleaseAsset({
...releaseArgs,
headers: {
"content-type": "application/zip",
"content-length": fs.statSync(latestDestPath).size
},
name: "latest.json",
data: fs.readFileSync(latestDestPath),
})