fix: clean up orphaned running entries, add timeout handling

- Remove initial 'running' history entry (orphans when PHP times out)
- Add purgeStaleRunning() to mark old 'running' entries as 'timeout' on next deploy
- Add set_time_limit(300) to prevent PHP timeout during large ZIP download
- Add timestamps to log file entries for easier debugging
- Include ZIP size and source URL in success detail
- History now keeps last 20 entries (was 12)
- Show dash instead of blank for entries with no detail
- Style 'timeout' status same as 'failed' (red pill)
This commit is contained in:
DevSoftTech Server 2026-08-04 03:59:38 +05:30
parent 3fed2b6740
commit 41f07357d2
2 changed files with 65 additions and 55 deletions

View File

@ -44,92 +44,78 @@ class SystemOperations
public function runUpdateDirect(): array public function runUpdateDirect(): array
{ {
$repoPath = ROOTPATH; $repoPath = ROOTPATH;
$logFile = WRITEPATH . 'system_update.log'; $logFile = WRITEPATH . 'system_update.log';
$startedAt = date('Y-m-d H:i:s');
// Extend PHP time limit for large ZIP download + file sync
$entry = [ @set_time_limit(300);
'timestamp' => $startedAt,
'type' => 'update', // Remove any stale 'running' entries from previous failed attempts
'status' => 'running', $this->purgeStaleRunning();
'message' => 'HMS update started (direct deployment from GitHub)',
];
$this->appendHistory($entry);
try { try {
// GitHub repo details - PUBLIC REPO, no auth needed
$githubOwner = 'infodevsofttech-admin'; $githubOwner = 'infodevsofttech-admin';
$githubRepo = 'devsofttech_hms_ci4'; $githubRepo = 'devsofttech_hms_ci4';
$branch = 'main'; $branch = 'main';
$zipUrl = "https://github.com/{$githubOwner}/{$githubRepo}/archive/refs/heads/{$branch}.zip";
// Download latest ZIP from GitHub $tempDir = sys_get_temp_dir();
$zipUrl = "https://github.com/{$githubOwner}/{$githubRepo}/archive/refs/heads/{$branch}.zip"; $zipFile = $tempDir . '/hms_update_' . time() . '.zip';
$tempDir = sys_get_temp_dir();
$zipFile = $tempDir . '/hms_update_' . time() . '.zip'; file_put_contents($logFile, '[' . date('Y-m-d H:i:s') . "] Downloading: {$zipUrl}\n", FILE_APPEND);
file_put_contents($logFile, "Downloading from: {$zipUrl}\n", FILE_APPEND);
$zipContent = @file_get_contents($zipUrl); $zipContent = @file_get_contents($zipUrl);
if ($zipContent === false) { if ($zipContent === false) {
throw new \Exception('Failed to download from GitHub. Check network connectivity and GitHub availability.'); throw new \Exception('Failed to download from GitHub. Check network connectivity.');
} }
if (!file_put_contents($zipFile, $zipContent)) { if (!file_put_contents($zipFile, $zipContent)) {
throw new \Exception('Failed to write downloaded ZIP to temporary directory.'); throw new \Exception('Failed to write ZIP to temp directory.');
} }
file_put_contents($logFile, "Downloaded ZIP: {$zipFile} (" . filesize($zipFile) . " bytes)\n", FILE_APPEND); $zipSize = number_format(filesize($zipFile) / 1024, 1) . ' KB';
file_put_contents($logFile, '[' . date('Y-m-d H:i:s') . "] Downloaded ZIP: {$zipSize}\n", FILE_APPEND);
// Extract ZIP
$zip = new \ZipArchive(); $zip = new \ZipArchive();
if ($zip->open($zipFile) !== true) { if ($zip->open($zipFile) !== true) {
throw new \Exception('Failed to open downloaded ZIP file.'); throw new \Exception('Failed to open downloaded ZIP file.');
} }
$extractDir = $tempDir . '/hms_extract_' . time(); $extractDir = $tempDir . '/hms_extract_' . time();
if (!mkdir($extractDir)) { if (!mkdir($extractDir)) {
throw new \Exception('Failed to create extraction directory.'); throw new \Exception('Failed to create extraction directory.');
} }
$zip->extractTo($extractDir); $zip->extractTo($extractDir);
$zip->close(); $zip->close();
// Find the extracted directory (GitHub creates a folder like "repo-main")
$dirs = array_diff(scandir($extractDir), ['.', '..']); $dirs = array_diff(scandir($extractDir), ['.', '..']);
if (empty($dirs)) { if (empty($dirs)) {
throw new \Exception('Downloaded ZIP appears to be empty.'); throw new \Exception('Downloaded ZIP appears to be empty.');
} }
$extractedRepoDir = $extractDir . '/' . reset($dirs); $extractedRepoDir = $extractDir . '/' . reset($dirs);
file_put_contents($logFile, "Extracted to: {$extractedRepoDir}\n", FILE_APPEND);
// Sync files (skip git, vendor, and certain local files)
$this->syncFiles($extractedRepoDir, $repoPath, $logFile); $this->syncFiles($extractedRepoDir, $repoPath, $logFile);
// Cleanup
$this->deleteDirectory($extractDir); $this->deleteDirectory($extractDir);
@unlink($zipFile); @unlink($zipFile);
$successMsg = 'HMS updated successfully from GitHub (direct deployment)'; $successMsg = 'HMS updated successfully from GitHub (direct deployment)';
$this->appendHistory([ $this->appendHistory([
'timestamp' => date('Y-m-d H:i:s'), 'timestamp' => date('Y-m-d H:i:s'),
'type' => 'update', 'type' => 'update',
'status' => 'success', 'status' => 'success',
'message' => $successMsg, 'message' => $successMsg,
'detail' => 'Files synced from ' . $zipUrl, 'detail' => "Downloaded: {$zipSize}\nSource: {$zipUrl}\nCompleted: " . date('Y-m-d H:i:s'),
]); ]);
file_put_contents($logFile, "SUCCESS: {$successMsg}\n", FILE_APPEND); file_put_contents($logFile, '[' . date('Y-m-d H:i:s') . "] SUCCESS: {$successMsg}\n", FILE_APPEND);
return ['ok' => true, 'message' => $successMsg]; return ['ok' => true, 'message' => $successMsg];
} catch (\Throwable $e) { } catch (\Throwable $e) {
$errorMsg = 'Update failed: ' . $e->getMessage(); $errorMsg = $e->getMessage();
$this->appendHistory([ $this->appendHistory([
'timestamp' => date('Y-m-d H:i:s'), 'timestamp' => date('Y-m-d H:i:s'),
'type' => 'update', 'type' => 'update',
'status' => 'failed', 'status' => 'failed',
'message' => 'HMS update failed', 'message' => 'HMS update failed',
'detail' => $errorMsg, 'detail' => $errorMsg,
]); ]);
file_put_contents($logFile, "FAILED: {$errorMsg}\n", FILE_APPEND); file_put_contents($logFile, '[' . date('Y-m-d H:i:s') . "] FAILED: {$errorMsg}\n", FILE_APPEND);
return ['ok' => false, 'message' => $errorMsg]; return ['ok' => false, 'message' => $errorMsg];
} }
} }
@ -412,7 +398,28 @@ class SystemOperations
} }
$data = json_decode($json, true); $data = json_decode($json, true);
return is_array($data) ? array_slice($data, -12) : []; return is_array($data) ? array_slice($data, -20) : [];
}
private function purgeStaleRunning(): void
{
if (!file_exists($this->historyPath)) return;
$json = @file_get_contents($this->historyPath);
$data = is_string($json) ? json_decode($json, true) : [];
if (!is_array($data)) return;
// Mark any existing 'running' entries as 'timeout' so they don't pile up
$changed = false;
foreach ($data as &$entry) {
if (($entry['status'] ?? '') === 'running') {
$entry['status'] = 'timeout';
$entry['detail'] = 'Process did not complete (PHP timeout or server error).';
$changed = true;
}
}
unset($entry);
if ($changed) {
file_put_contents($this->historyPath, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
} }
private function appendHistory(array $entry): void private function appendHistory(array $entry): void

View File

@ -273,14 +273,17 @@
<td><?= esc((string) ($entry['type'] ?? '')) ?></td> <td><?= esc((string) ($entry['type'] ?? '')) ?></td>
<td> <td>
<?php $st = (string) ($entry['status'] ?? ''); ?> <?php $st = (string) ($entry['status'] ?? ''); ?>
<span class="ops-pill <?= $st === 'success' ? 'success' : ($st === 'failed' ? 'danger' : 'warning') ?>"><?= esc($st) ?></span> <span class="ops-pill <?= match($st) { 'success' => 'success', 'failed' => 'danger', 'timeout' => 'danger', default => 'warning' } ?>"><?= esc($st) ?></span>
</td> </td>
<td><?= esc((string) ($entry['message'] ?? '')) ?></td> <td><?= esc((string) ($entry['message'] ?? '')) ?></td>
<td> <td>
<?php if (!empty($entry['detail'])) : ?> <?php $det = (string)($entry['detail'] ?? ''); ?>
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="modal" data-bs-target="#detailModal" data-detail="<?= esc((string) ($entry['detail'] ?? '')) ?>" title="Show details"> <?php if ($det !== ''): ?>
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="modal" data-bs-target="#detailModal" data-detail="<?= esc($det) ?>" title="Show details">
<i class="bi bi-info-circle"></i> <i class="bi bi-info-circle"></i>
</button> </button>
<?php else: ?>
<span class="text-muted small">—</span>
<?php endif; ?> <?php endif; ?>
</td> </td>
</tr> </tr>