Merge branch 'Dokploy:canary' into canary

This commit is contained in:
shiqocred
2025-01-13 13:31:31 +07:00
committed by GitHub
301 changed files with 19259 additions and 17260 deletions

View File

@@ -1,4 +1,3 @@
import { webcrypto } from "node:crypto";
import type { IncomingMessage, ServerResponse } from "node:http";
import { findAdminByAuthId } from "@dokploy/server/services/admin";
import { findUserByAuthId } from "@dokploy/server/services/user";
@@ -9,7 +8,6 @@ import type { Session, User } from "lucia/dist/core.js";
import { db } from "../db";
import { type DatabaseUser, auth, sessionTable } from "../db/schema";
globalThis.crypto = webcrypto as Crypto;
export const adapter = new DrizzlePostgreSQLAdapter(db, sessionTable, auth);
export const lucia = new Lucia(adapter, {

View File

@@ -129,10 +129,10 @@ export const applications = pgTable("application", {
false,
),
buildArgs: text("buildArgs"),
memoryReservation: integer("memoryReservation"),
memoryLimit: integer("memoryLimit"),
cpuReservation: integer("cpuReservation"),
cpuLimit: integer("cpuLimit"),
memoryReservation: text("memoryReservation"),
memoryLimit: text("memoryLimit"),
cpuReservation: text("cpuReservation"),
cpuLimit: text("cpuLimit"),
title: text("title"),
enabled: boolean("enabled"),
subtitle: text("subtitle"),
@@ -355,10 +355,10 @@ const createSchema = createInsertSchema(applications, {
buildArgs: z.string().optional(),
name: z.string().min(1),
description: z.string().optional(),
memoryReservation: z.number().optional(),
memoryLimit: z.number().optional(),
cpuReservation: z.number().optional(),
cpuLimit: z.number().optional(),
memoryReservation: z.string().optional(),
memoryLimit: z.string().optional(),
cpuReservation: z.string().optional(),
cpuLimit: z.string().optional(),
title: z.string().optional(),
enabled: z.boolean().optional(),
subtitle: z.string().optional(),

View File

@@ -29,10 +29,10 @@ export const mariadb = pgTable("mariadb", {
command: text("command"),
env: text("env"),
// RESOURCES
memoryReservation: integer("memoryReservation"),
memoryLimit: integer("memoryLimit"),
cpuReservation: integer("cpuReservation"),
cpuLimit: integer("cpuLimit"),
memoryReservation: text("memoryReservation"),
memoryLimit: text("memoryLimit"),
cpuReservation: text("cpuReservation"),
cpuLimit: text("cpuLimit"),
//
externalPort: integer("externalPort"),
applicationStatus: applicationStatus("applicationStatus")
@@ -74,10 +74,10 @@ const createSchema = createInsertSchema(mariadb, {
dockerImage: z.string().default("mariadb:6"),
command: z.string().optional(),
env: z.string().optional(),
memoryReservation: z.number().optional(),
memoryLimit: z.number().optional(),
cpuReservation: z.number().optional(),
cpuLimit: z.number().optional(),
memoryReservation: z.string().optional(),
memoryLimit: z.string().optional(),
cpuReservation: z.string().optional(),
cpuLimit: z.string().optional(),
projectId: z.string(),
applicationStatus: z.enum(["idle", "running", "done", "error"]),
externalPort: z.number(),

View File

@@ -26,10 +26,10 @@ export const mongo = pgTable("mongo", {
dockerImage: text("dockerImage").notNull(),
command: text("command"),
env: text("env"),
memoryReservation: integer("memoryReservation"),
memoryLimit: integer("memoryLimit"),
cpuReservation: integer("cpuReservation"),
cpuLimit: integer("cpuLimit"),
memoryReservation: text("memoryReservation"),
memoryLimit: text("memoryLimit"),
cpuReservation: text("cpuReservation"),
cpuLimit: text("cpuLimit"),
externalPort: integer("externalPort"),
applicationStatus: applicationStatus("applicationStatus")
.notNull()
@@ -69,10 +69,10 @@ const createSchema = createInsertSchema(mongo, {
dockerImage: z.string().default("mongo:15"),
command: z.string().optional(),
env: z.string().optional(),
memoryReservation: z.number().optional(),
memoryLimit: z.number().optional(),
cpuReservation: z.number().optional(),
cpuLimit: z.number().optional(),
memoryReservation: z.string().optional(),
memoryLimit: z.string().optional(),
cpuReservation: z.string().optional(),
cpuLimit: z.string().optional(),
projectId: z.string(),
applicationStatus: z.enum(["idle", "running", "done", "error"]),
externalPort: z.number(),

View File

@@ -28,10 +28,10 @@ export const mysql = pgTable("mysql", {
dockerImage: text("dockerImage").notNull(),
command: text("command"),
env: text("env"),
memoryReservation: integer("memoryReservation"),
memoryLimit: integer("memoryLimit"),
cpuReservation: integer("cpuReservation"),
cpuLimit: integer("cpuLimit"),
memoryReservation: text("memoryReservation"),
memoryLimit: text("memoryLimit"),
cpuReservation: text("cpuReservation"),
cpuLimit: text("cpuLimit"),
externalPort: integer("externalPort"),
applicationStatus: applicationStatus("applicationStatus")
.notNull()
@@ -72,10 +72,10 @@ const createSchema = createInsertSchema(mysql, {
dockerImage: z.string().default("mysql:8"),
command: z.string().optional(),
env: z.string().optional(),
memoryReservation: z.number().optional(),
memoryLimit: z.number().optional(),
cpuReservation: z.number().optional(),
cpuLimit: z.number().optional(),
memoryReservation: z.string().optional(),
memoryLimit: z.string().optional(),
cpuReservation: z.string().optional(),
cpuLimit: z.string().optional(),
projectId: z.string(),
applicationStatus: z.enum(["idle", "running", "done", "error"]),
externalPort: z.number(),

View File

@@ -27,11 +27,11 @@ export const postgres = pgTable("postgres", {
dockerImage: text("dockerImage").notNull(),
command: text("command"),
env: text("env"),
memoryReservation: integer("memoryReservation"),
memoryReservation: text("memoryReservation"),
externalPort: integer("externalPort"),
memoryLimit: integer("memoryLimit"),
cpuReservation: integer("cpuReservation"),
cpuLimit: integer("cpuLimit"),
memoryLimit: text("memoryLimit"),
cpuReservation: text("cpuReservation"),
cpuLimit: text("cpuLimit"),
applicationStatus: applicationStatus("applicationStatus")
.notNull()
.default("idle"),
@@ -68,10 +68,10 @@ const createSchema = createInsertSchema(postgres, {
dockerImage: z.string().default("postgres:15"),
command: z.string().optional(),
env: z.string().optional(),
memoryReservation: z.number().optional(),
memoryLimit: z.number().optional(),
cpuReservation: z.number().optional(),
cpuLimit: z.number().optional(),
memoryReservation: z.string().optional(),
memoryLimit: z.string().optional(),
cpuReservation: z.string().optional(),
cpuLimit: z.string().optional(),
projectId: z.string(),
applicationStatus: z.enum(["idle", "running", "done", "error"]),
externalPort: z.number(),

View File

@@ -24,10 +24,10 @@ export const redis = pgTable("redis", {
dockerImage: text("dockerImage").notNull(),
command: text("command"),
env: text("env"),
memoryReservation: integer("memoryReservation"),
memoryLimit: integer("memoryLimit"),
cpuReservation: integer("cpuReservation"),
cpuLimit: integer("cpuLimit"),
memoryReservation: text("memoryReservation"),
memoryLimit: text("memoryLimit"),
cpuReservation: text("cpuReservation"),
cpuLimit: text("cpuLimit"),
externalPort: integer("externalPort"),
createdAt: text("createdAt")
.notNull()
@@ -64,10 +64,10 @@ const createSchema = createInsertSchema(redis, {
dockerImage: z.string().default("redis:8"),
command: z.string().optional(),
env: z.string().optional(),
memoryReservation: z.number().optional(),
memoryLimit: z.number().optional(),
cpuReservation: z.number().optional(),
cpuLimit: z.number().optional(),
memoryReservation: z.string().optional(),
memoryLimit: z.string().optional(),
cpuReservation: z.string().optional(),
cpuLimit: z.string().optional(),
projectId: z.string(),
applicationStatus: z.enum(["idle", "running", "done", "error"]),
externalPort: z.number(),

View File

@@ -67,7 +67,7 @@ export const apiCreateRegistry = createSchema
});
export const apiTestRegistry = createSchema.pick({}).extend({
registryName: z.string().min(1),
registryName: z.string().optional(),
username: z.string().min(1),
password: z.string().min(1),
registryUrl: z.string(),

View File

@@ -120,23 +120,33 @@ export const findMariadbByBackupId = async (backupId: string) => {
return result[0];
};
export const deployMariadb = async (mariadbId: string) => {
export const deployMariadb = async (
mariadbId: string,
onData?: (data: any) => void,
) => {
const mariadb = await findMariadbById(mariadbId);
try {
await updateMariadbById(mariadbId, {
applicationStatus: "running",
});
onData?.("Starting mariadb deployment...");
if (mariadb.serverId) {
await execAsyncRemote(
mariadb.serverId,
`docker pull ${mariadb.dockerImage}`,
onData,
);
} else {
await pullImage(mariadb.dockerImage);
await pullImage(mariadb.dockerImage, onData);
}
await buildMariadb(mariadb);
await updateMariadbById(mariadbId, {
applicationStatus: "done",
});
onData?.("Deployment completed successfully!");
} catch (error) {
onData?.(`Error: ${error}`);
await updateMariadbById(mariadbId, {
applicationStatus: "error",
});

View File

@@ -112,20 +112,34 @@ export const removeMongoById = async (mongoId: string) => {
return result[0];
};
export const deployMongo = async (mongoId: string) => {
export const deployMongo = async (
mongoId: string,
onData?: (data: any) => void,
) => {
const mongo = await findMongoById(mongoId);
try {
await updateMongoById(mongoId, {
applicationStatus: "running",
});
onData?.("Starting mongo deployment...");
if (mongo.serverId) {
await execAsyncRemote(mongo.serverId, `docker pull ${mongo.dockerImage}`);
await execAsyncRemote(
mongo.serverId,
`docker pull ${mongo.dockerImage}`,
onData,
);
} else {
await pullImage(mongo.dockerImage);
await pullImage(mongo.dockerImage, onData);
}
await buildMongo(mongo);
await updateMongoById(mongoId, {
applicationStatus: "done",
});
onData?.("Deployment completed successfully!");
} catch (error) {
onData?.(`Error: ${error}`);
await updateMongoById(mongoId, {
applicationStatus: "error",
});

View File

@@ -1,6 +1,6 @@
import { db } from "@dokploy/server/db";
import { type apiCreateMySql, backups, mysql } from "@dokploy/server/db/schema";
import { buildAppName, cleanAppName } from "@dokploy/server/db/schema";
import { buildAppName } from "@dokploy/server/db/schema";
import { generatePassword } from "@dokploy/server/templates/utils";
import { buildMysql } from "@dokploy/server/utils/databases/mysql";
import { pullImage } from "@dokploy/server/utils/docker/utils";
@@ -116,20 +116,33 @@ export const removeMySqlById = async (mysqlId: string) => {
return result[0];
};
export const deployMySql = async (mysqlId: string) => {
export const deployMySql = async (
mysqlId: string,
onData?: (data: any) => void,
) => {
const mysql = await findMySqlById(mysqlId);
try {
await updateMySqlById(mysqlId, {
applicationStatus: "running",
});
onData?.("Starting mysql deployment...");
if (mysql.serverId) {
await execAsyncRemote(mysql.serverId, `docker pull ${mysql.dockerImage}`);
await execAsyncRemote(
mysql.serverId,
`docker pull ${mysql.dockerImage}`,
onData,
);
} else {
await pullImage(mysql.dockerImage);
await pullImage(mysql.dockerImage, onData);
}
await buildMysql(mysql);
await updateMySqlById(mysqlId, {
applicationStatus: "done",
});
onData?.("Deployment completed successfully!");
} catch (error) {
onData?.(`Error: ${error}`);
await updateMySqlById(mysqlId, {
applicationStatus: "error",
});

View File

@@ -115,24 +115,37 @@ export const removePostgresById = async (postgresId: string) => {
return result[0];
};
export const deployPostgres = async (postgresId: string) => {
export const deployPostgres = async (
postgresId: string,
onData?: (data: any) => void,
) => {
const postgres = await findPostgresById(postgresId);
try {
const promises = [];
await updatePostgresById(postgresId, {
applicationStatus: "running",
});
onData?.("Starting postgres deployment...");
if (postgres.serverId) {
const result = await execAsyncRemote(
await execAsyncRemote(
postgres.serverId,
`docker pull ${postgres.dockerImage}`,
onData,
);
} else {
await pullImage(postgres.dockerImage);
await pullImage(postgres.dockerImage, onData);
}
await buildPostgres(postgres);
await updatePostgresById(postgresId, {
applicationStatus: "done",
});
onData?.("Deployment completed successfully!");
} catch (error) {
onData?.(`Error: ${error}`);
await updatePostgresById(postgresId, {
applicationStatus: "error",
});

View File

@@ -89,20 +89,34 @@ export const removeRedisById = async (redisId: string) => {
return result[0];
};
export const deployRedis = async (redisId: string) => {
export const deployRedis = async (
redisId: string,
onData?: (data: any) => void,
) => {
const redis = await findRedisById(redisId);
try {
await updateRedisById(redisId, {
applicationStatus: "running",
});
onData?.("Starting redis deployment...");
if (redis.serverId) {
await execAsyncRemote(redis.serverId, `docker pull ${redis.dockerImage}`);
await execAsyncRemote(
redis.serverId,
`docker pull ${redis.dockerImage}`,
onData,
);
} else {
await pullImage(redis.dockerImage);
await pullImage(redis.dockerImage, onData);
}
await buildRedis(redis);
await updateRedisById(redisId, {
applicationStatus: "done",
});
onData?.("Deployment completed successfully!");
} catch (error) {
onData?.(`Error: ${error}`);
await updateRedisById(redisId, {
applicationStatus: "error",
});

View File

@@ -1,4 +1,3 @@
import { createWriteStream } from "node:fs";
import path from "node:path";
import { paths } from "@dokploy/server/constants";
import {
@@ -32,7 +31,10 @@ export const slugify = (text: string | undefined) => {
});
};
export const serverSetup = async (serverId: string) => {
export const serverSetup = async (
serverId: string,
onData?: (data: any) => void,
) => {
const server = await findServerById(serverId);
const { LOGS_PATH } = paths();
@@ -48,18 +50,18 @@ export const serverSetup = async (serverId: string) => {
description: "Setup Server",
});
const writeStream = createWriteStream(deployment.logPath, { flags: "a" });
try {
writeStream.write("\nInstalling Server Dependencies: ✅\n");
await installRequirements(serverId, deployment.logPath);
writeStream.close();
onData?.("\nInstalling Server Dependencies: ✅\n");
await installRequirements(serverId, onData);
await updateDeploymentStatus(deployment.deploymentId, "done");
onData?.("\nSetup Server: ✅\n");
} catch (err) {
console.log(err);
await updateDeploymentStatus(deployment.deploymentId, "error");
writeStream.write(`${err}\n`);
writeStream.close();
onData?.(`${err}\n`);
}
};
@@ -173,13 +175,14 @@ ${installBuildpacks()}
return bashCommand;
};
const installRequirements = async (serverId: string, logPath: string) => {
const writeStream = createWriteStream(logPath, { flags: "a" });
const installRequirements = async (
serverId: string,
onData?: (data: any) => void,
) => {
const client = new Client();
const server = await findServerById(serverId);
if (!server.sshKeyId) {
writeStream.write("❌ No SSH Key found");
writeStream.close();
onData?.("❌ No SSH Key found, please assign one to this server");
throw new Error("No SSH Key found");
}
@@ -189,7 +192,7 @@ const installRequirements = async (serverId: string, logPath: string) => {
const command = server.command || defaultCommand();
client.exec(command, (err, stream) => {
if (err) {
writeStream.write(err);
onData?.(err.message);
reject(err);
return;
}
@@ -199,17 +202,17 @@ const installRequirements = async (serverId: string, logPath: string) => {
resolve();
})
.on("data", (data: string) => {
writeStream.write(data.toString());
onData?.(data.toString());
})
.stderr.on("data", (data) => {
writeStream.write(data.toString());
onData?.(data.toString());
});
});
})
.on("error", (err) => {
client.end();
if (err.level === "client-authentication") {
writeStream.write(
onData?.(
`Authentication failed: Invalid SSH private key. ❌ Error: ${err.message} ${err.level}`,
);
reject(
@@ -218,9 +221,7 @@ const installRequirements = async (serverId: string, logPath: string) => {
),
);
} else {
writeStream.write(
`SSH connection error: ${err.message} ${err.level}`,
);
onData?.(`SSH connection error: ${err.message} ${err.level}`);
reject(new Error(`SSH connection error: ${err.message}`));
}
})

View File

@@ -2,7 +2,6 @@ import { createWriteStream } from "node:fs";
import { join } from "node:path";
import type { InferResultType } from "@dokploy/server/types/with";
import type { CreateServiceOptions } from "dockerode";
import { nanoid } from "nanoid";
import { uploadImage, uploadImageRemoteCommand } from "../cluster/upload";
import {
calculateResources,

View File

@@ -306,10 +306,10 @@ export const generateVolumeMounts = (mounts: ApplicationNested["mounts"]) => {
};
type Resources = {
memoryLimit: number | null;
memoryReservation: number | null;
cpuLimit: number | null;
cpuReservation: number | null;
memoryLimit: string | null;
memoryReservation: string | null;
cpuLimit: string | null;
cpuReservation: string | null;
};
export const calculateResources = ({
memoryLimit,
@@ -319,12 +319,14 @@ export const calculateResources = ({
}: Resources): ResourceRequirements => {
return {
Limits: {
MemoryBytes: memoryLimit ?? undefined,
NanoCPUs: cpuLimit ?? undefined,
MemoryBytes: memoryLimit ? Number.parseInt(memoryLimit) : undefined,
NanoCPUs: cpuLimit ? Number.parseInt(cpuLimit) : undefined,
},
Reservations: {
MemoryBytes: memoryReservation ?? undefined,
NanoCPUs: cpuReservation ?? undefined,
MemoryBytes: memoryReservation
? Number.parseInt(memoryReservation)
: undefined,
NanoCPUs: cpuReservation ? Number.parseInt(cpuReservation) : undefined,
},
};
};

View File

@@ -65,7 +65,7 @@ export const sendBuildSuccessNotifications = async ({
`${discord.decoration ? decoration : ""} ${text}`.trim();
await sendDiscordNotification(discord, {
title: "> `✅` Build Success",
title: decorate(">", "`✅` Build Success"),
color: 0x57f287,
fields: [
{

View File

@@ -41,15 +41,15 @@ export const sendDiscordNotification = async (
connection: typeof discord.$inferInsert,
embed: any,
) => {
try {
await fetch(connection.webhookUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ embeds: [embed] }),
});
} catch (err) {
console.log(err);
}
// try {
await fetch(connection.webhookUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ embeds: [embed] }),
});
// } catch (err) {
// console.log(err);
// }
};
export const sendTelegramNotification = async (

View File

@@ -7,6 +7,7 @@ export const execAsync = util.promisify(exec);
export const execAsyncRemote = async (
serverId: string | null,
command: string,
onData?: (data: string) => void,
): Promise<{ stdout: string; stderr: string }> => {
if (!serverId) return { stdout: "", stderr: "" };
const server = await findServerById(serverId);
@@ -21,7 +22,10 @@ export const execAsyncRemote = async (
conn
.once("ready", () => {
conn.exec(command, (err, stream) => {
if (err) throw err;
if (err) {
onData?.(err.message);
throw err;
}
stream
.on("close", (code: number, signal: string) => {
conn.end();
@@ -37,21 +41,27 @@ export const execAsyncRemote = async (
})
.on("data", (data: string) => {
stdout += data.toString();
onData?.(data.toString());
})
.stderr.on("data", (data) => {
stderr += data.toString();
onData?.(data.toString());
});
});
})
.on("error", (err) => {
conn.end();
if (err.level === "client-authentication") {
onData?.(
`Authentication failed: Invalid SSH private key. ❌ Error: ${err.message} ${err.level}`,
);
reject(
new Error(
`Authentication failed: Invalid SSH private key. ❌ Error: ${err.message} ${err.level}`,
),
);
} else {
onData?.(`SSH connection error: ${err.message}`);
reject(new Error(`SSH connection error: ${err.message}`));
}
})