後付けのApple Notarization Service（Appleの公証）対応を、シェルスクリプトでまとめて行ってみました

# 概要
- ターミナルでコマンドを打ち込み各アプリにそれぞれ公証を行う…というのが面倒なので、以下の通り一括で処理できるようなシェルスクリプトを作成しました。

# 背景
- macOS10.15よりAppleの公証（Apple Notarization Service）を受けていないアプリケーションは、起動に制限がかかるようになりました。
- Xcode内ビルドが完結する場合には`Archive`を`Organizer`でAppleへ公証することができるので、特に考えることも少なくて済みます。
- しかし既にビルドしてしまったアプリや、ビルドした後に`Resource`内のファイルを操作する場合、別途Appleの公証を行う必要があります。

# 参考
- [新しいLinuxの教科書](https://amzn.to/39HHC8a)
    - シェルスクリプトの作成の際に参考にしました。
    - ほぼ知識ゼロの状態から数日で、下記のようなシェルスクリプトが書けるようになる良本です！
- [リリース済みのアプリのApple Notarization Service \(Notary Service\) 対応](https://www.rk-k.com/archives/3458)
    - 使用したコマンドの詳細はこちらを参照下さい。

# 作成したシェルスクリプト

```sh
#!/bin/bash
# 指定したフォルダ内のファイルを公証するためのスクリプト

# 定義 
primary_bundle_id="jp.co.xxx.xxx" # 適当な文字列で良い？
developer_email="xxx@gmail.com" # 開発者メールアドレス

# Appleの公証
notary_applications ()
{
  xcrun altool --notarize-app \
  --primary-bundle-id "${1}" \
  --username "${2}" \
  --password "@keychain:AC_PASSWORD" \
  --file "${3}"
  
  if [ $? -ne 0 ]; then
    echo "[Error]apple notarization service error" 1>&2
    exit 4
  fi
}

# 公証情報をファイルに紐付ける
staple_notaryinfo () 
{
  local application_path=$1
  
  # 公証完了までwaitする（zipのアップロードから公証完了まで時間がかかるため）
  # 5s * 120 = 10m
  echo "waiting for completing notarization(${application_path})..."
  local number_of_try=120
  for try_count in $(seq 0 $number_of_try)
  do
    # 規定の回数を終えて尚エラーならば終了する  
    if [ $try_count -eq $number_of_try ]; then
      echo "[Error]staple notarization information error" 1>&2
      exit 4
    fi 
      
    xcrun stapler staple "${application_path}" > /dev/null  # stapleの試行
    
    if [ $? -eq 0 ]; then # 成功した時点で終了する
      echo ""
      break
    else      
      sleep 5s
    fi    
  done
}

#############
# Entry Point
#############
# 引数の例
# $1:公証対象を入れたフォルダへの相対パス。
folder_path=$1

# フォルダの存在確認
if [ ! -d "${folder_path}" ]; then
  echo "${0}: ${1}: No such folder" 1>&2
  exit 1
fi 

# 公証のためのzip化
zip_name="${folder_path%%/}.zip"
zip -r "${zip_name}" "${folder_path}" > /dev/null
  if [ $? -ne 0 ]; then
    echo "[Error]zip error" 1>&2
    exit 3
  fi

# 公証を行う
notary_applications "${primary_bundle_id}" "${developer_email}" "${zip_name}"

# オフラインの公証のため、ファイル毎にStapleを行う
if [ -d "${folder_path}" ]; then
  # 内部フィールド区切り文字を改行のみに指定
  _IFS=$IFS
  IFS=$'\n'
  for file_name in $(ls ${folder_path})
  do
    # 公証情報をファイルに紐付ける（オフライン用）
    staple_notaryinfo "${folder_path}${file_name}"
  done
  IFS=$_IFS
fi 
```

# 実行例
## 必要な準備
- 公証可能な条件でプログラムをビルドする
    - （私の環境で作業が必要なのは以下でした。その他の条件は自動で設定されるものと思います。）
    - Hardened Runtimeが有効になっている
    - 開発者IDで署名していること
        - ビルド後に`Resource`内を操作した場合はメタデータのリセットと再署名をしておく
- アプリパスワードを発行しキーチェーンに登録しておく
- シェルスクリプトと公証対象（`sample.app``sample2.app`）を含んだフォルダを下記の位置関係で配置します

![-w166](https://i.imgur.com/78fj39J.jpg)

- 呼び出しは`cd`でシェルスクリプトの存在するフォルダに移動してから、以下のように行います。
- 絶対パスの指定すると例えば`zip`の際に深い階層になってしまうので、正常に動作するか怪しいです。
- 正常に動作しているかどうか気になる場合は適宜`1>&2`を外して出力を確認すると良いです。

## スクリプトの実行

```sh
cd /Users/.../sample/
./notarization.sh FilesForNotarization/
（実行結果）
No errors uploading 'FilesForNotarization.zip'.
RequestUUID = xxx-xxx-xxx-xxx-xxxxxxxxxx
waiting for completing notarization(FilesForNotarization/sample.app)...

waiting for completing notarization(FilesForNotarization/sample2.app)...
```

## 備考
- Stapleを成功するまで繰り返すよりも、RequestUUIDを記憶しておいて、Successとなるまで確認するのが本筋かもしれません。
    - 参考：[Electronアプリをコード署名してApple 公証 \(Notary\) を通過させる方法 \| uechi\.io](https://uechi.io/blog/sign-and-notarize-electron-app)

```sh
xcrun altool --notarization-info xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -u xxxxx@xxx.co.jp -p "@keychain:AC_PASSWORD"
（実行結果）
No errors getting notarization info.

   RequestUUID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 
          Date: 2020-01-10 00:09:50 +0000
        Status: success
    LogFileURL: https://osxapps-ssl.itunes.apple.com/itunes-assets/Enigma113/v4/...
   Status Code: 0
Status Message: Package Approved
```