Revert "refactor: stash"

This reverts commit d256998677.
This commit is contained in:
Mauricio Siu
2024-10-02 22:37:14 -06:00
parent d256998677
commit f13e5d449c
32 changed files with 293 additions and 4023 deletions

View File

@@ -10,6 +10,7 @@
"dependencies": { "dependencies": {
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"@dokploy/builders": "workspace:*",
"@hono/node-server": "^1.12.1", "@hono/node-server": "^1.12.1",
"hono": "^4.5.8", "hono": "^4.5.8",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",

View File

@@ -1 +0,0 @@
ALTER TABLE "ssh-key" ADD COLUMN "privateKey" text DEFAULT '' NOT NULL;

File diff suppressed because it is too large Load Diff

View File

@@ -267,13 +267,6 @@
"when": 1726988289562, "when": 1726988289562,
"tag": "0037_legal_namor", "tag": "0037_legal_namor",
"breakpoints": true "breakpoints": true
},
{
"idx": 38,
"version": "6",
"when": 1727903587684,
"tag": "0038_mushy_blindfold",
"breakpoints": true
} }
] ]
} }

View File

@@ -34,6 +34,7 @@
"test": "vitest --config __test__/vitest.config.ts" "test": "vitest --config __test__/vitest.config.ts"
}, },
"dependencies": { "dependencies": {
"@dokploy/builders": "workspace:*",
"@codemirror/lang-json": "^6.0.1", "@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-yaml": "^6.1.1", "@codemirror/lang-yaml": "^6.1.1",
"@codemirror/language": "^6.10.1", "@codemirror/language": "^6.10.1",

View File

@@ -14,7 +14,6 @@ import {
findSSHKeyById, findSSHKeyById,
removeSSHKeyById, removeSSHKeyById,
updateSSHKeyById, updateSSHKeyById,
execAsync,
} from "@dokploy/builders"; } from "@dokploy/builders";
export const sshRouter = createTRPCRouter({ export const sshRouter = createTRPCRouter({

View File

@@ -3,30 +3,28 @@ import { z } from "zod";
export const sshKeyCreate = z.object({ export const sshKeyCreate = z.object({
name: z.string().min(1), name: z.string().min(1),
description: z.string().optional(), description: z.string().optional(),
publicKey: z.string(), publicKey: z.string().refine(
// .refine( (key) => {
// (key) => { const rsaPubPattern = /^ssh-rsa\s+([A-Za-z0-9+/=]+)\s*(.*)?\s*$/;
// // const rsaPubPattern = /^ssh-rsa\s+([A-Za-z0-9+/=]+)\s*(.*)?\s*$/; const ed25519PubPattern = /^ssh-ed25519\s+([A-Za-z0-9+/=]+)\s*(.*)?\s*$/;
// // const ed25519PubPattern = /^ssh-ed25519\s+([A-Za-z0-9+/=]+)\s*(.*)?\s*$/; return rsaPubPattern.test(key) || ed25519PubPattern.test(key);
// // return rsaPubPattern.test(key) || ed25519PubPattern.test(key); },
// }, {
// { message: "Invalid public key format",
// message: "Invalid public key format", },
// }, ),
// ) privateKey: z.string().refine(
privateKey: z.string(), (key) => {
// .refine( const rsaPrivPattern =
// (key) => { /^-----BEGIN RSA PRIVATE KEY-----\n([A-Za-z0-9+/=\n]+)-----END RSA PRIVATE KEY-----\s*$/;
// // const rsaPrivPattern = const ed25519PrivPattern =
// // /^-----BEGIN RSA PRIVATE KEY-----\n([A-Za-z0-9+/=\n]+)-----END RSA PRIVATE KEY-----\s*$/; /^-----BEGIN OPENSSH PRIVATE KEY-----\n([A-Za-z0-9+/=\n]+)-----END OPENSSH PRIVATE KEY-----\s*$/;
// // const ed25519PrivPattern = return rsaPrivPattern.test(key) || ed25519PrivPattern.test(key);
// // /^-----BEGIN OPENSSH PRIVATE KEY-----\n([A-Za-z0-9+/=\n]+)-----END OPENSSH PRIVATE KEY-----\s*$/; },
// // return rsaPrivPattern.test(key) || ed25519PrivPattern.test(key); {
// }, message: "Invalid private key format",
// { },
// message: "Invalid private key format", ),
// },
// ),
}); });
export const sshKeyUpdate = sshKeyCreate.pick({ export const sshKeyUpdate = sshKeyCreate.pick({

View File

@@ -2,7 +2,11 @@ import type http from "node:http";
import { spawn } from "node-pty"; import { spawn } from "node-pty";
import { Client } from "ssh2"; import { Client } from "ssh2";
import { WebSocketServer } from "ws"; import { WebSocketServer } from "ws";
import { findServerById, validateWebSocketRequest } from "@dokploy/builders"; import {
findServerById,
readSSHKey,
validateWebSocketRequest,
} from "@dokploy/builders";
import { getShell } from "./utils"; import { getShell } from "./utils";
export const setupDockerContainerLogsWebSocketServer = ( export const setupDockerContainerLogsWebSocketServer = (
@@ -48,6 +52,7 @@ export const setupDockerContainerLogsWebSocketServer = (
const server = await findServerById(serverId); const server = await findServerById(serverId);
if (!server.sshKeyId) return; if (!server.sshKeyId) return;
const keys = await readSSHKey(server.sshKeyId);
const client = new Client(); const client = new Client();
new Promise<void>((resolve, reject) => { new Promise<void>((resolve, reject) => {
client client
@@ -79,7 +84,7 @@ export const setupDockerContainerLogsWebSocketServer = (
host: server.ipAddress, host: server.ipAddress,
port: server.port, port: server.port,
username: server.username, username: server.username,
privateKey: server.sshKey?.privateKey, privateKey: keys.privateKey,
timeout: 99999, timeout: 99999,
}); });
}); });

View File

@@ -2,7 +2,11 @@ import type http from "node:http";
import { spawn } from "node-pty"; import { spawn } from "node-pty";
import { Client } from "ssh2"; import { Client } from "ssh2";
import { WebSocketServer } from "ws"; import { WebSocketServer } from "ws";
import { findServerById, validateWebSocketRequest } from "@dokploy/builders"; import {
findServerById,
readSSHKey,
validateWebSocketRequest,
} from "@dokploy/builders";
import { getShell } from "./utils"; import { getShell } from "./utils";
export const setupDockerContainerTerminalWebSocketServer = ( export const setupDockerContainerTerminalWebSocketServer = (
@@ -49,6 +53,7 @@ export const setupDockerContainerTerminalWebSocketServer = (
if (!server.sshKeyId) if (!server.sshKeyId)
throw new Error("No SSH key available for this server"); throw new Error("No SSH key available for this server");
const keys = await readSSHKey(server.sshKeyId);
const conn = new Client(); const conn = new Client();
let stdout = ""; let stdout = "";
let stderr = ""; let stderr = "";
@@ -104,7 +109,7 @@ export const setupDockerContainerTerminalWebSocketServer = (
host: server.ipAddress, host: server.ipAddress,
port: server.port, port: server.port,
username: server.username, username: server.username,
privateKey: server.sshKey?.privateKey, privateKey: keys.privateKey,
timeout: 99999, timeout: 99999,
}); });
} else { } else {

View File

@@ -2,7 +2,11 @@ import { spawn } from "node:child_process";
import type http from "node:http"; import type http from "node:http";
import { Client } from "ssh2"; import { Client } from "ssh2";
import { WebSocketServer } from "ws"; import { WebSocketServer } from "ws";
import { findServerById, validateWebSocketRequest } from "@dokploy/builders"; import {
findServerById,
readSSHKey,
validateWebSocketRequest,
} from "@dokploy/builders";
export const setupDeploymentLogsWebSocketServer = ( export const setupDeploymentLogsWebSocketServer = (
server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>, server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>,
@@ -47,6 +51,7 @@ export const setupDeploymentLogsWebSocketServer = (
const server = await findServerById(serverId); const server = await findServerById(serverId);
if (!server.sshKeyId) return; if (!server.sshKeyId) return;
const keys = await readSSHKey(server.sshKeyId);
const client = new Client(); const client = new Client();
new Promise<void>((resolve, reject) => { new Promise<void>((resolve, reject) => {
client client
@@ -78,7 +83,7 @@ export const setupDeploymentLogsWebSocketServer = (
host: server.ipAddress, host: server.ipAddress,
port: server.port, port: server.port,
username: server.username, username: server.username,
privateKey: server.sshKey?.privateKey, privateKey: keys.privateKey,
timeout: 99999, timeout: 99999,
}); });
}); });

View File

@@ -8,7 +8,7 @@ import {
createDefaultServerTraefikConfig, createDefaultServerTraefikConfig,
createDefaultTraefikConfig, createDefaultTraefikConfig,
initializeTraefik, initializeTraefik,
} from "../../packages/builders/src"; } from "@dokploy/builders";
(async () => { (async () => {
try { try {

View File

@@ -1,56 +1,52 @@
{ {
"compilerOptions": { "compilerOptions": {
/* Base Options: */ /* Base Options: */
"esModuleInterop": true, "esModuleInterop": true,
"skipLibCheck": true, "skipLibCheck": true,
"target": "es2022", "target": "es2022",
"allowJs": true, "allowJs": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"moduleDetection": "force", "moduleDetection": "force",
"isolatedModules": true, "isolatedModules": true,
/* Strictness */ /* Strictness */
"strict": true, "strict": true,
"noUncheckedIndexedAccess": true, "noUncheckedIndexedAccess": true,
"checkJs": true, "checkJs": true,
/* Bundled projects */ /* Bundled projects */
"lib": ["dom", "dom.iterable", "ES2022"], "lib": ["dom", "dom.iterable", "ES2022"],
"noEmit": true, "noEmit": true,
"module": "ESNext", "module": "ESNext",
"moduleResolution": "Bundler", "moduleResolution": "Bundler",
"jsx": "preserve", "jsx": "preserve",
"plugins": [{ "name": "next" }], "plugins": [{ "name": "next" }],
"incremental": true, "incremental": true,
/* Path Aliases */ /* Path Aliases */
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@dokploy/builders": ["../../packages/builders"], // Apunta a las fuentes de `builders` "@/*": ["./*"]
"@dokploy/builders/*": ["../../packages/builders/src/*"] }
} },
},
"references": [
{ "path": "../../packages/builders" } // Referencia explícita a builders
],
"include": [ "include": [
"next-env.d.ts", "next-env.d.ts",
"**/*.ts", "**/*.ts",
"**/*.tsx", "**/*.tsx",
"**/*.cjs", "**/*.cjs",
"**/*.js", "**/*.js",
".next/types/**/*.ts", ".next/types/**/*.ts",
"env.js", "env.js",
"next.config.mjs" "next.config.mjs"
], ],
"exclude": [ "exclude": [
"node_modules", "node_modules",
"dokploy", "dokploy",
"config", "config",
"dist", "dist",
"webpack.config.server.js", "webpack.config.server.js",
"migration.ts", "migration.ts",
"setup.ts" "setup.ts"
] ]
} }

View File

@@ -1,21 +1,16 @@
{ {
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"compilerOptions": { "compilerOptions": {
"module": "ESNext", "module": "ESNext",
"outDir": "dist/", "outDir": "dist/",
"target": "ESNext", "target": "ESNext",
"isolatedModules": false, "isolatedModules": false,
"noEmit": false, "noEmit": false,
"moduleResolution": "Node", "moduleResolution": "Node",
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@dokploy/builders": ["../../packages/builders/src"], // Apunta a las fuentes de `builders` "@/*": ["./*"]
"@dokploy/builders/*": ["../../packages/builders/src/*"] }
} },
}, "include": ["next-env.d.ts", "./server/**/*"]
"references": [
{ "path": "../../packages/builders" } // Referencia explícita a builders
],
"include": ["next-env.d.ts", "./server/**/*"]
} }

File diff suppressed because one or more lines are too long

View File

@@ -1,14 +1,12 @@
{ {
"name": "dokploy", "name": "dokploy",
"private": true, "private": true,
"workspaces": ["apps/*", "packages/*"],
"scripts": { "scripts": {
"dokploy:setup": "pnpm --filter=dokploy run setup", "dokploy:setup": "pnpm --filter=dokploy run setup",
"dokploy:dev": "pnpm --filter=dokploy run dev", "dokploy:dev": "pnpm --filter=dokploy run dev",
"dokploy:build": "pnpm --filter=dokploy run build", "dokploy:build": "pnpm --filter=dokploy run build",
"dokploy:start": "pnpm --filter=dokploy run start", "dokploy:start": "pnpm --filter=dokploy run start",
"test": "pnpm --filter=dokploy run test", "test": "pnpm --filter=dokploy run test",
"builder:dev": "pnpm --filter=builders run dev",
"docker:build:canary": "./apps/dokploy/docker/build.sh canary", "docker:build:canary": "./apps/dokploy/docker/build.sh canary",
"docs:dev": "pnpm --filter=docs run dev", "docs:dev": "pnpm --filter=docs run dev",
"docs:build": "pnpm --filter=docs run build", "docs:build": "pnpm --filter=docs run build",

View File

@@ -4,8 +4,10 @@ import path from "node:path";
build({ build({
entryPoints: ["./src/**/*.ts", "./src/**/*.tsx"], // Punto de entrada principal de tu aplicación entryPoints: ["./src/**/*.ts", "./src/**/*.tsx"], // Punto de entrada principal de tu aplicación
outdir: "dist", outdir: "dist",
bundle: false, // Cambia a true si deseas bundlear tu código
platform: "node", platform: "node",
format: "esm", format: "esm",
target: ["esnext"],
sourcemap: false, sourcemap: false,
tsconfig: "./tsconfig.server.json", tsconfig: "./tsconfig.server.json",
plugins: [ plugins: [

View File

@@ -1,13 +1,13 @@
{ {
"name": "@dokploy/builders", "name": "@dokploy/builders",
"version": "1.0.0", "version": "1.0.0",
"main": "src/index.ts", "main": "dist/index.js",
"types": "src/index.ts", "types": "dist/index.d.ts",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "tsc --project tsconfig.server.json --watch && tsc-alias -p tsconfig.server.json", "dev": "tsup --config ./tsup.ts --watch",
"build": "tsc --project tsconfig.server.json && tsc-alias -p tsconfig.server.json", "build": "tsc --project tsconfig.server.json && tsc-alias -p tsconfig.server.json",
"esbuild": "tsx --watch ./esbuild.config.ts & tsc-alias -p tsconfig.server.json --watch", "esbuild": "tsx ./esbuild.config.ts",
"build:types": "tsc --emitDeclarationOnly --experimenta-dts" "build:types": "tsc --emitDeclarationOnly --experimenta-dts"
}, },
"dependencies": { "dependencies": {

View File

@@ -12,7 +12,6 @@ export const sshKeys = pgTable("ssh-key", {
.notNull() .notNull()
.primaryKey() .primaryKey()
.$defaultFn(() => nanoid()), .$defaultFn(() => nanoid()),
privateKey: text("privateKey").notNull().default(""),
publicKey: text("publicKey").notNull(), publicKey: text("publicKey").notNull(),
name: text("name").notNull(), name: text("name").notNull(),
description: text("description"), description: text("description"),
@@ -38,7 +37,6 @@ export const apiCreateSshKey = createSchema
.pick({ .pick({
name: true, name: true,
description: true, description: true,
privateKey: true,
publicKey: true, publicKey: true,
}) })
.merge(sshKeyCreate.pick({ privateKey: true })); .merge(sshKeyCreate.pick({ privateKey: true }));

View File

@@ -3,30 +3,28 @@ import { z } from "zod";
export const sshKeyCreate = z.object({ export const sshKeyCreate = z.object({
name: z.string().min(1), name: z.string().min(1),
description: z.string().optional(), description: z.string().optional(),
publicKey: z.string(), publicKey: z.string().refine(
// .refine( (key) => {
// (key) => { const rsaPubPattern = /^ssh-rsa\s+([A-Za-z0-9+/=]+)\s*(.*)?\s*$/;
// const rsaPubPattern = /^ssh-rsa\s+([A-Za-z0-9+/=]+)\s*(.*)?\s*$/; const ed25519PubPattern = /^ssh-ed25519\s+([A-Za-z0-9+/=]+)\s*(.*)?\s*$/;
// const ed25519PubPattern = /^ssh-ed25519\s+([A-Za-z0-9+/=]+)\s*(.*)?\s*$/; return rsaPubPattern.test(key) || ed25519PubPattern.test(key);
// return rsaPubPattern.test(key) || ed25519PubPattern.test(key); },
// }, {
// { message: "Invalid public key format",
// message: "Invalid public key format", },
// }, ),
// ) privateKey: z.string().refine(
privateKey: z.string(), (key) => {
// .refine( const rsaPrivPattern =
// (key) => { /^-----BEGIN RSA PRIVATE KEY-----\n([A-Za-z0-9+/=\n]+)-----END RSA PRIVATE KEY-----\s*$/;
// const rsaPrivPattern = const ed25519PrivPattern =
// /^-----BEGIN RSA PRIVATE KEY-----\n([A-Za-z0-9+/=\n]+)-----END RSA PRIVATE KEY-----\s*$/; /^-----BEGIN OPENSSH PRIVATE KEY-----\n([A-Za-z0-9+/=\n]+)-----END OPENSSH PRIVATE KEY-----\s*$/;
// const ed25519PrivPattern = return rsaPrivPattern.test(key) || ed25519PrivPattern.test(key);
// /^-----BEGIN OPENSSH PRIVATE KEY-----\n([A-Za-z0-9+/=\n]+)-----END OPENSSH PRIVATE KEY-----\s*$/; },
// return rsaPrivPattern.test(key) || ed25519PrivPattern.test(key); {
// }, message: "Invalid private key format",
// { },
// message: "Invalid private key format", ),
// },
// ),
}); });
export const sshKeyUpdate = sshKeyCreate.pick({ export const sshKeyUpdate = sshKeyCreate.pick({

View File

@@ -6,10 +6,14 @@ import {
type apiUpdateSshKey, type apiUpdateSshKey,
sshKeys, sshKeys,
} from "@/server/db/schema"; } from "@/server/db/schema";
import { removeSSHKey, saveSSHKey } from "@/server/utils/filesystem/ssh";
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
export const createSshKey = async (input: typeof apiCreateSshKey._type) => { export const createSshKey = async ({
privateKey,
...input
}: typeof apiCreateSshKey._type) => {
await db.transaction(async (tx) => { await db.transaction(async (tx) => {
const sshKey = await tx const sshKey = await tx
.insert(sshKeys) .insert(sshKeys)
@@ -18,6 +22,10 @@ export const createSshKey = async (input: typeof apiCreateSshKey._type) => {
.then((response) => response[0]) .then((response) => response[0])
.catch((e) => console.error(e)); .catch((e) => console.error(e));
if (sshKey) {
saveSSHKey(sshKey.sshKeyId, sshKey.publicKey, privateKey);
}
if (!sshKey) { if (!sshKey) {
throw new TRPCError({ throw new TRPCError({
code: "BAD_REQUEST", code: "BAD_REQUEST",
@@ -36,6 +44,8 @@ export const removeSSHKeyById = async (
.where(eq(sshKeys.sshKeyId, sshKeyId)) .where(eq(sshKeys.sshKeyId, sshKeyId))
.returning(); .returning();
removeSSHKey(sshKeyId);
return result[0]; return result[0];
}; };

View File

@@ -12,6 +12,7 @@ import {
} from "@/server/setup/traefik-setup"; } from "@/server/setup/traefik-setup";
import { Client } from "ssh2"; import { Client } from "ssh2";
import { recreateDirectory } from "../utils/filesystem/directory"; import { recreateDirectory } from "../utils/filesystem/directory";
import { readSSHKey } from "../utils/filesystem/ssh";
import slug from "slugify"; import slug from "slugify";
@@ -69,7 +70,13 @@ const installRequirements = async (serverId: string, logPath: string) => {
writeStream.close(); writeStream.close();
throw new Error("No SSH Key found"); 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) => { return new Promise<void>((resolve, reject) => {
client client
.once("ready", () => { .once("ready", () => {
@@ -135,7 +142,7 @@ const installRequirements = async (serverId: string, logPath: string) => {
host: server.ipAddress, host: server.ipAddress,
port: server.port, port: server.port,
username: server.username, username: server.username,
privateKey: server.sshKey?.privateKey, privateKey: keys.privateKey,
timeout: 99999, timeout: 99999,
}); });
}); });

View File

@@ -9,6 +9,7 @@ import {
recreateDirectory, recreateDirectory,
recreateDirectoryRemote, recreateDirectoryRemote,
} from "../filesystem/directory"; } from "../filesystem/directory";
import { readSSHKey } from "../filesystem/ssh";
import { execAsyncRemote } from "../process/execAsync"; import { execAsyncRemote } from "../process/execAsync";
export const unzipDrop = async (zipFile: File, application: Application) => { export const unzipDrop = async (zipFile: File, application: Application) => {
@@ -89,6 +90,7 @@ const getSFTPConnection = async (serverId: string): Promise<SFTPWrapper> => {
const server = await findServerById(serverId); const server = await findServerById(serverId);
if (!server.sshKeyId) throw new Error("No SSH key available for this server"); if (!server.sshKeyId) throw new Error("No SSH key available for this server");
const keys = await readSSHKey(server.sshKeyId);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const conn = new Client(); const conn = new Client();
conn conn
@@ -102,7 +104,7 @@ const getSFTPConnection = async (serverId: string): Promise<SFTPWrapper> => {
host: server.ipAddress, host: server.ipAddress,
port: server.port, port: server.port,
username: server.username, username: server.username,
privateKey: server.sshKey?.privateKey, privateKey: keys.privateKey,
timeout: 99999, timeout: 99999,
}); });
}); });

View File

@@ -1,26 +1,99 @@
import { utils } from "ssh2"; import * as fs from "node:fs";
import * as path from "node:path";
import { paths } from "@/server/constants";
import { spawnAsync } from "../process/spawnAsync";
export const generateSSHKey = async (type: "rsa" | "ed25519" = "rsa") => { export const readSSHKey = async (id: string) => {
const { SSH_PATH } = paths();
try { try {
if (type === "rsa") { if (!fs.existsSync(SSH_PATH)) {
const keys = utils.generateKeyPairSync("rsa", { fs.mkdirSync(SSH_PATH, { recursive: true });
bits: 4096,
comment: "dokploy",
});
return {
privateKey: keys.private,
publicKey: keys.public,
};
} }
const keys = utils.generateKeyPairSync("ed25519", {
comment: "dokploy",
});
return { return {
privateKey: keys.private, privateKey: fs.readFileSync(path.join(SSH_PATH, `${id}_rsa`), {
publicKey: keys.public, encoding: "utf-8",
}),
publicKey: fs.readFileSync(path.join(SSH_PATH, `${id}_rsa.pub`), {
encoding: "utf-8",
}),
}; };
} catch (error) { } catch (error) {
throw 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;
}
};

View File

@@ -2,6 +2,7 @@ import { exec } from "node:child_process";
import util from "node:util"; import util from "node:util";
import { findServerById } from "@/server/services/server"; import { findServerById } from "@/server/services/server";
import { Client } from "ssh2"; import { Client } from "ssh2";
import { readSSHKey } from "../filesystem/ssh";
export const execAsync = util.promisify(exec); export const execAsync = util.promisify(exec);
export const execAsyncRemote = async ( export const execAsyncRemote = async (
@@ -12,6 +13,8 @@ export const execAsyncRemote = async (
const server = await findServerById(serverId); const server = await findServerById(serverId);
if (!server.sshKeyId) throw new Error("No SSH key available for this server"); if (!server.sshKeyId) throw new Error("No SSH key available for this server");
const keys = await readSSHKey(server.sshKeyId);
let stdout = ""; let stdout = "";
let stderr = ""; let stderr = "";
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@@ -59,7 +62,7 @@ export const execAsyncRemote = async (
host: server.ipAddress, host: server.ipAddress,
port: server.port, port: server.port,
username: server.username, username: server.username,
privateKey: server.sshKey?.privateKey, privateKey: keys.privateKey,
timeout: 99999, timeout: 99999,
}); });
}); });

View File

@@ -1,11 +1,13 @@
import { findServerById } from "@/server/services/server"; import { findServerById } from "@/server/services/server";
import { docker } from "@/server/constants"; import { docker } from "@/server/constants";
import Dockerode from "dockerode"; import Dockerode from "dockerode";
import { readSSHKey } from "../filesystem/ssh";
export const getRemoteDocker = async (serverId?: string | null) => { export const getRemoteDocker = async (serverId?: string | null) => {
if (!serverId) return docker; if (!serverId) return docker;
const server = await findServerById(serverId); const server = await findServerById(serverId);
if (!server.sshKeyId) return docker; if (!server.sshKeyId) return docker;
const keys = await readSSHKey(server.sshKeyId);
const dockerode = new Dockerode({ const dockerode = new Dockerode({
host: server.ipAddress, host: server.ipAddress,
port: server.port, port: server.port,
@@ -13,7 +15,7 @@ export const getRemoteDocker = async (serverId?: string | null) => {
protocol: "ssh", protocol: "ssh",
// @ts-ignore // @ts-ignore
sshOptions: { sshOptions: {
privateKey: server.sshKey?.privateKey, privateKey: keys.privateKey,
}, },
}); });

View File

@@ -4,6 +4,7 @@ import { Client } from "ssh2";
import { WebSocketServer } from "ws"; import { WebSocketServer } from "ws";
import { findServerById } from "@/server/services/server"; import { findServerById } from "@/server/services/server";
import { validateWebSocketRequest } from "../auth/auth"; import { validateWebSocketRequest } from "../auth/auth";
import { readSSHKey } from "../utils/filesystem/ssh";
import { getShell } from "./utils"; import { getShell } from "./utils";
export const setupDockerContainerLogsWebSocketServer = ( export const setupDockerContainerLogsWebSocketServer = (
@@ -49,6 +50,7 @@ export const setupDockerContainerLogsWebSocketServer = (
const server = await findServerById(serverId); const server = await findServerById(serverId);
if (!server.sshKeyId) return; if (!server.sshKeyId) return;
const keys = await readSSHKey(server.sshKeyId);
const client = new Client(); const client = new Client();
new Promise<void>((resolve, reject) => { new Promise<void>((resolve, reject) => {
client client
@@ -80,7 +82,7 @@ export const setupDockerContainerLogsWebSocketServer = (
host: server.ipAddress, host: server.ipAddress,
port: server.port, port: server.port,
username: server.username, username: server.username,
privateKey: server.sshKey?.privateKey, privateKey: keys.privateKey,
timeout: 99999, timeout: 99999,
}); });
}); });

View File

@@ -4,6 +4,7 @@ import { Client } from "ssh2";
import { WebSocketServer } from "ws"; import { WebSocketServer } from "ws";
import { findServerById } from "@/server/services/server"; import { findServerById } from "@/server/services/server";
import { validateWebSocketRequest } from "../auth/auth"; import { validateWebSocketRequest } from "../auth/auth";
import { readSSHKey } from "../utils/filesystem/ssh";
import { getShell } from "./utils"; import { getShell } from "./utils";
export const setupDockerContainerTerminalWebSocketServer = ( export const setupDockerContainerTerminalWebSocketServer = (
@@ -50,6 +51,7 @@ export const setupDockerContainerTerminalWebSocketServer = (
if (!server.sshKeyId) if (!server.sshKeyId)
throw new Error("No SSH key available for this server"); throw new Error("No SSH key available for this server");
const keys = await readSSHKey(server.sshKeyId);
const conn = new Client(); const conn = new Client();
let stdout = ""; let stdout = "";
let stderr = ""; let stderr = "";
@@ -105,7 +107,7 @@ export const setupDockerContainerTerminalWebSocketServer = (
host: server.ipAddress, host: server.ipAddress,
port: server.port, port: server.port,
username: server.username, username: server.username,
privateKey: server.sshKey?.privateKey, privateKey: keys.privateKey,
timeout: 99999, timeout: 99999,
}); });
} else { } else {

View File

@@ -4,6 +4,7 @@ import { Client } from "ssh2";
import { WebSocketServer } from "ws"; import { WebSocketServer } from "ws";
import { findServerById } from "@/server/services/server"; import { findServerById } from "@/server/services/server";
import { validateWebSocketRequest } from "../auth/auth"; import { validateWebSocketRequest } from "../auth/auth";
import { readSSHKey } from "../utils/filesystem/ssh";
export const setupDeploymentLogsWebSocketServer = ( export const setupDeploymentLogsWebSocketServer = (
server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>, server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>,
@@ -48,6 +49,7 @@ export const setupDeploymentLogsWebSocketServer = (
const server = await findServerById(serverId); const server = await findServerById(serverId);
if (!server.sshKeyId) return; if (!server.sshKeyId) return;
const keys = await readSSHKey(server.sshKeyId);
const client = new Client(); const client = new Client();
new Promise<void>((resolve, reject) => { new Promise<void>((resolve, reject) => {
client client
@@ -79,7 +81,7 @@ export const setupDeploymentLogsWebSocketServer = (
host: server.ipAddress, host: server.ipAddress,
port: server.port, port: server.port,
username: server.username, username: server.username,
privateKey: server.sshKey?.privateKey, privateKey: keys.privateKey,
timeout: 99999, timeout: 99999,
}); });
}); });

View File

@@ -6,17 +6,16 @@
"target": "es2022", "target": "es2022",
"allowJs": true, "allowJs": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"composite": true,
"moduleDetection": "force", "moduleDetection": "force",
"isolatedModules": true, "isolatedModules": true,
/* Strictness */ /* Strictness */
"strict": true, "strict": true,
"noUncheckedIndexedAccess": true, "noUncheckedIndexedAccess": true,
"checkJs": true, "checkJs": true,
"rootDir": "./src",
/* Bundled projects */ /* Bundled projects */
"lib": ["dom", "dom.iterable", "ES2022"], "lib": ["dom", "dom.iterable", "ES2022"],
"noEmit": false, "noEmit": true,
"module": "ESNext", "module": "ESNext",
"moduleResolution": "Bundler", "moduleResolution": "Bundler",
"jsx": "preserve", "jsx": "preserve",

View File

@@ -8,7 +8,6 @@
"noEmit": false, "noEmit": false,
"declaration": true, "declaration": true,
"moduleResolution": "Node", "moduleResolution": "Node",
"rootDir": "./src",
"baseUrl": ".", "baseUrl": ".",
"incremental": false, "incremental": false,
"jsx": "react-jsx", "jsx": "react-jsx",

46
pnpm-lock.yaml generated
View File

@@ -42,6 +42,9 @@ importers:
apps/api: apps/api:
dependencies: dependencies:
'@dokploy/builders':
specifier: workspace:*
version: link:../../packages/builders
'@hono/node-server': '@hono/node-server':
specifier: ^1.12.1 specifier: ^1.12.1
version: 1.12.1 version: 1.12.1
@@ -146,6 +149,9 @@ importers:
'@codemirror/view': '@codemirror/view':
specifier: 6.29.0 specifier: 6.29.0
version: 6.29.0 version: 6.29.0
'@dokploy/builders':
specifier: workspace:*
version: link:../../packages/builders
'@dokploy/trpc-openapi': '@dokploy/trpc-openapi':
specifier: 0.0.4 specifier: 0.0.4
version: 0.0.4(@trpc/server@10.45.2)(zod@3.23.8) version: 0.0.4(@trpc/server@10.45.2)(zod@3.23.8)
@@ -585,7 +591,7 @@ importers:
version: 3.1.2 version: 3.1.2
otpauth: otpauth:
specifier: ^9.2.3 specifier: ^9.2.3
version: 9.3.4 version: 9.3.1
postgres: postgres:
specifier: 3.4.4 specifier: 3.4.4
version: 3.4.4 version: 3.4.4
@@ -594,7 +600,7 @@ importers:
version: 6.0.2 version: 6.0.2
qrcode: qrcode:
specifier: ^1.5.3 specifier: ^1.5.3
version: 1.5.4 version: 1.5.3
react: react:
specifier: 18.2.0 specifier: 18.2.0
version: 18.2.0 version: 18.2.0
@@ -643,7 +649,7 @@ importers:
version: 2.1.6 version: 2.1.6
'@types/nodemailer': '@types/nodemailer':
specifier: ^6.4.15 specifier: ^6.4.15
version: 6.4.16 version: 6.4.15
'@types/qrcode': '@types/qrcode':
specifier: ^1.5.5 specifier: ^1.5.5
version: 1.5.5 version: 1.5.5
@@ -1834,9 +1840,9 @@ packages:
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@noble/hashes@1.5.0': '@noble/hashes@1.4.0':
resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==} resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==}
engines: {node: ^14.21.3 || >=16} engines: {node: '>= 16'}
'@node-rs/argon2-android-arm-eabi@1.7.0': '@node-rs/argon2-android-arm-eabi@1.7.0':
resolution: {integrity: sha512-udDqkr5P9E+wYX1SZwAVPdyfYvaF4ry9Tm+R9LkfSHbzWH0uhU6zjIwNRp7m+n4gx691rk+lqqDAIP8RLKwbhg==} resolution: {integrity: sha512-udDqkr5P9E+wYX1SZwAVPdyfYvaF4ry9Tm+R9LkfSHbzWH0uhU6zjIwNRp7m+n4gx691rk+lqqDAIP8RLKwbhg==}
@@ -3513,8 +3519,8 @@ packages:
'@types/node@20.4.6': '@types/node@20.4.6':
resolution: {integrity: sha512-q0RkvNgMweWWIvSMDiXhflGUKMdIxBo2M2tYM/0kEGDueQByFzK4KZAgu5YHGFNxziTlppNpTIBcqHQAxlfHdA==} resolution: {integrity: sha512-q0RkvNgMweWWIvSMDiXhflGUKMdIxBo2M2tYM/0kEGDueQByFzK4KZAgu5YHGFNxziTlppNpTIBcqHQAxlfHdA==}
'@types/nodemailer@6.4.16': '@types/nodemailer@6.4.15':
resolution: {integrity: sha512-uz6hN6Pp0upXMcilM61CoKyjT7sskBoOWpptkjjJp8jIMlTdc3xG01U7proKkXzruMS4hS0zqtHNkNPFB20rKQ==} resolution: {integrity: sha512-0EBJxawVNjPkng1zm2vopRctuWVCxk34JcIlRuXSf54habUWdz1FB7wHDqOqvDa8Mtpt0Q3LTXQkAs2LNyK5jQ==}
'@types/prop-types@15.7.12': '@types/prop-types@15.7.12':
resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==}
@@ -4730,6 +4736,9 @@ packages:
emoji-regex@9.2.2: emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
encode-utf8@1.0.3:
resolution: {integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==}
end-of-stream@1.4.4: end-of-stream@1.4.4:
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
@@ -6720,8 +6729,8 @@ packages:
oslo@1.2.0: oslo@1.2.0:
resolution: {integrity: sha512-OoFX6rDsNcOQVAD2gQD/z03u4vEjWZLzJtwkmgfRF+KpQUXwdgEXErD7zNhyowmHwHefP+PM9Pw13pgpHMRlzw==} resolution: {integrity: sha512-OoFX6rDsNcOQVAD2gQD/z03u4vEjWZLzJtwkmgfRF+KpQUXwdgEXErD7zNhyowmHwHefP+PM9Pw13pgpHMRlzw==}
otpauth@9.3.4: otpauth@9.3.1:
resolution: {integrity: sha512-qXv+lpsCUO9ewitLYfeDKbLYt7UUCivnU/fwGK2OqhgrCBsRkTUNKWsgKAhkXG3aistOY+jEeuL90JEBu6W3mQ==} resolution: {integrity: sha512-E6d2tMxPofHNk4sRFp+kqW7vQ+WJGO9VLI2N/W00DnI+ThskU12Qa10kyNSGklrzhN5c+wRUsN4GijVgCU2N9w==}
p-cancelable@3.0.0: p-cancelable@3.0.0:
resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==}
@@ -7038,8 +7047,8 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'} engines: {node: '>=6'}
qrcode@1.5.4: qrcode@1.5.3:
resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} resolution: {integrity: sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==}
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}
hasBin: true hasBin: true
@@ -9307,7 +9316,7 @@ snapshots:
'@next/swc-win32-x64-msvc@14.2.5': '@next/swc-win32-x64-msvc@14.2.5':
optional: true optional: true
'@noble/hashes@1.5.0': {} '@noble/hashes@1.4.0': {}
'@node-rs/argon2-android-arm-eabi@1.7.0': '@node-rs/argon2-android-arm-eabi@1.7.0':
optional: true optional: true
@@ -11585,7 +11594,7 @@ snapshots:
'@types/node@20.4.6': {} '@types/node@20.4.6': {}
'@types/nodemailer@6.4.16': '@types/nodemailer@6.4.15':
dependencies: dependencies:
'@types/node': 20.14.10 '@types/node': 20.14.10
@@ -12829,6 +12838,8 @@ snapshots:
emoji-regex@9.2.2: {} emoji-regex@9.2.2: {}
encode-utf8@1.0.3: {}
end-of-stream@1.4.4: end-of-stream@1.4.4:
dependencies: dependencies:
once: 1.4.0 once: 1.4.0
@@ -15441,9 +15452,9 @@ snapshots:
'@node-rs/argon2': 1.7.0 '@node-rs/argon2': 1.7.0
'@node-rs/bcrypt': 1.9.0 '@node-rs/bcrypt': 1.9.0
otpauth@9.3.4: otpauth@9.3.1:
dependencies: dependencies:
'@noble/hashes': 1.5.0 '@noble/hashes': 1.4.0
p-cancelable@3.0.0: {} p-cancelable@3.0.0: {}
@@ -15697,9 +15708,10 @@ snapshots:
punycode@2.3.1: {} punycode@2.3.1: {}
qrcode@1.5.4: qrcode@1.5.3:
dependencies: dependencies:
dijkstrajs: 1.0.3 dijkstrajs: 1.0.3
encode-utf8: 1.0.3
pngjs: 5.0.0 pngjs: 5.0.0
yargs: 15.4.1 yargs: 15.4.1

View File

@@ -1,7 +0,0 @@
{
"files": [],
"references": [
{ "path": "./packages/builders" },
{ "path": "./apps/dokploy" }
]
}