diff --git a/src/app/api/deployments/[uuid]/route.ts b/src/app/api/deployments/[uuid]/route.ts index e1a31e7..ff5cc25 100644 --- a/src/app/api/deployments/[uuid]/route.ts +++ b/src/app/api/deployments/[uuid]/route.ts @@ -1,6 +1,8 @@ import { NextResponse } from 'next/server'; const IS_PRODUCTION = process.env.NODE_ENV === 'production'; +// Internal API endpoint for production (served by Python script on NUC host) +const DEPLOYMENTS_API_URL = 'http://192.168.1.3:9876/deployments'; export async function GET( request: Request, @@ -9,12 +11,28 @@ export async function GET( const { uuid } = await params; try { - const { exec } = await import('child_process'); - const { promisify } = await import('util'); - const execAsync = promisify(exec); + let deployment: Record; - // PHP code to fetch single deployment with logs - const phpCode = ` + if (IS_PRODUCTION) { + // In production, use internal HTTP API served by coolify-api.py on NUC host + const response = await fetch(`${DEPLOYMENTS_API_URL}/${uuid}`, { + cache: 'no-store', + signal: AbortSignal.timeout(30000), + }); + + if (!response.ok) { + throw new Error(`Deployments API error: ${response.status}`); + } + + deployment = await response.json(); + } else { + // In development, use SSH to call docker exec on NUC + const { exec } = await import('child_process'); + const { promisify } = await import('util'); + const execAsync = promisify(exec); + + // PHP code to fetch single deployment with logs + const phpCode = ` $d = \\App\\Models\\ApplicationDeploymentQueue::with('application') ->where('deployment_uuid', '${uuid}') ->first(); @@ -39,45 +57,40 @@ echo json_encode([ ]); `; - const base64Code = Buffer.from(phpCode).toString('base64'); + const base64Code = Buffer.from(phpCode).toString('base64'); + const command = `ssh nuc "echo '${base64Code}' | base64 -d | docker exec -i coolify php artisan tinker"`; - let command: string; - if (IS_PRODUCTION) { - command = `echo '${base64Code}' | base64 -d | docker exec -i coolify php artisan tinker`; - } else { - command = `ssh nuc "echo '${base64Code}' | base64 -d | docker exec -i coolify php artisan tinker"`; - } + const { stdout } = await execAsync(command, { + maxBuffer: 10 * 1024 * 1024, + timeout: 30000, + }); - const { stdout } = await execAsync(command, { - maxBuffer: 10 * 1024 * 1024, - timeout: 30000, - }); + // Parse output - find JSON object in tinker output + const lines = stdout.split('\n'); + let jsonStr = ''; - // Parse output - find JSON object in tinker output - const lines = stdout.split('\n'); - let jsonStr = ''; + for (const line of lines) { + let cleaned = line; + if (cleaned.startsWith('. ')) { + cleaned = cleaned.substring(2); + } else if (cleaned.startsWith('> ')) { + continue; + } - for (const line of lines) { - let cleaned = line; - if (cleaned.startsWith('. ')) { - cleaned = cleaned.substring(2); - } else if (cleaned.startsWith('> ')) { - continue; + const trimmed = cleaned.trim(); + if (trimmed.startsWith('{')) { + jsonStr = trimmed; + break; + } } - const trimmed = cleaned.trim(); - if (trimmed.startsWith('{')) { - jsonStr = trimmed; - break; + if (!jsonStr) { + throw new Error('No JSON output found'); } - } - if (!jsonStr) { - throw new Error('No JSON output found'); + deployment = JSON.parse(jsonStr); } - const deployment = JSON.parse(jsonStr); - if (deployment.error) { return NextResponse.json({ error: deployment.error }, { status: 404 }); } diff --git a/src/app/api/deployments/route.ts b/src/app/api/deployments/route.ts index ba3752d..3c138b2 100644 --- a/src/app/api/deployments/route.ts +++ b/src/app/api/deployments/route.ts @@ -2,14 +2,31 @@ import { NextResponse } from 'next/server'; import type { Deployment, DeploymentStatus } from '@/lib/deployments'; const IS_PRODUCTION = process.env.NODE_ENV === 'production'; +// Internal API endpoint for production (served by Python script on NUC host) +const DEPLOYMENTS_API_URL = 'http://192.168.1.3:9876/deployments'; async function fetchDeploymentsFromCoolify(): Promise { - const { exec } = await import('child_process'); - const { promisify } = await import('util'); - const execAsync = promisify(exec); + let rawDeployments: Array>; - // Base64 encode the PHP code to avoid escaping issues - const phpCode = ` + if (IS_PRODUCTION) { + // In production, use internal HTTP API served by coolify-api.py on NUC host + const response = await fetch(DEPLOYMENTS_API_URL, { + cache: 'no-store', + signal: AbortSignal.timeout(30000), + }); + + if (!response.ok) { + throw new Error(`Deployments API error: ${response.status}`); + } + + rawDeployments = await response.json(); + } else { + // In development, use SSH to call docker exec on NUC + const { exec } = await import('child_process'); + const { promisify } = await import('util'); + const execAsync = promisify(exec); + + const phpCode = ` $deployments = \\App\\Models\\ApplicationDeploymentQueue::with('application') ->orderBy('created_at', 'desc') ->limit(50) @@ -33,51 +50,41 @@ $result = $deployments->map(function($d) { echo json_encode($result->toArray()); `; - const base64Code = Buffer.from(phpCode).toString('base64'); + const base64Code = Buffer.from(phpCode).toString('base64'); + const command = `ssh nuc "echo '${base64Code}' | base64 -d | docker exec -i coolify php artisan tinker"`; - let command: string; - if (IS_PRODUCTION) { - // Running on NUC - direct docker exec - command = `echo '${base64Code}' | base64 -d | docker exec -i coolify php artisan tinker`; - } else { - // Running locally - SSH to NUC - command = `ssh nuc "echo '${base64Code}' | base64 -d | docker exec -i coolify php artisan tinker"`; - } + const { stdout } = await execAsync(command, { + maxBuffer: 10 * 1024 * 1024, + timeout: 30000, + }); - const { stdout } = await execAsync(command, { - maxBuffer: 10 * 1024 * 1024, // 10MB buffer - timeout: 30000, // 30 second timeout - }); + // Parse tinker output to find JSON + const lines = stdout.split('\n'); + let jsonStr = ''; - // The output contains tinker prompts (lines starting with > or .) followed by JSON - // Tinker outputs ". " prefix on continuation lines and the result - const lines = stdout.split('\n'); - let jsonStr = ''; + for (const line of lines) { + let cleaned = line; + if (cleaned.startsWith('. ')) { + cleaned = cleaned.substring(2); + } else if (cleaned.startsWith('> ')) { + continue; + } - for (const line of lines) { - // Remove tinker prompt prefixes - let cleaned = line; - if (cleaned.startsWith('. ')) { - cleaned = cleaned.substring(2); - } else if (cleaned.startsWith('> ')) { - continue; // Skip command echo lines + const trimmed = cleaned.trim(); + if (trimmed.startsWith('[{') || trimmed.startsWith('[{"') || trimmed === '[]') { + jsonStr = trimmed; + break; + } } - const trimmed = cleaned.trim(); - // Look for line starting with [{ which indicates JSON array of objects - if (trimmed.startsWith('[{') || trimmed.startsWith('[{"') || trimmed === '[]') { - jsonStr = trimmed; - break; + if (!jsonStr) { + console.error('Raw output:', stdout.substring(0, 1000)); + throw new Error('No JSON output found in tinker response'); } - } - if (!jsonStr) { - console.error('Raw output:', stdout.substring(0, 1000)); - throw new Error('No JSON output found in tinker response'); + rawDeployments = JSON.parse(jsonStr); } - const rawDeployments = JSON.parse(jsonStr); - // Track latest deployment per application for "Current" badge const latestByApp = new Map();