fix(mcp v2.4.3): attach stack containers to coolify proxy network
The Twenty (and any service-template) stack was reachable on its private project network but invisible to coolify-proxy/Traefik because no container was joined to the `coolify` network. Public URLs like crm.mark.vibnai.com returned 503 "no available server" even though the underlying app was healthy. Coolify's UI deploy attaches the proxy network as a post-step after the full stack is up. When a sidecar (e.g. Twenty's worker, which waits ~3 min on twenty's healthcheck) fails its depends_on gate, that post-step can be skipped and the stack is left isolated. composeUp now calls attachToCoolifyProxyNetwork() after compose finishes (best-effort, idempotent), and ensureServiceUp does the same on the Coolify-queue happy path. Single apps.create call should now result in a publicly reachable app. Made-with: Cursor
This commit is contained in:
@@ -29,7 +29,12 @@ import { VIBN_GCS_LOCATION } from '@/lib/gcp/storage';
|
||||
import { getApplicationRuntimeLogs } from '@/lib/coolify-logs';
|
||||
import { execInCoolifyApp } from '@/lib/coolify-exec';
|
||||
import { isCoolifySshConfigured, runOnCoolifyHost } from '@/lib/coolify-ssh';
|
||||
import { composeUp, composePs, type ResourceKind } from '@/lib/coolify-compose';
|
||||
import {
|
||||
composeUp,
|
||||
composePs,
|
||||
attachToCoolifyProxyNetwork,
|
||||
type ResourceKind,
|
||||
} from '@/lib/coolify-compose';
|
||||
import { listContainersForApp } from '@/lib/coolify-containers';
|
||||
import {
|
||||
deployApplication,
|
||||
@@ -86,7 +91,7 @@ const GITEA_API_URL = process.env.GITEA_API_URL ?? 'https://git.vibnai.com';
|
||||
export async function GET() {
|
||||
return NextResponse.json({
|
||||
name: 'vibn-mcp',
|
||||
version: '2.4.2',
|
||||
version: '2.4.3',
|
||||
authentication: {
|
||||
scheme: 'Bearer',
|
||||
tokenPrefix: 'vibn_sk_',
|
||||
@@ -1262,6 +1267,12 @@ async function ensureServiceUp(uuid: string): Promise<{
|
||||
{ timeoutMs: 8_000 },
|
||||
);
|
||||
if (probe.stdout.trim().length > 0) {
|
||||
// Coolify started the stack. Even on this happy path we still
|
||||
// need to ensure the proxy-network attachment ran, since
|
||||
// Coolify only attaches at the end of its full deploy
|
||||
// pipeline (which can be skipped if a sidecar fails to come
|
||||
// up). Idempotent — already-attached containers are no-ops.
|
||||
await attachToCoolifyProxyNetwork(uuid).catch(() => { /* swallow */ });
|
||||
return { started: true, startMethod: 'coolify-queue', diag: '' };
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -1303,7 +1314,10 @@ async function ensureServiceUp(uuid: string): Promise<{
|
||||
// Something IS running — partial success. Surface the diag so
|
||||
// agents see WHY compose returned non-zero (usually a sidecar
|
||||
// depends_on timeout) but report started=true so happy-path
|
||||
// workflows don't panic.
|
||||
// workflows don't panic. composeUp already attached the proxy
|
||||
// network, but call once more to cover any container that came
|
||||
// up after the initial attach pass.
|
||||
await attachToCoolifyProxyNetwork(uuid).catch(() => { /* swallow */ });
|
||||
return { started: true, startMethod: 'compose-up', diag: composeDiag };
|
||||
}
|
||||
} catch { /* fall through */ }
|
||||
|
||||
@@ -82,13 +82,52 @@ async function composeRun(
|
||||
* Idempotent — Compose already-running containers are no-op'd.
|
||||
* Returns the raw SSH result so callers can surface diagnostics on
|
||||
* failure (most common: image-pull errors, port conflicts).
|
||||
*
|
||||
* After compose succeeds we also attach every stack container to the
|
||||
* `coolify` proxy network. Coolify's UI-driven deploy does this as a
|
||||
* post-step so Traefik can route public traffic to the container, but
|
||||
* the rendered compose file only declares the service-private network.
|
||||
* If we skip this step the stack runs fine on its own bridge but
|
||||
* `crm.mark.vibnai.com` returns "no available server" from Traefik.
|
||||
*/
|
||||
export async function composeUp(
|
||||
kind: ResourceKind,
|
||||
uuid: string,
|
||||
opts: { timeoutMs?: number } = {},
|
||||
): Promise<CoolifySshResult> {
|
||||
return composeRun(kind, uuid, ['up', '-d', '--remove-orphans'], opts);
|
||||
const r = await composeRun(kind, uuid, ['up', '-d', '--remove-orphans'], opts);
|
||||
// Best-effort: attach to the proxy network even if compose returned
|
||||
// non-zero (sidecar `depends_on` timeouts still leave primary
|
||||
// containers running, and we want them reachable).
|
||||
await attachToCoolifyProxyNetwork(uuid).catch(() => { /* swallow */ });
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach every container belonging to this Coolify resource to the
|
||||
* `coolify` proxy network. Idempotent — `network connect` errors when
|
||||
* the container is already attached, which we ignore.
|
||||
*/
|
||||
export async function attachToCoolifyProxyNetwork(
|
||||
uuid: string,
|
||||
): Promise<void> {
|
||||
// List containers on the resource's project network. Coolify names
|
||||
// the bridge network after the resource UUID, so all stack members
|
||||
// are reachable through it.
|
||||
const ls = await runOnCoolifyHost(
|
||||
`docker ps --filter network=${uuid} --format '{{.Names}}'`,
|
||||
{ timeoutMs: 10_000 },
|
||||
);
|
||||
const names = ls.stdout
|
||||
.split('\n')
|
||||
.map(s => s.trim())
|
||||
.filter(Boolean);
|
||||
if (names.length === 0) return;
|
||||
// Attach each one. `|| true` so already-connected returns 0.
|
||||
const attaches = names.map(n =>
|
||||
`docker network connect coolify ${sq(n)} 2>/dev/null || true`,
|
||||
).join(' && ');
|
||||
await runOnCoolifyHost(attaches, { timeoutMs: 30_000 });
|
||||
}
|
||||
|
||||
/** `docker compose down` — stops + removes containers; volumes preserved. */
|
||||
|
||||
Reference in New Issue
Block a user