Skip to content

Rolling release

Rolling release #625

name: Rolling release
on:
schedule:
- cron: "0 0,12 * * *"
workflow_dispatch:
inputs:
force_rebuild:
description: "Rebuild and replace assets even when the latest release is already at HEAD"
required: false
type: boolean
default: false
permissions:
contents: write
concurrency:
group: rolling-release
cancel-in-progress: false
jobs:
release:
name: Build rolling release
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Compute release metadata
id: meta
env:
GH_TOKEN: ${{ github.token }}
FORCE_REBUILD: ${{ github.event_name == 'workflow_dispatch' && inputs.force_rebuild }}
run: |
set -euo pipefail
latest_tag="$(gh release list --limit 100 --json tagName --jq '[.[].tagName | select(test("^r[0-9]+$"))] | sort_by(ltrimstr("r") | tonumber) | reverse | .[0] // ""')"
if [[ -n "$latest_tag" ]]; then
git fetch --force origin "refs/tags/${latest_tag}:refs/tags/${latest_tag}" || true
fi
release_has_expected_assets() {
local tag="$1"
local missing=0
local asset
local expected_assets=(
"koder-${tag}-linux-amd64"
"koder-${tag}-linux-amd64.sha256"
"koder-${tag}-linux-arm64"
"koder-${tag}-linux-arm64.sha256"
)
mapfile -t release_assets < <(gh release view "$tag" --json assets --jq '.assets[].name')
for asset in "${expected_assets[@]}"; do
if ! printf '%s\n' "${release_assets[@]}" | grep -Fxq "$asset"; then
echo "Release ${tag} is missing ${asset}."
missing=1
fi
done
return "$missing"
}
repair_release=false
if [[ -n "$latest_tag" ]] && git rev-parse -q --verify "refs/tags/${latest_tag}" >/dev/null; then
if [[ "$(git rev-parse HEAD)" == "$(git rev-parse "${latest_tag}")" ]]; then
if [[ "${FORCE_REBUILD}" != "true" ]] && release_has_expected_assets "$latest_tag"; then
echo "changed=false" >>"$GITHUB_OUTPUT"
echo "No changes since ${latest_tag}, and all expected assets exist; skipping release."
exit 0
fi
repair_release=true
tag="$latest_tag"
changelog_range="${latest_tag}..HEAD"
else
tag="$(scripts/build-number)"
changelog_range="${latest_tag}..HEAD"
fi
else
tag="$(scripts/build-number)"
changelog_range="HEAD"
fi
short_sha="$(git rev-parse --short=12 HEAD)"
built_at="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
{
echo "changed=true"
echo "repair_release=${repair_release}"
echo "tag=${tag}"
echo "short_sha=${short_sha}"
echo "built_at=${built_at}"
} >>"$GITHUB_OUTPUT"
{
echo "## koder ${tag}"
echo
echo "Linux x64 and Linux ARM64 rolling release built from ${short_sha}."
echo
echo "### Changelog"
echo
if [[ "$repair_release" == "true" ]]; then
echo "- Rebuilt release assets for ${short_sha}."
else
git log --no-merges --format='- %s (%h)' "$changelog_range"
fi
} > release-notes.md
- name: Set up Java
if: steps.meta.outputs.changed == 'true'
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "21"
- name: Set up Android SDK
if: steps.meta.outputs.changed == 'true'
uses: android-actions/setup-android@v3
- name: Install Android SDK components
if: steps.meta.outputs.changed == 'true'
run: sdkmanager "platforms;android-36" "build-tools;37.0.0"
- name: Install sandbox test dependency
if: steps.meta.outputs.changed == 'true'
run: |
set -euo pipefail
timeout 120 sudo apt-get install -y --no-install-recommends bubblewrap
timeout 10 sudo sysctl -w kernel.unprivileged_userns_clone=1 || true
timeout 10 sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 || true
bwrap --version
- name: Run tests
if: steps.meta.outputs.changed == 'true'
env:
KODER_BWRAP_DISABLE_NET_UNSHARE: "1"
run: |
set -euo pipefail
go test ./cmd/... ./internal/...
clients/android/gradlew -p clients/android testDebugUnitTest lintDebug
- name: Build signed official Android client
if: steps.meta.outputs.changed == 'true'
env:
ANDROID_RELEASE_KEYSTORE_BASE64: ${{ secrets.ANDROID_RELEASE_KEYSTORE_BASE64 }}
ANDROID_RELEASE_STORE_PASSWORD: ${{ secrets.ANDROID_RELEASE_STORE_PASSWORD }}
ANDROID_RELEASE_KEY_ALIAS: ${{ secrets.ANDROID_RELEASE_KEY_ALIAS }}
ANDROID_RELEASE_KEY_PASSWORD: ${{ secrets.ANDROID_RELEASE_KEY_PASSWORD }}
RELEASE_TAG: ${{ steps.meta.outputs.tag }}
run: |
set -euo pipefail
: "${ANDROID_RELEASE_KEYSTORE_BASE64:?missing ANDROID_RELEASE_KEYSTORE_BASE64 secret}"
: "${ANDROID_RELEASE_STORE_PASSWORD:?missing ANDROID_RELEASE_STORE_PASSWORD secret}"
: "${ANDROID_RELEASE_KEY_ALIAS:?missing ANDROID_RELEASE_KEY_ALIAS secret}"
: "${ANDROID_RELEASE_KEY_PASSWORD:?missing ANDROID_RELEASE_KEY_PASSWORD secret}"
signing_dir="$RUNNER_TEMP/koder-android-signing"
mkdir -p "$signing_dir"
chmod 700 "$signing_dir"
printf '%s' "$ANDROID_RELEASE_KEYSTORE_BASE64" | base64 --decode >"$signing_dir/release.jks"
chmod 600 "$signing_dir/release.jks"
{
echo "storeFile=$signing_dir/release.jks"
echo "storePassword=$ANDROID_RELEASE_STORE_PASSWORD"
echo "keyAlias=$ANDROID_RELEASE_KEY_ALIAS"
echo "keyPassword=$ANDROID_RELEASE_KEY_PASSWORD"
} >"$signing_dir/release.properties"
chmod 600 "$signing_dir/release.properties"
version_code=$((10#${RELEASE_TAG#r}))
KODER_ANDROID_RELEASE_SIGNING_PROPERTIES="$signing_dir/release.properties" \
KODER_ANDROID_VERSION_CODE="$version_code" \
KODER_ANDROID_VERSION_NAME="$RELEASE_TAG" \
scripts/prepare-embedded-android release >/dev/null
- name: Build Linux x64 artifact
if: steps.meta.outputs.changed == 'true'
env:
VERSION: ${{ steps.meta.outputs.tag }}
COMMIT: ${{ steps.meta.outputs.short_sha }}
DIRTY: "false"
BUILD_TIME: ${{ steps.meta.outputs.built_at }}
KODER_EXTRA_LDFLAGS: "-s -w"
GOOS: linux
GOARCH: amd64
CGO_ENABLED: "0"
KODER_EMBED_ANDROID: "0"
run: |
set -euo pipefail
mkdir -p dist
scripts/build-koder "dist/koder-${VERSION}-linux-amd64"
(cd dist && sha256sum "koder-${VERSION}-linux-amd64" > "koder-${VERSION}-linux-amd64.sha256")
- name: Build Linux ARM64 artifact
if: steps.meta.outputs.changed == 'true'
env:
VERSION: ${{ steps.meta.outputs.tag }}
COMMIT: ${{ steps.meta.outputs.short_sha }}
DIRTY: "false"
BUILD_TIME: ${{ steps.meta.outputs.built_at }}
KODER_EXTRA_LDFLAGS: "-s -w"
GOOS: linux
GOARCH: arm64
CGO_ENABLED: "0"
KODER_EMBED_ANDROID: "0"
run: |
set -euo pipefail
mkdir -p dist
scripts/build-koder "dist/koder-${VERSION}-linux-arm64"
(cd dist && sha256sum "koder-${VERSION}-linux-arm64" > "koder-${VERSION}-linux-arm64.sha256")
- name: Publish GitHub release
if: steps.meta.outputs.changed == 'true'
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.meta.outputs.tag }}
REPAIR_RELEASE: ${{ steps.meta.outputs.repair_release }}
run: |
set -euo pipefail
if [[ "$REPAIR_RELEASE" == "true" ]]; then
gh release upload "$TAG" \
"dist/koder-${TAG}-linux-amd64" \
"dist/koder-${TAG}-linux-amd64.sha256" \
"dist/koder-${TAG}-linux-arm64" \
"dist/koder-${TAG}-linux-arm64.sha256" \
--clobber
gh release edit "$TAG" \
--title "$TAG" \
--notes-file release-notes.md
else
gh release create "$TAG" \
--target "$GITHUB_SHA" \
--title "$TAG" \
--notes-file release-notes.md \
"dist/koder-${TAG}-linux-amd64" \
"dist/koder-${TAG}-linux-amd64.sha256" \
"dist/koder-${TAG}-linux-arm64" \
"dist/koder-${TAG}-linux-arm64.sha256"
fi