🖥️

GithubActionsでNuxt3+Electronアプリを自動ビルドする

2023/05/12に公開

副業で開発したアプリケーションのCI/CDについての備忘録です。
といっても、テストは含まれていませんが…

1.やりたいこと

Nuxt3+Electronで作成したアプリのビルドを自動化したい

開発したアプリはWindows版とmacOS版を提供しており、アプリストアは介さずに野良アプリとしてインストーラーを配布しています。

ポータブル版ではなくインストーラー形式のため、Windows向けにはEXE、macOS向けにはDMG、のインストーラーをそれぞれビルドする必要があります。

開発環境はWindows/macOS共にあるので手動でビルドすることも出来ますが、
せっかくGithubでコードを管理しているのですからGithubActionsを活用しない手はない!
ということで、ビルド&デプロイを自動化したいと考えました。

アプリの自動更新機能を提供したい

よくある「アプリの最新版があります、更新しますか?」的な機能。便利ですよね。
Electronでは標準でautoUpdaterというものが搭載されており、Github Releaseや自前のサーバーなどにアップデート用のファイルを置いておけば、最新版があるかどうかチェックしてユーザーに通知、あるいはそのまま更新ができるとのこと。

macOS版でセキュリティ警告が出ないようにしたい

Catalina以降のmacOSは野良アプリ(AppStore外のアプリ)に対してとても厳しいですね。
コード署名(CodeSign)だけでなく、Appleによる公証(Notarize)を受けていないアプリはGatekeeperが起動をブロックする仕様。まるでウイルスのような扱いを受けます。
システム設定を変えれば起動できるそうですがアプリの利用者様に「システム設定を変えてください」などと言うのは無理があります。

よって、Appleの要求通りコード署名と公証を行います。

署名と公証を行えば、Gatekeeperに起動のお許しが貰えます。
セキュリティのためとはいえ面倒になったものですね…お布施は年額ですし。
ペイできることを祈るばかりです。

2.事前準備(証明書の発行、環境変数の設定)

まずは公証を行うにあたり、いくつか事前準備が必要なことがあります。
これを行わないと、公証は実施できません。

2-1.AppleDeveloperProgram に参加する

公証を行うには、まずAppleDeveloperProgramに参加している必要があります。
年間$99の会費が必要となります。

2-2.DeveloperID証明書を発行する

AppleDeveloperProgramへの参加が完了した後、DeveloperID証明書を発行します。
証明書にはいくつかの種類がありますが、今回は野良アプリに対してコード署名を行うため
Developer ID Applicationを発行します。
証明書の発行は、AppleDeveloperのWebサイト上、またはAppleDeveloper登録済みのAppleIDでログインしているMac上でも発行が可能です。

A.Webサイト上で発行する場合

Web上で発行する場合はAppleDeveloperのWebサイト上で行います。

a.証明書発行ページを開く

「証明書、ID、プロファイル」から「証明書(英語)」を開きます。
ここから先は英語となりますが、特に難しい英語はないと思います。

b.証明書を発行する

Certificates横の「+」ボタンを押下し、Softwareの「Developer ID Application」を選択します。右上の「Continue」ボタンを押せば、証明書が発行されます。

c.証明書をダウンロード&Macへインポートする

発行された証明書をクリックすると「Download Your Certificate」という表示の右側に青い「Download」ボタンがあります。ボタンを押下し、証明書(.cer)をダウンロードします。
ダウンロードした.cerファイルをMac上で開くと、キーチェーンへインポートされます。

Webサイト経由での証明書の発行は以上です。

B.Mac上で発行する場合

証明書の発行は、AppleDeveloper契約済みのAppleIDでログイン中のMac上でも行えます。
証明書の発行はXCodeから行います。

a. XCodeを立ち上げる
b. Settings->AccountsでAppleDeveloper登録済みのAppleIDを追加

c. 右下のManageCertificatesからDeveloper ID Applicationを発行

2-3.証明書をp12形式で出力する

Macでキーチェーンを開き、準備2で発行およびインポートしたDeveloper ID Application証明書をp12形式でエクスポートします。
エクスポート先は自分がわかるところであればどこでも良いです。
ここではデスクトップに書き出します。

なお、作業完了後は不要なので削除したほうが良いと思います。

2-4.p12形式の証明書をGithubActionsのSecretsに登録する

p12形式で書き出した証明書はビルド時にコード署名に使用されます。
ワークフロー内で証明書を使用したいので、先程書き出した証明書をGithubActionsの
Secretsに登録する。

証明書は大切な機密情報ですので、くれぐれも無くさないよう、また外部に漏洩しないように
Secretsに登録します。

2-5.notarize.js を作成する

ビルド過程で公証を行う際に動く公証プログラムを作成します。
ここで作成したnotarize.jsはelectron-builder.ymlの定義に沿ってmacOSのインストーラービルド時に自動で行われます。なお、今回はプロジェクト直下に以下のように作成しました。

notarize.js
const { notarize } = require("@electron/notarize");
require('dotenv').config();

async function notarizing(context) {
  const { electronPlatformName, appOutDir } = context;
  if (electronPlatformName !== "darwin") {
    return;
  }

  const appName = context.packager.appInfo.productFilename;

  return await notarize({
    appBundleId: "com.example.hogefuga", // Your app's bundle identifier
    appPath: `${appOutDir}/${appName}.app`,
    appleId: process.env.APPLE_ID,
    appleIdPassword: process.env.APPLE_ID_PASSWORD,
    ascProvider: process.env.ASC_PROVIDER,
  });
}

