# [メタ情報] # 識別子: Xserver同期パイプラインの状態回復_exe # システム名: Xserver同期パイプラインの状態回復 # 技術種別: Misc # 機能名: Misc # 使用言語: ShellScript # 状態: 実行用 # [/メタ情報] 要約:このスクリプトは、Dropbox を介した pmedia / mmedia の Xserver 同期が停止・フリーズした場合に自動復旧するための仕組みである。Dropbox が起動していない、またはログ更新が15分以上止まっている場合に再起動を行い、その事実を記録する。あわせて、残留している rsync プロセスを強制終了し、メイン同期用 LaunchAgent を再読み込みして同期を再開させる。Dropbox をこのスクリプト自身が再起動した場合のみ、再起動履歴を保存し、日次(4:00)で前日・当日の再起動があった場合に1通だけメール通知を送信する。通常処理は3:30に自動実行され、詳細なログは専用ディレクトリに記録される。 /Users/XXXXXX/scripts/sync_xserver_selfheal.sh ``` #!/bin/bash # pmedia / mmedia 同期の自動復旧スクリプト # - pmedia / mmedia 関連の rsync が残っていれば kill # - Dropbox デスクトップの簡易ヘルスチェック(ログ更新停止なら再起動) # - メイン同期 LaunchAgent(com.XXXXXX.sync_dropbox_xserver_m1)を reload # - Dropboxを「このスクリプトが」再起動した場合のみ、日次(4:00)で1回メール通知 # ※sync_dropbox_xserver_m1.sh 本体は変更しない set -u # ===== 1. 金庫(.env)のロードとMeister 1 / 2 の環境差を自動吸収 ===== if [ -f "$HOME/python_scripts/.env" ]; then export $(grep -v '^#' "$HOME/python_scripts/.env" | xargs) fi 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.XXXXXX.sync_dropbox_xserver_m1.plist" # Dropboxログ(存在する範囲で一番新しいものを拾う) DROPBOX_LOG_DIR="$HOME/Library/Logs/Dropbox" DROPBOX_STALE_SEC=$((15 * 60)) # 15分以上ログ更新が止まっていたら怪しい DROPBOX_WAIT_AFTER_RESTART=20 # 再起動後に少し待つ # メール通知(共通環境変数 .env から安全に動的ロード。未設定時は既存値に自動フォールバック) MAIL_TO="${MAIL_TO:-[ご自身のメールアドレス]}" MAIL_SUBJECT_PREFIX="[XXXXXX] Dropbox selfheal report" STATE_RESTART_LOG="$LOG_DIR/dropbox_restart_events.log" # 再起動イベントを追記(時刻が残る) STATE_LAST_REPORT="$LOG_DIR/dropbox_restart_last_report.ts" # 最後に通知した時刻(epoch) # ========= ログ出力関数 ========= log() { local ts ts="$(date '+%Y-%m-%d %H:%M:%S')" echo "[$ts] $1" } # ========= Dropboxの稼働状態チェック ========= dropbox_running() { pgrep -x "Dropbox" >/dev/null 2>&1 } # ========= 最新Dropboxログファイルの特定 ========= latest_dropbox_log() { local l_file l_file=$(ls -t "$DROPBOX_LOG_DIR"/*.log 2>/dev/null | head -n 1) echo "${l_file:-}" } # ========= メール送信処理(Mail.app連携、タイムアウト設定) ========= send_mail_via_mailapp() { local subj body subj="$1" body="$2" log "📧 Mail.app 経由でメール送信を試みます... (to: $MAIL_TO)" /usr/bin/osascript <> "$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 # 過去24〜30時間程度のイベントを収集(昨日の分も漏らさないため) local filter_since events filter_since=\$(date -v-30H +%Y%m%d%H%M%S 2>/dev/null || date -d '30 hours ago' +%Y%m%d%H%M%S 2>/dev/null || echo 0) # ログから期間内の行を抽出(YYYY-MM-DD HH:MM:SS -> YYYYMMDDHHMMSS 比較) events=\$( while read -r line; do if [[ "\$line" =~ \\[([0-9]{4})-([0-9]{2})-([0-9]{2})[[:space:]]([0-9]{2}):([0-9]{2}):([0-9]{2})\\] ]]; then local dt_str="\${BASH_REMATCH[1]}\${BASH_REMATCH[2]}\${BASH_REMATCH[3]}\${BASH_REMATCH[4]}\${BASH_REMATCH[5]}\${BASH_REMATCH[6]}" if (( dt_str >= filter_since )); then echo "\$line" fi fi done < "\$STATE_RESTART_LOG" ) if [[ -z "\${events// /}" ]]; then log "✅ 直近30時間以内の再起動イベントはありません -> 終了" 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="" if [[ -n "\$PIDS_P" ]]; then RSYNC_PIDS="\$RSYNC_PIDS \$PIDS_P" fi if [[ -n "\$PIDS_M" ]]; then RSYNC_PIDS="\$RSYNC_PIDS \$PIDS_M" fi 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/XXXXXX/Library/LaunchAgents/com.XXXXXX.sync_xserver_selfheal.plist ``` Label com.XXXXXX.sync_xserver_selfheal ProgramArguments /bin/bash /Users/XXXXXX/scripts/sync_xserver_selfheal.sh StartCalendarInterval Hour 3 Minute 30 StandardOutPath /tmp/sync_xserver_selfheal.out StandardErrorPath /tmp/sync_xserver_selfheal.err ``` /Users/XXXXXX/Library/LaunchAgents/com.XXXXXX.sync_xserver_selfheal_report.plist ``` Label com.XXXXXX.sync_xserver_selfheal_report ProgramArguments /bin/bash /Users/XXXXXX/scripts/sync_xserver_selfheal.sh report StartCalendarInterval Hour 4 Minute 0 StandardOutPath /tmp/sync_xserver_selfheal_report.out StandardErrorPath /tmp/sync_xserver_selfheal_report.err ```