mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
feat: add ssh key
This commit is contained in:
@@ -4,10 +4,8 @@ import path from "node:path";
|
||||
build({
|
||||
entryPoints: ["./src/**/*.ts", "./src/**/*.tsx"], // Punto de entrada principal de tu aplicación
|
||||
outdir: "dist",
|
||||
bundle: false, // Cambia a true si deseas bundlear tu código
|
||||
platform: "node",
|
||||
format: "esm",
|
||||
target: ["esnext"],
|
||||
sourcemap: false,
|
||||
tsconfig: "./tsconfig.server.json",
|
||||
plugins: [
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
"types": "dist/index.d.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "tsup --config ./tsup.ts --watch",
|
||||
"build": "tsc --project tsconfig.server.json && tsc-alias -p tsconfig.server.json",
|
||||
"esbuild": "tsx ./esbuild.config.ts",
|
||||
"dev": "tsc --project tsconfig.server.json --watch && tsc-alias -p tsconfig.server.json",
|
||||
"build": "rm -rf ./dist && tsc --project tsconfig.server.json && tsc-alias -p tsconfig.server.json",
|
||||
"esbuild": "tsx --watch ./esbuild.config.ts & tsc-alias -p tsconfig.server.json --watch",
|
||||
"build:types": "tsc --emitDeclarationOnly --experimenta-dts"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -12,6 +12,7 @@ export const sshKeys = pgTable("ssh-key", {
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
privateKey: text("privateKey").notNull().default(""),
|
||||
publicKey: text("publicKey").notNull(),
|
||||
name: text("name").notNull(),
|
||||
description: text("description"),
|
||||
@@ -37,6 +38,7 @@ export const apiCreateSshKey = createSchema
|
||||
.pick({
|
||||
name: true,
|
||||
description: true,
|
||||
privateKey: true,
|
||||
publicKey: true,
|
||||
})
|
||||
.merge(sshKeyCreate.pick({ privateKey: true }));
|
||||
|
||||
@@ -6,14 +6,10 @@ import {
|
||||
type apiUpdateSshKey,
|
||||
sshKeys,
|
||||
} from "@/server/db/schema";
|
||||
import { removeSSHKey, saveSSHKey } from "@/server/utils/filesystem/ssh";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
export const createSshKey = async ({
|
||||
privateKey,
|
||||
...input
|
||||
}: typeof apiCreateSshKey._type) => {
|
||||
export const createSshKey = async (input: typeof apiCreateSshKey._type) => {
|
||||
await db.transaction(async (tx) => {
|
||||
const sshKey = await tx
|
||||
.insert(sshKeys)
|
||||
@@ -22,10 +18,6 @@ export const createSshKey = async ({
|
||||
.then((response) => response[0])
|
||||
.catch((e) => console.error(e));
|
||||
|
||||
if (sshKey) {
|
||||
saveSSHKey(sshKey.sshKeyId, sshKey.publicKey, privateKey);
|
||||
}
|
||||
|
||||
if (!sshKey) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
@@ -44,8 +36,6 @@ export const removeSSHKeyById = async (
|
||||
.where(eq(sshKeys.sshKeyId, sshKeyId))
|
||||
.returning();
|
||||
|
||||
removeSSHKey(sshKeyId);
|
||||
|
||||
return result[0];
|
||||
};
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
} from "@/server/setup/traefik-setup";
|
||||
import { Client } from "ssh2";
|
||||
import { recreateDirectory } from "../utils/filesystem/directory";
|
||||
import { readSSHKey } from "../utils/filesystem/ssh";
|
||||
|
||||
import slug from "slugify";
|
||||
|
||||
@@ -70,13 +69,7 @@ const installRequirements = async (serverId: string, logPath: string) => {
|
||||
writeStream.close();
|
||||
throw new Error("No SSH Key found");
|
||||
}
|
||||
const keys = await readSSHKey(server.sshKeyId);
|
||||
|
||||
if (!keys.privateKey) {
|
||||
writeStream.write("❌ No SSH Key found");
|
||||
writeStream.close();
|
||||
throw new Error("No SSH Key found");
|
||||
}
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
client
|
||||
.once("ready", () => {
|
||||
@@ -142,7 +135,7 @@ const installRequirements = async (serverId: string, logPath: string) => {
|
||||
host: server.ipAddress,
|
||||
port: server.port,
|
||||
username: server.username,
|
||||
privateKey: keys.privateKey,
|
||||
privateKey: server.sshKey?.privateKey,
|
||||
timeout: 99999,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
recreateDirectory,
|
||||
recreateDirectoryRemote,
|
||||
} from "../filesystem/directory";
|
||||
import { readSSHKey } from "../filesystem/ssh";
|
||||
import { execAsyncRemote } from "../process/execAsync";
|
||||
|
||||
export const unzipDrop = async (zipFile: File, application: Application) => {
|
||||
@@ -90,7 +89,6 @@ const getSFTPConnection = async (serverId: string): Promise<SFTPWrapper> => {
|
||||
const server = await findServerById(serverId);
|
||||
if (!server.sshKeyId) throw new Error("No SSH key available for this server");
|
||||
|
||||
const keys = await readSSHKey(server.sshKeyId);
|
||||
return new Promise((resolve, reject) => {
|
||||
const conn = new Client();
|
||||
conn
|
||||
@@ -104,7 +102,7 @@ const getSFTPConnection = async (serverId: string): Promise<SFTPWrapper> => {
|
||||
host: server.ipAddress,
|
||||
port: server.port,
|
||||
username: server.username,
|
||||
privateKey: keys.privateKey,
|
||||
privateKey: server.sshKey?.privateKey,
|
||||
timeout: 99999,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,99 +1,26 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { paths } from "@/server/constants";
|
||||
import { spawnAsync } from "../process/spawnAsync";
|
||||
import * as ssh2 from "ssh2";
|
||||
|
||||
export const readSSHKey = async (id: string) => {
|
||||
const { SSH_PATH } = paths();
|
||||
export const generateSSHKey = async (type: "rsa" | "ed25519" = "rsa") => {
|
||||
try {
|
||||
if (!fs.existsSync(SSH_PATH)) {
|
||||
fs.mkdirSync(SSH_PATH, { recursive: true });
|
||||
if (type === "rsa") {
|
||||
const keys = ssh2.utils.generateKeyPairSync("rsa", {
|
||||
bits: 4096,
|
||||
comment: "dokploy",
|
||||
});
|
||||
return {
|
||||
privateKey: keys.private,
|
||||
publicKey: keys.public,
|
||||
};
|
||||
}
|
||||
const keys = ssh2.utils.generateKeyPairSync("ed25519", {
|
||||
comment: "dokploy",
|
||||
});
|
||||
|
||||
return {
|
||||
privateKey: fs.readFileSync(path.join(SSH_PATH, `${id}_rsa`), {
|
||||
encoding: "utf-8",
|
||||
}),
|
||||
publicKey: fs.readFileSync(path.join(SSH_PATH, `${id}_rsa.pub`), {
|
||||
encoding: "utf-8",
|
||||
}),
|
||||
privateKey: keys.private,
|
||||
publicKey: keys.public,
|
||||
};
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const saveSSHKey = async (
|
||||
id: string,
|
||||
publicKey: string,
|
||||
privateKey: string,
|
||||
) => {
|
||||
const { SSH_PATH } = paths();
|
||||
const applicationDirectory = SSH_PATH;
|
||||
|
||||
const privateKeyPath = path.join(applicationDirectory, `${id}_rsa`);
|
||||
const publicKeyPath = path.join(applicationDirectory, `${id}_rsa.pub`);
|
||||
|
||||
const privateKeyStream = fs.createWriteStream(privateKeyPath, {
|
||||
mode: 0o600,
|
||||
});
|
||||
privateKeyStream.write(privateKey);
|
||||
privateKeyStream.end();
|
||||
|
||||
fs.writeFileSync(publicKeyPath, publicKey);
|
||||
};
|
||||
|
||||
export const generateSSHKey = async (type: "rsa" | "ed25519" = "rsa") => {
|
||||
const { SSH_PATH } = paths();
|
||||
const applicationDirectory = SSH_PATH;
|
||||
|
||||
if (!fs.existsSync(applicationDirectory)) {
|
||||
fs.mkdirSync(applicationDirectory, { recursive: true });
|
||||
}
|
||||
|
||||
const keyPath = path.join(applicationDirectory, "temp_rsa");
|
||||
|
||||
if (fs.existsSync(`${keyPath}`)) {
|
||||
fs.unlinkSync(`${keyPath}`);
|
||||
}
|
||||
|
||||
if (fs.existsSync(`${keyPath}.pub`)) {
|
||||
fs.unlinkSync(`${keyPath}.pub`);
|
||||
}
|
||||
|
||||
const args = [
|
||||
"-t",
|
||||
type,
|
||||
"-b",
|
||||
"4096",
|
||||
"-C",
|
||||
"dokploy",
|
||||
"-m",
|
||||
"PEM",
|
||||
"-f",
|
||||
keyPath,
|
||||
"-N",
|
||||
"",
|
||||
];
|
||||
|
||||
try {
|
||||
await spawnAsync("ssh-keygen", args);
|
||||
const data = await readSSHKey("temp");
|
||||
await removeSSHKey("temp");
|
||||
return data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const removeSSHKey = async (id: string) => {
|
||||
try {
|
||||
const { SSH_PATH } = paths();
|
||||
const publicKeyPath = path.join(SSH_PATH, `${id}_rsa.pub`);
|
||||
const privateKeyPath = path.join(SSH_PATH, `${id}_rsa`);
|
||||
await fs.promises.unlink(publicKeyPath);
|
||||
await fs.promises.unlink(privateKeyPath);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@ import { exec } from "node:child_process";
|
||||
import util from "node:util";
|
||||
import { findServerById } from "@/server/services/server";
|
||||
import { Client } from "ssh2";
|
||||
import { readSSHKey } from "../filesystem/ssh";
|
||||
export const execAsync = util.promisify(exec);
|
||||
|
||||
export const execAsyncRemote = async (
|
||||
@@ -13,8 +12,6 @@ export const execAsyncRemote = async (
|
||||
const server = await findServerById(serverId);
|
||||
if (!server.sshKeyId) throw new Error("No SSH key available for this server");
|
||||
|
||||
const keys = await readSSHKey(server.sshKeyId);
|
||||
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -62,7 +59,7 @@ export const execAsyncRemote = async (
|
||||
host: server.ipAddress,
|
||||
port: server.port,
|
||||
username: server.username,
|
||||
privateKey: keys.privateKey,
|
||||
privateKey: server.sshKey?.privateKey,
|
||||
timeout: 99999,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { findServerById } from "@/server/services/server";
|
||||
import { docker } from "@/server/constants";
|
||||
import Dockerode from "dockerode";
|
||||
import { readSSHKey } from "../filesystem/ssh";
|
||||
|
||||
export const getRemoteDocker = async (serverId?: string | null) => {
|
||||
if (!serverId) return docker;
|
||||
const server = await findServerById(serverId);
|
||||
if (!server.sshKeyId) return docker;
|
||||
const keys = await readSSHKey(server.sshKeyId);
|
||||
const dockerode = new Dockerode({
|
||||
host: server.ipAddress,
|
||||
port: server.port,
|
||||
@@ -15,7 +13,7 @@ export const getRemoteDocker = async (serverId?: string | null) => {
|
||||
protocol: "ssh",
|
||||
// @ts-ignore
|
||||
sshOptions: {
|
||||
privateKey: keys.privateKey,
|
||||
privateKey: server.sshKey?.privateKey,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { Client } from "ssh2";
|
||||
import { WebSocketServer } from "ws";
|
||||
import { findServerById } from "@/server/services/server";
|
||||
import { validateWebSocketRequest } from "../auth/auth";
|
||||
import { readSSHKey } from "../utils/filesystem/ssh";
|
||||
import { getShell } from "./utils";
|
||||
|
||||
export const setupDockerContainerLogsWebSocketServer = (
|
||||
@@ -50,7 +49,6 @@ export const setupDockerContainerLogsWebSocketServer = (
|
||||
const server = await findServerById(serverId);
|
||||
|
||||
if (!server.sshKeyId) return;
|
||||
const keys = await readSSHKey(server.sshKeyId);
|
||||
const client = new Client();
|
||||
new Promise<void>((resolve, reject) => {
|
||||
client
|
||||
@@ -82,7 +80,7 @@ export const setupDockerContainerLogsWebSocketServer = (
|
||||
host: server.ipAddress,
|
||||
port: server.port,
|
||||
username: server.username,
|
||||
privateKey: keys.privateKey,
|
||||
privateKey: server.sshKey?.privateKey,
|
||||
timeout: 99999,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,6 @@ import { Client } from "ssh2";
|
||||
import { WebSocketServer } from "ws";
|
||||
import { findServerById } from "@/server/services/server";
|
||||
import { validateWebSocketRequest } from "../auth/auth";
|
||||
import { readSSHKey } from "../utils/filesystem/ssh";
|
||||
import { getShell } from "./utils";
|
||||
|
||||
export const setupDockerContainerTerminalWebSocketServer = (
|
||||
@@ -51,7 +50,6 @@ export const setupDockerContainerTerminalWebSocketServer = (
|
||||
if (!server.sshKeyId)
|
||||
throw new Error("No SSH key available for this server");
|
||||
|
||||
const keys = await readSSHKey(server.sshKeyId);
|
||||
const conn = new Client();
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
@@ -107,7 +105,7 @@ export const setupDockerContainerTerminalWebSocketServer = (
|
||||
host: server.ipAddress,
|
||||
port: server.port,
|
||||
username: server.username,
|
||||
privateKey: keys.privateKey,
|
||||
privateKey: server.sshKey?.privateKey,
|
||||
timeout: 99999,
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -4,7 +4,6 @@ import { Client } from "ssh2";
|
||||
import { WebSocketServer } from "ws";
|
||||
import { findServerById } from "@/server/services/server";
|
||||
import { validateWebSocketRequest } from "../auth/auth";
|
||||
import { readSSHKey } from "../utils/filesystem/ssh";
|
||||
|
||||
export const setupDeploymentLogsWebSocketServer = (
|
||||
server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>,
|
||||
@@ -49,7 +48,6 @@ export const setupDeploymentLogsWebSocketServer = (
|
||||
const server = await findServerById(serverId);
|
||||
|
||||
if (!server.sshKeyId) return;
|
||||
const keys = await readSSHKey(server.sshKeyId);
|
||||
const client = new Client();
|
||||
new Promise<void>((resolve, reject) => {
|
||||
client
|
||||
@@ -81,7 +79,7 @@ export const setupDeploymentLogsWebSocketServer = (
|
||||
host: server.ipAddress,
|
||||
port: server.port,
|
||||
username: server.username,
|
||||
privateKey: keys.privateKey,
|
||||
privateKey: server.sshKey?.privateKey,
|
||||
timeout: 99999,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"noEmit": false,
|
||||
"declaration": true,
|
||||
"moduleResolution": "Node",
|
||||
"rootDir": "./src",
|
||||
"baseUrl": ".",
|
||||
"incremental": false,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
Reference in New Issue
Block a user