import path from "node:path";import { paths } from "@dokploy/server/constants";import { db } from "@dokploy/server/db";import { type apiCreateMount, mounts, type ServiceType,} from "@dokploy/server/db/schema";import { createFile, encodeBase64, getCreateFileCommand,} from "@dokploy/server/utils/docker/utils";import { removeFileOrDirectory } from "@dokploy/server/utils/filesystem/directory";import { execAsync, execAsyncRemote,} from "@dokploy/server/utils/process/execAsync";import { TRPCError } from "@trpc/server";import { eq, type SQL, sql } from "drizzle-orm";import type { z } from "zod"; export type Mount = typeof mounts.$inferSelect; export const createMount = async (input: z.infer<typeof apiCreateMount>) => { try { const { serviceId, ...rest } = input; const value = await db .insert(mounts) .values({ ...rest, ...(input.serviceType === "application" && { applicationId: serviceId, }), ...(input.serviceType === "compose" && { composeId: serviceId, }), ...(input.serviceType === "libsql" && { libsqlId: serviceId, }), ...(input.serviceType === "mariadb" && { mariadbId: serviceId, }), ...(input.serviceType === "mongo" && { mongoId: serviceId, }), ...(input.serviceType === "mysql" && { mysqlId: serviceId, }), ...(input.serviceType === "postgres" && { postgresId: serviceId, }), ...(input.serviceType === "redis" && { redisId: serviceId, }), }) .returning() .then((value) => value[0]); if (!value) { throw new TRPCError({ code: "BAD_REQUEST", message: "Error inserting mount", }); } if (value.type === "file") { await createFileMount(value.mountId); } return value; } catch (error) { console.log(error); throw new TRPCError({ code: "BAD_REQUEST", message: `Error ${error instanceof Error ? error.message : error}`, cause: error, }); }}; export const createFileMount = async (mountId: string) => { try { const mount = await findMountById(mountId); const baseFilePath = await getBaseFilesPath(mountId); const serverId = await getServerId(mount); if (serverId) { const command = getCreateFileCommand( baseFilePath, mount.filePath || "", mount.content || "", ); await execAsyncRemote(serverId, command); } else { await createFile(baseFilePath, mount.filePath || "", mount.content || ""); } } catch (error) { console.log(`Error creating the file mount: ${error}`); throw new TRPCError({ code: "BAD_REQUEST", message: `Error creating the mount ${error instanceof Error ? error.message : error}`, cause: error, }); }}; export const findMountById = async (mountId: string) => { const mount = await db.query.mounts.findFirst({ where: eq(mounts.mountId, mountId), with: { application: { with: { environment: { with: { project: true, }, }, }, }, compose: { with: { environment: { with: { project: true, }, }, }, }, libsql: { with: { environment: { with: { project: true, }, }, }, }, mariadb: { with: { environment: { with: { project: true, }, }, }, }, mongo: { with: { environment: { with: { project: true, }, }, }, }, mysql: { with: { environment: { with: { project: true, }, }, }, }, postgres: { with: { environment: { with: { project: true, }, }, }, }, redis: { with: { environment: { with: { project: true, }, }, }, }, }, }); if (!mount) { throw new TRPCError({ code: "NOT_FOUND", message: "Mount not found", }); } return mount;}; export const findMountOrganizationId = async (mountId: string) => { const mount = await findMountById(mountId); if (mount.application) { return mount.application.environment.project.organizationId; } if (mount.compose) { return mount.compose.environment.project.organizationId; } if (mount.libsql) { return mount.libsql.environment.project.organizationId; } if (mount.mariadb) { return mount.mariadb.environment.project.organizationId; } if (mount.mongo) { return mount.mongo.environment.project.organizationId; } if (mount.mysql) { return mount.mysql.environment.project.organizationId; } if (mount.postgres) { return mount.postgres.environment.project.organizationId; } if (mount.redis) { return mount.redis.environment.project.organizationId; } return null;}; export const updateMount = async ( mountId: string, mountData: Partial<Mount>,) => { const mount = await db.transaction(async (tx) => { const mount = await tx .update(mounts) .set({ ...mountData, }) .where(eq(mounts.mountId, mountId)) .returning() .then((value) => value[0]); if (!mount) { throw new TRPCError({ code: "NOT_FOUND", message: "Mount not found", }); } return await findMountById(mountId); }); if (mount.type === "file") { await updateFileMount(mountId); } return mount;}; export const findMountsByApplicationId = async ( serviceId: string, serviceType: ServiceType,) => { const sqlChunks: SQL[] = []; switch (serviceType) { case "application": sqlChunks.push(eq(mounts.applicationId, serviceId)); break; case "libsql": sqlChunks.push(eq(mounts.libsqlId, serviceId)); break; case "mariadb": sqlChunks.push(eq(mounts.mariadbId, serviceId)); break; case "mongo": sqlChunks.push(eq(mounts.mongoId, serviceId)); break; case "mysql": sqlChunks.push(eq(mounts.mysqlId, serviceId)); break; case "postgres": sqlChunks.push(eq(mounts.postgresId, serviceId)); break; case "redis": sqlChunks.push(eq(mounts.redisId, serviceId)); break; case "compose": sqlChunks.push(eq(mounts.composeId, serviceId)); break; default: throw new Error(`Unknown service type: ${serviceType}`); } const mount = await db.query.mounts.findMany({ where: sql.join(sqlChunks, sql.raw(" ")), }); return mount;}; export const deleteMount = async (mountId: string) => { const { type } = await findMountById(mountId); if (type === "file") { await deleteFileMount(mountId); } const deletedMount = await db .delete(mounts) .where(eq(mounts.mountId, mountId)) .returning(); return deletedMount[0];}; export const updateFileMount = async (mountId: string) => { const mount = await findMountById(mountId); if (!mount || !mount.filePath) return; const basePath = await getBaseFilesPath(mountId); const fullPath = path.join(basePath, mount.filePath); try { const serverId = await getServerId(mount); const encodedContent = encodeBase64(mount.content || ""); const command = `echo "${encodedContent}" | base64 -d > ${fullPath}`; if (serverId) { await execAsyncRemote(serverId, command); } else { await execAsync(command); } } catch { console.log("Error updating file mount"); }}; export const deleteFileMount = async (mountId: string) => { const mount = await findMountById(mountId); if (!mount.filePath) return; const basePath = await getBaseFilesPath(mountId); const fullPath = path.join(basePath, mount.filePath); try { const serverId = await getServerId(mount); if (serverId) { const command = `rm -rf ${fullPath}`; await execAsyncRemote(serverId, command); } else { await removeFileOrDirectory(fullPath); } } catch {}}; export const getBaseFilesPath = async (mountId: string) => { const mount = await findMountById(mountId); let absoluteBasePath = ""; let appName = ""; let directoryPath = ""; if (mount.serviceType === "application" && mount.application) { const { APPLICATIONS_PATH } = paths(!!mount.application.serverId); absoluteBasePath = path.resolve(APPLICATIONS_PATH); appName = mount.application.appName; } else if (mount.serviceType === "postgres" && mount.postgres) { const { APPLICATIONS_PATH } = paths(!!mount.postgres.serverId); absoluteBasePath = path.resolve(APPLICATIONS_PATH); appName = mount.postgres.appName; } else if (mount.serviceType === "mariadb" && mount.mariadb) { const { APPLICATIONS_PATH } = paths(!!mount.mariadb.serverId); absoluteBasePath = path.resolve(APPLICATIONS_PATH); appName = mount.mariadb.appName; } else if (mount.serviceType === "mongo" && mount.mongo) { const { APPLICATIONS_PATH } = paths(!!mount.mongo.serverId); absoluteBasePath = path.resolve(APPLICATIONS_PATH); appName = mount.mongo.appName; } else if (mount.serviceType === "mysql" && mount.mysql) { const { APPLICATIONS_PATH } = paths(!!mount.mysql.serverId); absoluteBasePath = path.resolve(APPLICATIONS_PATH); appName = mount.mysql.appName; } else if (mount.serviceType === "redis" && mount.redis) { const { APPLICATIONS_PATH } = paths(!!mount.redis.serverId); absoluteBasePath = path.resolve(APPLICATIONS_PATH); appName = mount.redis.appName; } else if (mount.serviceType === "compose" && mount.compose) { const { COMPOSE_PATH } = paths(!!mount.compose.serverId); appName = mount.compose.appName; absoluteBasePath = path.resolve(COMPOSE_PATH); } else if (mount.serviceType === "libsql" && mount.libsql) { const { APPLICATIONS_PATH } = paths(!!mount.libsql.serverId); absoluteBasePath = path.resolve(APPLICATIONS_PATH); appName = mount.libsql.appName; } directoryPath = path.join(absoluteBasePath, appName, "files"); return directoryPath;}; type MountNested = Awaited<ReturnType<typeof findMountById>>;export const getServerId = async (mount: MountNested) => { if (mount.serviceType === "application" && mount?.application?.serverId) { return mount.application.serverId; } if (mount.serviceType === "postgres" && mount?.postgres?.serverId) { return mount.postgres.serverId; } if (mount.serviceType === "mariadb" && mount?.mariadb?.serverId) { return mount.mariadb.serverId; } if (mount.serviceType === "mongo" && mount?.mongo?.serverId) { return mount.mongo.serverId; } if (mount.serviceType === "mysql" && mount?.mysql?.serverId) { return mount.mysql.serverId; } if (mount.serviceType === "redis" && mount?.redis?.serverId) { return mount.redis.serverId; } if (mount.serviceType === "compose" && mount?.compose?.serverId) { return mount.compose.serverId; } if (mount.serviceType === "libsql" && mount?.libsql?.serverId) { return mount.libsql.serverId; } return null;};
import path from "node:path";
import { paths } from "@dokploy/server/constants";
import { db } from "@dokploy/server/db";
import {
type apiCreateMount,
mounts,
type ServiceType,
} from "@dokploy/server/db/schema";
import {
createFile,
encodeBase64,
getCreateFileCommand,
} from "@dokploy/server/utils/docker/utils";
import { removeFileOrDirectory } from "@dokploy/server/utils/filesystem/directory";
import {
execAsync,
execAsyncRemote,
} from "@dokploy/server/utils/process/execAsync";
import { TRPCError } from "@trpc/server";
import { eq, type SQL, sql } from "drizzle-orm";
import type { z } from "zod";
export type Mount = typeof mounts.$inferSelect;
export const createMount = async (input: z.infer<typeof apiCreateMount>) => {
try {
const { serviceId, ...rest } = input;
const value = await db
.insert(mounts)
.values({
...rest,
...(input.serviceType === "application" && {
applicationId: serviceId,
}),
...(input.serviceType === "compose" && {
composeId: serviceId,
}),
...(input.serviceType === "libsql" && {
libsqlId: serviceId,
}),
...(input.serviceType === "mariadb" && {
mariadbId: serviceId,
}),
...(input.serviceType === "mongo" && {
mongoId: serviceId,
}),
...(input.serviceType === "mysql" && {
mysqlId: serviceId,
}),
...(input.serviceType === "postgres" && {
postgresId: serviceId,
}),
...(input.serviceType === "redis" && {
redisId: serviceId,
}),
})
.returning()
.then((value) => value[0]);
if (!value) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error inserting mount",
});
}
if (value.type === "file") {
await createFileMount(value.mountId);
}
return value;
} catch (error) {
console.log(error);
throw new TRPCError({
code: "BAD_REQUEST",
message: `Error ${error instanceof Error ? error.message : error}`,
cause: error,
});
}
};
export const createFileMount = async (mountId: string) => {
try {
const mount = await findMountById(mountId);
const baseFilePath = await getBaseFilesPath(mountId);
const serverId = await getServerId(mount);
if (serverId) {
const command = getCreateFileCommand(
baseFilePath,
mount.filePath || "",
mount.content || "",
);
await execAsyncRemote(serverId, command);
} else {
await createFile(baseFilePath, mount.filePath || "", mount.content || "");
}
} catch (error) {
console.log(`Error creating the file mount: ${error}`);
throw new TRPCError({
code: "BAD_REQUEST",
message: `Error creating the mount ${error instanceof Error ? error.message : error}`,
cause: error,
});
}
};
export const findMountById = async (mountId: string) => {
const mount = await db.query.mounts.findFirst({
where: eq(mounts.mountId, mountId),
with: {
application: {
with: {
environment: {
with: {
project: true,
},
},
},
},
compose: {
with: {
environment: {
with: {
project: true,
},
},
},
},
libsql: {
with: {
environment: {
with: {
project: true,
},
},
},
},
mariadb: {
with: {
environment: {
with: {
project: true,
},
},
},
},
mongo: {
with: {
environment: {
with: {
project: true,
},
},
},
},
mysql: {
with: {
environment: {
with: {
project: true,
},
},
},
},
postgres: {
with: {
environment: {
with: {
project: true,
},
},
},
},
redis: {
with: {
environment: {
with: {
project: true,
},
},
},
},
},
});
if (!mount) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Mount not found",
});
}
return mount;
};
export const findMountOrganizationId = async (mountId: string) => {
const mount = await findMountById(mountId);
if (mount.application) {
return mount.application.environment.project.organizationId;
}
if (mount.compose) {
return mount.compose.environment.project.organizationId;
}
if (mount.libsql) {
return mount.libsql.environment.project.organizationId;
}
if (mount.mariadb) {
return mount.mariadb.environment.project.organizationId;
}
if (mount.mongo) {
return mount.mongo.environment.project.organizationId;
}
if (mount.mysql) {
return mount.mysql.environment.project.organizationId;
}
if (mount.postgres) {
return mount.postgres.environment.project.organizationId;
}
if (mount.redis) {
return mount.redis.environment.project.organizationId;
}
return null;
};
export const updateMount = async (
mountId: string,
mountData: Partial<Mount>,
) => {
const mount = await db.transaction(async (tx) => {
const mount = await tx
.update(mounts)
.set({
...mountData,
})
.where(eq(mounts.mountId, mountId))
.returning()
.then((value) => value[0]);
if (!mount) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Mount not found",
});
}
return await findMountById(mountId);
});
if (mount.type === "file") {
await updateFileMount(mountId);
}
return mount;
};
export const findMountsByApplicationId = async (
serviceId: string,
serviceType: ServiceType,
) => {
const sqlChunks: SQL[] = [];
switch (serviceType) {
case "application":
sqlChunks.push(eq(mounts.applicationId, serviceId));
break;
case "libsql":
sqlChunks.push(eq(mounts.libsqlId, serviceId));
break;
case "mariadb":
sqlChunks.push(eq(mounts.mariadbId, serviceId));
break;
case "mongo":
sqlChunks.push(eq(mounts.mongoId, serviceId));
break;
case "mysql":
sqlChunks.push(eq(mounts.mysqlId, serviceId));
break;
case "postgres":
sqlChunks.push(eq(mounts.postgresId, serviceId));
break;
case "redis":
sqlChunks.push(eq(mounts.redisId, serviceId));
break;
case "compose":
sqlChunks.push(eq(mounts.composeId, serviceId));
break;
default:
throw new Error(`Unknown service type: ${serviceType}`);
}
const mount = await db.query.mounts.findMany({
where: sql.join(sqlChunks, sql.raw(" ")),
});
return mount;
};
export const deleteMount = async (mountId: string) => {
const { type } = await findMountById(mountId);
if (type === "file") {
await deleteFileMount(mountId);
}
const deletedMount = await db
.delete(mounts)
.where(eq(mounts.mountId, mountId))
.returning();
return deletedMount[0];
};
export const updateFileMount = async (mountId: string) => {
const mount = await findMountById(mountId);
if (!mount || !mount.filePath) return;
const basePath = await getBaseFilesPath(mountId);
const fullPath = path.join(basePath, mount.filePath);
try {
const serverId = await getServerId(mount);
const encodedContent = encodeBase64(mount.content || "");
const command = `echo "${encodedContent}" | base64 -d > ${fullPath}`;
if (serverId) {
await execAsyncRemote(serverId, command);
} else {
await execAsync(command);
}
} catch {
console.log("Error updating file mount");
}
};
export const deleteFileMount = async (mountId: string) => {
const mount = await findMountById(mountId);
if (!mount.filePath) return;
const basePath = await getBaseFilesPath(mountId);
const fullPath = path.join(basePath, mount.filePath);
try {
const serverId = await getServerId(mount);
if (serverId) {
const command = `rm -rf ${fullPath}`;
await execAsyncRemote(serverId, command);
} else {
await removeFileOrDirectory(fullPath);
}
} catch {}
};
export const getBaseFilesPath = async (mountId: string) => {
const mount = await findMountById(mountId);
let absoluteBasePath = "";
let appName = "";
let directoryPath = "";
if (mount.serviceType === "application" && mount.application) {
const { APPLICATIONS_PATH } = paths(!!mount.application.serverId);
absoluteBasePath = path.resolve(APPLICATIONS_PATH);
appName = mount.application.appName;
} else if (mount.serviceType === "postgres" && mount.postgres) {
const { APPLICATIONS_PATH } = paths(!!mount.postgres.serverId);
absoluteBasePath = path.resolve(APPLICATIONS_PATH);
appName = mount.postgres.appName;
} else if (mount.serviceType === "mariadb" && mount.mariadb) {
const { APPLICATIONS_PATH } = paths(!!mount.mariadb.serverId);
absoluteBasePath = path.resolve(APPLICATIONS_PATH);
appName = mount.mariadb.appName;
} else if (mount.serviceType === "mongo" && mount.mongo) {
const { APPLICATIONS_PATH } = paths(!!mount.mongo.serverId);
absoluteBasePath = path.resolve(APPLICATIONS_PATH);
appName = mount.mongo.appName;
} else if (mount.serviceType === "mysql" && mount.mysql) {
const { APPLICATIONS_PATH } = paths(!!mount.mysql.serverId);
absoluteBasePath = path.resolve(APPLICATIONS_PATH);
appName = mount.mysql.appName;
} else if (mount.serviceType === "redis" && mount.redis) {
const { APPLICATIONS_PATH } = paths(!!mount.redis.serverId);
absoluteBasePath = path.resolve(APPLICATIONS_PATH);
appName = mount.redis.appName;
} else if (mount.serviceType === "compose" && mount.compose) {
const { COMPOSE_PATH } = paths(!!mount.compose.serverId);
appName = mount.compose.appName;
absoluteBasePath = path.resolve(COMPOSE_PATH);
} else if (mount.serviceType === "libsql" && mount.libsql) {
const { APPLICATIONS_PATH } = paths(!!mount.libsql.serverId);
absoluteBasePath = path.resolve(APPLICATIONS_PATH);
appName = mount.libsql.appName;
}
directoryPath = path.join(absoluteBasePath, appName, "files");
return directoryPath;
};
type MountNested = Awaited<ReturnType<typeof findMountById>>;
export const getServerId = async (mount: MountNested) => {
if (mount.serviceType === "application" && mount?.application?.serverId) {
return mount.application.serverId;
}
if (mount.serviceType === "postgres" && mount?.postgres?.serverId) {
return mount.postgres.serverId;
}
if (mount.serviceType === "mariadb" && mount?.mariadb?.serverId) {
return mount.mariadb.serverId;
}
if (mount.serviceType === "mongo" && mount?.mongo?.serverId) {
return mount.mongo.serverId;
}
if (mount.serviceType === "mysql" && mount?.mysql?.serverId) {
return mount.mysql.serverId;
}
if (mount.serviceType === "redis" && mount?.redis?.serverId) {
return mount.redis.serverId;
}
if (mount.serviceType === "compose" && mount?.compose?.serverId) {
return mount.compose.serverId;
}
if (mount.serviceType === "libsql" && mount?.libsql?.serverId) {
return mount.libsql.serverId;
}
return null;
};