macOS Big SurでSelf Serviceアプリケーションがクラッシュしてしまう問題の暫定対応の自動化
macOS Big Sur、および執筆時点のSelf Serviceの最新バージョンである 10.26.0
において、Self Serviceアプリケーションがクラッシュして起動できない事象が発生しています。
@rotomxさんがmacOS Big SurでJamf ProのSelf Serviceがクラッシュする問題の暫定対応の記事で原因と解決方法を書かれているので、詳しくはそちらを参照してください。
こちらでは解決方法として、Self Serviceアプリケーションがクラッシュしたら~/Library/Application Support/com.jamfsoftware.selfservice.mac/CocoaAppCD.storedata
のファイルを都度消すという方法が取られています。
しかし、都度消すのは面倒ですので、今回はこの作業から解放されるべく対応を自動化する方法を紹介します。
暫定対応の自動化
以下のスクリプトのLAUNCH_AGENT_ID
に任意のID(例えば、io.github.kenchan0130.SelfServiceRecover
)を設定して実行すると暫定対応が完了します。
もちろんこのスクリプトはJamf Pro経由でも使用できます。 スクリプトを登録し、登録したスクリプトをアサインしたポリシーを配布するだけです。
#!/bin/zsh
export PATH=/usr/bin:/bin:/usr/sbin:/sbin
LAUNCH_AGENT_ID="YOUR_AGENT_ID"
DEPLOY_FOR_ALL_USERS=false # You can set 'true' if you want to deploy agent for every users
IS_REMOVE=false
APPLICATION_NAME="Self Service" # If you have changed your Self Service application name, please change this value
THROTTLE_INTERVAL="10" # 10 or more is recommended
# Functions
run_as_user() {
local uid
if [[ "$(whoami)" == "${CURRENT_USER}" ]];then
"$@"
else
uid=$(id -u "${CURRENT_USER}")
launchctl asuser "${uid}" sudo -u "${CURRENT_USER}" "$@"
fi
}
print_info_log(){
local timestamp
timestamp=$(date +%F\ %T)
echo "$timestamp [INFO] $1"
}
print_error_log(){
local timestamp
timestamp=$(date +%F\ %T)
echo "$timestamp [ERROR] $1"
}
# Main
if [[ "${1}" = "/" ]];then
# Jamf uses sends '/' as the first argument
print_info_log "Shifting arguments for Jamf."
shift 3
fi
if "${1}";then
print_info_log "Override 'DEPLOY_FOR_ALL_USERS' as ${1}..."
DEPLOY_FOR_ALL_USERS="${1}"
fi
if [[ "${2}" ]];then
IS_REMOVE=true
fi
if "${DEPLOY_FOR_ALL_USERS}" && [[ "$(whoami)" != "root" ]];then
print_error_log "This script needs to be run with root privileges when 'DEPLOY_FOR_ALL_USERS' is enabled."
exit 1
fi
CURRENT_USER=$(stat -f%Su /dev/console)
LAUNCH_AGENT_PLIST_PATH="/Library/LaunchAgents/${LAUNCH_AGENT_ID}.plist"
if ! "${DEPLOY_FOR_ALL_USERS}";then
if [[ ! "${CURRENT_USER}" ]];then
print_error_log "No one is logged in. Either run it after the user logs in, or set 'DEPLOY_FOR_ALL_USERS' variable of this script to true and distribute it to all users."
exit 1
fi
CURRENT_USER_HOME_DIRECTORY=$(dscl /Local/Default read "/Users/${CURRENT_USER}" NFSHomeDirectory | awk '{print $2}')
# Override LAUNCH_AGENT_PLIST_PATH
LAUNCH_AGENT_PLIST_PATH="${CURRENT_USER_HOME_DIRECTORY}${LAUNCH_AGENT_PLIST_PATH}"
fi
if "${IS_REMOVE}";then
print_info_log "Removing ${LAUNCH_AGENT_ID} agent..."
if [[ ! "${CURRENT_USER}" ]];then
run_as_user launchctl remove "${LAUNCH_AGENT_ID}"
fi
rm -rf "${LAUNCH_AGENT_PLIST_PATH}"
print_info_log "Removed ${LAUNCH_AGENT_ID} agent."
exit 0
fi
print_info_log "Deploying ${LAUNCH_AGENT_PLIST_PATH} file..."
cat > "${LAUNCH_AGENT_PLIST_PATH}" <<XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>${LAUNCH_AGENT_ID}</string>
<key>ProgramArguments</key>
<array>
<string>/bin/sh</string>
<string>-c</string>
<string>ps axc | grep "${APPLICATION_NAME}" || rm -rf "\${HOME}/Library/Application Support/com.jamfsoftware.selfservice.mac/CocoaAppCD.storedata" && sleep "${THROTTLE_INTERVAL}"</string>
</array>
<key>ThrottleInterval</key>
<integer>${THROTTLE_INTERVAL}</integer>
<key>WatchPaths</key>
<array>
<string>~/Library/Application Support/com.jamfsoftware.selfservice.mac/CocoaAppCD.storedata</string>
</array>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
XML
print_info_log "Deployed ${LAUNCH_AGENT_ID} agent."
if ! "${DEPLOY_FOR_ALL_USERS}";then
CURRENT_USER_GROUP=$(stat -f "%Sg" "${CURRENT_USER_HOME_DIRECTORY}")
chown "${CURRENT_USER}:${CURRENT_USER_GROUP}" "${LAUNCH_AGENT_PLIST_PATH}"
fi
if [[ "${CURRENT_USER}" ]];then
run_as_user launchctl unload "${LAUNCH_AGENT_PLIST_PATH}" &>/dev/null
run_as_user launchctl load "${LAUNCH_AGENT_PLIST_PATH}"
print_info_log "Launched ${LAUNCH_AGENT_ID} agent by ${CURRENT_USER}."
fi
exit 0
DEPLOY_FOR_ALL_USERS
変数をtrue
に設定するとすべてのユーザー、false
だと現在のユーザーにのみ適用されます。 コマンドライン経由の場合は、第1引数、Jamf Pro経由の場合は、第4引数がこれに該当します。
もし、Jamf ProサーバーのSelf Serviceの設定のApplication Name
の項目を変更している場合は、APPLICATION_NAME
の変数を変更してください。
暫定対応を外す
この問題が解消したら、暫定対応を外すことが考えられます。 プログラムの内容的にそこまで悪さをするものではないですが、無駄なプロセスは動かしたくはないものです。
暫定対応を外したい場合は、IS_REMOVE
変数をtrue
にして実行します。 コマンドライン経由の場合は、第2引数、Jamf Pro経由の場合は、第5引数がこれに該当します。
スクリプトの解説
これで終わってしまうのは短いので、スクリプトの処理を解説します。
やっていることの概要は、「macOSに搭載されているlaunchd
の機能(Agent)を使用して、原因のファイルが作られたら削除する」です。
Agentの設定ファイルの置き場所を決める
launchd
には、
- Daemon
- OS起動時に、PID 1(つまりroot権限であると考えて良い)の
launchd
によって起動されるプログラム - GUIが使えない
- OS起動時に、PID 1(つまりroot権限であると考えて良い)の
- Agent
- ユーザー権限で起動するlaunchdによって起動されるプログラム
の2つがあります。
今回はroot権限で動作させる必要はないので、Agentを使用します。
また、Agentの設定ファイルは
-
~/Library/LaunchAgents
- 各ユーザーが管理する各ユーザーごとに実行するAgentの設定ファイル置き場
-
/Library/LaunchAgents
- 管理者が管理する各ユーザーごとに実行するAgentの設定ファイル置き場
-
/System/Library/LaunchAgents
- OSが管理する各ユーザーごとに実行するAgentの設定ファイル置き場
- 基本的にはいじらないところ
の3箇所に置かれます。
このスクリプトでは、
LAUNCH_AGENT_PLIST_PATH="/Library/LaunchAgents/${LAUNCH_AGENT_ID}.plist"
if ! "${DEPLOY_FOR_ALL_USERS}";then
if [[ ! "${CURRENT_USER}" ]];then
print_error_log "No one is logged in. Either run it after the user logs in, or set 'DEPLOY_FOR_ALL_USERS' variable of this script to true and distribute it to all users."
exit 1
fi
CURRENT_USER_HOME_DIRECTORY=$(dscl /Local/Default read "/Users/${CURRENT_USER}" NFSHomeDirectory | awk '{print $2}')
LAUNCH_AGENT_PLIST_PATH="${CURRENT_USER_HOME_DIRECTORY}${LAUNCH_AGENT_PLIST_PATH}"
fi
のように、DEPLOY_FOR_ALL_USERS
変数がfalse
の場合は~/Library/LaunchAgents
、true
の場合は/Library/LaunchAgents
にファイルを設置するようにしています。
Agentの設定ファイルを定義
設定ファイルを置く場所が決まったので、次に設定ファイルを定義します。 この設定ファイルはプロパティリスト(plist)で定義されます。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>${LAUNCH_AGENT_ID}</string>
<key>ProgramArguments</key>
<array>
<string>/bin/sh</string>
<string>-c</string>
<string>ps axc | grep "${APPLICATION_NAME}" || rm -rf "\${HOME}/Library/Application Support/com.jamfsoftware.selfservice.mac/CocoaAppCD.storedata" && sleep "${THROTTLE_INTERVAL}"</string>
</array>
<key>ThrottleInterval</key>
<integer>${THROTTLE_INTERVAL}</integer>
<key>WatchPaths</key>
<array>
<string>~/Library/Application Support/com.jamfsoftware.selfservice.mac/CocoaAppCD.storedata</string>
</array>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
WatchPaths
で、~/Library/Application Support/com.jamfsoftware.selfservice.mac/CocoaAppCD.storedata
ファイルの変更を監視しています。
監視に引っかかった場合、ProgramArguments
で定義されている以下のプログラムが起動します。
/bin/sh -c ps axc | grep "設定したアプリケーション名" || rm -rf "${HOME}/Library/Application Support/com.jamfsoftware.selfservice.mac/CocoaAppCD.storedata" && sleep "設定したThrottle Interval"
一見単純そうなスクリプトですが、
/bin/sh -c
ps xc | grep "設定したアプリケーション名" ||
&
sleep "設定したThrottle Interval"
の4つのポイントがあります。
1つ目は、/bin/sh -c
です。
launchd
のProgramArguments
のトップレベルではデフォルトで定義されている環境変数が直接使用できません。 そのため、/bin/sh -c
として、一度shをかますことで、デフォルトで定義されている$HOME
という環境変数を使えるようにしています。
2つ目は、ps xc | grep "設定したアプリケーション名" ||
です。
Self Serviceのアプリケーションが起動中にファイルを消すと、アプリケーションに何かしらの不具合などが出る可能性があるため、Self Serviceのアプリケーションが起動していないときだけ該当のファイルを削除するようにしています。
3つ目は、&
です。
プロパティリストの場合、&
は&
にエスケープしないと期待通りに動作しません。
最後は、sleep "設定したThrottle Interval"
です。
launchd
の特徴として、即座に終了する処理が実行された場合、そのAgentは失敗したととらえられることがあります。 そのため、監視の繰り返し時間であるThrottleInterval
と同じ時間sleep処理を挟むようにしています。
Agentの起動
Agentの設定ファイルの置き場に設定ファイルを設置すると、ユーザーがログインした際に自動でAgentが起動します。
しかし、このスクリプトが実行された際に、すでにユーザーがログインしている場合は、Agentは起動されません。 そのため、
if [[ "${CURRENT_USER}" ]];then
run_as_user launchctl unload "${LAUNCH_AGENT_PLIST_PATH}" &>/dev/null
run_as_user launchctl load "${LAUNCH_AGENT_PLIST_PATH}"
print_info_log "Launched ${LAUNCH_AGENT_ID} agent by ${CURRENT_USER}."
fi
のように、明示的にAgentを起動するようにしています。
終わりに
macOS Big SurでSelf Serviceアプリケーションがクラッシュしてしまう問題の暫定対応の自動化する方法を紹介しました。
もちろんこんなことをぜずに済むに越したことはないのですが、根本はSelf Serviceアプリケーションの問題ですので、Jamf社の対応に期待しましょう。