File: //bigscoots/wpo/extras/future_posts_checker.sh
#!/usr/bin/env bash
set -euo pipefail
# Require domain argument
if [[ -z "${1:-}" ]]; then
echo "Usage: $0 <domain.com>"
exit 1
fi
DOMAIN="$1"
WP_PATH="/home/nginx/domains/${DOMAIN}/public"
if [[ ! -d "$WP_PATH" ]]; then
echo "Error: Directory $WP_PATH does not exist."
exit 1
fi
cd "$WP_PATH"
# shellcheck source=/dev/null
source "/bigscoots/includes/common.sh"
LOG_FILE="/root/.bigscoots/logs/wpcron/${DOMAIN}_future_publish.log"
STATE_FILE="/root/.bigscoots/logs/wpcron/${DOMAIN}_slack.state"
mkdir -p "$(dirname "$LOG_FILE")"
log() {
echo "$(date +"%Y-%m-%d %H:%M:%S") | $1" >> "$LOG_FILE"
}
log "--- START RUN ---"
### 1. RUN AND LOG DUE HOOKS ###
# Using native WP-CLI count format
DUE_COUNT=$(wpcli cron event list --hook=publish_future_post --next_run_relative=now --format=count 2>/dev/null || echo "0")
if [[ "$DUE_COUNT" -gt 0 ]]; then
log "Found $DUE_COUNT due publish_future_post hook(s). Executing with FULL environment..."
wp cron event run publish_future_post --due-now --allow-root --debug 2>&1 | while read -r line; do
if [[ "$line" == *"debug"* ]] || [[ "$line" == *"Error"* ]] || [[ "$line" == *"Success"* ]] || [[ "$line" == *"Exception"* ]] || [[ "$line" == *"Notice"* ]]; then
log "[WP-DEBUG] $line"
fi
done
log "Hook execution complete."
else
log "No publish_future_post hooks due in wp-cron."
fi
### 2. DETECT OVERDUE 'FUTURE' POSTS ###
CURRENT_GMT=$(date -u +"%Y-%m-%d %H:%M:%S")
CURRENT_TS=$(date +%s)
# The gsub(/"/, "", $2) strips the double quotes from WP-CLI's CSV output so math works properly
OVERDUE_IDS=$(wpcli post list --post_status=future --fields=ID,post_date_gmt --format=csv 2>/dev/null | awk -F, -v now="$CURRENT_GMT" 'NR>1 { gsub(/"/, "", $2); if ($2 < now) print $1 }' | paste -sd, - || true)
if [[ "$OVERDUE_IDS" == "" || "$OVERDUE_IDS" == $'\n' ]]; then
OVERDUE_IDS=""
fi
if [[ -n "$OVERDUE_IDS" ]]; then
log "[CRITICAL] Detected overdue future post(s) stuck in DB. IDs: $OVERDUE_IDS"
LAST_ALERT_TS=0
THREAD_ID=""
if [[ -f "$STATE_FILE" ]]; then
LAST_ALERT_TS=$(grep "^LAST_ALERT_TS=" "$STATE_FILE" | cut -d= -f2 || echo "0")
THREAD_ID=$(grep "^THREAD_ID=" "$STATE_FILE" | cut -d= -f2 || echo "")
fi
TIME_SINCE_LAST=$(( CURRENT_TS - LAST_ALERT_TS ))
if (( TIME_SINCE_LAST < 1800 )); then
log "Slack alert suppressed. Last alert was $(( TIME_SINCE_LAST / 60 )) minutes ago."
else
if [[ -z "$THREAD_ID" ]]; then
INITIAL_MSG=":rotating_light: [${DOMAIN}] Overdue future post(s) detected!"
NEW_THREAD_ID=$(send_slack_initial "$INITIAL_MSG" "wpo-publish-fail")
if [[ -n "$NEW_THREAD_ID" ]]; then
echo "LAST_ALERT_TS=${CURRENT_TS}" > "$STATE_FILE"
echo "THREAD_ID=${NEW_THREAD_ID}" >> "$STATE_FILE"
THREAD_MSG="*Stuck Post IDs:* ${OVERDUE_IDS}\n*Log File:* \`${LOG_FILE}\`"
send_slack_thread "$NEW_THREAD_ID" "$THREAD_MSG" "wpo-publish-fail"
if [[ "$DUE_COUNT" -gt 0 ]]; then
LOG_SNIPPET=$(tail -n 20 "$LOG_FILE")
send_slack_thread "$NEW_THREAD_ID" "Recent Debug Output:\n\`\`\`${LOG_SNIPPET}\`\`\`" "wpo-publish-fail"
else
send_slack_thread "$NEW_THREAD_ID" "_No cron hooks were executed this run to generate debug output._" "wpo-publish-fail"
fi
fi
else
echo "LAST_ALERT_TS=${CURRENT_TS}" > "$STATE_FILE"
echo "THREAD_ID=${THREAD_ID}" >> "$STATE_FILE"
THREAD_MSG=":warning: *Ongoing Issue:* Posts are STILL stuck 30+ minutes later. IDs: ${OVERDUE_IDS}"
send_slack_thread "$THREAD_ID" "$THREAD_MSG" "wpo-publish-fail"
if [[ "$DUE_COUNT" -gt 0 ]]; then
LOG_SNIPPET=$(tail -n 20 "$LOG_FILE")
send_slack_thread "$THREAD_ID" "Recent Debug Output:\n\`\`\`${LOG_SNIPPET}\`\`\`" "wpo-publish-fail"
fi
fi
fi
else
log "No overdue future posts found in the database."
if [[ -f "$STATE_FILE" ]]; then
rm -f "$STATE_FILE"
log "[RESOLVED] Overdue posts cleared. Slack state reset."
fi
fi
log "--- END RUN ---"