Skip to content

Merge branch 'tetherto-dev' #30

Merge branch 'tetherto-dev'

Merge branch 'tetherto-dev' #30

name: Build Appling
on:
push:
branches:
- main
- dev
workflow_dispatch:
inputs:
tag:
description: 'Version tag'
required: true
type: string
jobs:
version:
runs-on: ubuntu-22.04
if: github.repository_owner == 'tetherto'
outputs:
version: ${{ steps.meta.outputs.VERSION }}
strategy:
matrix:
arch:
- x64
steps:
- uses: actions/checkout@v4
- name: Read & validate version
id: meta
run: |
set -euo pipefail
VERSION=$(jq -r '.version' package.json)
echo "VERSION=$VERSION"
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "❌ Error: package.json version ('$VERSION') is not semantic versioning (MAJOR.MINOR.PATCH)."
exit 1
fi
TAG="v$VERSION"
echo "TAG=$TAG"
echo "VERSION=$VERSION" >> "$GITHUB_OUTPUT"
echo "TAG=$TAG" >> "$GITHUB_OUTPUT"
linux:
needs: [version]
strategy:
matrix:
include:
- arch: x64
runs_on: ubuntu-22.04
- arch: arm64
runs_on: ubuntu-22.04-arm
runs-on: ${{ matrix.runs_on }}
defaults:
run:
working-directory: ./appling
env:
SLACK_WEBHOOK_URL_PATH: ${{ secrets.SLACK_WEBHOOK_URL_PATH }}
GOOGLE_FORM_KEY: ${{ secrets.GOOGLE_FORM_KEY }}
GOOGLE_FORM_MAPPING_TIMESTAMP: ${{ secrets.GOOGLE_FORM_MAPPING_TIMESTAMP }}
GOOGLE_FORM_MAPPING_TOPIC: ${{ secrets.GOOGLE_FORM_MAPPING_TOPIC }}
GOOGLE_FORM_MAPPING_APP: ${{ secrets.GOOGLE_FORM_MAPPING_APP }}
GOOGLE_FORM_MAPPING_OPERATING_SYSTEM: ${{ secrets.GOOGLE_FORM_MAPPING_OPERATING_SYSTEM }}
GOOGLE_FORM_MAPPING_DEVICE_MODEL: ${{ secrets.GOOGLE_FORM_MAPPING_DEVICE_MODEL }}
GOOGLE_FORM_MAPPING_MESSAGE: ${{ secrets.GOOGLE_FORM_MAPPING_MESSAGE }}
GOOGLE_FORM_MAPPING_APP_VERSION: ${{ secrets.GOOGLE_FORM_MAPPING_APP_VERSION }}
TEST_SLACK_WEBHOOK_URL_PATH: ${{ secrets.TEST_SLACK_WEBHOOK_URL_PATH }}
TEST_GOOGLE_FORM_KEY: ${{ secrets.TEST_GOOGLE_FORM_KEY }}
TEST_GOOGLE_FORM_MAPPING_TIMESTAMP: ${{ secrets.TEST_GOOGLE_FORM_MAPPING_TIMESTAMP }}
TEST_GOOGLE_FORM_MAPPING_TOPIC: ${{ secrets.TEST_GOOGLE_FORM_MAPPING_TOPIC }}
TEST_GOOGLE_FORM_MAPPING_APP: ${{ secrets.TEST_GOOGLE_FORM_MAPPING_APP }}
TEST_GOOGLE_FORM_MAPPING_OPERATING_SYSTEM: ${{ secrets.TEST_GOOGLE_FORM_MAPPING_OPERATING_SYSTEM }}
TEST_GOOGLE_FORM_MAPPING_DEVICE_MODEL: ${{ secrets.TEST_GOOGLE_FORM_MAPPING_DEVICE_MODEL }}
TEST_GOOGLE_FORM_MAPPING_MESSAGE: ${{ secrets.TEST_GOOGLE_FORM_MAPPING_MESSAGE }}
TEST_GOOGLE_FORM_MAPPING_APP_VERSION: ${{ secrets.TEST_GOOGLE_FORM_MAPPING_APP_VERSION }}
APP_FILE: ${{ github.ref_name == 'refs/heads/main' && 'app' || 'app.dev' }}.cjs
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
registry-url: 'https://npm.pkg.github.com'
scope: '@tetherto'
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y curl ca-certificates build-essential git libgtk-4-dev pkg-config fuse file
# sudo apt-get install -y clang pkg-config libgtk-4-dev clang-tools fuse file
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 20
npm install --global bare-build
npm install
- name: Build Linux AppImage
run: |
bare-build --host=linux-${{ matrix.arch }} --package --icon lib/icons/linux/icon.png ${{ env.APP_FILE }}
- name: Find & rename AppImage file
id: find_file
run: |
set -euo pipefail
APPIMAGE_PATH=$(find "$GITHUB_WORKSPACE" -name "*.AppImage" -type f | head -n 1)
echo "Original AppImage: $APPIMAGE_PATH"
NEW_NAME="PearPass-Desktop-Linux-${{ matrix.arch }}-v${{ needs.version.outputs.version }}.AppImage"
NEW_PATH="$GITHUB_WORKSPACE/$NEW_NAME"
mv "$APPIMAGE_PATH" "$NEW_PATH"
echo "Renamed AppImage to: $NEW_PATH"
echo "APPIMAGE_PATH=$NEW_PATH" >> "$GITHUB_OUTPUT"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
echo "TIMESTAMP=$TIMESTAMP" >> $GITHUB_OUTPUT
- uses: actions/upload-artifact@v4
if: ${{ steps.find_file.outputs.APPIMAGE_PATH != '' }}
with:
name: PearPass-Desktop-Linux-${{ matrix.arch }}-v${{ needs.version.outputs.version }}.AppImage
path: "${{ steps.find_file.outputs.APPIMAGE_PATH }}"
macos:
runs-on: macos-14
needs: [version]
strategy:
matrix:
arch:
- x64
- arm64
defaults:
run:
working-directory: ./appling
env:
# Apple code signing identity - used for app, installer, and DMG signing
APPLE_SIGNING_IDENTITY: "959D2C8BB8323AEF9C2367ACA2F854AE4D7DE458"
SLACK_WEBHOOK_URL_PATH: ${{ secrets.SLACK_WEBHOOK_URL_PATH }}
GOOGLE_FORM_KEY: ${{ secrets.GOOGLE_FORM_KEY }}
GOOGLE_FORM_MAPPING_TIMESTAMP: ${{ secrets.GOOGLE_FORM_MAPPING_TIMESTAMP }}
GOOGLE_FORM_MAPPING_TOPIC: ${{ secrets.GOOGLE_FORM_MAPPING_TOPIC }}
GOOGLE_FORM_MAPPING_APP: ${{ secrets.GOOGLE_FORM_MAPPING_APP }}
GOOGLE_FORM_MAPPING_OPERATING_SYSTEM: ${{ secrets.GOOGLE_FORM_MAPPING_OPERATING_SYSTEM }}
GOOGLE_FORM_MAPPING_DEVICE_MODEL: ${{ secrets.GOOGLE_FORM_MAPPING_DEVICE_MODEL }}
GOOGLE_FORM_MAPPING_MESSAGE: ${{ secrets.GOOGLE_FORM_MAPPING_MESSAGE }}
GOOGLE_FORM_MAPPING_APP_VERSION: ${{ secrets.GOOGLE_FORM_MAPPING_APP_VERSION }}
TEST_SLACK_WEBHOOK_URL_PATH: ${{ secrets.TEST_SLACK_WEBHOOK_URL_PATH }}
TEST_GOOGLE_FORM_KEY: ${{ secrets.TEST_GOOGLE_FORM_KEY }}
TEST_GOOGLE_FORM_MAPPING_TIMESTAMP: ${{ secrets.TEST_GOOGLE_FORM_MAPPING_TIMESTAMP }}
TEST_GOOGLE_FORM_MAPPING_TOPIC: ${{ secrets.TEST_GOOGLE_FORM_MAPPING_TOPIC }}
TEST_GOOGLE_FORM_MAPPING_APP: ${{ secrets.TEST_GOOGLE_FORM_MAPPING_APP }}
TEST_GOOGLE_FORM_MAPPING_OPERATING_SYSTEM: ${{ secrets.TEST_GOOGLE_FORM_MAPPING_OPERATING_SYSTEM }}
TEST_GOOGLE_FORM_MAPPING_DEVICE_MODEL: ${{ secrets.TEST_GOOGLE_FORM_MAPPING_DEVICE_MODEL }}
TEST_GOOGLE_FORM_MAPPING_MESSAGE: ${{ secrets.TEST_GOOGLE_FORM_MAPPING_MESSAGE }}
TEST_GOOGLE_FORM_MAPPING_APP_VERSION: ${{ secrets.TEST_GOOGLE_FORM_MAPPING_APP_VERSION }}
APP_FILE: ${{ github.ref_name == 'refs/heads/main' && 'app' || 'app.dev' }}.cjs
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
registry-url: 'https://npm.pkg.github.com'
scope: '@tetherto'
- name: Install dependencies
run: |
npm install --global bare-build
npm install
- name: Audit dependencies
run: |
npm audit --production --audit-level=high || echo "::warning::npm audit found vulnerabilities"
- name: Install the Apple certificate and provisioning profile
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.APPLE_DISTRIBUTION_CERTIFICATE }}
P12_PASSWORD: ${{ secrets.APPLE_P12_PASSWORD }}
BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.APPLE_PROVISIONING_PROFILE }}
KEYCHAIN_PASSWORD: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }}
APIKEY_BASE64: ${{ secrets.APPLE_APIKEY }}
run: |
# create variables
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
PP_PATH=$RUNNER_TEMP/build_pp.provisionprofile
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
APIKEY_PATH=$RUNNER_TEMP/AuthKey.p8
# import certificate and provisioning profile from secrets
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH
echo -n "$APIKEY_BASE64" | base64 --decode -o $APIKEY_PATH
# create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# import certificate to keychain
security import $CERTIFICATE_PATH -P "$P12_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-keychain -d user -s $KEYCHAIN_PATH
# add notarytool credentials
xcrun notarytool store-credentials "pearpass" --key "$APIKEY_PATH" --key-id S3AG2UXSC5 --issuer 2067dae8-140d-4016-9a2b-d6ea80a20708
# apply provisioning profile
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
- name: Build macOS app
run: |
bare-build \
--identifier "com.pears.pass" \
--host=darwin-${{ matrix.arch }} \
--icon lib/icons/darwin/icon.png \
--sign \
--identity "$APPLE_SIGNING_IDENTITY" \
--application-identity "$APPLE_SIGNING_IDENTITY" \
--installer-identity "$APPLE_SIGNING_IDENTITY" \
--keychain "pearpass" \
--entitlements entitlements.plist \
--hardened-runtime \
${{ env.APP_FILE }}
- name: Find App file
id: find_file
run: |
set -euo pipefail
APP_PATH=$(find "$GITHUB_WORKSPACE" -name "*.app" -type d | head -n 1)
echo "Found app at: $APP_PATH"
echo "APP_PATH=$APP_PATH" >> "$GITHUB_OUTPUT"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
echo "TIMESTAMP=$TIMESTAMP" >> $GITHUB_OUTPUT
- name: Create DMG
id: create_dmg
if: ${{ steps.find_file.outputs.APP_PATH != '' }}
run: |
set -euo pipefail
npm install --global create-dmg
APP_PATH="${{ steps.find_file.outputs.APP_PATH }}"
VERSION="${{ needs.version.outputs.version }}"
ARCH="${{ matrix.arch }}"
create-dmg --identity="$APPLE_SIGNING_IDENTITY" --no-version-in-filename "$APP_PATH"
# Find the DMG that was just created in the current working directory (./appling)
RAW_DMG=$(ls ./*.dmg | head -n 1)
echo "Raw DMG from create-dmg: $RAW_DMG"
DMG_NAME="PearPass-Desktop-MacOS-${ARCH}-v${VERSION}.dmg"
mv "$RAW_DMG" "$DMG_NAME"
DMG_PATH="$PWD/$DMG_NAME"
echo "Renamed DMG to: $DMG_PATH"
echo "DMG_PATH=$DMG_PATH" >> "$GITHUB_OUTPUT"
- name: Notarize DMG
if: ${{ steps.create_dmg.outputs.DMG_PATH != '' }}
run: |
xcrun notarytool submit "${{ steps.create_dmg.outputs.DMG_PATH }}" --keychain-profile "pearpass" --wait
- name: Staple DMG
if: ${{ steps.create_dmg.outputs.DMG_PATH != '' }}
run: |
xcrun stapler staple "${{ steps.create_dmg.outputs.DMG_PATH }}"
xcrun stapler validate "${{ steps.create_dmg.outputs.DMG_PATH }}"
- uses: actions/upload-artifact@v4
if: ${{ steps.create_dmg.outputs.DMG_PATH != '' }}
with:
name: PearPass-Desktop-MacOS-${{ matrix.arch }}-v${{ needs.version.outputs.version }}.dmg
path: ${{ steps.create_dmg.outputs.DMG_PATH }}
- name: Clean up keychain and provisioning profile
if: ${{ always() }}
run: |
security delete-keychain $RUNNER_TEMP/app-signing.keychain-db
rm ~/Library/MobileDevice/Provisioning\ Profiles/build_pp.provisionprofile
release:
runs-on: ubuntu-latest
permissions:
contents: write
needs: [version, linux, macos]
if: github.ref == 'refs/heads/main'
steps:
- name: Download artifacts
uses: actions/download-artifact@v5
with:
path: artifacts/
merge-multiple: true
- name: Create Release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ needs.version.outputs.version }}
name: PearPass-Desktop-v${{ needs.version.outputs.version }}
files: artifacts/**