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).
90 lines
3.1 KiB
JavaScript
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);
|