#!/usr/bin/env bash # cron 래퍼 공통 — 프로젝트 루트, PYTHONPATH, 인터프리터 탐색. # shellcheck disable=SC2034 set -euo pipefail _CRON_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CRON_PROJECT_ROOT="$(cd "${_CRON_SCRIPT_DIR}/.." && pwd)" cd "${CRON_PROJECT_ROOT}" export PYTHONPATH=src # 로그 디렉터리 (cron 리다이렉트 전에 mkdir -p 가능) ensure_cron_log_dir() { local dir="$1" mkdir -p "$dir" } # python-dotenv + pandas 등 프로젝트 의존성이 있는 python3 resolve_bithumb_python() { local candidate="" for candidate in \ "${BITHUMB_PYTHON:-}" \ "${HOME}/opt/anaconda3/envs/coin/bin/python3" \ "${HOME}/opt/anaconda3/envs/ncue/bin/python3" \ "${HOME}/miniconda3/envs/xavis/bin/python3" \ "$(command -v python3 2>/dev/null || true)"; do if [ -n "$candidate" ] && [ -x "$candidate" ] \ && "$candidate" -c "import dotenv" 2>/dev/null; then echo "$candidate" return 0 fi done echo "$(date '+%Y-%m-%d %H:%M:%S') [ERROR] python-dotenv 가능한 python3를 찾지 못함 (BITHUMB_PYTHON 설정)" >&2 return 1 } # mkdir 기반 잠금 — stale/hung 프로세스 정리 후 획득 # 사용: acquire_cron_lock LOCKDIR pgrep_pattern MAX_AGE_SEC acquire_cron_lock() { local lockdir="$1" local pgrep_pattern="$2" local max_age_sec="${3:-900}" local now_ts pid elapsed lock_mtime now_ts="$(date +%s)" if [ -d "$lockdir" ]; then lock_mtime="$(stat -f %m "$lockdir" 2>/dev/null || stat -c %Y "$lockdir" 2>/dev/null || echo 0)" elapsed=$((now_ts - lock_mtime)) if pgrep -f "$pgrep_pattern" >/dev/null 2>&1; then if [ "$elapsed" -gt "$max_age_sec" ]; then echo "$(date '+%Y-%m-%d %H:%M:%S') [WARN] hung ${pgrep_pattern} (${elapsed}s) — 종료 후 lock 정리" >&2 pkill -f "$pgrep_pattern" 2>/dev/null || true sleep 1 fi fi if ! pgrep -f "$pgrep_pattern" >/dev/null 2>&1; then echo "$(date '+%Y-%m-%d %H:%M:%S') [WARN] stale lock 정리: ${lockdir}" >&2 rmdir "$lockdir" 2>/dev/null || true fi fi if ! mkdir "$lockdir" 2>/dev/null; then echo "$(date '+%Y-%m-%d %H:%M:%S') [SKIP] ${pgrep_pattern} 실행 중 (lock ${lockdir})" >&2 return 1 fi trap 'rmdir "'"$lockdir"'" 2>/dev/null || true' EXIT INT TERM return 0 }