Files
vibn-frontend/app/api/workspaces/[slug]/apps/[uuid]/logs/route.ts
Mark Henderson d86f2bea03 feat(mcp): apps.logs — compose-aware runtime logs
Adds apps.logs MCP tool + session REST endpoint for tailing runtime
container logs. Unblocks cold-start debugging for agent-deployed
compose apps (Twenty, Cal.com, Plane, etc.) where Coolify's own
/applications/{uuid}/logs endpoint returns empty.

Architecture:
  - dockerfile / nixpacks / static apps → Coolify's REST logs API
  - dockercompose apps                  → SSH into Coolify host,
                                          `docker logs` per service

New SSH path uses a dedicated `vibn-logs` user (docker group, no
sudo, no pty, no port-forwarding, single ed25519 key). Private key
lives in COOLIFY_SSH_PRIVATE_KEY_B64 on the vibn-frontend Coolify
app; authorized_key is installed by scripts/setup-vibn-logs-user.sh
on the Coolify host.

Tool shape:
  params:   { uuid, service?, lines? (default 200, max 5000) }
  returns:  { uuid, buildPack, source: 'coolify_api'|'ssh_docker'|'empty',
              services: { [name]: { container, lines, bytes, logs, status? } },
              warnings: string[], truncated: boolean }

Made-with: Cursor
2026-04-23 13:21:52 -07:00

50 lines
1.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* GET /api/workspaces/[slug]/apps/[uuid]/logs
*
* Runtime logs for a Coolify app. Compose-aware: Coolify's REST API
* for non-compose build packs, SSH into the Coolify host for per-
* service `docker logs` on compose apps.
*
* Query params:
* lines tail lines per container (default 200, max 5000)
* service limit to one compose service (optional)
*/
import { NextResponse } from 'next/server';
import { requireWorkspacePrincipal } from '@/lib/auth/workspace-auth';
import { getApplicationInProject, TenantError } from '@/lib/coolify';
import { getApplicationRuntimeLogs } from '@/lib/coolify-logs';
export async function GET(
request: Request,
{ params }: { params: Promise<{ slug: string; uuid: string }> }
) {
const { slug, uuid } = await params;
const principal = await requireWorkspacePrincipal(request, { targetSlug: slug });
if (principal instanceof NextResponse) return principal;
const ws = principal.workspace;
if (!ws.coolify_project_uuid) {
return NextResponse.json({ error: 'Workspace has no Coolify project yet' }, { status: 503 });
}
const url = new URL(request.url);
const linesRaw = Number(url.searchParams.get('lines') ?? '200');
const lines = Number.isFinite(linesRaw) ? linesRaw : 200;
const service = url.searchParams.get('service') ?? undefined;
try {
await getApplicationInProject(uuid, ws.coolify_project_uuid);
const result = await getApplicationRuntimeLogs(uuid, { lines, service });
return NextResponse.json(result);
} catch (err) {
if (err instanceof TenantError) {
return NextResponse.json({ error: err.message }, { status: 403 });
}
return NextResponse.json(
{ error: 'Failed to fetch runtime logs', details: err instanceof Error ? err.message : String(err) },
{ status: 502 }
);
}
}