Commit 154429fe authored by platyhouse's avatar platyhouse

# 프로젝트 문서 및 유틸리티 스크립트 추가

## 문서화

CLAUDE.md: 프로젝트 가이드 문서 개선
- ptyCliLog 사용법 섹션 추가 (인스턴스 메소드 사용법, 올바른/잘못된 사용 예시)
- ptyLibrary_PHP 구조 설명 업데이트 (ptyCliLog를 인스턴스 메소드로 명시)
- 유틸리티 스크립트 섹션 추가 (ptyRun, ptyCronBuild 사용법 및 예제)

## 유틸리티 스크립트

ptyCronBuild: 크론탭 자동 등록 스크립트 추가
- 다양한 스케줄 옵션 지원 (daily, daily-5min, daily-10min, daily-30min, daily-hour)
- 기존 크론탭 항목 자동 감지 및 업데이트
- MD5 해시 기반 마커로 중복 방지
- 임시 파일을 통한 안전한 크론탭 설치
- ptyCliLog를 활용한 상세 로그 출력

ptyRun: 스크립트 중복 실행 제어 스크립트 추가
- 중복 실행 차단 기능 (run-duplicate 옵션)
- 최대 실행 시간 설정 및 타임아웃 관리
- 프로세스 포크를 통한 비동기 실행
- 락 파일 및 PID 파일 기반 실행 상태 관리
- 타임아웃 시 SIGTERM/SIGKILL을 통한 강제 종료 및 재실행
parent e4108146
...@@ -90,13 +90,43 @@ try { ...@@ -90,13 +90,43 @@ try {
- `0`: 정상 종료 (--help 포함) - `0`: 정상 종료 (--help 포함)
- `1`: 에러 또는 필수 인자 누락 - `1`: 에러 또는 필수 인자 누락
## ptyCliLog 사용법
`ptyCliLog`**인스턴스 메소드**를 사용하는 클래스입니다. 정적 메소드가 아니므로 반드시 인스턴스를 생성해서 사용해야 합니다.
### 올바른 사용법
```php
// 헬퍼 함수로 인스턴스 생성
$log = ptyCliLog("APP", ptyCliLog::COLOR_CYAN, true);
// 인스턴스 메소드 호출
$log->info("정보 메시지");
$log->success("성공 메시지");
$log->warning("경고 메시지");
$log->error("에러 메시지");
```
### 잘못된 사용법 (금지)
```php
// ❌ 정적 메소드처럼 호출하면 에러 발생
ptyCliLog::info("메시지"); // Fatal error!
```
### 파라미터
- `$prefix`: 로그 prefix (예: "APP", "RUN", "CRON")
- `$color`: 로그 색상 (예: `ptyCliLog::COLOR_CYAN`)
- `$debug`: 디버그 모드 (기본값: true)
## ptyLibrary_PHP 구조 ## ptyLibrary_PHP 구조
``` ```
ptyLibrary_PHP/ ptyLibrary_PHP/
├── cli/ ├── cli/
│ ├── ptyCliOptionParser.php # CLI 옵션 파서 │ ├── ptyCliOptionParser.php # CLI 옵션 파서
│ ├── ptyCliLog.php # 컬러 로깅 │ ├── ptyCliLog.php # 컬러 로깅 (인스턴스 메소드)
│ └── ptyCliColor.php # ANSI 색상 코드 │ └── ptyCliColor.php # ANSI 색상 코드
├── elastic/ ├── elastic/
│ ├── ptyElasticConfig.php # Elasticsearch 설정 로더 │ ├── ptyElasticConfig.php # Elasticsearch 설정 로더
...@@ -132,3 +162,56 @@ password="your_password" ...@@ -132,3 +162,56 @@ password="your_password"
database=your_db database=your_db
charset=utf8mb4 charset=utf8mb4
``` ```
## 유틸리티 스크립트
### ptyRun - 스크립트 중복 실행 제어
스크립트의 중복 실행을 제어하고 타임아웃을 관리하는 래퍼 스크립트입니다.
```bash
# 기본 사용 (중복 실행 차단)
ptyRun "php /path/to/script.php"
# 중복 실행 허용
ptyRun "php script.php" --run-duplicate=true
# 최대 실행 시간 설정 (60분)
ptyRun "php script.php" --max-run-time=60
```
**옵션:**
- `--run-duplicate=true|false`: 중복 실행 허용 여부 (기본값: **false**)
- `--max-run-time=분`: 최대 실행 시간(분), 초과 시 강제 종료 후 재실행 (기본값: 0 - 무제한)
### ptyCronBuild - 크론탭 자동 등록
스크립트를 크론탭에 자동으로 등록하는 유틸리티입니다.
```bash
# 5분마다 실행
ptyCronBuild "ptyRun 'php /path/to/script.php' --max-run-time=60" --daily-5min
# 매일 0시 실행
ptyCronBuild "php /path/to/backup.php" --daily
# 1시간마다 실행
ptyCronBuild "php script.php" --daily-hour
# 10분마다 실행하되, 10분 내에 한 번만 실행 (중복 실행 차단 + 최대 10분 타임아웃)
# ptyRun의 기본 동작(--run-duplicate=false)으로 10분 내에 이미 실행 중이면 스킵
# --max-run-time=600으로 10분 초과 시 강제 종료 후 재실행
ptyCronBuild 'ptyRun "cd /home/redmine/git; php git_update.php" --max-run-time=600' --daily-10min
```
**스케줄 옵션:**
- `--daily`: 매일 0시에 실행
- `--daily-5min`: 매일 5분마다 실행
- `--daily-10min`: 매일 10분마다 실행
- `--daily-30min`: 매일 30분마다 실행
- `--daily-hour`: 매일 1시간마다 실행
**ptyRun과 함께 사용 시:**
- `ptyRun`의 기본값(`--run-duplicate=false`)으로 중복 실행이 자동 차단됨
- `--max-run-time`으로 최대 실행 시간 설정 시, 해당 시간 초과 시 프로세스를 강제 종료하고 재실행
- 예: `--daily-10min``--max-run-time=600`을 함께 사용하면 10분마다 크론이 실행되지만, 이전 실행이 아직 진행 중이면 스킵하고, 10분 이상 실행 중이면 강제 종료 후 재실행
#!/usr/bin/env php
<?php
/**
* ptyCronBuild - 크론탭 스케줄 자동 설정
*
* Usage: ./ptyCronBuild "스크립트명" [옵션]
*
* 스케줄 옵션 (하나만 선택):
* --daily 매일 0시에 실행
* --daily-5min 매일 5분마다 실행
* --daily-10min 매일 10분마다 실행
* --daily-30min 매일 30분마다 실행
* --daily-hour 매일 1시간마다 실행
* --help 도움말 출력
*
* 예제:
* ./ptyCronBuild "php /path/to/script.php" --daily-5min
* ./ptyCronBuild "/var/lib/backup.sh" --daily
*/
namespace platyFramework;
require_once __DIR__ . '/ptyLibrary_PHP/cli/ptyCliOptionParser.php';
require_once __DIR__ . '/ptyLibrary_PHP/cli/ptyCliLog.php';
// 인자 파싱
$parsed = ptyCliOptionParser::parse($argv);
$positionalArgs = $parsed['positional'];
$options = $parsed['options'];
// 도움말
if (empty($positionalArgs) || isset($options['help'])) {
echo "사용법: {$argv[0]} \"스크립트명\" [옵션]\n";
echo "\n";
echo "스케줄 옵션 (하나만 선택):\n";
echo " --daily 매일 0시에 실행\n";
echo " --daily-5min 매일 5분마다 실행\n";
echo " --daily-10min 매일 10분마다 실행\n";
echo " --daily-30min 매일 30분마다 실행\n";
echo " --daily-hour 매일 1시간마다 실행\n";
echo " --help 도움말 출력\n";
echo "\n";
echo "예제:\n";
echo " {$argv[0]} \"php /path/to/script.php\" --daily-5min\n";
echo " {$argv[0]} \"/var/lib/backup.sh\" --daily\n";
echo " {$argv[0]} \"php script.php\" --daily-hour\n";
echo "\n";
exit(isset($options['help']) ? 0 : 1);
}
$scriptCommand = $positionalArgs[0];
// 로거 인스턴스 생성
$log = ptyCliLog("CRON", ptyCliLog::COLOR_MAGENTA, true);
// 스케줄 타입 확인
$scheduleType = null;
$cronExpression = null;
if (isset($options['daily'])) {
$scheduleType = 'daily';
$cronExpression = '0 0 * * *';
} elseif (isset($options['daily-5min'])) {
$scheduleType = 'daily-5min';
$cronExpression = '*/5 * * * *';
} elseif (isset($options['daily-10min'])) {
$scheduleType = 'daily-10min';
$cronExpression = '*/10 * * * *';
} elseif (isset($options['daily-30min'])) {
$scheduleType = 'daily-30min';
$cronExpression = '*/30 * * * *';
} elseif (isset($options['daily-hour'])) {
$scheduleType = 'daily-hour';
$cronExpression = '0 * * * *';
} else {
$log->error("스케줄 옵션을 지정해야 합니다. (--daily, --daily-5min, --daily-10min, --daily-30min, --daily-hour)");
exit(1);
}
try {
$log->info("크론탭 설정 시작...");
$log->info("스케줄 타입: {$scheduleType}");
$log->info("크론 표현식: {$cronExpression}");
$log->info("스크립트: {$scriptCommand}");
// 현재 크론탭 읽기
exec('crontab -l 2>/dev/null', $currentCron, $returnCode);
// 크론탭이 없는 경우 빈 배열로 초기화
if ($returnCode !== 0) {
$currentCron = [];
}
// 같은 스크립트가 이미 등록되어 있는지 확인
$marker = "# ptyCronBuild: " . md5($scriptCommand);
$found = false;
$newCron = [];
foreach ($currentCron as $line) {
if (strpos($line, $marker) !== false) {
$found = true;
$log->warning("기존 크론탭 항목을 찾았습니다. 업데이트합니다.");
// 다음 줄(실제 크론 명령)을 건너뛰기 위한 플래그
$skipNext = true;
continue;
}
if (isset($skipNext) && $skipNext) {
$skipNext = false;
continue;
}
$newCron[] = $line;
}
// 새로운 크론 항목 추가
$newCron[] = $marker;
$newCron[] = "{$cronExpression} {$scriptCommand}";
// 임시 파일에 크론탭 작성
$tmpFile = tempnam(sys_get_temp_dir(), 'cron_');
file_put_contents($tmpFile, implode("\n", $newCron) . "\n");
// 크론탭 설치
exec("crontab {$tmpFile}", $output, $returnCode);
unlink($tmpFile);
if ($returnCode !== 0) {
throw new \Exception("크론탭 설치 실패");
}
if ($found) {
$log->success("크론탭 항목이 업데이트되었습니다.");
} else {
$log->success("크론탭 항목이 추가되었습니다.");
}
// 등록된 크론탭 확인
$log->info("\n현재 크론탭:");
exec('crontab -l', $finalCron);
foreach ($finalCron as $line) {
if (!empty(trim($line))) {
echo " {$line}\n";
}
}
} catch (\Exception $e) {
$log->error("Error: " . $e->getMessage());
exit(1);
}
#!/usr/bin/env php
<?php
/**
* ptyRun - 스크립트 중복 실행 제어 및 타임아웃 관리
*
* Usage: ./ptyRun "스크립트명" [옵션]
*
* 옵션:
* --run-duplicate=true|false 중복 실행 허용 여부 (기본값: false)
* --max-run-time=분 최대 실행 시간(분), 초과 시 강제 종료 후 재실행 (기본값: 0 - 무제한)
* --help 도움말 출력
*
* 예제:
* ./ptyRun "php /path/to/script.php" --max-run-time=20
*/
namespace platyFramework;
require_once __DIR__ . '/ptyLibrary_PHP/cli/ptyCliOptionParser.php';
require_once __DIR__ . '/ptyLibrary_PHP/cli/ptyCliLog.php';
// 인자 파싱
$parsed = ptyCliOptionParser::parse($argv);
$positionalArgs = $parsed['positional'];
$options = $parsed['options'];
// 도움말
if (empty($positionalArgs) || isset($options['help'])) {
echo "사용법: {$argv[0]} \"스크립트명\" [옵션]\n";
echo "\n";
echo "옵션:\n";
echo " --run-duplicate=true|false 중복 실행 허용 여부 (기본값: false)\n";
echo " --max-run-time=분 최대 실행 시간(분), 초과 시 강제 종료 후 재실행 (기본값: 0 - 무제한)\n";
echo " --help 도움말 출력\n";
echo "\n";
echo "예제:\n";
echo " {$argv[0]} \"php /path/to/script.php\" --max-run-time=20\n";
echo " {$argv[0]} \"php script.php\" --run-duplicate=true\n";
echo " {$argv[0]} \"./myScript.sh\"\n";
echo "\n";
exit(isset($options['help']) ? 0 : 1);
}
$scriptCommand = $positionalArgs[0];
$runDuplicate = isset($options['run-duplicate']) && $options['run-duplicate'] === 'true';
$maxRunTime = isset($options['max-run-time']) ? (int)$options['max-run-time'] : 0;
// 로거 인스턴스 생성
$log = ptyCliLog("RUN", ptyCliLog::COLOR_CYAN, true);
// 락 파일명 생성 (스크립트 경로를 해시로 변환)
$lockFile = '/tmp/ptyRun_' . md5($scriptCommand) . '.lock';
$pidFile = '/tmp/ptyRun_' . md5($scriptCommand) . '.pid';
try {
// 중복 실행 체크
if (!$runDuplicate) {
if (file_exists($lockFile)) {
$lockData = json_decode(file_get_contents($lockFile), true);
$pid = $lockData['pid'] ?? 0;
$startTime = $lockData['start_time'] ?? 0;
// 프로세스가 실제로 실행 중인지 확인
if ($pid && posix_kill($pid, 0)) {
// 최대 실행 시간 체크
if ($maxRunTime > 0 && $startTime > 0) {
$elapsedMinutes = (time() - $startTime) / 60;
if ($elapsedMinutes > $maxRunTime) {
$log->warning("최대 실행 시간({$maxRunTime}분) 초과. 프로세스(PID: {$pid})를 강제 종료합니다.");
posix_kill($pid, SIGTERM);
sleep(2);
// SIGTERM으로 종료되지 않으면 SIGKILL
if (posix_kill($pid, 0)) {
posix_kill($pid, SIGKILL);
sleep(1);
}
@unlink($lockFile);
@unlink($pidFile);
} else {
$log->info("스크립트가 이미 실행 중입니다. (PID: {$pid}, 경과: " . round($elapsedMinutes, 1) . "분)");
exit(0);
}
} else {
$log->info("스크립트가 이미 실행 중입니다. (PID: {$pid})");
exit(0);
}
} else {
// 프로세스가 없으면 락 파일 제거
@unlink($lockFile);
@unlink($pidFile);
}
}
}
// 스크립트 실행
$log->info("스크립트 실행: {$scriptCommand}");
$pid = pcntl_fork();
if ($pid == -1) {
throw new \Exception("프로세스 fork 실패");
} elseif ($pid == 0) {
// 자식 프로세스
exec($scriptCommand, $output, $returnCode);
exit($returnCode);
} else {
// 부모 프로세스
if (!$runDuplicate) {
// 락 파일 생성
$lockData = [
'pid' => $pid,
'command' => $scriptCommand,
'start_time' => time(),
];
file_put_contents($lockFile, json_encode($lockData));
file_put_contents($pidFile, $pid);
}
// 자식 프로세스 대기
$status = 0;
pcntl_waitpid($pid, $status);
$exitCode = pcntl_wexitstatus($status);
// 락 파일 제거
if (!$runDuplicate) {
@unlink($lockFile);
@unlink($pidFile);
}
if ($exitCode === 0) {
$log->success("스크립트 실행 완료");
} else {
$log->error("스크립트 실행 실패 (Exit Code: {$exitCode})");
}
exit($exitCode);
}
} catch (\Exception $e) {
$log->error("Error: " . $e->getMessage());
@unlink($lockFile);
@unlink($pidFile);
exit(1);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment