Express API server on :3100 exposing all Coolify operations: - CRUD for apps, env vars, servers - Full upsert pipeline (create/update + env + route + deploy) - Drift detection, Traefik route management via SSH - Scalar API docs at /reference, OpenAPI 3.1 spec UI: New Coolify page with app cards, deploy/delete actions, env var expansion. Sidebar nav + React Query hooks + fetch client. Both UI and LLM/CLI use the same HTTP endpoints. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
46 lines
1.3 KiB
JavaScript
46 lines
1.3 KiB
JavaScript
import { Client } from 'ssh2';
|
|
|
|
/**
|
|
* Execute a command on the remote server via SSH.
|
|
* Uses the ssh2 library (already a project dependency) instead of
|
|
* the plink/sshpass fallback chain from gitea.repo.management.
|
|
*/
|
|
export function sshExec(config, command) {
|
|
const host = config.coolify?.sshHost;
|
|
const user = config.coolify?.sshUser;
|
|
const password = config.coolify?.sshPassword;
|
|
|
|
if (!host || !user) {
|
|
return Promise.reject(new Error('SSH not configured — set coolify.sshHost and coolify.sshUser in config.json'));
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const conn = new Client();
|
|
|
|
conn.on('ready', () => {
|
|
conn.exec(command, (err, stream) => {
|
|
if (err) { conn.end(); return reject(err); }
|
|
|
|
let stdout = '';
|
|
let stderr = '';
|
|
|
|
stream.on('close', (code) => {
|
|
conn.end();
|
|
if (code !== 0 && stderr) {
|
|
reject(new Error(stderr.trim()));
|
|
} else {
|
|
resolve(stdout.trim());
|
|
}
|
|
});
|
|
|
|
stream.on('data', (data) => { stdout += data.toString(); });
|
|
stream.stderr.on('data', (data) => { stderr += data.toString(); });
|
|
});
|
|
});
|
|
|
|
conn.on('error', (err) => reject(err));
|
|
|
|
conn.connect({ host, port: 22, username: user, password });
|
|
});
|
|
}
|