Skip to content

feat(web): finish design-handoff port — Dashboard, ⌘K palette, Agents… #39

feat(web): finish design-handoff port — Dashboard, ⌘K palette, Agents…

feat(web): finish design-handoff port — Dashboard, ⌘K palette, Agents… #39

Workflow file for this run

name: Release
on:
push:
tags: ['v*']
permissions:
contents: write
packages: write
jobs:
build-agent:
name: Build Agent (${{ matrix.name }})
runs-on: ${{ matrix.runner }}
strategy:
matrix:
include:
- goos: darwin
goarch: arm64
name: macOS-arm64
runner: ubuntu-latest
- goos: darwin
goarch: amd64
name: macOS-x64
runner: ubuntu-latest
- goos: linux
goarch: amd64
name: Linux-x64
runner: ubuntu-latest
- goos: linux
goarch: arm64
name: Linux-arm64
runner: ubuntu-latest
- goos: windows
goarch: amd64
name: Windows-x64
runner: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Build
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: '0'
BUILD_NAME: ${{ matrix.name }}
run: |
cd agent
VERSION="${GITHUB_REF#refs/tags/}"
COMMIT="${GITHUB_SHA::8}"
EXT=""
if [ "$GOOS" = "windows" ]; then EXT=".exe"; fi
go build -trimpath -ldflags="-s -w -X main.Version=${VERSION} -X main.GitCommit=${COMMIT} -X main.BuildDate=$(date -u +%Y-%m-%dT%H:%M:%SZ)" -o "../rttys-agent-${BUILD_NAME}${EXT}" .
- uses: actions/upload-artifact@v4
with:
name: rttys-agent-${{ matrix.name }}
path: rttys-agent-${{ matrix.name }}*
build-relay:
name: Build Relay + Web
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
- run: npm ci
- run: cd packages/relay && npx tsc
- run: VITE_APP_VERSION="${GITHUB_REF#refs/tags/}" npx vite build
working-directory: packages/web
- run: cp -r packages/web/dist packages/relay/public
- name: Package relay
run: tar -czf rttys-relay.tar.gz -C packages/relay dist public package.json
- uses: actions/upload-artifact@v4
with:
name: rttys-relay
path: rttys-relay.tar.gz
build-docker:
name: Build & Push Docker Image
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version from tag
id: version
run: echo "tag=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
- uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
build-args: |
RTTYS_VERSION=${{ steps.version.outputs.tag }}
RTTYS_COMMIT=${{ github.sha }}
tags: |
ghcr.io/${{ github.repository_owner }}/remotettys:${{ steps.version.outputs.tag }}
ghcr.io/${{ github.repository_owner }}/remotettys:latest
build-macos-app:
name: Build macOS App
runs-on: macos-26
environment: APPLE_CERTIFICATE
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Extract version info
id: version
run: |
TAG="${GITHUB_REF#refs/tags/}"
VERSION="${TAG#v}"
COMMIT="${GITHUB_SHA::8}"
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
BUILD_NUMBER=$(( ${MAJOR:-0} * 10000 + ${MINOR:-0} * 100 + ${PATCH:-0} ))
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "commit=$COMMIT" >> "$GITHUB_OUTPUT"
echo "build_number=$BUILD_NUMBER" >> "$GITHUB_OUTPUT"
- name: Import Apple certificate
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: |
CERT_PATH="$RUNNER_TEMP/certificate.p12"
KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"
KEYCHAIN_PASSWORD="$(openssl rand -hex 16)"
echo "$APPLE_CERTIFICATE" | base64 --decode > "$CERT_PATH"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security import "$CERT_PATH" -P "$APPLE_CERTIFICATE_PASSWORD" \
-A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
security set-key-partition-list -S apple-tool:,apple: \
-k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security list-keychains -d user -s "$KEYCHAIN_PATH" login.keychain-db
security default-keychain -d user -s "$KEYCHAIN_PATH"
security find-identity -v -p codesigning "$KEYCHAIN_PATH"
- name: Build Go universal binary
env:
CGO_ENABLED: '0'
run: |
cd agent
VERSION="${{ steps.version.outputs.tag }}"
COMMIT="${{ steps.version.outputs.commit }}"
LDFLAGS="-s -w -X main.Version=${VERSION} -X main.GitCommit=${COMMIT} -X main.BuildDate=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
mkdir -p ../build ../agent-mac/RttysAgent/Resources
GOOS=darwin GOARCH=arm64 go build -trimpath -ldflags="$LDFLAGS" -o ../build/rttys-agent-arm64 .
GOOS=darwin GOARCH=amd64 go build -trimpath -ldflags="$LDFLAGS" -o ../build/rttys-agent-amd64 .
lipo -create ../build/rttys-agent-arm64 ../build/rttys-agent-amd64 \
-output ../agent-mac/RttysAgent/Resources/rttys-agent
chmod +x ../agent-mac/RttysAgent/Resources/rttys-agent
- name: Select Xcode 26.2
run: sudo xcode-select -s /Applications/Xcode_26.2.app
- name: Resolve SPM dependencies
run: |
cd agent-mac
xcodebuild -resolvePackageDependencies \
-project RttysAgent.xcodeproj \
-scheme RttysAgent
- name: Archive Xcode project
env:
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
run: |
cd agent-mac
xcodebuild archive \
-project RttysAgent.xcodeproj \
-scheme RttysAgent \
-configuration Release \
-archivePath ../build/RttysAgent.xcarchive \
MARKETING_VERSION="${{ steps.version.outputs.version }}" \
CURRENT_PROJECT_VERSION="${{ steps.version.outputs.build_number }}" \
DEVELOPMENT_TEAM="$APPLE_TEAM_ID" \
CODE_SIGN_STYLE=Manual \
CODE_SIGN_IDENTITY="$APPLE_SIGNING_IDENTITY" \
PROVISIONING_PROFILE_SPECIFIER=""
- name: Export archive
env:
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
EXPORT_PLIST="$RUNNER_TEMP/ExportOptions.plist"
sed "s/__APPLE_TEAM_ID__/$APPLE_TEAM_ID/g" agent-mac/ExportOptions.plist > "$EXPORT_PLIST"
xcodebuild -exportArchive \
-archivePath build/RttysAgent.xcarchive \
-exportPath build/export \
-exportOptionsPlist "$EXPORT_PLIST"
- name: Inject and sign rttys-agent
env:
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
EXPECTED_VERSION: ${{ steps.version.outputs.version }}
EXPECTED_BUILD: ${{ steps.version.outputs.build_number }}
run: |
APP="build/export/RttysAgent.app"
if [ ! -d "$APP" ]; then
echo "::error::Exported app not found at $APP"
ls -la build/export/ || true
exit 1
fi
ACTUAL_VERSION=$(plutil -extract CFBundleShortVersionString raw "$APP/Contents/Info.plist")
ACTUAL_BUILD=$(plutil -extract CFBundleVersion raw "$APP/Contents/Info.plist")
if [ "$ACTUAL_VERSION" != "$EXPECTED_VERSION" ] || [ "$ACTUAL_BUILD" != "$EXPECTED_BUILD" ]; then
echo "::error::Info.plist version mismatch — got ${ACTUAL_VERSION}(${ACTUAL_BUILD}), want ${EXPECTED_VERSION}(${EXPECTED_BUILD})"
exit 1
fi
mkdir -p "$APP/Contents/Resources"
cp agent-mac/RttysAgent/Resources/rttys-agent "$APP/Contents/Resources/rttys-agent"
chmod +x "$APP/Contents/Resources/rttys-agent"
codesign --force --sign "$APPLE_SIGNING_IDENTITY" \
--options runtime --timestamp \
"$APP/Contents/Resources/rttys-agent"
codesign --force --sign "$APPLE_SIGNING_IDENTITY" \
--options runtime --timestamp \
--entitlements agent-mac/RttysAgent/RttysAgent.entitlements \
"$APP"
codesign --verify --deep --strict --verbose=2 "$APP"
- name: Notarize app
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
ditto -c -k --keepParent build/export/RttysAgent.app build/RttysAgent-notarize.zip
xcrun notarytool submit build/RttysAgent-notarize.zip \
--apple-id "$APPLE_ID" \
--password "$APPLE_PASSWORD" \
--team-id "$APPLE_TEAM_ID" \
--wait
xcrun stapler staple build/export/RttysAgent.app
- name: Package for Sparkle
id: package
run: |
ditto -c -k --keepParent build/export/RttysAgent.app build/RttysAgent.zip
echo "file_size=$(stat -f%z build/RttysAgent.zip)" >> "$GITHUB_OUTPUT"
- name: EdDSA sign for Sparkle
id: sparkle_sign
env:
SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }}
run: |
SPARKLE_BIN=$(find ~/Library/Developer/Xcode/DerivedData -path "*/Sparkle/bin/sign_update" -type f 2>/dev/null | head -1)
if [ -z "$SPARKLE_BIN" ]; then
mkdir -p /tmp/sparkle
curl -sL "https://github.com/sparkle-project/Sparkle/releases/download/2.9.1/Sparkle-2.9.1.tar.xz" | tar xJ -C /tmp/sparkle
SPARKLE_BIN="/tmp/sparkle/bin/sign_update"
fi
KEY_FILE="$RUNNER_TEMP/sparkle_ed25519_key"
umask 077
printf '%s' "$SPARKLE_PRIVATE_KEY" > "$KEY_FILE"
SIGN_OUTPUT=$("$SPARKLE_BIN" -f "$KEY_FILE" build/RttysAgent.zip)
rm -f "$KEY_FILE"
ED_SIGNATURE=$(echo "$SIGN_OUTPUT" | sed -n 's/.*edSignature="\([^"]*\)".*/\1/p')
if [ -z "$ED_SIGNATURE" ]; then
echo "::error::Failed to extract Sparkle edSignature from output: $SIGN_OUTPUT"
exit 1
fi
echo "ed_signature=$ED_SIGNATURE" >> "$GITHUB_OUTPUT"
- name: Generate appcast.xml
run: |
PUB_DATE=$(date -R)
sed \
-e "s|SPARKLE_VERSION|${{ steps.version.outputs.version }}|g" \
-e "s|SPARKLE_BUILD_NUMBER|${{ steps.version.outputs.build_number }}|g" \
-e "s|SPARKLE_TAG|${{ steps.version.outputs.tag }}|g" \
-e "s|SPARKLE_PUB_DATE|${PUB_DATE}|g" \
-e "s|SPARKLE_FILE_SIZE|${{ steps.package.outputs.file_size }}|g" \
-e "s|SPARKLE_ED_SIGNATURE|${{ steps.sparkle_sign.outputs.ed_signature }}|g" \
agent-mac/appcast-template.xml > build/appcast.xml
- uses: actions/upload-artifact@v4
with:
name: macos-app
path: |
build/RttysAgent.zip
build/appcast.xml
- name: Cleanup keychain
if: always()
run: security delete-keychain "$RUNNER_TEMP/app-signing.keychain-db" 2>/dev/null || true
release:
name: Create Release
needs: [build-agent, build-relay, build-macos-app]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
path: artifacts
pattern: '{rttys-*,macos-app}'
merge-multiple: false
- name: Create release
uses: softprops/action-gh-release@v2
with:
files: |
artifacts/rttys-agent-*/rttys-agent-*
artifacts/rttys-relay/rttys-relay.tar.gz
artifacts/macos-app/RttysAgent.zip
artifacts/macos-app/appcast.xml
generate_release_notes: true