Files
vibn-agent-runner/vibn-frontend/scripts/fetch-app-logs-ssh.mjs
mawkone eb198e2d4d ship: project dashboard pages + sidebar/chat overhaul + log tooling
Ships accumulated WIP that was sitting uncommitted:
- New (home) dashboard route pages: overview, code, data/tables, hosting,
  infrastructure, services, domains, integrations, agents, analytics, api,
  automations, billing, logs, market, marketing(+seo/social), product, security,
  storage, users, settings(app/auth).
- dashboard-sidebar, project-icon-rail, chat-panel updates; mcp + anatomy route
  changes; package.json/lock dependency bumps.
- Coolify log tooling (scripts/fetch-app-logs.mjs + fetch-app-logs-ssh.mjs) and
  ai-new-thread.md "Fetching Production Logs" section.

Excludes throwaway debug scripts and telemetry audit dumps (the latter contain
live credentials and must not be committed).
2026-06-12 18:09:09 -07:00

90 lines
3.1 KiB
JavaScript

#!/usr/bin/env node
/**
* Fetch a Coolify app's FULL container logs via SSH + `docker logs`.
*
* More powerful than the REST API path (scripts/fetch-app-logs.mjs): it includes
* timestamps, supports a `--since` date filter, and works for both dockerfile and
* dockercompose apps. Use this when the REST `/logs` endpoint returns little or
* nothing (e.g. quiet services, or compose apps Coolify can't tail via the API).
*
* Usage (from the vibn-frontend/ directory):
* node --env-file=.env.local scripts/fetch-app-logs-ssh.mjs <appUuid> [sinceISO] [tail]
*
* Examples:
* # Everything since the start of today (UTC)
* node --env-file=.env.local scripts/fetch-app-logs-ssh.mjs hou4vy5mtyg5mrx3w4nl2lxv 2026-06-12
* # Last 500 lines, no date filter
* node --env-file=.env.local scripts/fetch-app-logs-ssh.mjs hou4vy5mtyg5mrx3w4nl2lxv "" 500
*
* The <appUuid> is the last path segment of the Coolify app URL.
* Env (from vibn-frontend/.env.local):
* COOLIFY_SSH_HOST, COOLIFY_SSH_PORT, COOLIFY_SSH_USER, COOLIFY_SSH_PRIVATE_KEY_B64
*/
import ssh2 from "ssh2";
const { Client } = ssh2;
const uuid = process.argv[2];
const since = process.argv[3] || ""; // optional ISO date, e.g. 2026-06-12
const tail = process.argv[4] || "2000";
if (!uuid) {
console.error(
"Usage: node --env-file=.env.local scripts/fetch-app-logs-ssh.mjs <appUuid> [sinceISO] [tail]",
);
process.exit(1);
}
const keyB64 = process.env.COOLIFY_SSH_PRIVATE_KEY_B64;
if (!process.env.COOLIFY_SSH_HOST || !keyB64) {
console.error("Missing COOLIFY_SSH_HOST / COOLIFY_SSH_PRIVATE_KEY_B64 in .env.local");
process.exit(1);
}
const cfg = {
host: process.env.COOLIFY_SSH_HOST,
port: Number(process.env.COOLIFY_SSH_PORT ?? 22),
username: process.env.COOLIFY_SSH_USER ?? "vibn-logs",
privateKey: Buffer.from(keyB64, "base64").toString("utf8"),
readyTimeout: 8000,
};
function runRemote(cmd) {
return new Promise((resolve, reject) => {
const conn = new Client();
let out = "";
let errOut = "";
conn
.on("ready", () => {
conn.exec(cmd, (err, stream) => {
if (err) {
conn.end();
return reject(err);
}
stream
.on("close", (code) => {
conn.end();
resolve({ code, out, errOut });
})
.on("data", (d) => (out += d.toString()))
.stderr.on("data", (d) => (errOut += d.toString()));
});
})
.on("error", reject)
.connect(cfg);
});
}
// Resolve the container by name (Coolify names them with the app UUID), then
// dump its logs. The $(...) here runs on the REMOTE host, not locally.
const sinceFlag = since ? `--since ${since}` : "";
const cmd =
`cid=$(docker ps -a --filter name=${uuid} --format '{{.Names}}' | head -1); ` +
`if [ -z "$cid" ]; then echo "NO_CONTAINER for ${uuid}"; exit 2; fi; ` +
`echo "# container=$cid"; ` +
`docker logs --timestamps ${sinceFlag} --tail ${tail} "$cid" 2>&1`;
const { code, out, errOut } = await runRemote(cmd);
if (out) process.stdout.write(out);
if (errOut) process.stderr.write(errOut);
process.exit(code ?? 0);