exports.default = notarizing;

以上で公証に必要な事前準備は完了です。
以降は事前準備したものを使用しつつ、実際にビルドを行っていきます。

3.完成したワークフロー

お急ぎの方向けに、以下に完成したGithubActionsのワークフローを載せておきます。
以降は、このワークフローの解説等になります。

完成したワークフロー
build.yml
name: auto-build
on:
  push:
    tags:
      - "v*"

jobs:
  build:
    # v*タグが付与されたときに実行する
    strategy:
      matrix:
        os: [windows-latest, macos-latest]
    name: Build ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    steps:
      # リポジトリのチェックアウト
      - name: Checkout
        uses: actions/checkout@v3
      # Node.jsのセットアップ
      - name: Setup Node18
        uses: actions/setup-node@v3
        with:
          node-version: 18
          cache: "npm"
      # パッケージのインストール
      - name: Install Dependencies
        run: npm ci
      # 証明書のインストール
      - name: Install Certificates
        if: matrix.os == 'macos-latest'
        uses: apple-actions/import-codesign-certs@v2
        with:
          p12-file-base64: ${{ secrets.CERTIFICATES_P12 }}
          p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }}
      # Electronのビルド(&公証)
      - name: Build Applicatiton
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          APPLE_ID: ${{ secrets.APPLE_ID }}
          APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
          ASC_PROVIDER: ${{ secrets.ASC_PROVIDER }}
	  # -- その他アプリ内で使用する環境変数 -- #
        run: npm run electron:build
      # ビルドしたファイルをアップロード
      - name: Upload Artifacts
        uses: actions/upload-artifact@v3
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          name: Artifacts
          path: |
            release/*.exe
            release/*.exe.blockmap
            release/*.dmg
            release/*.zip
            release/latest*.yml

3-1.各ステップについて

リポジトリのチェックアウト

build.yml
# リポジトリのチェックアウト
- name: Checkout
  uses: actions/checkout@v3

Node.jsのセットアップ

build.yml
# Node.jsのセットアップ
- name: Setup Node18
  uses: actions/setup-node@v3
    with:
      node-version: 18
      cache: "npm"

Node.jsの環境を構築します。専用のアクションがあるのでそれを使っています。
2023年04月現在、Node.jsのLTSはver.18ですので、 node-version:には18を指定しています。
また、今回はパッケージマネージャーにnpmを使用しているのでキャッシュはnpmを指定しています。

パッケージのインストール

build.yml
# パッケージのインストール
- name: Install Dependencies
  run: npm ci

npm install or npm ci どちらを使うか?問題があるそうですが、 npm ci だとpackage.jsonではなくpackage-lock.jsonを見てパッケージのバージョンを決定するとのこと。
CI環境では基本的にビルド都度環境が構築されるので npm ci を使ってクリーンインストールしてビルドするのが良いという記事も散見されるのでとりあえずinstallではなくciを使います。

証明書のインストール

build.yml
# 証明書のインストール
- name: Install Certificates
  if: matrix.os == 'macos-latest'
  uses: apple-actions/import-codesign-certs@v2
    with:
      p12-file-base64: ${{ secrets.CERTIFICATES_P12 }}
      p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }}

公証を行うために必要な証明書をsecretsから読み込んでインストールしています。
公証はmacOS版の場合のみ実施するステップのためif: matrix.os == 'macos-latest'でWindows実行時はスキップされるようにしていいます。

Electronのビルド(&公証)

build.yml
# Electronのビルド(&公証)
- name: Build Applicatiton
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    APPLE_ID: ${{ secrets.APPLE_ID }}
    APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
    ASC_PROVIDER: ${{ secrets.ASC_PROVIDER }}
    # -- その他アプリ内で使用する環境変数 -- #
  run: npm run electron:build

アプリのビルドと公証を行います。
事前準備で作成したnotarize.jsとelectron-builderのビルド設定ファイル(ここではelectron-builder.yml)をもとにビルドと交渉を行います。
公証の際に必要な情報は環境変数としてsecretsから読み込んでいます。
その他、アプリ内で使用する環境変数(Firebase認証情報等々)もenv:配下に指定することで設定できます。

ビルドしたファイルをアップロード

build.yml
# ビルドしたファイルをアップロード
- name: Upload Artifacts
  uses: actions/upload-artifact@v3
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  with:
    name: Artifacts
    path: |
      release/*.exe
      release/*.exe.blockmap
      release/*.dmg
      release/*.zip
      release/latest*.yml

ビルドしたインストーラーをArtifactsに保存します。
これにより、ジョブ間で共有出来るようになります。

さいごに

ひとまず、これで認証済みのmacOS版のインストーラと、Windows版のインストーラを作るところまではできました。
この後、Artifactsからドラフト版のReleaseを作ったり、それを検証した後にドラフトを解除したらデプロイを開始する…といった動きをしようと思っています。ドラフトが解除されると同時に更新データが利用可能になるのでautoUpdaterでそれを拾ってアップデートしたり…

そういった部分については、別の記事で書こうと思います。
今回はひとまず以上となります。

Discussion