# [メタ情報] # 識別子: メディアおよびJSONをXserverへ更新および自動復帰_exe # システム名: 未分類 # 技術種別: Misc # 機能名: Misc # 使用言語: Shell Script # 状態: 公開用 # [/メタ情報] 要約: M1 MacからXserverへのDropboxデータ同期システムは、主に以下のスクリプトとLaunchAgentで構成されています。 `sync_dropbox_xserver_m1.sh`は、Dropbox内のpmedia、mmedia、メタデータをrsyncでXserverの指定ディレクトリへ60秒間隔で自動同期します。このスクリプトは、必要な環境変数の厳格なチェック、SSHキーの検証、Homebrew版rsyncの優先使用、空JSONファイルの同期スキップなど、安全性を重視した設計です。 システムの健全性を維持するため、`sync_xserver_selfheal.sh`が自動復旧機能を提供します。このスクリプトは毎日3:30に実行され、残留しているrsyncプロセスを強制終了し、Dropboxデスクトップアプリのフリーズ(ログ更新が15分以上停止)を検知するとアプリを再起動します。その後、メインの同期LaunchAgentをリロードし、タスクを再始動させます。 さらに、selfhealスクリプトがDropboxを再起動した場合、毎日4:00に管理者へメールで通知するレポート機能も備えています。 全てのスクリプトはmacOSのLaunchAgentによって自動実行され、`.env`ファイルから環境変数を安全にロードし、不足している場合は実行を停止するなど、堅牢な運用がなされています。 <任意のフォルダパス>/sync_dropbox_xserver_m1.sh ``` #!/bin/bash # ===== LaunchAgent 用:PATH を明示的に設定 ===== export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" export LANG=en_US.UTF-8 # ===== 1. 金庫(.env)のロードとMeisterの環境差を自動吸収 ===== if [ -f "$HOME/python_scripts/.env" ]; then export $(grep -v '^#' "$HOME/python_scripts/.env" | xargs) fi # ===== 2. 鉄壁の自律停止ガード(安全な起動時チェック) ===== REQUIRED_VARS=("LOCAL_DROPBOX_BASE" "LOCAL_META_DIR" "XS_USER" "XS_HOST" "XS_SSH_PORT" "SSH_KEY_PATH" "XSERVER_PMEDIA_DIR" "XSERVER_MMEDIA_DIR" "XSERVER_DEST_DIR") MISSING_VARS=() for var in "${REQUIRED_VARS[@]}"; do if [ -z "${!var}" ]; then MISSING_VARS+=("$var") fi done if [ ${#MISSING_VARS[@]} -ne 0 ]; then echo "❌ [ERROR] 必要な環境変数が .env に設定されていません: ${MISSING_VARS[*]}" >> /tmp/sync_dropbox_trace.log exit 1 fi # SSHキーパスの「~」記号を絶対パスへ強制展開 SSH_KEY_PATH="${SSH_KEY_PATH/#\~/$HOME}" if [ ! -f "$SSH_KEY_PATH" ]; then echo "❌ [ERROR] SSHキーファイルが物理的に存在しません: $SSH_KEY_PATH" >> /tmp/sync_dropbox_trace.log exit 1 fi # ===== rsync: Homebrew 版を優先的に特定 ===== if [ -x /opt/homebrew/bin/rsync ]; then RSYNC="/opt/homebrew/bin/rsync" else RSYNC="/usr/bin/rsync" fi # ===== ログディレクトリ ===== LOG_DIR="$HOME/scripts/logs" mkdir -p "$LOG_DIR" # ---- /tmp にトレース(launchd 実行確認) ---- TS="$(date '+%Y-%m-%d %H:%M:%S')" echo "[$TS] sync_dropbox_xserver_m1.sh START (pid=$$, user=$(id -un))" >> /tmp/sync_dropbox_trace.log echo "[$TS] ==== sync_dropbox_xserver_m1.sh START ====" >> "$LOG_DIR/sync_dropbox_debug.log" # --------------------------------------------------------- # 1) pmedia → Xserver: wp-content/pmedia # --------------------------------------------------------- echo "[$(date '+%Y-%m-%d %H:%M:%S')] rsync pmedia START" >> "$LOG_DIR/sync_dropbox_debug.log" "$RSYNC" -av --iconv=UTF-8-MAC,UTF-8 \ --delete --perms --chmod=Da+rx,Fa+r \ -e "ssh -i $SSH_KEY_PATH -p $XS_SSH_PORT -o StrictHostKeyChecking=accept-new" \ --exclude='@esDir' \ "$LOCAL_DROPBOX_BASE/pmedia/" \ "$XS_USER@$XS_HOST:$XSERVER_PMEDIA_DIR/" \ >> "$LOG_DIR/rsync_pmedia.log" 2>>"$LOG_DIR/rsync_pmedia_error.log" P_RET=$? echo "[$(date '+%Y-%m-%d %H:%M:%S')] rsync pmedia END (exit=$P_RET)" >> "$LOG_DIR/sync_dropbox_debug.log" sleep 1 # --------------------------------------------------------- # 2) mmedia → Xserver: wp-content/mmedia # --------------------------------------------------------- echo "[$(date '+%Y-%m-%d %H:%M:%S')] rsync mmedia START" >> "$LOG_DIR/sync_dropbox_debug.log" "$RSYNC" -av --iconv=UTF-8-MAC,UTF-8 \ --delete --perms --chmod=Da+rx,Fa+r \ -e "ssh -i $SSH_KEY_PATH -p $XS_SSH_PORT -o StrictHostKeyChecking=accept-new" \ --exclude='@esDir' \ "$LOCAL_DROPBOX_BASE/mmedia/" \ "$XS_USER@$XS_HOST:$XSERVER_MMEDIA_DIR/" \ >> "$LOG_DIR/rsync_mmedia.log" 2>>"$LOG_DIR/rsync_mmedia_error.log" M_RET=$? echo "[$(date '+%Y-%m-%d %H:%M:%S')] rsync mmedia END (exit=$M_RET)" >> "$LOG_DIR/sync_dropbox_debug.log" sleep 1 # --------------------------------------------------------- # 3) meta_gd_wp_data → Xserver: _secure/wp_json # --------------------------------------------------------- echo "[$(date '+%Y-%m-%d %H:%M:%S')] rsync meta_gd_wp_data START" >> "$LOG_DIR/sync_dropbox_debug.log" # ★ 修正:JSONが空(0バイト)の場合は同期をスキップする安全装置 if [ ! -s "$LOCAL_META_DIR/dropbox_wp_library.json" ]; then echo "⚠️ JSONファイルが空(0バイト)のため、転送をスキップします。" >> "$LOG_DIR/sync_dropbox_debug.log" else "$RSYNC" -av \ --delete --perms --chmod=Da+rx,Fa+r \ -e "ssh -i $SSH_KEY_PATH -p $XS_SSH_PORT -o StrictHostKeyChecking=accept-new" \ "$LOCAL_META_DIR/" \ "$XS_USER@$XS_HOST:$XSERVER_DEST_DIR/" \ >> "$LOG_DIR/rsync_meta_gd_wp_data.log" 2>>"$LOG_DIR/rsync_meta_gd_wp_data_error.log" fi J_RET=$? echo "[$(date '+%Y-%m-%d %H:%M:%S')] rsync meta_gd_wp_data END (exit=$J_RET)" >> "$LOG_DIR/sync_dropbox_debug.log" exit 0 ``` <任意のフォルダパス>/com.XXXXXX.sync_dropbox_xserver_m1.plist ``` Label com.XXXXXX.sync_dropbox_xserver_m1 ProgramArguments /bin/bash <任意のフォルダパス>/sync_dropbox_xserver_m1.sh RunAtLoad StartInterval 60 StandardOutPath /tmp/sync_dropbox_xserver_m1.out StandardErrorPath /tmp/sync_dropbox_xserver_m1.err ``` 上と合わせて、毎朝3:30にrsyncの残留をチェックして、 残っていればkill unload loadする。 <任意のフォルダパス>/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の環境差を自動吸収 ===== if [ -f "$HOME/python_scripts/.env" ]; then export $(grep -v '^#' "$HOME/python_scripts/.env" | xargs) fi # ===== 2. 鉄壁の自律停止ガード(安全な起動時チェック) ===== REQUIRED_VARS=("MAIL_TO" "XS_USER" "XS_HOST") MISSING_VARS=() for var in "${REQUIRED_VARS[@]}"; do if [ -z "${!var}" ]; then MISSING_VARS+=("$var") fi done if [ ${#MISSING_VARS[@]} -ne 0 ]; then echo "❌ [ERROR] 必要な環境変数が .env に設定されていません: ${MISSING_VARS[*]}" exit 1 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_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) # ログディレクトリ作成(既存の sync_dropbox_xserver_m1.sh と同じ場所) mkdir -p "$LOG_DIR" # ========= ログ出力関数 ========= 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)。検出精度は落ちます。" 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") else last_ts=$((now - 30 * 3600)) # なければ約30時間前 fi # スナップショット時刻を書き込み echo "$now" > "$STATE_LAST_REPORT" if [[ ! -f "$STATE_RESTART_LOG" ]]; then log "✅ 再起動履歴ファイルが存在しません -> 終了" return 0 fi # 履歴ファイルから「前回の通知以降に起きた再起動」をフィルタ local filter_since events filter_since=$(date -r "$last_ts" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || date -jf '%s' "$last_ts" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "") log "🔎 フィルタ基準時刻: $filter_since" events=$( while read -r line; do if [[ "$line" =~ ^\[([0-9]{4}-[0-9]{2}-[0-9]{2}\ [0-9]{2}:[0-9]{2}:[0-9]{2})\] ]]; then local dt_str="${BASH_REMATCH[1]}" 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 ``` <任意のフォルダパス>/com.XXXXXX.sync_xserver_selfheal.plist ``` Label com.XXXXXX.sync_xserver_selfheal ProgramArguments /bin/bash <任意のフォルダパス>/sync_xserver_selfheal.sh StartCalendarInterval Hour 3 Minute 30 StandardOutPath /tmp/sync_xserver_selfheal.out StandardErrorPath /tmp/sync_xserver_selfheal.err ``` <任意のフォルダパス>/com.XXXXXX.sync_xserver_selfheal_report.plist ``` Label com.XXXXXX.sync_xserver_selfheal_report ProgramArguments /bin/bash <任意のフォルダパス>/sync_xserver_selfheal.sh report StartCalendarInterval Hour 4 Minute 0 StandardOutPath /tmp/sync_xserver_selfheal_report.out StandardErrorPath /tmp/sync_xserver_selfheal_report.err ```