# [メタ情報] # 識別子: Xserver同期パイプラインの状態回復_exe # システム名: Xserver同期パイプラインの状態回復 # 技術種別: Misc # 機能名: Misc # 使用言語: ShellScript # 状態: 実行用 # [/メタ情報] 要約:このスクリプトは、Dropbox を介した pmedia / mmedia の Xserver 同期が停止・フリーズした場合に自動復旧するための仕組みである。Dropbox が起動していない、またはログ更新が15分以上止まっている場合に再起動を行い、その事実を記録する。あわせて、残留している rsync プロセスを強制終了し、メイン同期用 LaunchAgent を再読み込みして同期を再開させる。Dropbox をこのスクリプト自身が再起動した場合のみ、再起動履歴を保存し、日次(4:00)で前日・当日の再起動があった場合に1通だけメール通知を送信する。通常処理は3:30に自動実行され、詳細なログは専用ディレクトリに記録される。 /Users/xxxxxxxxm1/scripts/sync_xserver_selfheal.sh #!/bin/bash # pmedia / mmedia 同期の自動復旧スクリプト # - pmedia / mmedia 関連の rsync が残っていれば kill # - Dropbox デスクトップの簡易ヘルスチェック(ログ更新停止なら再起動) # - メイン同期 LaunchAgent(com.xxxxxxxxm1.sync_dropbox_xserver_m1)を reload # - Dropboxを「このスクリプトが」再起動した場合のみ、日次(4:00)で1回メール通知 # ※sync_dropbox_xserver_m1.sh 本体は変更しない set -u export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" export LANG=en_US.UTF-8 # ========= 設定 ========= LOG_DIR="$HOME/scripts/logs" SELFHEAL_LOG="$LOG_DIR/sync_xserver_selfheal.log" PLIST="$HOME/Library/LaunchAgents/com.xxxxxxxxm1.sync_dropbox_xserver_m1.plist" # Dropboxログ(存在する範囲で一番新しいものを拾う) DROPBOX_LOG_DIR="$HOME/Library/Logs/Dropbox" DROPBOX_STALE_SEC=$((15 * 60)) # 15分以上ログ更新が止まっていたら怪しい DROPBOX_WAIT_AFTER_RESTART=20 # 再起動後に少し待つ # メール通知 MAIL_TO="bbbbbbbb.yyyyyyyy@gmail.com" MAIL_SUBJECT_PREFIX="[xxxxxxxxm1] Dropbox selfheal report" STATE_RESTART_LOG="$LOG_DIR/dropbox_restart_events.log" # 再起動イベントを追記(時刻が残る) STATE_LAST_REPORT="$LOG_DIR/dropbox_restart_last_report.ts" # 最後に通知した時刻(epoch) mkdir -p "$LOG_DIR" log(){ echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; } # ========= Dropbox: ログ最新ファイル ========= latest_dropbox_log() { if [[ -d "$DROPBOX_LOG_DIR" ]]; then ls -t "$DROPBOX_LOG_DIR"/* 2>/dev/null | head -n 1 fi } dropbox_running() { pgrep -x "Dropbox" >/dev/null 2>&1 && return 0 pgrep -f "/Dropbox.app/" >/dev/null 2>&1 && return 0 return 1 } # ========= メール送信(Mail.app 経由) ========= send_mail_via_mailapp() { local subject="$1" local body="$2" # Mail.app の設定がない環境だと失敗します(その場合はログに残すだけ) /usr/bin/osascript >/dev/null 2>&1 <> "$STATE_RESTART_LOG" } restart_dropbox() { log "🟠 Dropbox 再起動を試行します" /usr/bin/osascript -e 'quit app "Dropbox"' >/dev/null 2>&1 || true sleep 3 pkill -x "Dropbox" >/dev/null 2>&1 || true sleep 2 open -a "Dropbox" || log "⚠ Dropbox.app を open できませんでした" log "🟡 Dropbox 起動後の待機: ${DROPBOX_WAIT_AFTER_RESTART}s" sleep "$DROPBOX_WAIT_AFTER_RESTART" # ★「このスクリプトが再起動した」記録 record_dropbox_restart_event } # ========= Dropboxヘルスチェック(再起動したかどうか返す) ========= # return: # 0 = OK(再起動なし) # 10 = 再起動した dropbox_healthcheck() { log "🔎 Dropbox ヘルスチェック開始" if ! dropbox_running; then log "⚠ Dropbox が起動していないようです" restart_dropbox return 10 fi local lf lf="$(latest_dropbox_log)" if [[ -z "${lf:-}" || ! -f "$lf" ]]; then log "ℹ Dropbox ログが見つかりません($DROPBOX_LOG_DIR)。検出精度は落ちます。" # ログがない環境は「起動しているならOK扱い」 return 0 fi local now mt age now=$(date +%s) mt=$(stat -f %m "$lf" 2>/dev/null || echo 0) age=$((now - mt)) log "ℹ 最新Dropboxログ: $lf(更新から ${age}s)" if (( age > DROPBOX_STALE_SEC )); then log "⚠ Dropbox ログ更新が ${DROPBOX_STALE_SEC}s 以上止まっています → フリーズ疑い" restart_dropbox # 再起動後にもう一度チェック(参考) lf="$(latest_dropbox_log)" if [[ -n "${lf:-}" && -f "$lf" ]]; then mt=$(stat -f %m "$lf" 2>/dev/null || echo 0) now=$(date +%s) age=$((now - mt)) log "✅ 再起動後ログ更新チェック: 更新から ${age}s" fi return 10 else log "✅ Dropbox は概ね正常(ログ更新が動いています)" return 0 fi } # ========= 日次レポート(4:00に呼ぶ想定) ========= # - 前回通知以降に「このスクリプトが再起動した記録」があれば1通だけメール # - なければ送らない daily_report() { log "📧 日次レポート開始(再起動があった日だけ送信)" local now last_ts now=$(date +%s) if [[ -f "$STATE_LAST_REPORT" ]]; then last_ts=$(cat "$STATE_LAST_REPORT" 2>/dev/null || echo 0) else last_ts=0 fi # 今回のレポート時刻を先に保存(失敗しても“1日1回”の抑制が効く) echo "$now" > "$STATE_LAST_REPORT" if [[ ! -f "$STATE_RESTART_LOG" ]]; then log "ℹ 再起動イベントログがまだありません。メール送信なし。" return 0 fi # last_ts以降のイベントだけ抽出(ログ先頭の [YYYY-MM-DD HH:MM:SS] を epoch化しない簡易方式) # → 代わりに「ファイル更新時刻」で近似しつつ、本文には全イベントを載せると管理が楽です。 # ここでは「昨日〜今日の分」を送る実装(過剰送信を避けたい実用寄り)。 local today today="$(date '+%Y-%m-%d')" local yday yday="$(date -v-1d '+%Y-%m-%d')" local events events="$(grep -E "^\[$today|^\[$yday" "$STATE_RESTART_LOG" 2>/dev/null || true)" if [[ -z "${events// /}" ]]; then log "✅ 昨日/今日の再起動イベントが見当たりません。メール送信なし。" return 0 fi local subject body subject="${MAIL_SUBJECT_PREFIX} - restart detected" body=$( cat <> "$SELFHEAL_LOG" fi } # ========= メイン:引数で動作切替 ========= MODE="${1:-selfheal}" if [[ "$MODE" == "report" ]]; then { daily_report } >>"$SELFHEAL_LOG" 2>&1 exit 0 fi # ===== 通常 self-heal ===== { log "===== self-heal START =====" # ★ 0) Dropbox の健全性チェック(先にやる:クラウド→ローカル反映を期待) dropbox_healthcheck || true # 1) pmedia / mmedia 関連の rsync プロセスを探して kill PIDS_P=$(pgrep -f "rsync .*pmedia" || true) PIDS_M=$(pgrep -f "rsync .*mmedia" || true) RSYNC_PIDS="" [[ -n "${PIDS_P:-}" ]] && RSYNC_PIDS="$RSYNC_PIDS $PIDS_P" [[ -n "${PIDS_M:-}" ]] && RSYNC_PIDS="$RSYNC_PIDS $PIDS_M" if [[ -n "${RSYNC_PIDS// /}" ]]; then log "🔍 残留 rsync プロセス検出: $RSYNC_PIDS" for pid in $RSYNC_PIDS; do log "➡ kill $pid" kill "$pid" 2>/dev/null || log "⚠ kill 失敗: $pid" done sleep 2 else log "✅ pmedia/mmedia 関連 rsync 残留なし" fi # 2) メイン同期 LaunchAgent の reload if [[ -f "$PLIST" ]]; then log "🔁 LaunchAgent reload: $PLIST" launchctl unload "$PLIST" 2>/dev/null || log "ℹ unload でエラー (無視可)" launchctl load "$PLIST" || log "⚠ launchctl load でエラー" else log "⚠ メイン同期 plist が見つかりません: $PLIST" fi log "===== self-heal END =====" } >>"$SELFHEAL_LOG" 2>&1 /Users/xxxxxxxxm1/Library/LaunchAgents/com.xxxxxxxxm1.sync_xserver_selfheal.plist Label com.xxxxxxxxm1.sync_xserver_selfheal ProgramArguments /bin/bash /Users/xxxxxxxxm1/scripts/sync_xserver_selfheal.sh StartCalendarInterval Hour 3 Minute 30 StandardOutPath /tmp/sync_xserver_selfheal.out StandardErrorPath /tmp/sync_xserver_selfheal.err /Users/xxxxxxxxm1/Library/LaunchAgents/com.xxxxxxxxm1.sync_xserver_selfheal_report.plist Label com.xxxxxxxxm1.sync_xserver_selfheal_report ProgramArguments /bin/bash /Users/xxxxxxxxm1/scripts/sync_xserver_selfheal.sh report StartCalendarInterval Hour4 Minute0 StandardOutPath /tmp/sync_xserver_selfheal_report.out StandardErrorPath /tmp/sync_xserver_selfheal_report.err