Merge branch 'canary' into feat/stack-env-support

This commit is contained in:
Mauricio Siu
2025-01-30 23:39:54 -06:00
614 changed files with 71918 additions and 18508 deletions

View File

@@ -30,7 +30,7 @@ export const createInvitation = async (
if (!result) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the user",
message: "Error creating the user",
});
}
const expiresIn24Hours = new Date();
@@ -88,6 +88,9 @@ export const isAdminPresent = async () => {
export const findAdminByAuthId = async (authId: string) => {
const admin = await db.query.admins.findFirst({
where: eq(admins.authId, authId),
with: {
users: true,
},
});
if (!admin) {
throw new TRPCError({
@@ -141,6 +144,24 @@ export const removeUserByAuthId = async (authId: string) => {
.then((res) => res[0]);
};
export const removeAdminByAuthId = async (authId: string) => {
const admin = await findAdminByAuthId(authId);
if (!admin) return null;
// First delete all associated users
const users = admin.users;
for (const user of users) {
await removeUserByAuthId(user.authId);
}
// Then delete the auth record which will cascade delete the admin
return await db
.delete(auth)
.where(eq(auth.id, authId))
.returning()
.then((res) => res[0]);
};
export const getDokployUrl = async () => {
if (IS_CLOUD) {
return "https://app.dokploy.com";

View File

@@ -3,10 +3,10 @@ import { db } from "@dokploy/server/db";
import {
type apiCreateApplication,
applications,
buildAppName,
cleanAppName,
} from "@dokploy/server/db/schema";
import { generateAppName } from "@dokploy/server/db/schema";
import { getAdvancedStats } from "@dokploy/server/monitoring/utilts";
import { generatePassword } from "@dokploy/server/templates/utils";
import {
buildApplication,
getBuildCommand,
@@ -40,40 +40,38 @@ import { createTraefikConfig } from "@dokploy/server/utils/traefik/application";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { encodeBase64 } from "../utils/docker/utils";
import { getDokployUrl } from "./admin";
import { findAdminById, getDokployUrl } from "./admin";
import {
createDeployment,
createDeploymentPreview,
updateDeploymentStatus,
} from "./deployment";
import { validUniqueServerAppName } from "./project";
import {
findPreviewDeploymentById,
updatePreviewDeployment,
} from "./preview-deployment";
import { type Domain, getDomainHost } from "./domain";
import {
createPreviewDeploymentComment,
getIssueComment,
issueCommentExists,
updateIssueComment,
} from "./github";
import { type Domain, getDomainHost } from "./domain";
import {
findPreviewDeploymentById,
updatePreviewDeployment,
} from "./preview-deployment";
import { validUniqueServerAppName } from "./project";
import { cleanupFullDocker } from "./settings";
export type Application = typeof applications.$inferSelect;
export const createApplication = async (
input: typeof apiCreateApplication._type,
) => {
input.appName =
`${input.appName}-${generatePassword(6)}` || generateAppName("app");
if (input.appName) {
const valid = await validUniqueServerAppName(input.appName);
const appName = buildAppName("app", input.appName);
if (!valid) {
throw new TRPCError({
code: "CONFLICT",
message: "Application with this 'AppName' already exists",
});
}
const valid = await validUniqueServerAppName(appName);
if (!valid) {
throw new TRPCError({
code: "CONFLICT",
message: "Application with this 'AppName' already exists",
});
}
return await db.transaction(async (tx) => {
@@ -81,6 +79,7 @@ export const createApplication = async (
.insert(applications)
.values({
...input,
appName,
})
.returning()
.then((value) => value[0]);
@@ -88,7 +87,7 @@ export const createApplication = async (
if (!newApplication) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the application",
message: "Error creating the application",
});
}
@@ -140,10 +139,11 @@ export const updateApplication = async (
applicationId: string,
applicationData: Partial<Application>,
) => {
const { appName, ...rest } = applicationData;
const application = await db
.update(applications)
.set({
...applicationData,
...rest,
})
.where(eq(applications.applicationId, applicationId))
.returning();
@@ -176,6 +176,7 @@ export const deployApplication = async ({
descriptionLog: string;
}) => {
const application = await findApplicationById(applicationId);
const buildLink = `${await getDokployUrl()}/dashboard/project/${application.projectId}/services/application/${application.applicationId}?tab=deployments`;
const deployment = await createDeployment({
applicationId: applicationId,
@@ -184,6 +185,12 @@ export const deployApplication = async ({
});
try {
const admin = await findAdminById(application.project.adminId);
if (admin.cleanupCacheApplications) {
await cleanupFullDocker(application?.serverId);
}
if (application.sourceType === "github") {
await cloneGithubRepository({
...application,
@@ -214,6 +221,7 @@ export const deployApplication = async ({
applicationType: "application",
buildLink,
adminId: application.project.adminId,
domains: application.domains,
});
} catch (error) {
await updateDeploymentStatus(deployment.deploymentId, "error");
@@ -223,7 +231,7 @@ export const deployApplication = async ({
applicationName: application.name,
applicationType: "application",
// @ts-ignore
errorMessage: error?.message || "Error to build",
errorMessage: error?.message || "Error building",
buildLink,
adminId: application.project.adminId,
});
@@ -244,6 +252,7 @@ export const rebuildApplication = async ({
descriptionLog: string;
}) => {
const application = await findApplicationById(applicationId);
const deployment = await createDeployment({
applicationId: applicationId,
title: titleLog,
@@ -251,6 +260,11 @@ export const rebuildApplication = async ({
});
try {
const admin = await findAdminById(application.project.adminId);
if (admin.cleanupCacheApplications) {
await cleanupFullDocker(application?.serverId);
}
if (application.sourceType === "github") {
await buildApplication(application, deployment.logPath);
} else if (application.sourceType === "gitlab") {
@@ -285,6 +299,7 @@ export const deployRemoteApplication = async ({
descriptionLog: string;
}) => {
const application = await findApplicationById(applicationId);
const buildLink = `${await getDokployUrl()}/dashboard/project/${application.projectId}/services/application/${application.applicationId}?tab=deployments`;
const deployment = await createDeployment({
applicationId: applicationId,
@@ -294,6 +309,11 @@ export const deployRemoteApplication = async ({
try {
if (application.serverId) {
const admin = await findAdminById(application.project.adminId);
if (admin.cleanupCacheApplications) {
await cleanupFullDocker(application?.serverId);
}
let command = "set -e;";
if (application.sourceType === "github") {
command += await getGithubCloneCommand({
@@ -333,6 +353,7 @@ export const deployRemoteApplication = async ({
applicationType: "application",
buildLink,
adminId: application.project.adminId,
domains: application.domains,
});
} catch (error) {
// @ts-ignore
@@ -353,19 +374,11 @@ export const deployRemoteApplication = async ({
applicationName: application.name,
applicationType: "application",
// @ts-ignore
errorMessage: error?.message || "Error to build",
errorMessage: error?.message || "Error building",
buildLink,
adminId: application.project.adminId,
});
console.log(
"Error on ",
application.buildType,
"/",
application.sourceType,
error,
);
throw error;
}
@@ -384,6 +397,7 @@ export const deployPreviewApplication = async ({
previewDeploymentId: string;
}) => {
const application = await findApplicationById(applicationId);
const deployment = await createDeploymentPreview({
title: titleLog,
description: descriptionLog,
@@ -437,9 +451,15 @@ export const deployPreviewApplication = async ({
body: `### Dokploy Preview Deployment\n\n${buildingComment}`,
});
application.appName = previewDeployment.appName;
application.env = application.previewEnv;
application.env = `${application.previewEnv}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain}`;
application.buildArgs = application.previewBuildArgs;
const admin = await findAdminById(application.project.adminId);
if (admin.cleanupCacheOnPreviews) {
await cleanupFullDocker(application?.serverId);
}
if (application.sourceType === "github") {
await cloneGithubRepository({
...application,
@@ -449,7 +469,6 @@ export const deployPreviewApplication = async ({
});
await buildApplication(application, deployment.logPath);
}
// 4eef09efc46009187d668cf1c25f768d0bde4f91
const successComment = getIssueComment(
application.name,
"success",
@@ -491,6 +510,7 @@ export const deployRemotePreviewApplication = async ({
previewDeploymentId: string;
}) => {
const application = await findApplicationById(applicationId);
const deployment = await createDeploymentPreview({
title: titleLog,
description: descriptionLog,
@@ -544,14 +564,21 @@ export const deployRemotePreviewApplication = async ({
body: `### Dokploy Preview Deployment\n\n${buildingComment}`,
});
application.appName = previewDeployment.appName;
application.env = application.previewEnv;
application.env = `${application.previewEnv}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain}`;
application.buildArgs = application.previewBuildArgs;
if (application.serverId) {
const admin = await findAdminById(application.project.adminId);
if (admin.cleanupCacheOnPreviews) {
await cleanupFullDocker(application?.serverId);
}
let command = "set -e;";
if (application.sourceType === "github") {
command += await getGithubCloneCommand({
...application,
appName: previewDeployment.appName,
branch: previewDeployment.branch,
serverId: application.serverId,
logPath: deployment.logPath,
});
@@ -601,6 +628,7 @@ export const rebuildRemoteApplication = async ({
descriptionLog: string;
}) => {
const application = await findApplicationById(applicationId);
const deployment = await createDeployment({
applicationId: applicationId,
title: titleLog,
@@ -609,6 +637,11 @@ export const rebuildRemoteApplication = async ({
try {
if (application.serverId) {
const admin = await findAdminById(application.project.adminId);
if (admin.cleanupCacheApplications) {
await cleanupFullDocker(application?.serverId);
}
if (application.sourceType !== "docker") {
let command = "set -e;";
command += getBuildCommand(application, deployment.logPath);

View File

@@ -7,7 +7,7 @@ import {
auth,
users,
} from "@dokploy/server/db/schema";
import { getPublicIpWithFallback } from "@dokploy/server/wss/terminal";
import { getPublicIpWithFallback } from "@dokploy/server/wss/utils";
import { TRPCError } from "@trpc/server";
import * as bcrypt from "bcrypt";
import { eq } from "drizzle-orm";
@@ -34,7 +34,7 @@ export const createAdmin = async (input: typeof apiCreateAdmin._type) => {
if (!newAuth) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the user",
message: "Error creating the user",
});
}
@@ -68,7 +68,7 @@ export const createUser = async (input: typeof apiCreateUser._type) => {
if (!res) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the user",
message: "Error creating the user",
});
}

View File

@@ -2,11 +2,13 @@ import { db } from "@dokploy/server/db";
import { type apiCreateBackup, backups } from "@dokploy/server/db/schema";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { IS_CLOUD } from "../constants";
import { removeScheduleBackup, scheduleBackup } from "../utils/backups/utils";
export type Backup = typeof backups.$inferSelect;
export type BackupSchedule = Awaited<ReturnType<typeof findBackupById>>;
export type BackupScheduleList = Awaited<ReturnType<typeof findBackupsByDbId>>;
export const createBackup = async (input: typeof apiCreateBackup._type) => {
const newBackup = await db
.insert(backups)
@@ -19,7 +21,7 @@ export const createBackup = async (input: typeof apiCreateBackup._type) => {
if (!newBackup) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the Backup",
message: "Error creating the Backup",
});
}
@@ -69,3 +71,20 @@ export const removeBackupById = async (backupId: string) => {
return result[0];
};
export const findBackupsByDbId = async (
id: string,
type: "postgres" | "mysql" | "mariadb" | "mongo",
) => {
const result = await db.query.backups.findMany({
where: eq(backups[`${type}Id`], id),
with: {
postgres: true,
mysql: true,
mariadb: true,
mongo: true,
destination: true,
},
});
return result || [];
};

View File

@@ -28,7 +28,7 @@ export const createBitbucket = async (
if (!newGitProvider) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the git provider",
message: "Error creating the Bitbucket provider",
});
}

View File

@@ -2,8 +2,7 @@ import { join } from "node:path";
import { paths } from "@dokploy/server/constants";
import { db } from "@dokploy/server/db";
import { type apiCreateCompose, compose } from "@dokploy/server/db/schema";
import { generateAppName } from "@dokploy/server/db/schema";
import { generatePassword } from "@dokploy/server/templates/utils";
import { buildAppName, cleanAppName } from "@dokploy/server/db/schema";
import {
buildCompose,
getBuildComposeCommand,
@@ -45,24 +44,22 @@ import {
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { encodeBase64 } from "../utils/docker/utils";
import { getDokployUrl } from "./admin";
import { findAdminById, getDokployUrl } from "./admin";
import { createDeploymentCompose, updateDeploymentStatus } from "./deployment";
import { validUniqueServerAppName } from "./project";
import { cleanupFullDocker } from "./settings";
export type Compose = typeof compose.$inferSelect;
export const createCompose = async (input: typeof apiCreateCompose._type) => {
input.appName =
`${input.appName}-${generatePassword(6)}` || generateAppName("compose");
if (input.appName) {
const valid = await validUniqueServerAppName(input.appName);
const appName = buildAppName("compose", input.appName);
if (!valid) {
throw new TRPCError({
code: "CONFLICT",
message: "Service with this 'AppName' already exists",
});
}
const valid = await validUniqueServerAppName(appName);
if (!valid) {
throw new TRPCError({
code: "CONFLICT",
message: "Service with this 'AppName' already exists",
});
}
const newDestination = await db
@@ -70,6 +67,7 @@ export const createCompose = async (input: typeof apiCreateCompose._type) => {
.values({
...input,
composeFile: "",
appName,
})
.returning()
.then((value) => value[0]);
@@ -87,8 +85,9 @@ export const createCompose = async (input: typeof apiCreateCompose._type) => {
export const createComposeByTemplate = async (
input: typeof compose.$inferInsert,
) => {
if (input.appName) {
const valid = await validUniqueServerAppName(input.appName);
const appName = cleanAppName(input.appName);
if (appName) {
const valid = await validUniqueServerAppName(appName);
if (!valid) {
throw new TRPCError({
@@ -101,6 +100,7 @@ export const createComposeByTemplate = async (
.insert(compose)
.values({
...input,
appName,
})
.returning()
.then((value) => value[0]);
@@ -184,10 +184,11 @@ export const updateCompose = async (
composeId: string,
composeData: Partial<Compose>,
) => {
const { appName, ...rest } = composeData;
const composeResult = await db
.update(compose)
.set({
...composeData,
...rest,
})
.where(eq(compose.composeId, composeId))
.returning();
@@ -205,7 +206,10 @@ export const deployCompose = async ({
descriptionLog: string;
}) => {
const compose = await findComposeById(composeId);
const buildLink = `${await getDokployUrl()}/dashboard/project/${compose.projectId}/services/compose/${compose.composeId}?tab=deployments`;
const buildLink = `${await getDokployUrl()}/dashboard/project/${
compose.projectId
}/services/compose/${compose.composeId}?tab=deployments`;
const deployment = await createDeploymentCompose({
composeId: composeId,
title: titleLog,
@@ -213,6 +217,10 @@ export const deployCompose = async ({
});
try {
const admin = await findAdminById(compose.project.adminId);
if (admin.cleanupCacheOnCompose) {
await cleanupFullDocker(compose?.serverId);
}
if (compose.sourceType === "github") {
await cloneGithubRepository({
...compose,
@@ -240,6 +248,7 @@ export const deployCompose = async ({
applicationType: "compose",
buildLink,
adminId: compose.project.adminId,
domains: compose.domains,
});
} catch (error) {
await updateDeploymentStatus(deployment.deploymentId, "error");
@@ -251,7 +260,7 @@ export const deployCompose = async ({
applicationName: compose.name,
applicationType: "compose",
// @ts-ignore
errorMessage: error?.message || "Error to build",
errorMessage: error?.message || "Error building",
buildLink,
adminId: compose.project.adminId,
});
@@ -269,6 +278,7 @@ export const rebuildCompose = async ({
descriptionLog: string;
}) => {
const compose = await findComposeById(composeId);
const deployment = await createDeploymentCompose({
composeId: composeId,
title: titleLog,
@@ -276,6 +286,10 @@ export const rebuildCompose = async ({
});
try {
const admin = await findAdminById(compose.project.adminId);
if (admin.cleanupCacheOnCompose) {
await cleanupFullDocker(compose?.serverId);
}
if (compose.serverId) {
await getBuildComposeCommand(compose, deployment.logPath);
} else {
@@ -307,7 +321,10 @@ export const deployRemoteCompose = async ({
descriptionLog: string;
}) => {
const compose = await findComposeById(composeId);
const buildLink = `${await getDokployUrl()}/dashboard/project/${compose.projectId}/services/compose/${compose.composeId}?tab=deployments`;
const buildLink = `${await getDokployUrl()}/dashboard/project/${
compose.projectId
}/services/compose/${compose.composeId}?tab=deployments`;
const deployment = await createDeploymentCompose({
composeId: composeId,
title: titleLog,
@@ -315,6 +332,10 @@ export const deployRemoteCompose = async ({
});
try {
if (compose.serverId) {
const admin = await findAdminById(compose.project.adminId);
if (admin.cleanupCacheOnCompose) {
await cleanupFullDocker(compose?.serverId);
}
let command = "set -e;";
if (compose.sourceType === "github") {
@@ -361,6 +382,7 @@ export const deployRemoteCompose = async ({
applicationType: "compose",
buildLink,
adminId: compose.project.adminId,
domains: compose.domains,
});
} catch (error) {
// @ts-ignore
@@ -382,7 +404,7 @@ export const deployRemoteCompose = async ({
applicationName: compose.name,
applicationType: "compose",
// @ts-ignore
errorMessage: error?.message || "Error to build",
errorMessage: error?.message || "Error building",
buildLink,
adminId: compose.project.adminId,
});
@@ -400,6 +422,7 @@ export const rebuildRemoteCompose = async ({
descriptionLog: string;
}) => {
const compose = await findComposeById(composeId);
const deployment = await createDeploymentCompose({
composeId: composeId,
title: titleLog,
@@ -407,6 +430,10 @@ export const rebuildRemoteCompose = async ({
});
try {
const admin = await findAdminById(compose.project.adminId);
if (admin.cleanupCacheOnCompose) {
await cleanupFullDocker(compose?.serverId);
}
if (compose.serverId) {
await getBuildComposeCommand(compose, deployment.logPath);
}
@@ -436,13 +463,17 @@ export const rebuildRemoteCompose = async ({
return true;
};
export const removeCompose = async (compose: Compose) => {
export const removeCompose = async (
compose: Compose,
deleteVolumes: boolean,
) => {
try {
const { COMPOSE_PATH } = paths(!!compose.serverId);
const projectPath = join(COMPOSE_PATH, compose.appName);
if (compose.composeType === "stack") {
const command = `cd ${projectPath} && docker stack rm ${compose.appName} && rm -rf ${projectPath}`;
if (compose.serverId) {
await execAsyncRemote(compose.serverId, command);
} else {
@@ -452,7 +483,13 @@ export const removeCompose = async (compose: Compose) => {
cwd: projectPath,
});
} else {
const command = `cd ${projectPath} && docker compose -p ${compose.appName} down && rm -rf ${projectPath}`;
let command: string;
if (deleteVolumes) {
command = `cd ${projectPath} && docker compose -p ${compose.appName} down --volumes && rm -rf ${projectPath}`;
} else {
command = `cd ${projectPath} && docker compose -p ${compose.appName} down && rm -rf ${projectPath}`;
}
if (compose.serverId) {
await execAsyncRemote(compose.serverId, command);
} else {
@@ -476,7 +513,11 @@ export const startCompose = async (composeId: string) => {
if (compose.serverId) {
await execAsyncRemote(
compose.serverId,
`cd ${join(COMPOSE_PATH, compose.appName, "code")} && docker compose -p ${compose.appName} up -d`,
`cd ${join(
COMPOSE_PATH,
compose.appName,
"code",
)} && docker compose -p ${compose.appName} up -d`,
);
} else {
await execAsync(`docker compose -p ${compose.appName} up -d`, {
@@ -506,7 +547,9 @@ export const stopCompose = async (composeId: string) => {
if (compose.serverId) {
await execAsyncRemote(
compose.serverId,
`cd ${join(COMPOSE_PATH, compose.appName)} && docker compose -p ${compose.appName} stop`,
`cd ${join(COMPOSE_PATH, compose.appName)} && docker compose -p ${
compose.appName
} stop`,
);
} else {
await execAsync(`docker compose -p ${compose.appName} stop`, {

View File

@@ -23,8 +23,8 @@ import { type Server, findServerById } from "./server";
import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync";
import {
findPreviewDeploymentById,
type PreviewDeployment,
findPreviewDeploymentById,
updatePreviewDeployment,
} from "./preview-deployment";
@@ -93,16 +93,27 @@ export const createDeployment = async (
if (deploymentCreate.length === 0 || !deploymentCreate[0]) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the deployment",
message: "Error creating the deployment",
});
}
return deploymentCreate[0];
} catch (error) {
await db
.insert(deployments)
.values({
applicationId: deployment.applicationId,
title: deployment.title || "Deployment",
status: "error",
logPath: "",
description: deployment.description || "",
errorMessage: `An error have occured: ${error instanceof Error ? error.message : error}`,
})
.returning();
await updateApplicationStatus(application.applicationId, "error");
console.log(error);
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the deployment",
message: "Error creating the deployment",
});
}
};
@@ -159,18 +170,29 @@ export const createDeploymentPreview = async (
if (deploymentCreate.length === 0 || !deploymentCreate[0]) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the deployment",
message: "Error creating the deployment",
});
}
return deploymentCreate[0];
} catch (error) {
await db
.insert(deployments)
.values({
previewDeploymentId: deployment.previewDeploymentId,
title: deployment.title || "Deployment",
status: "error",
logPath: "",
description: deployment.description || "",
errorMessage: `An error have occured: ${error instanceof Error ? error.message : error}`,
})
.returning();
await updatePreviewDeployment(deployment.previewDeploymentId, {
previewStatus: "error",
});
console.log(error);
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the deployment",
message: "Error creating the deployment",
});
}
};
@@ -221,18 +243,29 @@ echo "Initializing deployment" >> ${logFilePath};
if (deploymentCreate.length === 0 || !deploymentCreate[0]) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the deployment",
message: "Error creating the deployment",
});
}
return deploymentCreate[0];
} catch (error) {
await db
.insert(deployments)
.values({
composeId: deployment.composeId,
title: deployment.title || "Deployment",
status: "error",
logPath: "",
description: deployment.description || "",
errorMessage: `An error have occured: ${error instanceof Error ? error.message : error}`,
})
.returning();
await updateCompose(compose.composeId, {
composeStatus: "error",
});
console.log(error);
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the deployment",
message: "Error creating the deployment",
});
}
};
@@ -247,7 +280,7 @@ export const removeDeployment = async (deploymentId: string) => {
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to delete this deployment",
message: "Error deleting this deployment",
});
}
};
@@ -497,14 +530,14 @@ export const createServerDeployment = async (
if (deploymentCreate.length === 0 || !deploymentCreate[0]) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the deployment",
message: "Error creating the deployment",
});
}
return deploymentCreate[0];
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the deployment",
message: "Error creating the deployment",
});
}
};

View File

@@ -157,6 +157,124 @@ export const getContainersByAppNameMatch = async (
return [];
};
export const getStackContainersByAppName = async (
appName: string,
serverId?: string,
) => {
try {
let result: string[] = [];
const command = `docker stack ps ${appName} --format 'CONTAINER ID : {{.ID}} | Name: {{.Name}} | State: {{.DesiredState}} | Node: {{.Node}}'`;
if (serverId) {
const { stdout, stderr } = await execAsyncRemote(serverId, command);
if (stderr) {
return [];
}
if (!stdout) return [];
result = stdout.trim().split("\n");
} else {
const { stdout, stderr } = await execAsync(command);
if (stderr) {
return [];
}
if (!stdout) return [];
result = stdout.trim().split("\n");
}
const containers = result.map((line) => {
const parts = line.split(" | ");
const containerId = parts[0]
? parts[0].replace("CONTAINER ID : ", "").trim()
: "No container id";
const name = parts[1]
? parts[1].replace("Name: ", "").trim()
: "No container name";
const state = parts[2]
? parts[2].replace("State: ", "").trim().toLowerCase()
: "No state";
const node = parts[3]
? parts[3].replace("Node: ", "").trim()
: "No specific node";
return {
containerId,
name,
state,
node,
};
});
return containers || [];
} catch (error) {}
return [];
};
export const getServiceContainersByAppName = async (
appName: string,
serverId?: string,
) => {
try {
let result: string[] = [];
const command = `docker service ps ${appName} --format 'CONTAINER ID : {{.ID}} | Name: {{.Name}} | State: {{.DesiredState}} | Node: {{.Node}}'`;
if (serverId) {
const { stdout, stderr } = await execAsyncRemote(serverId, command);
if (stderr) {
return [];
}
if (!stdout) return [];
result = stdout.trim().split("\n");
} else {
const { stdout, stderr } = await execAsync(command);
if (stderr) {
return [];
}
if (!stdout) return [];
result = stdout.trim().split("\n");
}
const containers = result.map((line) => {
const parts = line.split(" | ");
const containerId = parts[0]
? parts[0].replace("CONTAINER ID : ", "").trim()
: "No container id";
const name = parts[1]
? parts[1].replace("Name: ", "").trim()
: "No container name";
const state = parts[2]
? parts[2].replace("State: ", "").trim().toLowerCase()
: "No state";
const node = parts[3]
? parts[3].replace("Node: ", "").trim()
: "No specific node";
return {
containerId,
name,
state,
node,
};
});
return containers || [];
} catch (error) {}
return [];
};
export const getContainersByAppLabel = async (
appName: string,
serverId?: string,
@@ -224,3 +342,123 @@ export const containerRestart = async (containerId: string) => {
return config;
} catch (error) {}
};
export const getSwarmNodes = async (serverId?: string) => {
try {
let stdout = "";
let stderr = "";
const command = "docker node ls --format '{{json .}}'";
if (serverId) {
const result = await execAsyncRemote(serverId, command);
stdout = result.stdout;
stderr = result.stderr;
} else {
const result = await execAsync(command);
stdout = result.stdout;
stderr = result.stderr;
}
if (stderr) {
console.error(`Error: ${stderr}`);
return;
}
const nodesArray = stdout
.trim()
.split("\n")
.map((line) => JSON.parse(line));
return nodesArray;
} catch (error) {}
};
export const getNodeInfo = async (nodeId: string, serverId?: string) => {
try {
const command = `docker node inspect ${nodeId} --format '{{json .}}'`;
let stdout = "";
let stderr = "";
if (serverId) {
const result = await execAsyncRemote(serverId, command);
stdout = result.stdout;
stderr = result.stderr;
} else {
const result = await execAsync(command);
stdout = result.stdout;
stderr = result.stderr;
}
if (stderr) {
console.error(`Error: ${stderr}`);
return;
}
const nodeInfo = JSON.parse(stdout);
return nodeInfo;
} catch (error) {}
};
export const getNodeApplications = async (serverId?: string) => {
try {
let stdout = "";
let stderr = "";
const command = `docker service ls --format '{{json .}}'`;
if (serverId) {
const result = await execAsyncRemote(serverId, command);
stdout = result.stdout;
stderr = result.stderr;
} else {
const result = await execAsync(command);
stdout = result.stdout;
stderr = result.stderr;
}
if (stderr) {
console.error(`Error: ${stderr}`);
return;
}
const appArray = stdout
.trim()
.split("\n")
.map((line) => JSON.parse(line))
.filter((service) => !service.Name.startsWith("dokploy-"));
return appArray;
} catch (error) {}
};
export const getApplicationInfo = async (
appName: string,
serverId?: string,
) => {
try {
let stdout = "";
let stderr = "";
const command = `docker service ps ${appName} --format '{{json .}}' --no-trunc`;
if (serverId) {
const result = await execAsyncRemote(serverId, command);
stdout = result.stdout;
stderr = result.stderr;
} else {
const result = await execAsync(command);
stdout = result.stdout;
stderr = result.stderr;
}
if (stderr) {
console.error(`Error: ${stderr}`);
return;
}
const appArray = stdout
.trim()
.split("\n")
.map((line) => JSON.parse(line));
return appArray;
} catch (error) {}
};

View File

@@ -28,7 +28,7 @@ export const createGithub = async (
if (!newGitProvider) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the git provider",
message: "Error creating the Git provider",
});
}

View File

@@ -29,7 +29,7 @@ export const createGitlab = async (
if (!newGitProvider) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the git provider",
message: "Error creating the Git provider",
});
}

View File

@@ -4,7 +4,7 @@ import {
backups,
mariadb,
} from "@dokploy/server/db/schema";
import { generateAppName } from "@dokploy/server/db/schema";
import { buildAppName, cleanAppName } from "@dokploy/server/db/schema";
import { generatePassword } from "@dokploy/server/templates/utils";
import { buildMariadb } from "@dokploy/server/utils/databases/mariadb";
import { pullImage } from "@dokploy/server/utils/docker/utils";
@@ -17,17 +17,14 @@ import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync";
export type Mariadb = typeof mariadb.$inferSelect;
export const createMariadb = async (input: typeof apiCreateMariaDB._type) => {
input.appName =
`${input.appName}-${generatePassword(6)}` || generateAppName("mariadb");
if (input.appName) {
const valid = await validUniqueServerAppName(input.appName);
const appName = buildAppName("mariadb", input.appName);
if (!valid) {
throw new TRPCError({
code: "CONFLICT",
message: "Service with this 'AppName' already exists",
});
}
const valid = await validUniqueServerAppName(input.appName);
if (!valid) {
throw new TRPCError({
code: "CONFLICT",
message: "Service with this 'AppName' already exists",
});
}
const newMariadb = await db
@@ -40,6 +37,7 @@ export const createMariadb = async (input: typeof apiCreateMariaDB._type) => {
databaseRootPassword: input.databaseRootPassword
? input.databaseRootPassword
: generatePassword(),
appName,
})
.returning()
.then((value) => value[0]);
@@ -82,10 +80,11 @@ export const updateMariadbById = async (
mariadbId: string,
mariadbData: Partial<Mariadb>,
) => {
const { appName, ...rest } = mariadbData;
const result = await db
.update(mariadb)
.set({
...mariadbData,
...rest,
})
.where(eq(mariadb.mariadbId, mariadbId))
.returning();
@@ -121,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

@@ -1,6 +1,6 @@
import { db } from "@dokploy/server/db";
import { type apiCreateMongo, backups, mongo } from "@dokploy/server/db/schema";
import { generateAppName } from "@dokploy/server/db/schema";
import { buildAppName, cleanAppName } from "@dokploy/server/db/schema";
import { generatePassword } from "@dokploy/server/templates/utils";
import { buildMongo } from "@dokploy/server/utils/databases/mongo";
import { pullImage } from "@dokploy/server/utils/docker/utils";
@@ -13,17 +13,14 @@ import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync";
export type Mongo = typeof mongo.$inferSelect;
export const createMongo = async (input: typeof apiCreateMongo._type) => {
input.appName =
`${input.appName}-${generatePassword(6)}` || generateAppName("mongo");
if (input.appName) {
const valid = await validUniqueServerAppName(input.appName);
const appName = buildAppName("mongo", input.appName);
if (!valid) {
throw new TRPCError({
code: "CONFLICT",
message: "Service with this 'AppName' already exists",
});
}
const valid = await validUniqueServerAppName(appName);
if (!valid) {
throw new TRPCError({
code: "CONFLICT",
message: "Service with this 'AppName' already exists",
});
}
const newMongo = await db
@@ -33,6 +30,7 @@ export const createMongo = async (input: typeof apiCreateMongo._type) => {
databasePassword: input.databasePassword
? input.databasePassword
: generatePassword(),
appName,
})
.returning()
.then((value) => value[0]);
@@ -74,10 +72,11 @@ export const updateMongoById = async (
mongoId: string,
mongoData: Partial<Mongo>,
) => {
const { appName, ...rest } = mongoData;
const result = await db
.update(mongo)
.set({
...mongoData,
...rest,
})
.where(eq(mongo.mongoId, mongoId))
.returning();
@@ -113,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

@@ -52,7 +52,7 @@ export const createMount = async (input: typeof apiCreateMount._type) => {
if (!value) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error input: Inserting mount",
message: "Error inserting mount",
});
}
@@ -64,7 +64,7 @@ export const createMount = async (input: typeof apiCreateMount._type) => {
console.log(error);
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the mount",
message: `Error ${error instanceof Error ? error.message : error}`,
cause: error,
});
}
@@ -88,10 +88,10 @@ export const createFileMount = async (mountId: string) => {
await createFile(baseFilePath, mount.filePath || "", mount.content || "");
}
} catch (error) {
console.log(`Error to create the file mount: ${error}`);
console.log(`Error creating the file mount: ${error}`);
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the mount",
message: `Error creating the mount ${error instanceof Error ? error.message : error}`,
cause: error,
});
}

View File

@@ -1,6 +1,6 @@
import { db } from "@dokploy/server/db";
import { type apiCreateMySql, backups, mysql } from "@dokploy/server/db/schema";
import { generateAppName } 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";
@@ -13,18 +13,14 @@ import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync";
export type MySql = typeof mysql.$inferSelect;
export const createMysql = async (input: typeof apiCreateMySql._type) => {
input.appName =
`${input.appName}-${generatePassword(6)}` || generateAppName("mysql");
const appName = buildAppName("mysql", input.appName);
if (input.appName) {
const valid = await validUniqueServerAppName(input.appName);
if (!valid) {
throw new TRPCError({
code: "CONFLICT",
message: "Service with this 'AppName' already exists",
});
}
const valid = await validUniqueServerAppName(appName);
if (!valid) {
throw new TRPCError({
code: "CONFLICT",
message: "Service with this 'AppName' already exists",
});
}
const newMysql = await db
@@ -37,6 +33,7 @@ export const createMysql = async (input: typeof apiCreateMySql._type) => {
databaseRootPassword: input.databaseRootPassword
? input.databaseRootPassword
: generatePassword(),
appName,
})
.returning()
.then((value) => value[0]);
@@ -79,10 +76,11 @@ export const updateMySqlById = async (
mysqlId: string,
mysqlData: Partial<MySql>,
) => {
const { appName, ...rest } = mysqlData;
const result = await db
.update(mysql)
.set({
...mysqlData,
...rest,
})
.where(eq(mysql.mysqlId, mysqlId))
.returning();
@@ -118,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

@@ -2,14 +2,17 @@ import { db } from "@dokploy/server/db";
import {
type apiCreateDiscord,
type apiCreateEmail,
type apiCreateGotify,
type apiCreateSlack,
type apiCreateTelegram,
type apiUpdateDiscord,
type apiUpdateEmail,
type apiUpdateGotify,
type apiUpdateSlack,
type apiUpdateTelegram,
discord,
email,
gotify,
notifications,
slack,
telegram,
@@ -204,6 +207,7 @@ export const createDiscordNotification = async (
.insert(discord)
.values({
webhookUrl: input.webhookUrl,
decoration: input.decoration,
})
.returning()
.then((value) => value[0]);
@@ -272,6 +276,7 @@ export const updateDiscordNotification = async (
.update(discord)
.set({
webhookUrl: input.webhookUrl,
decoration: input.decoration,
})
.where(eq(discord.discordId, input.discordId))
.returning()
@@ -377,6 +382,96 @@ export const updateEmailNotification = async (
});
};
export const createGotifyNotification = async (
input: typeof apiCreateGotify._type,
adminId: string,
) => {
await db.transaction(async (tx) => {
const newGotify = await tx
.insert(gotify)
.values({
serverUrl: input.serverUrl,
appToken: input.appToken,
priority: input.priority,
decoration: input.decoration,
})
.returning()
.then((value) => value[0]);
if (!newGotify) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error input: Inserting gotify",
});
}
const newDestination = await tx
.insert(notifications)
.values({
gotifyId: newGotify.gotifyId,
name: input.name,
appDeploy: input.appDeploy,
appBuildError: input.appBuildError,
databaseBackup: input.databaseBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "gotify",
adminId: adminId,
})
.returning()
.then((value) => value[0]);
if (!newDestination) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error input: Inserting notification",
});
}
return newDestination;
});
};
export const updateGotifyNotification = async (
input: typeof apiUpdateGotify._type,
) => {
await db.transaction(async (tx) => {
const newDestination = await tx
.update(notifications)
.set({
name: input.name,
appDeploy: input.appDeploy,
appBuildError: input.appBuildError,
databaseBackup: input.databaseBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
adminId: input.adminId,
})
.where(eq(notifications.notificationId, input.notificationId))
.returning()
.then((value) => value[0]);
if (!newDestination) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error Updating notification",
});
}
await tx
.update(gotify)
.set({
serverUrl: input.serverUrl,
appToken: input.appToken,
priority: input.priority,
decoration: input.decoration,
})
.where(eq(gotify.gotifyId, input.gotifyId));
return newDestination;
});
};
export const findNotificationById = async (notificationId: string) => {
const notification = await db.query.notifications.findFirst({
where: eq(notifications.notificationId, notificationId),
@@ -385,6 +480,7 @@ export const findNotificationById = async (notificationId: string) => {
telegram: true,
discord: true,
email: true,
gotify: true,
},
});
if (!notification) {

View File

@@ -4,7 +4,7 @@ import {
backups,
postgres,
} from "@dokploy/server/db/schema";
import { generateAppName } from "@dokploy/server/db/schema";
import { buildAppName, cleanAppName } from "@dokploy/server/db/schema";
import { generatePassword } from "@dokploy/server/templates/utils";
import { buildPostgres } from "@dokploy/server/utils/databases/postgres";
import { pullImage } from "@dokploy/server/utils/docker/utils";
@@ -17,17 +17,14 @@ import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync";
export type Postgres = typeof postgres.$inferSelect;
export const createPostgres = async (input: typeof apiCreatePostgres._type) => {
input.appName =
`${input.appName}-${generatePassword(6)}` || generateAppName("postgres");
if (input.appName) {
const valid = await validUniqueServerAppName(input.appName);
const appName = buildAppName("postgres", input.appName);
if (!valid) {
throw new TRPCError({
code: "CONFLICT",
message: "Service with this 'AppName' already exists",
});
}
const valid = await validUniqueServerAppName(appName);
if (!valid) {
throw new TRPCError({
code: "CONFLICT",
message: "Service with this 'AppName' already exists",
});
}
const newPostgres = await db
@@ -37,6 +34,7 @@ export const createPostgres = async (input: typeof apiCreatePostgres._type) => {
databasePassword: input.databasePassword
? input.databasePassword
: generatePassword(),
appName,
})
.returning()
.then((value) => value[0]);
@@ -96,10 +94,11 @@ export const updatePostgresById = async (
postgresId: string,
postgresData: Partial<Postgres>,
) => {
const { appName, ...rest } = postgresData;
const result = await db
.update(postgres)
.set({
...postgresData,
...rest,
})
.where(eq(postgres.postgresId, postgresId))
.returning();
@@ -116,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

@@ -7,20 +7,20 @@ import {
import { TRPCError } from "@trpc/server";
import { and, desc, eq } from "drizzle-orm";
import { slugify } from "../setup/server-setup";
import { findApplicationById } from "./application";
import { createDomain } from "./domain";
import { generatePassword, generateRandomDomain } from "../templates/utils";
import { removeService } from "../utils/docker/utils";
import { removeDirectoryCode } from "../utils/filesystem/directory";
import { authGithub } from "../utils/providers/github";
import { removeTraefikConfig } from "../utils/traefik/application";
import { manageDomain } from "../utils/traefik/domain";
import { findAdminById } from "./admin";
import { findApplicationById } from "./application";
import {
removeDeployments,
removeDeploymentsByPreviewDeploymentId,
} from "./deployment";
import { removeDirectoryCode } from "../utils/filesystem/directory";
import { removeTraefikConfig } from "../utils/traefik/application";
import { removeService } from "../utils/docker/utils";
import { authGithub } from "../utils/providers/github";
import { getIssueComment, type Github } from "./github";
import { findAdminById } from "./admin";
import { createDomain } from "./domain";
import { type Github, getIssueComment } from "./github";
export type PreviewDeployment = typeof previewDeployments.$inferSelect;
@@ -112,7 +112,7 @@ export const removePreviewDeployment = async (previewDeploymentId: string) => {
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to delete this preview deployment",
message: "Error deleting this preview deployment",
});
}
};
@@ -189,7 +189,7 @@ export const createPreviewDeployment = async (
if (!previewDeployment) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the preview deployment",
message: "Error creating the preview deployment",
});
}

View File

@@ -30,7 +30,7 @@ export const createProject = async (
if (!newProject) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the project",
message: "Error creating the project",
});
}

View File

@@ -40,7 +40,7 @@ export const createRedirect = async (
if (!redirect) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the redirect",
message: "Error creating the redirect",
});
}
@@ -53,7 +53,7 @@ export const createRedirect = async (
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create this redirect",
message: "Error creating this redirect",
cause: error,
});
}
@@ -82,7 +82,7 @@ export const removeRedirectById = async (redirectId: string) => {
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to remove this redirect",
message: "Error removing this redirect",
cause: error,
});
}
@@ -116,7 +116,7 @@ export const updateRedirectById = async (
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to update this redirect",
message: "Error updating this redirect",
});
}
};

View File

@@ -1,6 +1,6 @@
import { db } from "@dokploy/server/db";
import { type apiCreateRedis, redis } from "@dokploy/server/db/schema";
import { generateAppName } from "@dokploy/server/db/schema";
import { buildAppName, cleanAppName } from "@dokploy/server/db/schema";
import { generatePassword } from "@dokploy/server/templates/utils";
import { buildRedis } from "@dokploy/server/utils/databases/redis";
import { pullImage } from "@dokploy/server/utils/docker/utils";
@@ -14,17 +14,14 @@ export type Redis = typeof redis.$inferSelect;
// https://github.com/drizzle-team/drizzle-orm/discussions/1483#discussioncomment-7523881
export const createRedis = async (input: typeof apiCreateRedis._type) => {
input.appName =
`${input.appName}-${generatePassword(6)}` || generateAppName("redis");
if (input.appName) {
const valid = await validUniqueServerAppName(input.appName);
const appName = buildAppName("redis", input.appName);
if (!valid) {
throw new TRPCError({
code: "CONFLICT",
message: "Service with this 'AppName' already exists",
});
}
const valid = await validUniqueServerAppName(appName);
if (!valid) {
throw new TRPCError({
code: "CONFLICT",
message: "Service with this 'AppName' already exists",
});
}
const newRedis = await db
@@ -34,6 +31,7 @@ export const createRedis = async (input: typeof apiCreateRedis._type) => {
databasePassword: input.databasePassword
? input.databasePassword
: generatePassword(),
appName,
})
.returning()
.then((value) => value[0]);
@@ -70,10 +68,11 @@ export const updateRedisById = async (
redisId: string,
redisData: Partial<Redis>,
) => {
const { appName, ...rest } = redisData;
const result = await db
.update(redis)
.set({
...redisData,
...rest,
})
.where(eq(redis.redisId, redisId))
.returning();
@@ -90,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

@@ -71,7 +71,7 @@ export const removeRegistry = async (registryId: string) => {
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to remove this registry",
message: "Error removing this registry",
cause: error,
});
}
@@ -114,7 +114,7 @@ export const updateRegistry = async (
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to update this registry",
message: "Error updating this registry",
});
}
};

View File

@@ -41,7 +41,7 @@ export const createSecurity = async (
if (!securityResponse) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the security",
message: "Error creating the security",
});
}
await createSecurityMiddleware(application, securityResponse);
@@ -50,7 +50,7 @@ export const createSecurity = async (
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create this security",
message: "Error creating this security",
cause: error,
});
}
@@ -78,7 +78,7 @@ export const deleteSecurityById = async (securityId: string) => {
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to remove this security",
message: "Error removing this security",
});
}
};
@@ -100,7 +100,7 @@ export const updateSecurityById = async (
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to update this security",
message: "Error updating this security",
});
}
};

View File

@@ -21,7 +21,7 @@ export const createServer = async (
if (!newServer) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the server",
message: "Error creating the server",
});
}

View File

@@ -1,41 +1,109 @@
import { readdirSync } from "node:fs";
import { join } from "node:path";
import { docker } from "@dokploy/server/constants";
import { getServiceContainer } from "@dokploy/server/utils/docker/utils";
import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync";
import {
execAsync,
execAsyncRemote,
} from "@dokploy/server/utils/process/execAsync";
import { findAdminById } from "./admin";
// import packageInfo from "../../../package.json";
const updateIsAvailable = async () => {
try {
const service = await getServiceContainer("dokploy");
export interface IUpdateData {
latestVersion: string | null;
updateAvailable: boolean;
}
const localImage = await docker.getImage(getDokployImage()).inspect();
return localImage.Id !== service?.ImageID;
} catch (error) {
return false;
}
export const DEFAULT_UPDATE_DATA: IUpdateData = {
latestVersion: null,
updateAvailable: false,
};
/** Returns current Dokploy docker image tag or `latest` by default. */
export const getDokployImageTag = () => {
return process.env.RELEASE_TAG || "latest";
};
export const getDokployImage = () => {
return `dokploy/dokploy:${process.env.RELEASE_TAG || "latest"}`;
return `dokploy/dokploy:${getDokployImageTag()}`;
};
export const pullLatestRelease = async () => {
try {
const stream = await docker.pull(getDokployImage(), {});
await new Promise((resolve, reject) => {
docker.modem.followProgress(stream, (err, res) =>
err ? reject(err) : resolve(res),
);
});
const newUpdateIsAvailable = await updateIsAvailable();
return newUpdateIsAvailable;
} catch (error) {}
return false;
const stream = await docker.pull(getDokployImage());
await new Promise((resolve, reject) => {
docker.modem.followProgress(stream, (err, res) =>
err ? reject(err) : resolve(res),
);
});
};
export const getDokployVersion = () => {
// return packageInfo.version;
/** Returns Dokploy docker service image digest */
export const getServiceImageDigest = async () => {
const { stdout } = await execAsync(
"docker service inspect dokploy --format '{{.Spec.TaskTemplate.ContainerSpec.Image}}'",
);
const currentDigest = stdout.trim().split("@")[1];
if (!currentDigest) {
throw new Error("Could not get current service image digest");
}
return currentDigest;
};
/** Returns latest version number and information whether server update is available by comparing current image's digest against digest for provided image tag via Docker hub API. */
export const getUpdateData = async (): Promise<IUpdateData> => {
let currentDigest: string;
try {
currentDigest = await getServiceImageDigest();
} catch {
// Docker service might not exist locally
// You can run the # Installation command for docker service create mentioned in the below docs to test it locally:
// https://docs.dokploy.com/docs/core/manual-installation
return DEFAULT_UPDATE_DATA;
}
const baseUrl = "https://hub.docker.com/v2/repositories/dokploy/dokploy/tags";
let url: string | null = `${baseUrl}?page_size=100`;
let allResults: { digest: string; name: string }[] = [];
while (url) {
const response = await fetch(url, {
method: "GET",
headers: { "Content-Type": "application/json" },
});
const data = (await response.json()) as {
next: string | null;
results: { digest: string; name: string }[];
};
allResults = allResults.concat(data.results);
url = data?.next;
}
const imageTag = getDokployImageTag();
const searchedDigest = allResults.find((t) => t.name === imageTag)?.digest;
if (!searchedDigest) {
return DEFAULT_UPDATE_DATA;
}
if (imageTag === "latest") {
const versionedTag = allResults.find(
(t) => t.digest === searchedDigest && t.name.startsWith("v"),
);
if (!versionedTag) {
return DEFAULT_UPDATE_DATA;
}
const { name: latestVersion, digest } = versionedTag;
const updateAvailable = digest !== currentDigest;
return { latestVersion, updateAvailable };
}
const updateAvailable = searchedDigest !== currentDigest;
return { latestVersion: imageTag, updateAvailable };
};
interface TreeDataItem {
@@ -146,3 +214,35 @@ echo "$json_output"
}
return result;
};
export const cleanupFullDocker = async (serverId?: string | null) => {
const cleanupImages = "docker image prune --force";
const cleanupVolumes = "docker volume prune --force";
const cleanupContainers = "docker container prune --force";
const cleanupSystem = "docker system prune --all --force --volumes";
const cleanupBuilder = "docker builder prune --all --force";
try {
if (serverId) {
await execAsyncRemote(
serverId,
`
${cleanupImages}
${cleanupVolumes}
${cleanupContainers}
${cleanupSystem}
${cleanupBuilder}
`,
);
}
await execAsync(`
${cleanupImages}
${cleanupVolumes}
${cleanupContainers}
${cleanupSystem}
${cleanupBuilder}
`);
} catch (error) {
console.log(error);
}
};

View File

@@ -21,7 +21,7 @@ export const createSshKey = async (input: typeof apiCreateSshKey._type) => {
if (!sshKey) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the ssh key",
message: "Error creating the SSH Key",
});
}
return sshKey;

View File

@@ -54,7 +54,7 @@ export const addNewProject = async (authId: string, projectId: string) => {
await db
.update(users)
.set({
accesedProjects: [...user.accesedProjects, projectId],
accessedProjects: [...user.accessedProjects, projectId],
})
.where(eq(users.authId, authId));
};
@@ -64,7 +64,7 @@ export const addNewService = async (authId: string, serviceId: string) => {
await db
.update(users)
.set({
accesedServices: [...user.accesedServices, serviceId],
accessedServices: [...user.accessedServices, serviceId],
})
.where(eq(users.authId, authId));
};
@@ -73,8 +73,9 @@ export const canPerformCreationService = async (
userId: string,
projectId: string,
) => {
const { accesedProjects, canCreateServices } = await findUserByAuthId(userId);
const haveAccessToProject = accesedProjects.includes(projectId);
const { accessedProjects, canCreateServices } =
await findUserByAuthId(userId);
const haveAccessToProject = accessedProjects.includes(projectId);
if (canCreateServices && haveAccessToProject) {
return true;
@@ -87,8 +88,8 @@ export const canPerformAccessService = async (
userId: string,
serviceId: string,
) => {
const { accesedServices } = await findUserByAuthId(userId);
const haveAccessToService = accesedServices.includes(serviceId);
const { accessedServices } = await findUserByAuthId(userId);
const haveAccessToService = accessedServices.includes(serviceId);
if (haveAccessToService) {
return true;
@@ -101,8 +102,9 @@ export const canPeformDeleteService = async (
authId: string,
serviceId: string,
) => {
const { accesedServices, canDeleteServices } = await findUserByAuthId(authId);
const haveAccessToService = accesedServices.includes(serviceId);
const { accessedServices, canDeleteServices } =
await findUserByAuthId(authId);
const haveAccessToService = accessedServices.includes(serviceId);
if (canDeleteServices && haveAccessToService) {
return true;
@@ -135,9 +137,9 @@ export const canPerformAccessProject = async (
authId: string,
projectId: string,
) => {
const { accesedProjects } = await findUserByAuthId(authId);
const { accessedProjects } = await findUserByAuthId(authId);
const haveAccessToProject = accesedProjects.includes(projectId);
const haveAccessToProject = accessedProjects.includes(projectId);
if (haveAccessToProject) {
return true;