Files
OwnCord/.github/workflows/release.yml
T
J3vbandClaude Fable 5 4bbfaa6a8d fix(ci): install xdg-utils for Linux AppImage bundling
The ubuntu-22.04-arm runner image does not preinstall xdg-utils, so
AppImage bundling fails with 'xdg-open binary not found'. Add it to
every Linux system-dependency list in ci.yml and release.yml (the
release ARM job shares the same list and would have failed identically
at tag time).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 15:11:26 +02:00

439 lines
15 KiB
YAML

name: Release
on:
push:
tags:
- "v*"
jobs:
release-client-windows:
name: Build Tauri (Windows)
runs-on: windows-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: Client/tauri-client/package-lock.json
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: Client/tauri-client/src-tauri
- name: Install npm dependencies
working-directory: Client/tauri-client
run: npm ci
- name: Build Tauri app
working-directory: Client/tauri-client
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
VITE_KLIPY_API_KEY: ${{ secrets.VITE_KLIPY_API_KEY }}
run: npm run tauri build
- name: Stage Windows release assets
shell: bash
run: |
mkdir -p release-staging
NSIS_DIR="Client/tauri-client/src-tauri/target/release/bundle/nsis"
INSTALLER=$(find "$NSIS_DIR" -name "*.exe" | head -1)
cp "$INSTALLER" release-staging/
NSIS_ZIP=$(find "$NSIS_DIR" -name "*_x64-setup.nsis.zip" ! -name "*.sig" | head -1)
if [ -n "$NSIS_ZIP" ] && [ -f "$NSIS_ZIP" ]; then cp "$NSIS_ZIP" release-staging/; fi
NSIS_SIG=$(find "$NSIS_DIR" -name "*_x64-setup.nsis.zip.sig" | head -1)
if [ -n "$NSIS_SIG" ] && [ -f "$NSIS_SIG" ]; then cp "$NSIS_SIG" release-staging/; fi
- name: Upload Windows release assets
uses: actions/upload-artifact@v4
with:
name: windows-release-assets
path: release-staging/
release-client-linux:
name: Build Tauri (Linux)
runs-on: ubuntu-22.04
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: Client/tauri-client/package-lock.json
- name: Install Linux system dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
libgtk-3-dev \
libayatana-appindicator3-dev \
libsecret-1-dev \
libasound2-dev \
libssl-dev \
patchelf \
librsvg2-dev \
xdg-utils
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: Client/tauri-client/src-tauri
- name: Install npm dependencies
working-directory: Client/tauri-client
run: npm ci
- name: Build Tauri app (AppImage + deb)
working-directory: Client/tauri-client
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
VITE_KLIPY_API_KEY: ${{ secrets.VITE_KLIPY_API_KEY }}
run: npm run tauri build -- --bundles appimage,deb
- name: Stage Linux release assets
shell: bash
run: |
mkdir -p linux-staging
BUNDLE_DIR="Client/tauri-client/src-tauri/target/release/bundle"
# AppImage
APPIMAGE=$(find "$BUNDLE_DIR/appimage" -name "*.AppImage" ! -name "*.sig" | head -1)
if [ -n "$APPIMAGE" ] && [ -f "$APPIMAGE" ]; then cp "$APPIMAGE" linux-staging/; fi
APPIMAGE_SIG=$(find "$BUNDLE_DIR/appimage" -name "*.AppImage.sig" | head -1)
if [ -n "$APPIMAGE_SIG" ] && [ -f "$APPIMAGE_SIG" ]; then cp "$APPIMAGE_SIG" linux-staging/; fi
APPIMAGE_TAR=$(find "$BUNDLE_DIR/appimage" -name "*.AppImage.tar.gz" ! -name "*.sig" | head -1)
if [ -n "$APPIMAGE_TAR" ] && [ -f "$APPIMAGE_TAR" ]; then cp "$APPIMAGE_TAR" linux-staging/; fi
APPIMAGE_TAR_SIG=$(find "$BUNDLE_DIR/appimage" -name "*.AppImage.tar.gz.sig" | head -1)
if [ -n "$APPIMAGE_TAR_SIG" ] && [ -f "$APPIMAGE_TAR_SIG" ]; then cp "$APPIMAGE_TAR_SIG" linux-staging/; fi
# .deb
DEB=$(find "$BUNDLE_DIR/deb" -name "*.deb" | head -1)
if [ -n "$DEB" ] && [ -f "$DEB" ]; then cp "$DEB" linux-staging/; fi
- name: Upload Linux release assets
uses: actions/upload-artifact@v4
with:
name: linux-release-assets
path: linux-staging/
release-server:
name: Build server (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
artifact: server-windows
- os: ubuntu-latest
artifact: server-linux
runs-on: ${{ matrix.os }}
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
- name: Extract version from tag
shell: bash
run: |
VERSION="${GITHUB_REF_NAME#v}"
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
- name: Build server (Windows)
if: matrix.os == 'windows-latest'
shell: bash
run: cd Server && go build -o chatserver.exe -ldflags "-s -w -X main.version=$VERSION" .
- name: Build server (Linux)
if: matrix.os == 'ubuntu-latest'
working-directory: Server
env:
CGO_ENABLED: "0"
run: go build -o chatserver -ldflags "-s -w -X main.version=$VERSION" .
- name: Create tar.gz (Linux)
if: matrix.os == 'ubuntu-latest'
working-directory: Server
run: tar czf ../chatserver-linux-amd64.tar.gz chatserver
- name: Upload Windows binary
if: matrix.os == 'windows-latest'
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact }}
path: Server/chatserver.exe
- name: Upload Linux archive
if: matrix.os == 'ubuntu-latest'
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact }}
path: chatserver-linux-amd64.tar.gz
release-client-linux-arm64:
name: Build Tauri (Linux ARM64)
runs-on: ubuntu-22.04-arm
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: Client/tauri-client/package-lock.json
- name: Install Linux system dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
libgtk-3-dev \
libayatana-appindicator3-dev \
libsecret-1-dev \
libasound2-dev \
libssl-dev \
patchelf \
librsvg2-dev \
xdg-utils
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: Client/tauri-client/src-tauri
- name: Install npm dependencies
working-directory: Client/tauri-client
run: npm ci
- name: Build Tauri app (AppImage + deb)
working-directory: Client/tauri-client
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
VITE_KLIPY_API_KEY: ${{ secrets.VITE_KLIPY_API_KEY }}
run: npm run tauri build -- --bundles appimage,deb
- name: Stage Linux ARM64 release assets
shell: bash
run: |
mkdir -p linux-arm64-staging
BUNDLE_DIR="Client/tauri-client/src-tauri/target/release/bundle"
# AppImage (ensure arch is in filename)
for f in "$BUNDLE_DIR"/appimage/*.AppImage; do
[ -f "$f" ] || continue
[[ "$f" == *.sig ]] && continue
dest="linux-arm64-staging/$(basename "$f")"
# Append _aarch64 if bundler omits arch from filename
[[ "$(basename "$f")" == *aarch64* ]] || dest="${dest%.AppImage}_aarch64.AppImage"
cp "$f" "$dest"
done
for f in "$BUNDLE_DIR"/appimage/*.AppImage.tar.gz; do
[ -f "$f" ] && cp "$f" linux-arm64-staging/
done
for f in "$BUNDLE_DIR"/appimage/*.sig; do
[ -f "$f" ] && cp "$f" linux-arm64-staging/
done
# .deb
for f in "$BUNDLE_DIR"/deb/*.deb; do
[ -f "$f" ] && cp "$f" linux-arm64-staging/
done
- name: Upload Linux ARM64 release assets
uses: actions/upload-artifact@v4
with:
name: linux-arm64-release-assets
path: linux-arm64-staging/
release-server-docker:
name: Build & Push Server Docker Image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Extract version from tag
shell: bash
run: |
VERSION="${GITHUB_REF_NAME#v}"
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository_owner }}/owncord-server
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest
- name: Build and push
uses: docker/build-push-action@v6
with:
context: Server/
push: true
build-args: VERSION=${{ env.VERSION }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
publish:
name: Publish GitHub Release
needs: [release-client-windows, release-client-linux, release-client-linux-arm64, release-server, release-server-docker]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: Client/tauri-client/package-lock.json
- name: Download Windows client assets
uses: actions/download-artifact@v4
with:
name: windows-release-assets
path: windows
- name: Download Linux x86_64 client assets
uses: actions/download-artifact@v4
with:
name: linux-release-assets
path: linux
- name: Download Linux ARM64 client assets
uses: actions/download-artifact@v4
with:
name: linux-arm64-release-assets
path: linux
- name: Download Windows server binary
uses: actions/download-artifact@v4
with:
name: server-windows
path: windows
- name: Download Linux server archive
uses: actions/download-artifact@v4
with:
name: server-linux
path: linux
- name: Extract version from tag
shell: bash
run: |
VERSION="${GITHUB_REF_NAME#v}"
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
- name: Create source snapshot (AGPL source availability)
shell: bash
run: |
git archive --format=tar.gz --prefix="OwnCord-${VERSION}/" \
-o "owncord-src-${{ github.ref_name }}.tar.gz" HEAD
- name: Generate SHA256 checksums
run: |
find windows linux -type f -exec sha256sum {} \; > checksums.sha256
sha256sum owncord-src-*.tar.gz >> checksums.sha256
- name: Generate server update manifest
shell: bash
run: |
SERVER_HASH=$(sha256sum windows/chatserver.exe | awk '{print $1}')
printf '{"version":"v%s","asset":"chatserver.exe","sha256":"%s"}' "$VERSION" "$SERVER_HASH" > windows/server-update-manifest.json
- name: Sign server update assets
working-directory: Client/tauri-client
shell: bash
env:
SERVER_UPDATE_SIGNING_PRIVATE_KEY: ${{ secrets.SERVER_UPDATE_SIGNING_PRIVATE_KEY }}
SERVER_UPDATE_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.SERVER_UPDATE_SIGNING_PRIVATE_KEY_PASSWORD }}
run: |
KEY_PATH=$(mktemp)
printf '%s' "$SERVER_UPDATE_SIGNING_PRIVATE_KEY" > "$KEY_PATH"
trap 'rm -f "$KEY_PATH"' EXIT
npm ci
npx tauri signer sign -k "$KEY_PATH" -p "$SERVER_UPDATE_SIGNING_PRIVATE_KEY_PASSWORD" ../../windows/chatserver.exe
npx tauri signer sign -k "$KEY_PATH" -p "$SERVER_UPDATE_SIGNING_PRIVATE_KEY_PASSWORD" ../../windows/server-update-manifest.json
- name: Install root dependencies (changelogen)
run: npm ci
- name: Generate changelog
shell: bash
run: npx changelogen --output CHANGELOG.md
- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mapfile -t assets < <(find windows linux -type f)
assets+=(checksums.sha256 owncord-src-*.tar.gz)
gh release create "${{ github.ref_name }}" \
--notes-file CHANGELOG.md \
"${assets[@]}"
# The public releases repo is what deployed servers and clients poll for
# updates, and it carries the AGPL source snapshot while the source repo
# is private. Publishing there must never be skipped silently once the
# source repo is private.
- name: Publish to public releases repo
shell: bash
env:
RELEASES_TOKEN: ${{ secrets.RELEASES_REPO_TOKEN }}
SOURCE_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ -z "$RELEASES_TOKEN" ]; then
PRIVATE=$(GH_TOKEN="$SOURCE_REPO_TOKEN" gh api "repos/$GITHUB_REPOSITORY" --jq .private)
if [ "$PRIVATE" = "true" ]; then
echo "::error::Source repo is private and RELEASES_REPO_TOKEN is unset — binaries would ship with no public source or update feed (AGPL violation, broken updater)."
exit 1
fi
echo "::warning::RELEASES_REPO_TOKEN not set — skipping publish to J3vb/OwnCord-releases."
exit 0
fi
mapfile -t assets < <(find windows linux -type f)
assets+=(checksums.sha256 owncord-src-*.tar.gz)
GH_TOKEN="$RELEASES_TOKEN" gh release create "${{ github.ref_name }}" \
--repo J3vb/OwnCord-releases \
--notes-file CHANGELOG.md \
"${assets[@]}"