diff --git a/apps/dokploy/server/api/services/admin.ts b/apps/dokploy/server/api/services/admin.ts deleted file mode 100644 index b9394bc5..00000000 --- a/apps/dokploy/server/api/services/admin.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { randomBytes } from "node:crypto"; -import { db } from "@/server/db"; -import { - admins, - type apiCreateUserInvitation, - auth, - users, -} from "@/server/db/schema"; -import { TRPCError } from "@trpc/server"; -import * as bcrypt from "bcrypt"; -import { eq } from "drizzle-orm"; - -export type Admin = typeof admins.$inferSelect; -export const createInvitation = async ( - input: typeof apiCreateUserInvitation._type, -) => { - await db.transaction(async (tx) => { - const admin = await findAdmin(); - - const result = await tx - .insert(auth) - .values({ - email: input.email, - rol: "user", - password: bcrypt.hashSync("01231203012312", 10), - }) - .returning() - .then((res) => res[0]); - - if (!result) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create the user", - }); - } - const expiresIn24Hours = new Date(); - expiresIn24Hours.setDate(expiresIn24Hours.getDate() + 1); - const token = randomBytes(32).toString("hex"); - await tx - .insert(users) - .values({ - adminId: admin.adminId, - authId: result.id, - token, - expirationDate: expiresIn24Hours.toISOString(), - }) - .returning(); - }); -}; - -export const findAdminById = async (adminId: string) => { - const admin = await db.query.admins.findFirst({ - where: eq(admins.adminId, adminId), - }); - if (!admin) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Admin not found", - }); - } - return admin; -}; - -export const updateAdmin = async ( - authId: string, - adminData: Partial, -) => { - const admin = await db - .update(admins) - .set({ - ...adminData, - }) - .where(eq(admins.authId, authId)) - .returning() - .then((res) => res[0]); - - return admin; -}; - -export const isAdminPresent = async () => { - const admin = await db.query.admins.findFirst(); - if (!admin) { - return false; - } - return true; -}; - -export const findAdminByAuthId = async (authId: string) => { - const admin = await db.query.admins.findFirst({ - where: eq(admins.authId, authId), - }); - if (!admin) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Admin not found", - }); - } - return admin; -}; - -export const findAdmin = async () => { - const admin = await db.query.admins.findFirst({}); - if (!admin) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Admin not found", - }); - } - return admin; -}; - -export const getUserByToken = async (token: string) => { - const user = await db.query.users.findFirst({ - where: eq(users.token, token), - with: { - auth: { - columns: { - password: false, - }, - }, - }, - }); - - if (!user) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Invitation not found", - }); - } - return { - ...user, - isExpired: user.isRegistered, - }; -}; - -export const removeUserByAuthId = async (authId: string) => { - await db - .delete(auth) - .where(eq(auth.id, authId)) - .returning() - .then((res) => res[0]); -}; - -export const getDokployUrl = async () => { - const admin = await findAdmin(); - - if (admin.host) { - return `https://${admin.host}`; - } - return `http://${admin.serverIp}:${process.env.PORT}`; -}; diff --git a/apps/dokploy/server/api/services/application.ts b/apps/dokploy/server/api/services/application.ts deleted file mode 100644 index c56f3901..00000000 --- a/apps/dokploy/server/api/services/application.ts +++ /dev/null @@ -1,394 +0,0 @@ -import { docker } from "@/server/constants"; -import { db } from "@/server/db"; -import { type apiCreateApplication, applications } from "@/server/db/schema"; -import { generateAppName } from "@/server/db/schema"; -import { getAdvancedStats } from "@/server/monitoring/utilts"; -import { - buildApplication, - getBuildCommand, - mechanizeDockerContainer, -} from "@/server/utils/builders"; -import { sendBuildErrorNotifications } from "@/server/utils/notifications/build-error"; -import { sendBuildSuccessNotifications } from "@/server/utils/notifications/build-success"; -import { execAsyncRemote } from "@/server/utils/process/execAsync"; -import { - cloneBitbucketRepository, - getBitbucketCloneCommand, -} from "@/server/utils/providers/bitbucket"; -import { - buildDocker, - buildRemoteDocker, -} from "@/server/utils/providers/docker"; -import { - cloneGitRepository, - getCustomGitCloneCommand, -} from "@/server/utils/providers/git"; -import { - cloneGithubRepository, - getGithubCloneCommand, -} from "@/server/utils/providers/github"; -import { - cloneGitlabRepository, - getGitlabCloneCommand, -} from "@/server/utils/providers/gitlab"; -import { createTraefikConfig } from "@/server/utils/traefik/application"; -import { generatePassword } from "@/templates/utils"; -import { TRPCError } from "@trpc/server"; -import { eq } from "drizzle-orm"; -import { getDokployUrl } from "./admin"; -import { createDeployment, updateDeploymentStatus } from "./deployment"; -import { validUniqueServerAppName } from "./project"; -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); - - if (!valid) { - throw new TRPCError({ - code: "CONFLICT", - message: "Application with this 'AppName' already exists", - }); - } - } - - return await db.transaction(async (tx) => { - const newApplication = await tx - .insert(applications) - .values({ - ...input, - }) - .returning() - .then((value) => value[0]); - - if (!newApplication) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create the application", - }); - } - - if (process.env.NODE_ENV === "development") { - createTraefikConfig(newApplication.appName); - } - - return newApplication; - }); -}; - -export const findApplicationById = async (applicationId: string) => { - const application = await db.query.applications.findFirst({ - where: eq(applications.applicationId, applicationId), - with: { - project: true, - domains: true, - deployments: true, - mounts: true, - redirects: true, - security: true, - ports: true, - registry: true, - gitlab: true, - github: true, - bitbucket: true, - server: true, - }, - }); - if (!application) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Application not found", - }); - } - return application; -}; - -export const findApplicationByName = async (appName: string) => { - const application = await db.query.applications.findFirst({ - where: eq(applications.appName, appName), - }); - - return application; -}; - -export const updateApplication = async ( - applicationId: string, - applicationData: Partial, -) => { - const application = await db - .update(applications) - .set({ - ...applicationData, - }) - .where(eq(applications.applicationId, applicationId)) - .returning(); - - return application[0]; -}; - -export const updateApplicationStatus = async ( - applicationId: string, - applicationStatus: Application["applicationStatus"], -) => { - const application = await db - .update(applications) - .set({ - applicationStatus: applicationStatus, - }) - .where(eq(applications.applicationId, applicationId)) - .returning(); - - return application; -}; - -export const deployApplication = async ({ - applicationId, - titleLog = "Manual deployment", - descriptionLog = "", -}: { - applicationId: string; - titleLog: string; - 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, - title: titleLog, - description: descriptionLog, - }); - - try { - if (application.sourceType === "github") { - await cloneGithubRepository(application, deployment.logPath); - await buildApplication(application, deployment.logPath); - } else if (application.sourceType === "gitlab") { - await cloneGitlabRepository(application, deployment.logPath); - await buildApplication(application, deployment.logPath); - } else if (application.sourceType === "bitbucket") { - await cloneBitbucketRepository(application, deployment.logPath); - await buildApplication(application, deployment.logPath); - } else if (application.sourceType === "docker") { - await buildDocker(application, deployment.logPath); - } else if (application.sourceType === "git") { - await cloneGitRepository(application, deployment.logPath); - await buildApplication(application, deployment.logPath); - } else if (application.sourceType === "drop") { - await buildApplication(application, deployment.logPath); - } - - await updateDeploymentStatus(deployment.deploymentId, "done"); - await updateApplicationStatus(applicationId, "done"); - - await sendBuildSuccessNotifications({ - projectName: application.project.name, - applicationName: application.name, - applicationType: "application", - buildLink, - }); - } catch (error) { - await updateDeploymentStatus(deployment.deploymentId, "error"); - await updateApplicationStatus(applicationId, "error"); - await sendBuildErrorNotifications({ - projectName: application.project.name, - applicationName: application.name, - applicationType: "application", - // @ts-ignore - errorMessage: error?.message || "Error to build", - buildLink, - }); - - console.log( - "Error on ", - application.buildType, - "/", - application.sourceType, - error, - ); - - throw error; - } - - return true; -}; - -export const rebuildApplication = async ({ - applicationId, - titleLog = "Rebuild deployment", - descriptionLog = "", -}: { - applicationId: string; - titleLog: string; - descriptionLog: string; -}) => { - const application = await findApplicationById(applicationId); - const deployment = await createDeployment({ - applicationId: applicationId, - title: titleLog, - description: descriptionLog, - }); - - try { - if (application.sourceType === "github") { - await buildApplication(application, deployment.logPath); - } else if (application.sourceType === "gitlab") { - await buildApplication(application, deployment.logPath); - } else if (application.sourceType === "bitbucket") { - await buildApplication(application, deployment.logPath); - } else if (application.sourceType === "docker") { - await buildDocker(application, deployment.logPath); - } else if (application.sourceType === "git") { - await buildApplication(application, deployment.logPath); - } else if (application.sourceType === "drop") { - await buildApplication(application, deployment.logPath); - } - await updateDeploymentStatus(deployment.deploymentId, "done"); - await updateApplicationStatus(applicationId, "done"); - } catch (error) { - await updateDeploymentStatus(deployment.deploymentId, "error"); - await updateApplicationStatus(applicationId, "error"); - throw error; - } - - return true; -}; - -export const deployRemoteApplication = async ({ - applicationId, - titleLog = "Manual deployment", - descriptionLog = "", -}: { - applicationId: string; - titleLog: string; - 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, - title: titleLog, - description: descriptionLog, - }); - - try { - if (application.serverId) { - let command = "set -e;"; - if (application.sourceType === "github") { - command += await getGithubCloneCommand(application, deployment.logPath); - } else if (application.sourceType === "gitlab") { - command += await getGitlabCloneCommand(application, deployment.logPath); - } else if (application.sourceType === "bitbucket") { - command += await getBitbucketCloneCommand( - application, - deployment.logPath, - ); - } else if (application.sourceType === "git") { - command += await getCustomGitCloneCommand( - application, - deployment.logPath, - ); - } else if (application.sourceType === "docker") { - command += await buildRemoteDocker(application, deployment.logPath); - } - - if (application.sourceType !== "docker") { - command += getBuildCommand(application, deployment.logPath); - } - await execAsyncRemote(application.serverId, command); - await mechanizeDockerContainer(application); - } - - await updateDeploymentStatus(deployment.deploymentId, "done"); - await updateApplicationStatus(applicationId, "done"); - - await sendBuildSuccessNotifications({ - projectName: application.project.name, - applicationName: application.name, - applicationType: "application", - buildLink, - }); - } catch (error) { - await updateDeploymentStatus(deployment.deploymentId, "error"); - await updateApplicationStatus(applicationId, "error"); - await sendBuildErrorNotifications({ - projectName: application.project.name, - applicationName: application.name, - applicationType: "application", - // @ts-ignore - errorMessage: error?.message || "Error to build", - buildLink, - }); - - console.log( - "Error on ", - application.buildType, - "/", - application.sourceType, - error, - ); - - throw error; - } - - return true; -}; - -export const rebuildRemoteApplication = async ({ - applicationId, - titleLog = "Rebuild deployment", - descriptionLog = "", -}: { - applicationId: string; - titleLog: string; - descriptionLog: string; -}) => { - const application = await findApplicationById(applicationId); - const deployment = await createDeployment({ - applicationId: applicationId, - title: titleLog, - description: descriptionLog, - }); - - try { - if (application.serverId) { - if (application.sourceType !== "docker") { - let command = "set -e;"; - command += getBuildCommand(application, deployment.logPath); - await execAsyncRemote(application.serverId, command); - } - await mechanizeDockerContainer(application); - } - await updateDeploymentStatus(deployment.deploymentId, "done"); - await updateApplicationStatus(applicationId, "done"); - } catch (error) { - await updateDeploymentStatus(deployment.deploymentId, "error"); - await updateApplicationStatus(applicationId, "error"); - throw error; - } - - return true; -}; - -export const getApplicationStats = async (appName: string) => { - const filter = { - status: ["running"], - label: [`com.docker.swarm.service.name=${appName}`], - }; - - const containers = await docker.listContainers({ - filters: JSON.stringify(filter), - }); - - const container = containers[0]; - if (!container || container?.State !== "running") { - return null; - } - - const data = await getAdvancedStats(appName); - - return data; -}; diff --git a/apps/dokploy/server/api/services/auth.ts b/apps/dokploy/server/api/services/auth.ts deleted file mode 100644 index 6bbf325c..00000000 --- a/apps/dokploy/server/api/services/auth.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { randomBytes } from "node:crypto"; -import { db } from "@/server/db"; -import { - admins, - type apiCreateAdmin, - type apiCreateUser, - auth, - users, -} from "@/server/db/schema"; -import { getPublicIpWithFallback } from "@/server/wss/terminal"; -import { TRPCError } from "@trpc/server"; -import * as bcrypt from "bcrypt"; -import { eq } from "drizzle-orm"; -import encode from "hi-base32"; -import { TOTP } from "otpauth"; -import QRCode from "qrcode"; - -export type Auth = typeof auth.$inferSelect; - -export const createAdmin = async (input: typeof apiCreateAdmin._type) => { - return await db.transaction(async (tx) => { - const hashedPassword = bcrypt.hashSync(input.password, 10); - const newAuth = await tx - .insert(auth) - .values({ - email: input.email, - password: hashedPassword, - rol: "admin", - }) - .returning() - .then((res) => res[0]); - - if (!newAuth) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create the user", - }); - } - - await tx - .insert(admins) - .values({ - authId: newAuth.id, - serverIp: await getPublicIpWithFallback(), - }) - .returning(); - - return newAuth; - }); -}; - -export const createUser = async (input: typeof apiCreateUser._type) => { - return await db.transaction(async (tx) => { - const hashedPassword = bcrypt.hashSync(input.password, 10); - const res = await tx - .update(auth) - .set({ - password: hashedPassword, - }) - .where(eq(auth.id, input.id)) - .returning() - .then((res) => res[0]); - - if (!res) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create the user", - }); - } - - const user = await tx - .update(users) - .set({ - isRegistered: true, - expirationDate: undefined, - }) - .where(eq(users.token, input.token)) - .returning() - .then((res) => res[0]); - - return user; - }); -}; - -export const findAuthByEmail = async (email: string) => { - const result = await db.query.auth.findFirst({ - where: eq(auth.email, email), - }); - if (!result) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Auth not found", - }); - } - return result; -}; - -export const findAuthById = async (authId: string) => { - const result = await db.query.auth.findFirst({ - where: eq(auth.id, authId), - columns: { - password: false, - }, - }); - if (!result) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Auth not found", - }); - } - return result; -}; - -export const updateAuthById = async ( - authId: string, - authData: Partial, -) => { - const result = await db - .update(auth) - .set({ - ...authData, - }) - .where(eq(auth.id, authId)) - .returning(); - - return result[0]; -}; - -export const generate2FASecret = async (authId: string) => { - const auth = await findAuthById(authId); - - const base32_secret = generateBase32Secret(); - - const totp = new TOTP({ - issuer: "Dokploy", - label: `${auth?.email}`, - algorithm: "SHA1", - digits: 6, - secret: base32_secret, - }); - - const otpauth_url = totp.toString(); - - const qrUrl = await QRCode.toDataURL(otpauth_url); - - return { - qrCodeUrl: qrUrl, - secret: base32_secret, - }; -}; - -export const verify2FA = async ( - auth: Omit, - secret: string, - pin: string, -) => { - const totp = new TOTP({ - issuer: "Dokploy", - label: `${auth?.email}`, - algorithm: "SHA1", - digits: 6, - secret: secret, - }); - - const delta = totp.validate({ token: pin }); - - if (delta === null) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Invalid 2FA code", - }); - } - return auth; -}; - -const generateBase32Secret = () => { - const buffer = randomBytes(15); - const base32 = encode.encode(buffer).replace(/=/g, "").substring(0, 24); - return base32; -}; diff --git a/apps/dokploy/server/api/services/backup.ts b/apps/dokploy/server/api/services/backup.ts deleted file mode 100644 index 55b38c22..00000000 --- a/apps/dokploy/server/api/services/backup.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { db } from "@/server/db"; -import { type apiCreateBackup, backups } from "@/server/db/schema"; -import { TRPCError } from "@trpc/server"; -import { eq } from "drizzle-orm"; - -export type Backup = typeof backups.$inferSelect; - -export type BackupSchedule = Awaited>; - -export const createBackup = async (input: typeof apiCreateBackup._type) => { - const newBackup = await db - .insert(backups) - .values({ - ...input, - }) - .returning() - .then((value) => value[0]); - - if (!newBackup) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create the Backup", - }); - } - - return newBackup; -}; - -export const findBackupById = async (backupId: string) => { - const backup = await db.query.backups.findFirst({ - where: eq(backups.backupId, backupId), - with: { - postgres: true, - mysql: true, - mariadb: true, - mongo: true, - destination: true, - }, - }); - if (!backup) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Backup not found", - }); - } - return backup; -}; - -export const updateBackupById = async ( - backupId: string, - backupData: Partial, -) => { - const result = await db - .update(backups) - .set({ - ...backupData, - }) - .where(eq(backups.backupId, backupId)) - .returning(); - - return result[0]; -}; - -export const removeBackupById = async (backupId: string) => { - const result = await db - .delete(backups) - .where(eq(backups.backupId, backupId)) - .returning(); - - return result[0]; -}; diff --git a/apps/dokploy/server/api/services/bitbucket.ts b/apps/dokploy/server/api/services/bitbucket.ts deleted file mode 100644 index 11ae0903..00000000 --- a/apps/dokploy/server/api/services/bitbucket.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { db } from "@/server/db"; -import { - type apiCreateBitbucket, - type apiUpdateBitbucket, - bitbucket, - gitProvider, -} from "@/server/db/schema"; -import { TRPCError } from "@trpc/server"; -import { eq } from "drizzle-orm"; - -export type Bitbucket = typeof bitbucket.$inferSelect; - -export const createBitbucket = async ( - input: typeof apiCreateBitbucket._type, -) => { - return await db.transaction(async (tx) => { - const newGitProvider = await tx - .insert(gitProvider) - .values({ - providerType: "bitbucket", - authId: input.authId, - name: input.name, - }) - .returning() - .then((response) => response[0]); - - if (!newGitProvider) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create the git provider", - }); - } - - await tx - .insert(bitbucket) - .values({ - ...input, - gitProviderId: newGitProvider?.gitProviderId, - }) - .returning() - .then((response) => response[0]); - }); -}; - -export const findBitbucketById = async (bitbucketId: string) => { - const bitbucketProviderResult = await db.query.bitbucket.findFirst({ - where: eq(bitbucket.bitbucketId, bitbucketId), - with: { - gitProvider: true, - }, - }); - - if (!bitbucketProviderResult) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Bitbucket Provider not found", - }); - } - - return bitbucketProviderResult; -}; - -export const updateBitbucket = async ( - bitbucketId: string, - input: typeof apiUpdateBitbucket._type, -) => { - return await db.transaction(async (tx) => { - const result = await tx - .update(bitbucket) - .set({ - ...input, - }) - .where(eq(bitbucket.bitbucketId, bitbucketId)) - .returning(); - - if (input.name) { - await tx - .update(gitProvider) - .set({ - name: input.name, - }) - .where(eq(gitProvider.gitProviderId, input.gitProviderId)) - .returning(); - } - - return result[0]; - }); -}; diff --git a/apps/dokploy/server/api/services/certificate.ts b/apps/dokploy/server/api/services/certificate.ts deleted file mode 100644 index 3ed1dcd1..00000000 --- a/apps/dokploy/server/api/services/certificate.ts +++ /dev/null @@ -1,108 +0,0 @@ -import fs from "node:fs"; -import path from "node:path"; -import { paths } from "@/server/constants"; -import { db } from "@/server/db"; -import { type apiCreateCertificate, certificates } from "@/server/db/schema"; -import { removeDirectoryIfExistsContent } from "@/server/utils/filesystem/directory"; -import { TRPCError } from "@trpc/server"; -import { eq } from "drizzle-orm"; -import { dump } from "js-yaml"; -import type { z } from "zod"; - -export type Certificate = typeof certificates.$inferSelect; - -export const findCertificateById = async (certificateId: string) => { - const certificate = await db.query.certificates.findFirst({ - where: eq(certificates.certificateId, certificateId), - }); - - if (!certificate) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Certificate not found", - }); - } - - return certificate; -}; - -export const createCertificate = async ( - certificateData: z.infer, -) => { - const certificate = await db - .insert(certificates) - .values({ - ...certificateData, - }) - .returning(); - - if (!certificate || certificate[0] === undefined) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Failed to create the certificate", - }); - } - - const cer = certificate[0]; - - createCertificateFiles(cer); - return cer; -}; - -export const removeCertificateById = async (certificateId: string) => { - const { CERTIFICATES_PATH } = paths(); - const certificate = await findCertificateById(certificateId); - const certDir = path.join(CERTIFICATES_PATH, certificate.certificatePath); - - await removeDirectoryIfExistsContent(certDir); - const result = await db - .delete(certificates) - .where(eq(certificates.certificateId, certificateId)) - .returning(); - - if (!result) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Failed to delete the certificate", - }); - } - - return result; -}; - -export const findCertificates = async () => { - return await db.query.certificates.findMany(); -}; - -const createCertificateFiles = (certificate: Certificate) => { - const { CERTIFICATES_PATH } = paths(); - const dockerPath = "/etc/traefik"; - const certDir = path.join(CERTIFICATES_PATH, certificate.certificatePath); - const crtPath = path.join(certDir, "chain.crt"); - const keyPath = path.join(certDir, "privkey.key"); - - const chainPath = path.join(dockerPath, certDir, "chain.crt"); - const keyPathDocker = path.join(dockerPath, certDir, "privkey.key"); - - if (!fs.existsSync(certDir)) { - fs.mkdirSync(certDir, { recursive: true }); - } - - fs.writeFileSync(crtPath, certificate.certificateData); - fs.writeFileSync(keyPath, certificate.privateKey); - - const traefikConfig = { - tls: { - certificates: [ - { - certFile: chainPath, - keyFile: keyPathDocker, - }, - ], - }, - }; - - const yamlConfig = dump(traefikConfig); - const configFile = path.join(certDir, "certificate.yml"); - fs.writeFileSync(configFile, yamlConfig); -}; diff --git a/apps/dokploy/server/api/services/cluster.ts b/apps/dokploy/server/api/services/cluster.ts deleted file mode 100644 index ea71d1ae..00000000 --- a/apps/dokploy/server/api/services/cluster.ts +++ /dev/null @@ -1,41 +0,0 @@ -export interface DockerNode { - ID: string; - Version: { - Index: number; - }; - CreatedAt: string; - UpdatedAt: string; - Spec: { - Name: string; - Labels: Record; - Role: "worker" | "manager"; - Availability: "active" | "pause" | "drain"; - }; - Description: { - Hostname: string; - Platform: { - Architecture: string; - OS: string; - }; - Resources: { - NanoCPUs: number; - MemoryBytes: number; - }; - Engine: { - EngineVersion: string; - Plugins: Array<{ - Type: string; - Name: string; - }>; - }; - }; - Status: { - State: "unknown" | "down" | "ready" | "disconnected"; - Message: string; - Addr: string; - }; - ManagerStatus?: { - Leader: boolean; - Addr: string; - }; -} diff --git a/apps/dokploy/server/api/services/compose.ts b/apps/dokploy/server/api/services/compose.ts deleted file mode 100644 index 4df97bf2..00000000 --- a/apps/dokploy/server/api/services/compose.ts +++ /dev/null @@ -1,467 +0,0 @@ -import { join } from "node:path"; -import { paths } from "@/server/constants"; -import { db } from "@/server/db"; -import { type apiCreateCompose, compose } from "@/server/db/schema"; -import { generateAppName } from "@/server/db/schema"; -import { - buildCompose, - getBuildComposeCommand, -} from "@/server/utils/builders/compose"; -import { randomizeSpecificationFile } from "@/server/utils/docker/compose"; -import { - cloneCompose, - cloneComposeRemote, - loadDockerCompose, - loadDockerComposeRemote, -} from "@/server/utils/docker/domain"; -import type { ComposeSpecification } from "@/server/utils/docker/types"; -import { sendBuildErrorNotifications } from "@/server/utils/notifications/build-error"; -import { sendBuildSuccessNotifications } from "@/server/utils/notifications/build-success"; -import { execAsync, execAsyncRemote } from "@/server/utils/process/execAsync"; -import { - cloneBitbucketRepository, - getBitbucketCloneCommand, -} from "@/server/utils/providers/bitbucket"; -import { - cloneGitRepository, - getCustomGitCloneCommand, -} from "@/server/utils/providers/git"; -import { - cloneGithubRepository, - getGithubCloneCommand, -} from "@/server/utils/providers/github"; -import { - cloneGitlabRepository, - getGitlabCloneCommand, -} from "@/server/utils/providers/gitlab"; -import { - createComposeFile, - getCreateComposeFileCommand, -} from "@/server/utils/providers/raw"; -import { generatePassword } from "@/templates/utils"; -import { TRPCError } from "@trpc/server"; -import { eq } from "drizzle-orm"; -import { getDokployUrl } from "./admin"; -import { createDeploymentCompose, updateDeploymentStatus } from "./deployment"; -import { validUniqueServerAppName } from "./project"; - -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); - - if (!valid) { - throw new TRPCError({ - code: "CONFLICT", - message: "Service with this 'AppName' already exists", - }); - } - } - - const newDestination = await db - .insert(compose) - .values({ - ...input, - composeFile: "", - }) - .returning() - .then((value) => value[0]); - - if (!newDestination) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error input: Inserting compose", - }); - } - - return newDestination; -}; - -export const createComposeByTemplate = async ( - input: typeof compose.$inferInsert, -) => { - if (input.appName) { - const valid = await validUniqueServerAppName(input.appName); - - if (!valid) { - throw new TRPCError({ - code: "CONFLICT", - message: "Service with this 'AppName' already exists", - }); - } - } - const newDestination = await db - .insert(compose) - .values({ - ...input, - }) - .returning() - .then((value) => value[0]); - - if (!newDestination) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error input: Inserting compose", - }); - } - - return newDestination; -}; - -export const findComposeById = async (composeId: string) => { - const result = await db.query.compose.findFirst({ - where: eq(compose.composeId, composeId), - with: { - project: true, - deployments: true, - mounts: true, - domains: true, - github: true, - gitlab: true, - bitbucket: true, - server: true, - }, - }); - if (!result) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Compose not found", - }); - } - return result; -}; - -export const loadServices = async ( - composeId: string, - type: "fetch" | "cache" = "fetch", -) => { - const compose = await findComposeById(composeId); - - if (type === "fetch") { - if (compose.serverId) { - await cloneComposeRemote(compose); - } else { - await cloneCompose(compose); - } - } - - let composeData: ComposeSpecification | null; - - if (compose.serverId) { - composeData = await loadDockerComposeRemote(compose); - } else { - composeData = await loadDockerCompose(compose); - } - - if (compose.randomize && composeData) { - const randomizedCompose = randomizeSpecificationFile( - composeData, - compose.suffix, - ); - composeData = randomizedCompose; - } - - if (!composeData?.services) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Services not found", - }); - } - - const services = Object.keys(composeData.services); - - return [...services]; -}; - -export const updateCompose = async ( - composeId: string, - composeData: Partial, -) => { - const composeResult = await db - .update(compose) - .set({ - ...composeData, - }) - .where(eq(compose.composeId, composeId)) - .returning(); - - return composeResult[0]; -}; - -export const deployCompose = async ({ - composeId, - titleLog = "Manual deployment", - descriptionLog = "", -}: { - composeId: string; - titleLog: string; - descriptionLog: string; -}) => { - const compose = await findComposeById(composeId); - const buildLink = `${await getDokployUrl()}/dashboard/project/${compose.projectId}/services/compose/${compose.composeId}?tab=deployments`; - const deployment = await createDeploymentCompose({ - composeId: composeId, - title: titleLog, - description: descriptionLog, - }); - - try { - if (compose.sourceType === "github") { - await cloneGithubRepository(compose, deployment.logPath, true); - } else if (compose.sourceType === "gitlab") { - await cloneGitlabRepository(compose, deployment.logPath, true); - } else if (compose.sourceType === "bitbucket") { - await cloneBitbucketRepository(compose, deployment.logPath, true); - } else if (compose.sourceType === "git") { - await cloneGitRepository(compose, deployment.logPath, true); - } else if (compose.sourceType === "raw") { - await createComposeFile(compose, deployment.logPath); - } - await buildCompose(compose, deployment.logPath); - await updateDeploymentStatus(deployment.deploymentId, "done"); - await updateCompose(composeId, { - composeStatus: "done", - }); - - await sendBuildSuccessNotifications({ - projectName: compose.project.name, - applicationName: compose.name, - applicationType: "compose", - buildLink, - }); - } catch (error) { - await updateDeploymentStatus(deployment.deploymentId, "error"); - await updateCompose(composeId, { - composeStatus: "error", - }); - await sendBuildErrorNotifications({ - projectName: compose.project.name, - applicationName: compose.name, - applicationType: "compose", - // @ts-ignore - errorMessage: error?.message || "Error to build", - buildLink, - }); - throw error; - } -}; - -export const rebuildCompose = async ({ - composeId, - titleLog = "Rebuild deployment", - descriptionLog = "", -}: { - composeId: string; - titleLog: string; - descriptionLog: string; -}) => { - const compose = await findComposeById(composeId); - const deployment = await createDeploymentCompose({ - composeId: composeId, - title: titleLog, - description: descriptionLog, - }); - - try { - if (compose.serverId) { - await getBuildComposeCommand(compose, deployment.logPath); - } else { - await buildCompose(compose, deployment.logPath); - } - - await updateDeploymentStatus(deployment.deploymentId, "done"); - await updateCompose(composeId, { - composeStatus: "done", - }); - } catch (error) { - await updateDeploymentStatus(deployment.deploymentId, "error"); - await updateCompose(composeId, { - composeStatus: "error", - }); - throw error; - } - - return true; -}; - -export const deployRemoteCompose = async ({ - composeId, - titleLog = "Manual deployment", - descriptionLog = "", -}: { - composeId: string; - titleLog: string; - descriptionLog: string; -}) => { - const compose = await findComposeById(composeId); - const buildLink = `${await getDokployUrl()}/dashboard/project/${compose.projectId}/services/compose/${compose.composeId}?tab=deployments`; - const deployment = await createDeploymentCompose({ - composeId: composeId, - title: titleLog, - description: descriptionLog, - }); - try { - if (compose.serverId) { - let command = "set -e;"; - - if (compose.sourceType === "github") { - command += await getGithubCloneCommand( - compose, - deployment.logPath, - true, - ); - } else if (compose.sourceType === "gitlab") { - command += await getGitlabCloneCommand( - compose, - deployment.logPath, - true, - ); - } else if (compose.sourceType === "bitbucket") { - command += await getBitbucketCloneCommand( - compose, - deployment.logPath, - true, - ); - } else if (compose.sourceType === "git") { - command += await getCustomGitCloneCommand( - compose, - deployment.logPath, - true, - ); - } else if (compose.sourceType === "raw") { - command += getCreateComposeFileCommand(compose, deployment.logPath); - } - - await execAsyncRemote(compose.serverId, command); - await getBuildComposeCommand(compose, deployment.logPath); - } - - await updateDeploymentStatus(deployment.deploymentId, "done"); - await updateCompose(composeId, { - composeStatus: "done", - }); - - await sendBuildSuccessNotifications({ - projectName: compose.project.name, - applicationName: compose.name, - applicationType: "compose", - buildLink, - }); - } catch (error) { - console.log(error); - await updateDeploymentStatus(deployment.deploymentId, "error"); - await updateCompose(composeId, { - composeStatus: "error", - }); - await sendBuildErrorNotifications({ - projectName: compose.project.name, - applicationName: compose.name, - applicationType: "compose", - // @ts-ignore - errorMessage: error?.message || "Error to build", - buildLink, - }); - throw error; - } -}; - -export const rebuildRemoteCompose = async ({ - composeId, - titleLog = "Rebuild deployment", - descriptionLog = "", -}: { - composeId: string; - titleLog: string; - descriptionLog: string; -}) => { - const compose = await findComposeById(composeId); - const deployment = await createDeploymentCompose({ - composeId: composeId, - title: titleLog, - description: descriptionLog, - }); - - try { - if (compose.serverId) { - await getBuildComposeCommand(compose, deployment.logPath); - } - - await updateDeploymentStatus(deployment.deploymentId, "done"); - await updateCompose(composeId, { - composeStatus: "done", - }); - } catch (error) { - await updateDeploymentStatus(deployment.deploymentId, "error"); - await updateCompose(composeId, { - composeStatus: "error", - }); - throw error; - } - - return true; -}; - -export const removeCompose = async (compose: Compose) => { - 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 { - await execAsync(command); - } - await execAsync(command, { - cwd: projectPath, - }); - } else { - const command = `cd ${projectPath} && docker compose -p ${compose.appName} down && rm -rf ${projectPath}`; - if (compose.serverId) { - await execAsyncRemote(compose.serverId, command); - } else { - await execAsync(command, { - cwd: projectPath, - }); - } - } - } catch (error) { - throw error; - } - - return true; -}; - -export const stopCompose = async (composeId: string) => { - const compose = await findComposeById(composeId); - try { - const { COMPOSE_PATH } = paths(!!compose.serverId); - if (compose.composeType === "docker-compose") { - if (compose.serverId) { - await execAsyncRemote( - compose.serverId, - `cd ${join(COMPOSE_PATH, compose.appName)} && docker compose -p ${compose.appName} stop`, - ); - } else { - await execAsync(`docker compose -p ${compose.appName} stop`, { - cwd: join(COMPOSE_PATH, compose.appName), - }); - } - } - - await updateCompose(composeId, { - composeStatus: "idle", - }); - } catch (error) { - await updateCompose(composeId, { - composeStatus: "error", - }); - throw error; - } - - return true; -}; diff --git a/apps/dokploy/server/api/services/deployment.ts b/apps/dokploy/server/api/services/deployment.ts deleted file mode 100644 index 5ea39c57..00000000 --- a/apps/dokploy/server/api/services/deployment.ts +++ /dev/null @@ -1,372 +0,0 @@ -import { existsSync, promises as fsPromises } from "node:fs"; -import path from "node:path"; -import { paths } from "@/server/constants"; -import { db } from "@/server/db"; -import { - type apiCreateDeployment, - type apiCreateDeploymentCompose, - type apiCreateDeploymentServer, - deployments, -} from "@/server/db/schema"; -import { removeDirectoryIfExistsContent } from "@/server/utils/filesystem/directory"; -import { TRPCError } from "@trpc/server"; -import { format } from "date-fns"; -import { desc, eq } from "drizzle-orm"; -import { - type Application, - findApplicationById, - updateApplicationStatus, -} from "./application"; -import { type Compose, findComposeById, updateCompose } from "./compose"; -import { type Server, findServerById } from "./server"; - -import { execAsyncRemote } from "@/server/utils/process/execAsync"; - -export type Deployment = typeof deployments.$inferSelect; - -export const findDeploymentById = async (applicationId: string) => { - const application = await db.query.deployments.findFirst({ - where: eq(deployments.applicationId, applicationId), - with: { - application: true, - }, - }); - if (!application) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Deployment not found", - }); - } - return application; -}; - -export const createDeployment = async ( - deployment: Omit< - typeof apiCreateDeployment._type, - "deploymentId" | "createdAt" | "status" | "logPath" - >, -) => { - const application = await findApplicationById(deployment.applicationId); - - try { - // await removeLastTenDeployments(deployment.applicationId); - const { LOGS_PATH } = paths(!!application.serverId); - const formattedDateTime = format(new Date(), "yyyy-MM-dd:HH:mm:ss"); - const fileName = `${application.appName}-${formattedDateTime}.log`; - const logFilePath = path.join(LOGS_PATH, application.appName, fileName); - - if (application.serverId) { - const server = await findServerById(application.serverId); - - const command = ` - mkdir -p ${LOGS_PATH}/${application.appName}; - echo "Initializing deployment" >> ${logFilePath}; - `; - - await execAsyncRemote(server.serverId, command); - } else { - await fsPromises.mkdir(path.join(LOGS_PATH, application.appName), { - recursive: true, - }); - await fsPromises.writeFile(logFilePath, "Initializing deployment"); - } - - const deploymentCreate = await db - .insert(deployments) - .values({ - applicationId: deployment.applicationId, - title: deployment.title || "Deployment", - status: "running", - logPath: logFilePath, - description: deployment.description || "", - }) - .returning(); - if (deploymentCreate.length === 0 || !deploymentCreate[0]) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create the deployment", - }); - } - return deploymentCreate[0]; - } catch (error) { - await updateApplicationStatus(application.applicationId, "error"); - console.log(error); - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create the deployment", - }); - } -}; - -export const createDeploymentCompose = async ( - deployment: Omit< - typeof apiCreateDeploymentCompose._type, - "deploymentId" | "createdAt" | "status" | "logPath" - >, -) => { - const compose = await findComposeById(deployment.composeId); - try { - // await removeLastTenComposeDeployments(deployment.composeId); - const { LOGS_PATH } = paths(!!compose.serverId); - const formattedDateTime = format(new Date(), "yyyy-MM-dd:HH:mm:ss"); - const fileName = `${compose.appName}-${formattedDateTime}.log`; - const logFilePath = path.join(LOGS_PATH, compose.appName, fileName); - - if (compose.serverId) { - const server = await findServerById(compose.serverId); - - const command = ` -mkdir -p ${LOGS_PATH}/${compose.appName}; -echo "Initializing deployment" >> ${logFilePath}; -`; - - await execAsyncRemote(server.serverId, command); - } else { - await fsPromises.mkdir(path.join(LOGS_PATH, compose.appName), { - recursive: true, - }); - await fsPromises.writeFile(logFilePath, "Initializing deployment"); - } - - const deploymentCreate = await db - .insert(deployments) - .values({ - composeId: deployment.composeId, - title: deployment.title || "Deployment", - description: deployment.description || "", - status: "running", - logPath: logFilePath, - }) - .returning(); - if (deploymentCreate.length === 0 || !deploymentCreate[0]) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create the deployment", - }); - } - return deploymentCreate[0]; - } catch (error) { - await updateCompose(compose.composeId, { - composeStatus: "error", - }); - console.log(error); - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create the deployment", - }); - } -}; - -export const removeDeployment = async (deploymentId: string) => { - try { - const deployment = await db - .delete(deployments) - .where(eq(deployments.deploymentId, deploymentId)) - .returning(); - return deployment[0]; - } catch (error) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to delete this deployment", - }); - } -}; - -export const removeDeploymentsByApplicationId = async ( - applicationId: string, -) => { - await db - .delete(deployments) - .where(eq(deployments.applicationId, applicationId)) - .returning(); -}; - -const removeLastTenDeployments = async (applicationId: string) => { - const deploymentList = await db.query.deployments.findMany({ - where: eq(deployments.applicationId, applicationId), - orderBy: desc(deployments.createdAt), - }); - if (deploymentList.length > 10) { - const deploymentsToDelete = deploymentList.slice(10); - for (const oldDeployment of deploymentsToDelete) { - const logPath = path.join(oldDeployment.logPath); - if (existsSync(logPath)) { - await fsPromises.unlink(logPath); - } - await removeDeployment(oldDeployment.deploymentId); - } - } -}; - -const removeLastTenComposeDeployments = async (composeId: string) => { - const deploymentList = await db.query.deployments.findMany({ - where: eq(deployments.composeId, composeId), - orderBy: desc(deployments.createdAt), - }); - if (deploymentList.length > 10) { - const deploymentsToDelete = deploymentList.slice(10); - for (const oldDeployment of deploymentsToDelete) { - const logPath = path.join(oldDeployment.logPath); - if (existsSync(logPath)) { - await fsPromises.unlink(logPath); - } - await removeDeployment(oldDeployment.deploymentId); - } - } -}; - -export const removeDeployments = async (application: Application) => { - const { appName, applicationId } = application; - const { LOGS_PATH } = paths(!!application.serverId); - const logsPath = path.join(LOGS_PATH, appName); - if (application.serverId) { - await execAsyncRemote(application.serverId, `rm -rf ${logsPath}`); - } else { - await removeDirectoryIfExistsContent(logsPath); - } - await removeDeploymentsByApplicationId(applicationId); -}; - -export const removeDeploymentsByComposeId = async (compose: Compose) => { - const { appName } = compose; - const { LOGS_PATH } = paths(!!compose.serverId); - const logsPath = path.join(LOGS_PATH, appName); - if (compose.serverId) { - await execAsyncRemote(compose.serverId, `rm -rf ${logsPath}`); - } else { - await removeDirectoryIfExistsContent(logsPath); - } - - await db - .delete(deployments) - .where(eq(deployments.composeId, compose.composeId)) - .returning(); -}; - -export const findAllDeploymentsByApplicationId = async ( - applicationId: string, -) => { - const deploymentsList = await db.query.deployments.findMany({ - where: eq(deployments.applicationId, applicationId), - orderBy: desc(deployments.createdAt), - }); - return deploymentsList; -}; - -export const findAllDeploymentsByComposeId = async (composeId: string) => { - const deploymentsList = await db.query.deployments.findMany({ - where: eq(deployments.composeId, composeId), - orderBy: desc(deployments.createdAt), - }); - return deploymentsList; -}; - -export const updateDeployment = async ( - deploymentId: string, - deploymentData: Partial, -) => { - const application = await db - .update(deployments) - .set({ - ...deploymentData, - }) - .where(eq(deployments.deploymentId, deploymentId)) - .returning(); - - return application; -}; - -export const updateDeploymentStatus = async ( - deploymentId: string, - deploymentStatus: Deployment["status"], -) => { - const application = await db - .update(deployments) - .set({ - status: deploymentStatus, - }) - .where(eq(deployments.deploymentId, deploymentId)) - .returning(); - - return application; -}; - -export const createServerDeployment = async ( - deployment: Omit< - typeof apiCreateDeploymentServer._type, - "deploymentId" | "createdAt" | "status" | "logPath" - >, -) => { - try { - const { LOGS_PATH } = paths(); - - const server = await findServerById(deployment.serverId); - await removeLastFiveDeployments(deployment.serverId); - const formattedDateTime = format(new Date(), "yyyy-MM-dd:HH:mm:ss"); - const fileName = `${server.appName}-${formattedDateTime}.log`; - const logFilePath = path.join(LOGS_PATH, server.appName, fileName); - await fsPromises.mkdir(path.join(LOGS_PATH, server.appName), { - recursive: true, - }); - await fsPromises.writeFile(logFilePath, "Initializing Setup Server"); - const deploymentCreate = await db - .insert(deployments) - .values({ - serverId: server.serverId, - title: deployment.title || "Deployment", - description: deployment.description || "", - status: "running", - logPath: logFilePath, - }) - .returning(); - if (deploymentCreate.length === 0 || !deploymentCreate[0]) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create the deployment", - }); - } - return deploymentCreate[0]; - } catch (error) { - console.log(error); - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create the deployment", - }); - } -}; - -export const removeLastFiveDeployments = async (serverId: string) => { - const deploymentList = await db.query.deployments.findMany({ - where: eq(deployments.serverId, serverId), - orderBy: desc(deployments.createdAt), - }); - if (deploymentList.length >= 5) { - const deploymentsToDelete = deploymentList.slice(4); - for (const oldDeployment of deploymentsToDelete) { - const logPath = path.join(oldDeployment.logPath); - if (existsSync(logPath)) { - await fsPromises.unlink(logPath); - } - await removeDeployment(oldDeployment.deploymentId); - } - } -}; - -export const removeDeploymentsByServerId = async (server: Server) => { - const { LOGS_PATH } = paths(); - const { appName } = server; - const logsPath = path.join(LOGS_PATH, appName); - await removeDirectoryIfExistsContent(logsPath); - await db - .delete(deployments) - .where(eq(deployments.serverId, server.serverId)) - .returning(); -}; - -export const findAllDeploymentsByServerId = async (serverId: string) => { - const deploymentsList = await db.query.deployments.findMany({ - where: eq(deployments.serverId, serverId), - orderBy: desc(deployments.createdAt), - }); - return deploymentsList; -}; diff --git a/apps/dokploy/server/api/services/destination.ts b/apps/dokploy/server/api/services/destination.ts deleted file mode 100644 index dfc15649..00000000 --- a/apps/dokploy/server/api/services/destination.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { db } from "@/server/db"; -import { type apiCreateDestination, destinations } from "@/server/db/schema"; -import { TRPCError } from "@trpc/server"; -import { eq } from "drizzle-orm"; -import { findAdmin } from "./admin"; - -export type Destination = typeof destinations.$inferSelect; - -export const createDestintation = async ( - input: typeof apiCreateDestination._type, -) => { - const adminResponse = await findAdmin(); - const newDestination = await db - .insert(destinations) - .values({ - ...input, - adminId: adminResponse.adminId, - }) - .returning() - .then((value) => value[0]); - - if (!newDestination) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error input: Inserting destination", - }); - } - - return newDestination; -}; - -export const findDestinationById = async (destinationId: string) => { - const destination = await db.query.destinations.findFirst({ - where: eq(destinations.destinationId, destinationId), - }); - if (!destination) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Destination not found", - }); - } - return destination; -}; - -export const removeDestinationById = async (destinationId: string) => { - const result = await db - .delete(destinations) - .where(eq(destinations.destinationId, destinationId)) - .returning(); - - return result[0]; -}; - -export const updateDestinationById = async ( - destinationId: string, - destinationData: Partial, -) => { - const result = await db - .update(destinations) - .set({ - ...destinationData, - }) - .where(eq(destinations.destinationId, destinationId)) - .returning(); - - return result[0]; -}; diff --git a/apps/dokploy/server/api/services/docker.ts b/apps/dokploy/server/api/services/docker.ts deleted file mode 100644 index d611a11d..00000000 --- a/apps/dokploy/server/api/services/docker.ts +++ /dev/null @@ -1,223 +0,0 @@ -import { execAsync, execAsyncRemote } from "@/server/utils/process/execAsync"; - -export const getContainers = async (serverId?: string | null) => { - try { - const command = - "docker ps -a --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | Image: {{.Image}} | Ports: {{.Ports}} | State: {{.State}} | Status: {{.Status}}'"; - 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 lines = stdout.trim().split("\n"); - - const containers = lines - .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 image = parts[2] - ? parts[2].replace("Image: ", "").trim() - : "No image"; - const ports = parts[3] - ? parts[3].replace("Ports: ", "").trim() - : "No ports"; - const state = parts[4] - ? parts[4].replace("State: ", "").trim() - : "No state"; - const status = parts[5] - ? parts[5].replace("Status: ", "").trim() - : "No status"; - return { - containerId, - name, - image, - ports, - state, - status, - serverId, - }; - }) - .filter((container) => !container.name.includes("dokploy")); - - return containers; - } catch (error) { - console.error(error); - - return []; - } -}; - -export const getConfig = async ( - containerId: string, - serverId?: string | null, -) => { - try { - const command = `docker inspect ${containerId} --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 config = JSON.parse(stdout); - - return config; - } catch (error) {} -}; - -export const getContainersByAppNameMatch = async ( - appName: string, - appType?: "stack" | "docker-compose", - serverId?: string, -) => { - try { - let result: string[] = []; - const cmd = - "docker ps -a --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | State: {{.State}}'"; - - const command = - appType === "docker-compose" - ? `${cmd} --filter='label=com.docker.compose.project=${appName}'` - : `${cmd} | grep ${appName}`; - 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() - : "No state"; - return { - containerId, - name, - state, - }; - }); - - return containers || []; - } catch (error) {} - - return []; -}; - -export const getContainersByAppLabel = async ( - appName: string, - serverId?: string, -) => { - try { - let stdout = ""; - let stderr = ""; - - const command = `docker ps --filter "label=com.docker.swarm.service.name=${appName}" --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | State: {{.State}}'`; - 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; - } - - if (!stdout) return []; - - const lines = stdout.trim().split("\n"); - - const containers = lines.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() - : "No state"; - return { - containerId, - name, - state, - }; - }); - - return containers || []; - } catch (error) {} - - return []; -}; - -export const containerRestart = async (containerId: string) => { - try { - const { stdout, stderr } = await execAsync( - `docker container restart ${containerId}`, - ); - - if (stderr) { - console.error(`Error: ${stderr}`); - return; - } - - const config = JSON.parse(stdout); - - return config; - } catch (error) {} -}; diff --git a/apps/dokploy/server/api/services/domain.ts b/apps/dokploy/server/api/services/domain.ts deleted file mode 100644 index df180a45..00000000 --- a/apps/dokploy/server/api/services/domain.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { db } from "@/server/db"; -import { type apiCreateDomain, domains } from "@/server/db/schema"; -import { manageDomain } from "@/server/utils/traefik/domain"; -import { generateRandomDomain } from "@/templates/utils"; -import { TRPCError } from "@trpc/server"; -import { eq } from "drizzle-orm"; -import { findAdmin, findAdminById } from "./admin"; -import { findApplicationById } from "./application"; -import { findServerById } from "./server"; - -export type Domain = typeof domains.$inferSelect; - -export const createDomain = async (input: typeof apiCreateDomain._type) => { - const result = await db.transaction(async (tx) => { - const domain = await tx - .insert(domains) - .values({ - ...input, - }) - .returning() - .then((response) => response[0]); - - if (!domain) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error creating domain", - }); - } - - if (domain.applicationId) { - const application = await findApplicationById(domain.applicationId); - await manageDomain(application, domain); - } - - return domain; - }); - - return result; -}; - -export const generateTraefikMeDomain = async ( - appName: string, - adminId: string, - serverId?: string, -) => { - if (serverId) { - const server = await findServerById(serverId); - return generateRandomDomain({ - serverIp: server.ipAddress, - projectName: appName, - }); - } - - if (process.env.NODE_ENV === "development") { - return generateRandomDomain({ - serverIp: "", - projectName: appName, - }); - } - const admin = await findAdminById(adminId); - return generateRandomDomain({ - serverIp: admin?.serverIp || "", - projectName: appName, - }); -}; - -export const generateWildcardDomain = ( - appName: string, - serverDomain: string, -) => { - return `${appName}-${serverDomain}`; -}; - -export const findDomainById = async (domainId: string) => { - const domain = await db.query.domains.findFirst({ - where: eq(domains.domainId, domainId), - with: { - application: true, - }, - }); - if (!domain) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Domain not found", - }); - } - return domain; -}; - -export const findDomainsByApplicationId = async (applicationId: string) => { - const domainsArray = await db.query.domains.findMany({ - where: eq(domains.applicationId, applicationId), - with: { - application: true, - }, - }); - - return domainsArray; -}; - -export const findDomainsByComposeId = async (composeId: string) => { - const domainsArray = await db.query.domains.findMany({ - where: eq(domains.composeId, composeId), - with: { - compose: true, - }, - }); - - return domainsArray; -}; - -export const updateDomainById = async ( - domainId: string, - domainData: Partial, -) => { - const domain = await db - .update(domains) - .set({ - ...domainData, - }) - .where(eq(domains.domainId, domainId)) - .returning(); - - return domain[0]; -}; - -export const removeDomainById = async (domainId: string) => { - await findDomainById(domainId); - // TODO: fix order - const result = await db - .delete(domains) - .where(eq(domains.domainId, domainId)) - .returning(); - - return result[0]; -}; diff --git a/apps/dokploy/server/api/services/git-provider.ts b/apps/dokploy/server/api/services/git-provider.ts deleted file mode 100644 index 6846de4d..00000000 --- a/apps/dokploy/server/api/services/git-provider.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { db } from "@/server/db"; -import { type apiCreateGithub, gitProvider, github } from "@/server/db/schema"; -import { TRPCError } from "@trpc/server"; -import { eq } from "drizzle-orm"; - -export type GitProvider = typeof gitProvider.$inferSelect; - -export const removeGitProvider = async (gitProviderId: string) => { - const result = await db - .delete(gitProvider) - .where(eq(gitProvider.gitProviderId, gitProviderId)) - .returning(); - - return result[0]; -}; - -export const updateGitProvider = async ( - gitProviderId: string, - input: Partial, -) => { - return await db - .update(gitProvider) - .set({ - ...input, - }) - .where(eq(gitProvider.gitProviderId, gitProviderId)) - .returning() - .then((response) => response[0]); -}; diff --git a/apps/dokploy/server/api/services/github.ts b/apps/dokploy/server/api/services/github.ts deleted file mode 100644 index 54b55419..00000000 --- a/apps/dokploy/server/api/services/github.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { db } from "@/server/db"; -import { type apiCreateGithub, gitProvider, github } from "@/server/db/schema"; -import { TRPCError } from "@trpc/server"; -import { eq } from "drizzle-orm"; - -export type Github = typeof github.$inferSelect; -export const createGithub = async (input: typeof apiCreateGithub._type) => { - return await db.transaction(async (tx) => { - const newGitProvider = await tx - .insert(gitProvider) - .values({ - providerType: "github", - authId: input.authId, - name: input.name, - }) - .returning() - .then((response) => response[0]); - - if (!newGitProvider) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create the git provider", - }); - } - - return await tx - .insert(github) - .values({ - ...input, - gitProviderId: newGitProvider?.gitProviderId, - }) - .returning() - .then((response) => response[0]); - }); -}; - -export const findGithubById = async (githubId: string) => { - const githubProviderResult = await db.query.github.findFirst({ - where: eq(github.githubId, githubId), - with: { - gitProvider: true, - }, - }); - - if (!githubProviderResult) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Github Provider not found", - }); - } - - return githubProviderResult; -}; - -export const haveGithubRequirements = (github: Github) => { - return !!( - github?.githubAppId && - github?.githubPrivateKey && - github?.githubInstallationId - ); -}; - -export const updateGithub = async ( - githubId: string, - input: Partial, -) => { - return await db - .update(github) - .set({ - ...input, - }) - .where(eq(github.githubId, githubId)) - .returning() - .then((response) => response[0]); -}; diff --git a/apps/dokploy/server/api/services/gitlab.ts b/apps/dokploy/server/api/services/gitlab.ts deleted file mode 100644 index 05e2bb0f..00000000 --- a/apps/dokploy/server/api/services/gitlab.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { db } from "@/server/db"; -import { - type apiCreateGitlab, - type bitbucket, - gitProvider, - type github, - gitlab, -} from "@/server/db/schema"; -import { TRPCError } from "@trpc/server"; -import { eq } from "drizzle-orm"; - -export type Github = typeof github.$inferSelect; -export type Bitbucket = typeof bitbucket.$inferSelect; -export type Gitlab = typeof gitlab.$inferSelect; - -export const createGitlab = async (input: typeof apiCreateGitlab._type) => { - return await db.transaction(async (tx) => { - const newGitProvider = await tx - .insert(gitProvider) - .values({ - providerType: "gitlab", - authId: input.authId, - name: input.name, - }) - .returning() - .then((response) => response[0]); - - if (!newGitProvider) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create the git provider", - }); - } - - await tx - .insert(gitlab) - .values({ - ...input, - gitProviderId: newGitProvider?.gitProviderId, - }) - .returning() - .then((response) => response[0]); - }); -}; - -export const findGitlabById = async (gitlabId: string) => { - const gitlabProviderResult = await db.query.gitlab.findFirst({ - where: eq(gitlab.gitlabId, gitlabId), - with: { - gitProvider: true, - }, - }); - - if (!gitlabProviderResult) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Gitlab Provider not found", - }); - } - - return gitlabProviderResult; -}; - -export const updateGitlab = async ( - gitlabId: string, - input: Partial, -) => { - return await db - .update(gitlab) - .set({ - ...input, - }) - .where(eq(gitlab.gitlabId, gitlabId)) - .returning() - .then((response) => response[0]); -}; diff --git a/apps/dokploy/server/api/services/mariadb.ts b/apps/dokploy/server/api/services/mariadb.ts deleted file mode 100644 index 13b521d5..00000000 --- a/apps/dokploy/server/api/services/mariadb.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { generateRandomPassword } from "@/server/auth/random-password"; -import { db } from "@/server/db"; -import { type apiCreateMariaDB, backups, mariadb } from "@/server/db/schema"; -import { generateAppName } from "@/server/db/schema"; -import { buildMariadb } from "@/server/utils/databases/mariadb"; -import { pullImage } from "@/server/utils/docker/utils"; -import { generatePassword } from "@/templates/utils"; -import { TRPCError } from "@trpc/server"; -import { eq, getTableColumns } from "drizzle-orm"; -import { validUniqueServerAppName } from "./project"; - -import { execAsyncRemote } from "@/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); - - if (!valid) { - throw new TRPCError({ - code: "CONFLICT", - message: "Service with this 'AppName' already exists", - }); - } - } - - const newMariadb = await db - .insert(mariadb) - .values({ - ...input, - databasePassword: input.databasePassword - ? input.databasePassword - : (await generateRandomPassword()).randomPassword, - databaseRootPassword: input.databaseRootPassword - ? input.databaseRootPassword - : (await generateRandomPassword()).randomPassword, - }) - .returning() - .then((value) => value[0]); - - if (!newMariadb) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error input: Inserting mariadb database", - }); - } - - return newMariadb; -}; - -// https://github.com/drizzle-team/drizzle-orm/discussions/1483#discussioncomment-7523881 -export const findMariadbById = async (mariadbId: string) => { - const result = await db.query.mariadb.findFirst({ - where: eq(mariadb.mariadbId, mariadbId), - with: { - project: true, - mounts: true, - server: true, - backups: { - with: { - destination: true, - }, - }, - }, - }); - if (!result) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Mariadb not found", - }); - } - return result; -}; - -export const updateMariadbById = async ( - mariadbId: string, - mariadbData: Partial, -) => { - const result = await db - .update(mariadb) - .set({ - ...mariadbData, - }) - .where(eq(mariadb.mariadbId, mariadbId)) - .returning(); - - return result[0]; -}; - -export const removeMariadbById = async (mariadbId: string) => { - const result = await db - .delete(mariadb) - .where(eq(mariadb.mariadbId, mariadbId)) - .returning(); - - return result[0]; -}; - -export const findMariadbByBackupId = async (backupId: string) => { - const result = await db - .select({ - ...getTableColumns(mariadb), - }) - .from(mariadb) - .innerJoin(backups, eq(mariadb.mariadbId, backups.mariadbId)) - .where(eq(backups.backupId, backupId)) - .limit(1); - - if (!result || !result[0]) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "MariaDB not found", - }); - } - return result[0]; -}; - -export const deployMariadb = async (mariadbId: string) => { - const mariadb = await findMariadbById(mariadbId); - try { - if (mariadb.serverId) { - await execAsyncRemote( - mariadb.serverId, - `docker pull ${mariadb.dockerImage}`, - ); - } else { - await pullImage(mariadb.dockerImage); - } - - await buildMariadb(mariadb); - await updateMariadbById(mariadbId, { - applicationStatus: "done", - }); - } catch (error) { - await updateMariadbById(mariadbId, { - applicationStatus: "error", - }); - - throw new TRPCError({ - code: "INTERNAL_SERVER_ERROR", - message: `Error on deploy mariadb${error}`, - }); - } - return mariadb; -}; diff --git a/apps/dokploy/server/api/services/mongo.ts b/apps/dokploy/server/api/services/mongo.ts deleted file mode 100644 index 30ed3f64..00000000 --- a/apps/dokploy/server/api/services/mongo.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { generateRandomPassword } from "@/server/auth/random-password"; -import { db } from "@/server/db"; -import { type apiCreateMongo, backups, mongo } from "@/server/db/schema"; -import { generateAppName } from "@/server/db/schema"; -import { buildMongo } from "@/server/utils/databases/mongo"; -import { pullImage } from "@/server/utils/docker/utils"; -import { generatePassword } from "@/templates/utils"; -import { TRPCError } from "@trpc/server"; -import { eq, getTableColumns } from "drizzle-orm"; -import { validUniqueServerAppName } from "./project"; - -import { execAsyncRemote } from "@/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("postgres"); - if (input.appName) { - const valid = await validUniqueServerAppName(input.appName); - - if (!valid) { - throw new TRPCError({ - code: "CONFLICT", - message: "Service with this 'AppName' already exists", - }); - } - } - - const newMongo = await db - .insert(mongo) - .values({ - ...input, - databasePassword: input.databasePassword - ? input.databasePassword - : (await generateRandomPassword()).randomPassword, - }) - .returning() - .then((value) => value[0]); - - if (!newMongo) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error input: Inserting mongo database", - }); - } - - return newMongo; -}; - -export const findMongoById = async (mongoId: string) => { - const result = await db.query.mongo.findFirst({ - where: eq(mongo.mongoId, mongoId), - with: { - project: true, - mounts: true, - server: true, - backups: { - with: { - destination: true, - }, - }, - }, - }); - if (!result) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Mongo not found", - }); - } - return result; -}; - -export const updateMongoById = async ( - mongoId: string, - postgresData: Partial, -) => { - const result = await db - .update(mongo) - .set({ - ...postgresData, - }) - .where(eq(mongo.mongoId, mongoId)) - .returning(); - - return result[0]; -}; - -export const findMongoByBackupId = async (backupId: string) => { - const result = await db - .select({ - ...getTableColumns(mongo), - }) - .from(mongo) - .innerJoin(backups, eq(mongo.mongoId, backups.mongoId)) - .where(eq(backups.backupId, backupId)) - .limit(1); - - if (!result || !result[0]) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Mongo not found", - }); - } - return result[0]; -}; - -export const removeMongoById = async (mongoId: string) => { - const result = await db - .delete(mongo) - .where(eq(mongo.mongoId, mongoId)) - .returning(); - - return result[0]; -}; - -export const deployMongo = async (mongoId: string) => { - const mongo = await findMongoById(mongoId); - try { - if (mongo.serverId) { - await execAsyncRemote(mongo.serverId, `docker pull ${mongo.dockerImage}`); - } else { - await pullImage(mongo.dockerImage); - } - - await buildMongo(mongo); - await updateMongoById(mongoId, { - applicationStatus: "done", - }); - } catch (error) { - await updateMongoById(mongoId, { - applicationStatus: "error", - }); - - throw new TRPCError({ - code: "INTERNAL_SERVER_ERROR", - message: `Error on deploy mongo${error}`, - }); - } - return mongo; -}; diff --git a/apps/dokploy/server/api/services/mount.ts b/apps/dokploy/server/api/services/mount.ts deleted file mode 100644 index 97528aa5..00000000 --- a/apps/dokploy/server/api/services/mount.ts +++ /dev/null @@ -1,280 +0,0 @@ -import path from "node:path"; -import { paths } from "@/server/constants"; -import { db } from "@/server/db"; -import { - type ServiceType, - type apiCreateMount, - mounts, -} from "@/server/db/schema"; -import { createFile, getCreateFileCommand } from "@/server/utils/docker/utils"; -import { removeFileOrDirectory } from "@/server/utils/filesystem/directory"; -import { execAsyncRemote } from "@/server/utils/process/execAsync"; -import { TRPCError } from "@trpc/server"; -import { type SQL, eq, sql } from "drizzle-orm"; - -export type Mount = typeof mounts.$inferSelect; - -export const createMount = async (input: typeof apiCreateMount._type) => { - try { - const { serviceId, ...rest } = input; - const value = await db - .insert(mounts) - .values({ - ...rest, - ...(input.serviceType === "application" && { - applicationId: serviceId, - }), - ...(input.serviceType === "postgres" && { - postgresId: serviceId, - }), - ...(input.serviceType === "mariadb" && { - mariadbId: serviceId, - }), - ...(input.serviceType === "mongo" && { - mongoId: serviceId, - }), - ...(input.serviceType === "mysql" && { - mysqlId: serviceId, - }), - ...(input.serviceType === "redis" && { - redisId: serviceId, - }), - ...(input.serviceType === "compose" && { - composeId: serviceId, - }), - }) - .returning() - .then((value) => value[0]); - - if (!value) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error input: 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 to create the mount", - 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 to create the file mount: ${error}`); - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create the mount", - cause: error, - }); - } -}; - -export const findMountById = async (mountId: string) => { - const mount = await db.query.mounts.findFirst({ - where: eq(mounts.mountId, mountId), - with: { - application: true, - postgres: true, - mariadb: true, - mongo: true, - mysql: true, - redis: true, - compose: true, - }, - }); - if (!mount) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Mount not found", - }); - } - return mount; -}; - -export const updateMount = async ( - mountId: string, - mountData: Partial, -) => { - return await db.transaction(async (transaction) => { - const mount = await db - .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", - }); - } - - if (mount.type === "file") { - await deleteFileMount(mountId); - await createFileMount(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 "postgres": - sqlChunks.push(eq(mounts.postgresId, 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 "redis": - sqlChunks.push(eq(mounts.redisId, 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 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 (error) {} -}; - -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); - } - directoryPath = path.join(absoluteBasePath, appName, "files"); - - return directoryPath; -}; - -type MountNested = Awaited>; -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; - } - - return null; -}; diff --git a/apps/dokploy/server/api/services/mysql.ts b/apps/dokploy/server/api/services/mysql.ts deleted file mode 100644 index 3a5c2884..00000000 --- a/apps/dokploy/server/api/services/mysql.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { generateRandomPassword } from "@/server/auth/random-password"; -import { db } from "@/server/db"; -import { type apiCreateMySql, backups, mysql } from "@/server/db/schema"; -import { generateAppName } from "@/server/db/schema"; -import { buildMysql } from "@/server/utils/databases/mysql"; -import { pullImage } from "@/server/utils/docker/utils"; -import { generatePassword } from "@/templates/utils"; -import { TRPCError } from "@trpc/server"; -import { eq, getTableColumns } from "drizzle-orm"; -import { validUniqueServerAppName } from "./project"; - -import { execAsyncRemote } from "@/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"); - - if (input.appName) { - const valid = await validUniqueServerAppName(input.appName); - - if (!valid) { - throw new TRPCError({ - code: "CONFLICT", - message: "Service with this 'AppName' already exists", - }); - } - } - - const newMysql = await db - .insert(mysql) - .values({ - ...input, - databasePassword: input.databasePassword - ? input.databasePassword - : (await generateRandomPassword()).randomPassword, - databaseRootPassword: input.databaseRootPassword - ? input.databaseRootPassword - : (await generateRandomPassword()).randomPassword, - }) - .returning() - .then((value) => value[0]); - - if (!newMysql) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error input: Inserting mysql database", - }); - } - - return newMysql; -}; - -// https://github.com/drizzle-team/drizzle-orm/discussions/1483#discussioncomment-7523881 -export const findMySqlById = async (mysqlId: string) => { - const result = await db.query.mysql.findFirst({ - where: eq(mysql.mysqlId, mysqlId), - with: { - project: true, - mounts: true, - server: true, - backups: { - with: { - destination: true, - }, - }, - }, - }); - if (!result) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "MySql not found", - }); - } - return result; -}; - -export const updateMySqlById = async ( - mysqlId: string, - mysqlData: Partial, -) => { - const result = await db - .update(mysql) - .set({ - ...mysqlData, - }) - .where(eq(mysql.mysqlId, mysqlId)) - .returning(); - - return result[0]; -}; - -export const findMySqlByBackupId = async (backupId: string) => { - const result = await db - .select({ - ...getTableColumns(mysql), - }) - .from(mysql) - .innerJoin(backups, eq(mysql.mysqlId, backups.mysqlId)) - .where(eq(backups.backupId, backupId)) - .limit(1); - - if (!result || !result[0]) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Mysql not found", - }); - } - return result[0]; -}; - -export const removeMySqlById = async (mysqlId: string) => { - const result = await db - .delete(mysql) - .where(eq(mysql.mysqlId, mysqlId)) - .returning(); - - return result[0]; -}; - -export const deployMySql = async (mysqlId: string) => { - const mysql = await findMySqlById(mysqlId); - try { - if (mysql.serverId) { - await execAsyncRemote(mysql.serverId, `docker pull ${mysql.dockerImage}`); - } else { - await pullImage(mysql.dockerImage); - } - - await buildMysql(mysql); - await updateMySqlById(mysqlId, { - applicationStatus: "done", - }); - } catch (error) { - await updateMySqlById(mysqlId, { - applicationStatus: "error", - }); - throw new TRPCError({ - code: "INTERNAL_SERVER_ERROR", - message: `Error on deploy mysql${error}`, - }); - } - return mysql; -}; diff --git a/apps/dokploy/server/api/services/notification.ts b/apps/dokploy/server/api/services/notification.ts deleted file mode 100644 index 31ff91ea..00000000 --- a/apps/dokploy/server/api/services/notification.ts +++ /dev/null @@ -1,409 +0,0 @@ -import { db } from "@/server/db"; -import { - type apiCreateDiscord, - type apiCreateEmail, - type apiCreateSlack, - type apiCreateTelegram, - type apiUpdateDiscord, - type apiUpdateEmail, - type apiUpdateSlack, - type apiUpdateTelegram, - discord, - email, - notifications, - slack, - telegram, -} from "@/server/db/schema"; -import { TRPCError } from "@trpc/server"; -import { eq } from "drizzle-orm"; - -export type Notification = typeof notifications.$inferSelect; - -export const createSlackNotification = async ( - input: typeof apiCreateSlack._type, -) => { - await db.transaction(async (tx) => { - const newSlack = await tx - .insert(slack) - .values({ - channel: input.channel, - webhookUrl: input.webhookUrl, - }) - .returning() - .then((value) => value[0]); - - if (!newSlack) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error input: Inserting slack", - }); - } - - const newDestination = await tx - .insert(notifications) - .values({ - slackId: newSlack.slackId, - name: input.name, - appDeploy: input.appDeploy, - appBuildError: input.appBuildError, - databaseBackup: input.databaseBackup, - dokployRestart: input.dokployRestart, - dockerCleanup: input.dockerCleanup, - notificationType: "slack", - }) - .returning() - .then((value) => value[0]); - - if (!newDestination) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error input: Inserting notification", - }); - } - - return newDestination; - }); -}; - -export const updateSlackNotification = async ( - input: typeof apiUpdateSlack._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, - }) - .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(slack) - .set({ - channel: input.channel, - webhookUrl: input.webhookUrl, - }) - .where(eq(slack.slackId, input.slackId)) - .returning() - .then((value) => value[0]); - - return newDestination; - }); -}; - -export const createTelegramNotification = async ( - input: typeof apiCreateTelegram._type, -) => { - await db.transaction(async (tx) => { - const newTelegram = await tx - .insert(telegram) - .values({ - botToken: input.botToken, - chatId: input.chatId, - }) - .returning() - .then((value) => value[0]); - - if (!newTelegram) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error input: Inserting telegram", - }); - } - - const newDestination = await tx - .insert(notifications) - .values({ - telegramId: newTelegram.telegramId, - name: input.name, - appDeploy: input.appDeploy, - appBuildError: input.appBuildError, - databaseBackup: input.databaseBackup, - dokployRestart: input.dokployRestart, - dockerCleanup: input.dockerCleanup, - notificationType: "telegram", - }) - .returning() - .then((value) => value[0]); - - if (!newDestination) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error input: Inserting notification", - }); - } - - return newDestination; - }); -}; - -export const updateTelegramNotification = async ( - input: typeof apiUpdateTelegram._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, - }) - .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(telegram) - .set({ - botToken: input.botToken, - chatId: input.chatId, - }) - .where(eq(telegram.telegramId, input.telegramId)) - .returning() - .then((value) => value[0]); - - return newDestination; - }); -}; - -export const createDiscordNotification = async ( - input: typeof apiCreateDiscord._type, -) => { - await db.transaction(async (tx) => { - const newDiscord = await tx - .insert(discord) - .values({ - webhookUrl: input.webhookUrl, - }) - .returning() - .then((value) => value[0]); - - if (!newDiscord) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error input: Inserting discord", - }); - } - - const newDestination = await tx - .insert(notifications) - .values({ - discordId: newDiscord.discordId, - name: input.name, - appDeploy: input.appDeploy, - appBuildError: input.appBuildError, - databaseBackup: input.databaseBackup, - dokployRestart: input.dokployRestart, - dockerCleanup: input.dockerCleanup, - notificationType: "discord", - }) - .returning() - .then((value) => value[0]); - - if (!newDestination) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error input: Inserting notification", - }); - } - - return newDestination; - }); -}; - -export const updateDiscordNotification = async ( - input: typeof apiUpdateDiscord._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, - }) - .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(discord) - .set({ - webhookUrl: input.webhookUrl, - }) - .where(eq(discord.discordId, input.discordId)) - .returning() - .then((value) => value[0]); - - return newDestination; - }); -}; - -export const createEmailNotification = async ( - input: typeof apiCreateEmail._type, -) => { - await db.transaction(async (tx) => { - const newEmail = await tx - .insert(email) - .values({ - smtpServer: input.smtpServer, - smtpPort: input.smtpPort, - username: input.username, - password: input.password, - fromAddress: input.fromAddress, - toAddresses: input.toAddresses, - }) - .returning() - .then((value) => value[0]); - - if (!newEmail) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error input: Inserting email", - }); - } - - const newDestination = await tx - .insert(notifications) - .values({ - emailId: newEmail.emailId, - name: input.name, - appDeploy: input.appDeploy, - appBuildError: input.appBuildError, - databaseBackup: input.databaseBackup, - dokployRestart: input.dokployRestart, - dockerCleanup: input.dockerCleanup, - notificationType: "email", - }) - .returning() - .then((value) => value[0]); - - if (!newDestination) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error input: Inserting notification", - }); - } - - return newDestination; - }); -}; - -export const updateEmailNotification = async ( - input: typeof apiUpdateEmail._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, - }) - .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(email) - .set({ - smtpServer: input.smtpServer, - smtpPort: input.smtpPort, - username: input.username, - password: input.password, - fromAddress: input.fromAddress, - toAddresses: input.toAddresses, - }) - .where(eq(email.emailId, input.emailId)) - .returning() - .then((value) => value[0]); - - return newDestination; - }); -}; - -export const findNotificationById = async (notificationId: string) => { - const notification = await db.query.notifications.findFirst({ - where: eq(notifications.notificationId, notificationId), - with: { - slack: true, - telegram: true, - discord: true, - email: true, - }, - }); - if (!notification) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Notification not found", - }); - } - return notification; -}; - -export const removeNotificationById = async (notificationId: string) => { - const result = await db - .delete(notifications) - .where(eq(notifications.notificationId, notificationId)) - .returning(); - - return result[0]; -}; - -export const updateDestinationById = async ( - notificationId: string, - notificationData: Partial, -) => { - const result = await db - .update(notifications) - .set({ - ...notificationData, - }) - .where(eq(notifications.notificationId, notificationId)) - .returning(); - - return result[0]; -}; diff --git a/apps/dokploy/server/api/services/port.ts b/apps/dokploy/server/api/services/port.ts deleted file mode 100644 index 19229a9c..00000000 --- a/apps/dokploy/server/api/services/port.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { db } from "@/server/db"; -import { type apiCreatePort, ports } from "@/server/db/schema"; -import { TRPCError } from "@trpc/server"; -import { eq } from "drizzle-orm"; - -export type Port = typeof ports.$inferSelect; - -export const createPort = async (input: typeof apiCreatePort._type) => { - const newPort = await db - .insert(ports) - .values({ - ...input, - }) - .returning() - .then((value) => value[0]); - - if (!newPort) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error input: Inserting port", - }); - } - - return newPort; -}; - -export const finPortById = async (portId: string) => { - const result = await db.query.ports.findFirst({ - where: eq(ports.portId, portId), - }); - if (!result) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Port not found", - }); - } - return result; -}; - -export const removePortById = async (portId: string) => { - const result = await db - .delete(ports) - .where(eq(ports.portId, portId)) - .returning(); - - return result[0]; -}; - -export const updatePortById = async ( - portId: string, - portData: Partial, -) => { - const result = await db - .update(ports) - .set({ - ...portData, - }) - .where(eq(ports.portId, portId)) - .returning(); - - return result[0]; -}; diff --git a/apps/dokploy/server/api/services/postgres.ts b/apps/dokploy/server/api/services/postgres.ts deleted file mode 100644 index 03adaf9b..00000000 --- a/apps/dokploy/server/api/services/postgres.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { generateRandomPassword } from "@/server/auth/random-password"; -import { db } from "@/server/db"; -import { type apiCreatePostgres, backups, postgres } from "@/server/db/schema"; -import { generateAppName } from "@/server/db/schema"; -import { buildPostgres } from "@/server/utils/databases/postgres"; -import { pullImage } from "@/server/utils/docker/utils"; -import { generatePassword } from "@/templates/utils"; -import { TRPCError } from "@trpc/server"; -import { eq, getTableColumns } from "drizzle-orm"; -import { validUniqueServerAppName } from "./project"; - -import { execAsyncRemote } from "@/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); - - if (!valid) { - throw new TRPCError({ - code: "CONFLICT", - message: "Service with this 'AppName' already exists", - }); - } - } - - const newPostgres = await db - .insert(postgres) - .values({ - ...input, - databasePassword: input.databasePassword - ? input.databasePassword - : (await generateRandomPassword()).randomPassword, - }) - .returning() - .then((value) => value[0]); - - if (!newPostgres) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error input: Inserting postgresql database", - }); - } - - return newPostgres; -}; -export const findPostgresById = async (postgresId: string) => { - const result = await db.query.postgres.findFirst({ - where: eq(postgres.postgresId, postgresId), - with: { - project: true, - mounts: true, - server: true, - backups: { - with: { - destination: true, - }, - }, - }, - }); - if (!result) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Postgres not found", - }); - } - return result; -}; - -export const findPostgresByBackupId = async (backupId: string) => { - const result = await db - .select({ - ...getTableColumns(postgres), - }) - .from(postgres) - .innerJoin(backups, eq(postgres.postgresId, backups.postgresId)) - .where(eq(backups.backupId, backupId)) - .limit(1); - - if (!result || !result[0]) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Postgres not found", - }); - } - return result[0]; -}; - -export const updatePostgresById = async ( - postgresId: string, - postgresData: Partial, -) => { - const result = await db - .update(postgres) - .set({ - ...postgresData, - }) - .where(eq(postgres.postgresId, postgresId)) - .returning(); - - return result[0]; -}; - -export const removePostgresById = async (postgresId: string) => { - const result = await db - .delete(postgres) - .where(eq(postgres.postgresId, postgresId)) - .returning(); - - return result[0]; -}; - -export const deployPostgres = async (postgresId: string) => { - const postgres = await findPostgresById(postgresId); - try { - const promises = []; - if (postgres.serverId) { - const result = await execAsyncRemote( - postgres.serverId, - `docker pull ${postgres.dockerImage}`, - ); - } else { - await pullImage(postgres.dockerImage); - } - - await buildPostgres(postgres); - await updatePostgresById(postgresId, { - applicationStatus: "done", - }); - } catch (error) { - await updatePostgresById(postgresId, { - applicationStatus: "error", - }); - throw new TRPCError({ - code: "INTERNAL_SERVER_ERROR", - message: `Error on deploy postgres${error}`, - }); - } - return postgres; -}; diff --git a/apps/dokploy/server/api/services/project.ts b/apps/dokploy/server/api/services/project.ts deleted file mode 100644 index 902dd06b..00000000 --- a/apps/dokploy/server/api/services/project.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { db } from "@/server/db"; -import { - type apiCreateProject, - applications, - mariadb, - mongo, - mysql, - postgres, - projects, - redis, -} from "@/server/db/schema"; -import { TRPCError } from "@trpc/server"; -import { eq } from "drizzle-orm"; - -export type Project = typeof projects.$inferSelect; - -export const createProject = async ( - input: typeof apiCreateProject._type, - adminId: string, -) => { - const newProject = await db - .insert(projects) - .values({ - ...input, - adminId: adminId, - }) - .returning() - .then((value) => value[0]); - - if (!newProject) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create the project", - }); - } - - return newProject; -}; - -export const findProjectById = async (projectId: string) => { - const project = await db.query.projects.findFirst({ - where: eq(projects.projectId, projectId), - with: { - applications: true, - mariadb: true, - mongo: true, - mysql: true, - postgres: true, - redis: true, - compose: true, - }, - }); - if (!project) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Project not found", - }); - } - return project; -}; - -export const deleteProject = async (projectId: string) => { - const project = await db - .delete(projects) - .where(eq(projects.projectId, projectId)) - .returning() - .then((value) => value[0]); - - return project; -}; - -export const updateProjectById = async ( - projectId: string, - projectData: Partial, -) => { - const result = await db - .update(projects) - .set({ - ...projectData, - }) - .where(eq(projects.projectId, projectId)) - .returning() - .then((res) => res[0]); - - return result; -}; - -export const validUniqueServerAppName = async (appName: string) => { - const query = await db.query.projects.findMany({ - with: { - applications: { - where: eq(applications.appName, appName), - }, - mariadb: { - where: eq(mariadb.appName, appName), - }, - mongo: { - where: eq(mongo.appName, appName), - }, - mysql: { - where: eq(mysql.appName, appName), - }, - postgres: { - where: eq(postgres.appName, appName), - }, - redis: { - where: eq(redis.appName, appName), - }, - }, - }); - - // Filter out items with non-empty fields - const nonEmptyProjects = query.filter( - (project) => - project.applications.length > 0 || - project.mariadb.length > 0 || - project.mongo.length > 0 || - project.mysql.length > 0 || - project.postgres.length > 0 || - project.redis.length > 0, - ); - - return nonEmptyProjects.length === 0; -}; diff --git a/apps/dokploy/server/api/services/redirect.ts b/apps/dokploy/server/api/services/redirect.ts deleted file mode 100644 index 972603f2..00000000 --- a/apps/dokploy/server/api/services/redirect.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { db } from "@/server/db"; -import { type apiCreateRedirect, redirects } from "@/server/db/schema"; -import { - createRedirectMiddleware, - removeRedirectMiddleware, - updateRedirectMiddleware, -} from "@/server/utils/traefik/redirect"; -import { TRPCError } from "@trpc/server"; -import { desc, eq } from "drizzle-orm"; -import type { z } from "zod"; -import { findApplicationById } from "./application"; -export type Redirect = typeof redirects.$inferSelect; - -export const findRedirectById = async (redirectId: string) => { - const application = await db.query.redirects.findFirst({ - where: eq(redirects.redirectId, redirectId), - }); - if (!application) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Redirect not found", - }); - } - return application; -}; - -export const createRedirect = async ( - redirectData: z.infer, -) => { - try { - await db.transaction(async (tx) => { - const redirect = await tx - .insert(redirects) - .values({ - ...redirectData, - }) - .returning() - .then((res) => res[0]); - - if (!redirect) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create the redirect", - }); - } - - const application = await findApplicationById(redirect.applicationId); - - createRedirectMiddleware(application, redirect); - }); - - return true; - } catch (error) { - console.log(error); - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create this redirect", - cause: error, - }); - } -}; - -export const removeRedirectById = async (redirectId: string) => { - try { - const response = await db - .delete(redirects) - .where(eq(redirects.redirectId, redirectId)) - .returning() - .then((res) => res[0]); - - if (!response) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Redirect not found", - }); - } - - const application = await findApplicationById(response.applicationId); - - await removeRedirectMiddleware(application, response); - - return response; - } catch (error) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to remove this redirect", - cause: error, - }); - } -}; - -export const updateRedirectById = async ( - redirectId: string, - redirectData: Partial, -) => { - try { - const redirect = await db - .update(redirects) - .set({ - ...redirectData, - }) - .where(eq(redirects.redirectId, redirectId)) - .returning() - .then((res) => res[0]); - - if (!redirect) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Redirect not found", - }); - } - const application = await findApplicationById(redirect.applicationId); - - await updateRedirectMiddleware(application, redirect); - - return redirect; - } catch (error) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to update this redirect", - }); - } -}; diff --git a/apps/dokploy/server/api/services/redis.ts b/apps/dokploy/server/api/services/redis.ts deleted file mode 100644 index a06e979e..00000000 --- a/apps/dokploy/server/api/services/redis.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { generateRandomPassword } from "@/server/auth/random-password"; -import { db } from "@/server/db"; -import { type apiCreateRedis, redis } from "@/server/db/schema"; -import { generateAppName } from "@/server/db/schema"; -import { buildRedis } from "@/server/utils/databases/redis"; -import { pullImage } from "@/server/utils/docker/utils"; -import { generatePassword } from "@/templates/utils"; -import { TRPCError } from "@trpc/server"; -import { eq } from "drizzle-orm"; -import { validUniqueServerAppName } from "./project"; - -import { execAsyncRemote } from "@/server/utils/process/execAsync"; - -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); - - if (!valid) { - throw new TRPCError({ - code: "CONFLICT", - message: "Service with this 'AppName' already exists", - }); - } - } - - const newRedis = await db - .insert(redis) - .values({ - ...input, - databasePassword: input.databasePassword - ? input.databasePassword - : (await generateRandomPassword()).randomPassword, - }) - .returning() - .then((value) => value[0]); - - if (!newRedis) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error input: Inserting redis database", - }); - } - - return newRedis; -}; - -export const findRedisById = async (redisId: string) => { - const result = await db.query.redis.findFirst({ - where: eq(redis.redisId, redisId), - with: { - project: true, - mounts: true, - server: true, - }, - }); - if (!result) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Redis not found", - }); - } - return result; -}; - -export const updateRedisById = async ( - redisId: string, - redisData: Partial, -) => { - const result = await db - .update(redis) - .set({ - ...redisData, - }) - .where(eq(redis.redisId, redisId)) - .returning(); - - return result[0]; -}; - -export const removeRedisById = async (redisId: string) => { - const result = await db - .delete(redis) - .where(eq(redis.redisId, redisId)) - .returning(); - - return result[0]; -}; - -export const deployRedis = async (redisId: string) => { - const redis = await findRedisById(redisId); - try { - if (redis.serverId) { - await execAsyncRemote(redis.serverId, `docker pull ${redis.dockerImage}`); - } else { - await pullImage(redis.dockerImage); - } - - await buildRedis(redis); - await updateRedisById(redisId, { - applicationStatus: "done", - }); - } catch (error) { - await updateRedisById(redisId, { - applicationStatus: "error", - }); - - throw new TRPCError({ - code: "INTERNAL_SERVER_ERROR", - message: `Error on deploy redis${error}`, - }); - } - return redis; -}; diff --git a/apps/dokploy/server/api/services/registry.ts b/apps/dokploy/server/api/services/registry.ts deleted file mode 100644 index 83dcc2a2..00000000 --- a/apps/dokploy/server/api/services/registry.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { db } from "@/server/db"; -import { type apiCreateRegistry, registry } from "@/server/db/schema"; -import { initializeRegistry } from "@/server/setup/registry-setup"; -import { removeService } from "@/server/utils/docker/utils"; -import { execAsync } from "@/server/utils/process/execAsync"; -import { - manageRegistry, - removeSelfHostedRegistry, -} from "@/server/utils/traefik/registry"; -import { TRPCError } from "@trpc/server"; -import { eq } from "drizzle-orm"; -import { findAdmin } from "./admin"; - -export type Registry = typeof registry.$inferSelect; - -export const createRegistry = async (input: typeof apiCreateRegistry._type) => { - const admin = await findAdmin(); - - return await db.transaction(async (tx) => { - const newRegistry = await tx - .insert(registry) - .values({ - ...input, - adminId: admin.adminId, - }) - .returning() - .then((value) => value[0]); - - if (!newRegistry) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error input: Inserting registry", - }); - } - - if (newRegistry.registryType === "cloud") { - const loginCommand = `echo ${input.password} | docker login ${input.registryUrl} --username ${input.username} --password-stdin`; - await execAsync(loginCommand); - } - - return newRegistry; - }); -}; - -export const removeRegistry = async (registryId: string) => { - try { - const response = await db - .delete(registry) - .where(eq(registry.registryId, registryId)) - .returning() - .then((res) => res[0]); - - if (!response) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Registry not found", - }); - } - - if (response.registryType === "selfHosted") { - await removeSelfHostedRegistry(); - await removeService("dokploy-registry"); - } - - await execAsync(`docker logout ${response.registryUrl}`); - - return response; - } catch (error) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to remove this registry", - cause: error, - }); - } -}; - -export const updateRegistry = async ( - registryId: string, - registryData: Partial, -) => { - try { - const response = await db - .update(registry) - .set({ - ...registryData, - }) - .where(eq(registry.registryId, registryId)) - .returning() - .then((res) => res[0]); - - if (response?.registryType === "selfHosted") { - await manageRegistry(response); - await initializeRegistry(response.username, response.password); - } - - return response; - } catch (error) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to update this registry", - }); - } -}; - -export const findRegistryById = async (registryId: string) => { - const registryResponse = await db.query.registry.findFirst({ - where: eq(registry.registryId, registryId), - columns: { - password: false, - }, - }); - if (!registryResponse) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Registry not found", - }); - } - return registryResponse; -}; - -export const findAllRegistry = async () => { - const registryResponse = await db.query.registry.findMany(); - return registryResponse; -}; diff --git a/apps/dokploy/server/api/services/security.ts b/apps/dokploy/server/api/services/security.ts deleted file mode 100644 index 2da78e26..00000000 --- a/apps/dokploy/server/api/services/security.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { db } from "@/server/db"; -import { type apiCreateSecurity, security } from "@/server/db/schema"; -import { - createSecurityMiddleware, - removeSecurityMiddleware, -} from "@/server/utils/traefik/security"; -import { TRPCError } from "@trpc/server"; -import { eq } from "drizzle-orm"; -import type { z } from "zod"; -import { findApplicationById } from "./application"; -export type Security = typeof security.$inferSelect; - -export const findSecurityById = async (securityId: string) => { - const application = await db.query.security.findFirst({ - where: eq(security.securityId, securityId), - }); - if (!application) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Security not found", - }); - } - return application; -}; - -export const createSecurity = async ( - data: z.infer, -) => { - try { - await db.transaction(async (tx) => { - const application = await findApplicationById(data.applicationId); - - const securityResponse = await tx - .insert(security) - .values({ - ...data, - }) - .returning() - .then((res) => res[0]); - - if (!securityResponse) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create the security", - }); - } - await createSecurityMiddleware(application, securityResponse); - return true; - }); - } catch (error) { - console.log(error); - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create this security", - cause: error, - }); - } -}; - -export const deleteSecurityById = async (securityId: string) => { - try { - const result = await db - .delete(security) - .where(eq(security.securityId, securityId)) - .returning() - .then((res) => res[0]); - - if (!result) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Security not found", - }); - } - - const application = await findApplicationById(result.applicationId); - - await removeSecurityMiddleware(application, result); - return result; - } catch (error) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to remove this security", - }); - } -}; - -export const updateSecurityById = async ( - securityId: string, - data: Partial, -) => { - try { - const response = await db - .update(security) - .set({ - ...data, - }) - .where(eq(security.securityId, securityId)) - .returning(); - - return response[0]; - } catch (error) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to update this security", - }); - } -}; diff --git a/apps/dokploy/server/api/services/server.ts b/apps/dokploy/server/api/services/server.ts deleted file mode 100644 index 9f4c3270..00000000 --- a/apps/dokploy/server/api/services/server.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { db } from "@/server/db"; - -import { type apiCreateServer, server } from "@/server/db/schema"; -import { TRPCError } from "@trpc/server"; -import { desc, eq } from "drizzle-orm"; - -export type Server = typeof server.$inferSelect; - -export const createServer = async ( - input: typeof apiCreateServer._type, - adminId: string, -) => { - const newServer = await db - .insert(server) - .values({ - ...input, - adminId: adminId, - }) - .returning() - .then((value) => value[0]); - - if (!newServer) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create the server", - }); - } - - return newServer; -}; - -export const findServerById = async (serverId: string) => { - const currentServer = await db.query.server.findFirst({ - where: eq(server.serverId, serverId), - with: { - deployments: true, - sshKey: true, - }, - }); - if (!currentServer) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Server not found", - }); - } - return currentServer; -}; - -export const findServersByAdminId = async (adminId: string) => { - const servers = await db.query.server.findMany({ - where: eq(server.adminId, adminId), - orderBy: desc(server.createdAt), - }); - - return servers; -}; - -export const deleteServer = async (serverId: string) => { - const currentServer = await db - .delete(server) - .where(eq(server.serverId, serverId)) - .returning() - .then((value) => value[0]); - - return currentServer; -}; - -export const haveActiveServices = async (serverId: string) => { - const currentServer = await db.query.server.findFirst({ - where: eq(server.serverId, serverId), - with: { - applications: true, - compose: true, - redis: true, - mariadb: true, - mongo: true, - mysql: true, - postgres: true, - }, - }); - - if (!currentServer) { - return false; - } - - const total = - currentServer?.applications?.length + - currentServer?.compose?.length + - currentServer?.redis?.length + - currentServer?.mariadb?.length + - currentServer?.mongo?.length + - currentServer?.mysql?.length + - currentServer?.postgres?.length; - - if (total === 0) { - return false; - } - - return true; -}; - -export const updateServerById = async ( - serverId: string, - serverData: Partial, -) => { - const result = await db - .update(server) - .set({ - ...serverData, - }) - .where(eq(server.serverId, serverId)) - .returning() - .then((res) => res[0]); - - return result; -}; - -export const getAllServers = async () => { - const servers = await db.query.server.findMany(); - return servers; -}; diff --git a/apps/dokploy/server/api/services/settings.ts b/apps/dokploy/server/api/services/settings.ts deleted file mode 100644 index ae1c275c..00000000 --- a/apps/dokploy/server/api/services/settings.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { readdirSync } from "node:fs"; -import { join } from "node:path"; -import { docker } from "@/server/constants"; -import { getServiceContainer } from "@/server/utils/docker/utils"; -import { execAsyncRemote } from "@/server/utils/process/execAsync"; -import packageInfo from "../../../package.json"; - -const updateIsAvailable = async () => { - try { - const service = await getServiceContainer("dokploy"); - - const localImage = await docker.getImage(getDokployImage()).inspect(); - return localImage.Id !== service?.ImageID; - } catch (error) { - return false; - } -}; - -export const getDokployImage = () => { - return `dokploy/dokploy:${process.env.RELEASE_TAG || "latest"}`; -}; - -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; -}; -export const getDokployVersion = () => { - return packageInfo.version; -}; - -interface TreeDataItem { - id: string; - name: string; - type: "file" | "directory"; - children?: TreeDataItem[]; -} - -export const readDirectory = async ( - dirPath: string, - serverId?: string, -): Promise => { - if (serverId) { - const { stdout } = await execAsyncRemote( - serverId, - ` -process_items() { - local parent_dir="$1" - local __resultvar=$2 - - local items_json="" - local first=true - for item in "$parent_dir"/*; do - [ -e "$item" ] || continue - process_item "$item" item_json - if [ "$first" = true ]; then - first=false - items_json="$item_json" - else - items_json="$items_json,$item_json" - fi - done - - eval $__resultvar="'[$items_json]'" -} - -process_item() { - local item_path="$1" - local __resultvar=$2 - - local item_name=$(basename "$item_path") - local escaped_name=$(echo "$item_name" | sed 's/"/\\"/g') - local escaped_path=$(echo "$item_path" | sed 's/"/\\"/g') - - if [ -d "$item_path" ]; then - # Is directory - process_items "$item_path" children_json - local json='{"id":"'"$escaped_path"'","name":"'"$escaped_name"'","type":"directory","children":'"$children_json"'}' - else - # Is file - local json='{"id":"'"$escaped_path"'","name":"'"$escaped_name"'","type":"file"}' - fi - - eval $__resultvar="'$json'" -} - -root_dir=${dirPath} - -process_items "$root_dir" json_output - -echo "$json_output" - `, - ); - const result = JSON.parse(stdout); - return result; - } - const items = readdirSync(dirPath, { withFileTypes: true }); - - const stack = [dirPath]; - const result: TreeDataItem[] = []; - const parentMap: Record = {}; - - while (stack.length > 0) { - const currentPath = stack.pop(); - if (!currentPath) continue; - - const items = readdirSync(currentPath, { withFileTypes: true }); - const currentDirectoryResult: TreeDataItem[] = []; - - for (const item of items) { - const fullPath = join(currentPath, item.name); - if (item.isDirectory()) { - stack.push(fullPath); - const directoryItem: TreeDataItem = { - id: fullPath, - name: item.name, - type: "directory", - children: [], - }; - currentDirectoryResult.push(directoryItem); - parentMap[fullPath] = directoryItem.children as TreeDataItem[]; - } else { - const fileItem: TreeDataItem = { - id: fullPath, - name: item.name, - type: "file", - }; - currentDirectoryResult.push(fileItem); - } - } - - if (parentMap[currentPath]) { - parentMap[currentPath].push(...currentDirectoryResult); - } else { - result.push(...currentDirectoryResult); - } - } - return result; -}; diff --git a/apps/dokploy/server/api/services/ssh-key.ts b/apps/dokploy/server/api/services/ssh-key.ts deleted file mode 100644 index 9d6f0070..00000000 --- a/apps/dokploy/server/api/services/ssh-key.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { db } from "@/server/db"; -import { - type apiCreateSshKey, - type apiFindOneSshKey, - type apiRemoveSshKey, - type apiUpdateSshKey, - sshKeys, -} from "@/server/db/schema"; -import { removeSSHKey, saveSSHKey } from "@/server/utils/filesystem/ssh"; -import { TRPCError } from "@trpc/server"; -import { eq } from "drizzle-orm"; - -export const createSshKey = async ({ - privateKey, - ...input -}: typeof apiCreateSshKey._type) => { - await db.transaction(async (tx) => { - const sshKey = await tx - .insert(sshKeys) - .values(input) - .returning() - .then((response) => response[0]) - .catch((e) => console.error(e)); - - if (sshKey) { - saveSSHKey(sshKey.sshKeyId, sshKey.publicKey, privateKey); - } - - if (!sshKey) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to create the ssh key", - }); - } - return sshKey; - }); -}; - -export const removeSSHKeyById = async ( - sshKeyId: (typeof apiRemoveSshKey._type)["sshKeyId"], -) => { - const result = await db - .delete(sshKeys) - .where(eq(sshKeys.sshKeyId, sshKeyId)) - .returning(); - - removeSSHKey(sshKeyId); - - return result[0]; -}; - -export const updateSSHKeyById = async ({ - sshKeyId, - ...input -}: typeof apiUpdateSshKey._type) => { - const result = await db - .update(sshKeys) - .set(input) - .where(eq(sshKeys.sshKeyId, sshKeyId)) - .returning(); - - return result[0]; -}; - -export const findSSHKeyById = async ( - sshKeyId: (typeof apiFindOneSshKey._type)["sshKeyId"], -) => { - const sshKey = await db.query.sshKeys.findFirst({ - where: eq(sshKeys.sshKeyId, sshKeyId), - }); - if (!sshKey) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "SSH Key not found", - }); - } - return sshKey; -}; diff --git a/apps/dokploy/server/api/services/user.ts b/apps/dokploy/server/api/services/user.ts deleted file mode 100644 index 044bc3cb..00000000 --- a/apps/dokploy/server/api/services/user.ts +++ /dev/null @@ -1,207 +0,0 @@ -import { db } from "@/server/db"; -import { users } from "@/server/db/schema"; -import { TRPCError } from "@trpc/server"; -import { eq } from "drizzle-orm"; - -export type User = typeof users.$inferSelect; - -export const findUserById = async (userId: string) => { - const user = await db.query.users.findFirst({ - where: eq(users.userId, userId), - }); - if (!user) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "User not found", - }); - } - return user; -}; - -export const findUserByAuthId = async (authId: string) => { - const user = await db.query.users.findFirst({ - where: eq(users.authId, authId), - with: { - auth: true, - }, - }); - if (!user) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "User not found", - }); - } - return user; -}; - -export const findUsers = async () => { - const users = await db.query.users.findMany({ - with: { - auth: { - columns: { - secret: false, - }, - }, - }, - }); - return users; -}; - -export const addNewProject = async (authId: string, projectId: string) => { - const user = await findUserByAuthId(authId); - - await db - .update(users) - .set({ - accesedProjects: [...user.accesedProjects, projectId], - }) - .where(eq(users.authId, authId)); -}; - -export const addNewService = async (authId: string, serviceId: string) => { - const user = await findUserByAuthId(authId); - await db - .update(users) - .set({ - accesedServices: [...user.accesedServices, serviceId], - }) - .where(eq(users.authId, authId)); -}; - -export const canPerformCreationService = async ( - userId: string, - projectId: string, -) => { - const { accesedProjects, canCreateServices } = await findUserByAuthId(userId); - const haveAccessToProject = accesedProjects.includes(projectId); - - if (canCreateServices && haveAccessToProject) { - return true; - } - - return false; -}; - -export const canPerformAccessService = async ( - userId: string, - serviceId: string, -) => { - const { accesedServices } = await findUserByAuthId(userId); - const haveAccessToService = accesedServices.includes(serviceId); - - if (haveAccessToService) { - return true; - } - - return false; -}; - -export const canPeformDeleteService = async ( - authId: string, - serviceId: string, -) => { - const { accesedServices, canDeleteServices } = await findUserByAuthId(authId); - const haveAccessToService = accesedServices.includes(serviceId); - - if (canDeleteServices && haveAccessToService) { - return true; - } - - return false; -}; - -export const canPerformCreationProject = async (authId: string) => { - const { canCreateProjects } = await findUserByAuthId(authId); - - if (canCreateProjects) { - return true; - } - - return false; -}; - -export const canPerformDeleteProject = async (authId: string) => { - const { canDeleteProjects } = await findUserByAuthId(authId); - - if (canDeleteProjects) { - return true; - } - - return false; -}; - -export const canPerformAccessProject = async ( - authId: string, - projectId: string, -) => { - const { accesedProjects } = await findUserByAuthId(authId); - - const haveAccessToProject = accesedProjects.includes(projectId); - - if (haveAccessToProject) { - return true; - } - return false; -}; - -export const canAccessToTraefikFiles = async (authId: string) => { - const { canAccessToTraefikFiles } = await findUserByAuthId(authId); - return canAccessToTraefikFiles; -}; - -export const checkServiceAccess = async ( - authId: string, - serviceId: string, - action = "access" as "access" | "create" | "delete", -) => { - let hasPermission = false; - switch (action) { - case "create": - hasPermission = await canPerformCreationService(authId, serviceId); - break; - case "access": - hasPermission = await canPerformAccessService(authId, serviceId); - break; - case "delete": - hasPermission = await canPeformDeleteService(authId, serviceId); - break; - default: - hasPermission = false; - } - if (!hasPermission) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "Permission denied", - }); - } -}; - -export const checkProjectAccess = async ( - authId: string, - action: "create" | "delete" | "access", - projectId?: string, -) => { - let hasPermission = false; - switch (action) { - case "access": - hasPermission = await canPerformAccessProject( - authId, - projectId as string, - ); - break; - case "create": - hasPermission = await canPerformCreationProject(authId); - break; - case "delete": - hasPermission = await canPerformDeleteProject(authId); - break; - default: - hasPermission = false; - } - if (!hasPermission) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "Permission denied", - }); - } -}; diff --git a/apps/dokploy/server/auth/auth.ts b/apps/dokploy/server/auth/auth.ts index a0971437..12c07e54 100644 --- a/apps/dokploy/server/auth/auth.ts +++ b/apps/dokploy/server/auth/auth.ts @@ -4,8 +4,7 @@ import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle"; import { TimeSpan } from "lucia"; import { Lucia } from "lucia/dist/core.js"; import type { Session, User } from "lucia/dist/core.js"; -import { findAdminByAuthId } from "../api/services/admin"; -import { findUserByAuthId } from "../api/services/user"; +import { findAdminByAuthId, findUserByAuthId } from "@dokploy/builders"; import { db } from "../db"; import { type DatabaseUser, auth, sessionTable } from "../db/schema"; diff --git a/apps/dokploy/server/queues/deployments-queue.ts b/apps/dokploy/server/queues/deployments-queue.ts index 7fcae4f3..07ad258a 100644 --- a/apps/dokploy/server/queues/deployments-queue.ts +++ b/apps/dokploy/server/queues/deployments-queue.ts @@ -1,18 +1,23 @@ import { type Job, Worker } from "bullmq"; +// import { +// deployApplication, +// deployRemoteApplication, +// rebuildApplication, +// rebuildRemoteApplication, +// updateApplicationStatus, +// } from "../api/services/application"; import { deployApplication, deployRemoteApplication, rebuildApplication, rebuildRemoteApplication, updateApplicationStatus, -} from "../api/services/application"; -import { deployCompose, deployRemoteCompose, rebuildCompose, rebuildRemoteCompose, updateCompose, -} from "../api/services/compose"; +} from "@dokploy/builders"; import { myQueue, redisConfig } from "./queueSetup"; type DeployJob = diff --git a/apps/dokploy/server/server.ts b/apps/dokploy/server/server.ts index d93d717e..1f4f67e3 100644 --- a/apps/dokploy/server/server.ts +++ b/apps/dokploy/server/server.ts @@ -4,18 +4,17 @@ import { config } from "dotenv"; import next from "next"; // import { IS_CLOUD } from "./constants"; import { deploymentWorker } from "./queues/deployments-queue"; -// import { deploymentWorker } from "./queues/deployments-queue"; -import { setupDirectories } from "./setup/config-paths"; -import { initializePostgres } from "./setup/postgres-setup"; -import { initializeRedis } from "./setup/redis-setup"; -import { initializeNetwork } from "./setup/setup"; import { + setupDirectories, + initializePostgres, + initializeRedis, + initializeNetwork, createDefaultMiddlewares, createDefaultServerTraefikConfig, createDefaultTraefikConfig, initializeTraefik, -} from "./setup/traefik-setup"; -import { initCronJobs } from "./utils/backups"; + initCronJobs, +} from "@dokploy/builders"; import { sendDokployRestartNotifications } from "./utils/notifications/dokploy-restart"; import { setupDockerContainerLogsWebSocketServer } from "./wss/docker-container-logs"; import { setupDockerContainerTerminalWebSocketServer } from "./wss/docker-container-terminal"; diff --git a/apps/dokploy/server/utils/backups/index.ts b/apps/dokploy/server/utils/backups/index.ts deleted file mode 100644 index 747611d9..00000000 --- a/apps/dokploy/server/utils/backups/index.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { findAdmin } from "@/server/api/services/admin"; -import { getAllServers } from "@/server/api/services/server"; -import { scheduleJob } from "node-schedule"; -import { db } from "../../db/index"; -import { - cleanUpDockerBuilder, - cleanUpSystemPrune, - cleanUpUnusedImages, -} from "../docker/utils"; -import { runMariadbBackup } from "./mariadb"; -import { runMongoBackup } from "./mongo"; -import { runMySqlBackup } from "./mysql"; -import { runPostgresBackup } from "./postgres"; - -export const initCronJobs = async () => { - console.log("Setting up cron jobs...."); - - const admin = await findAdmin(); - - if (admin?.enableDockerCleanup) { - scheduleJob("docker-cleanup", "0 0 * * *", async () => { - console.log( - `Docker Cleanup ${new Date().toLocaleString()}] Running docker cleanup`, - ); - await cleanUpUnusedImages(); - await cleanUpDockerBuilder(); - await cleanUpSystemPrune(); - }); - } - - const servers = await getAllServers(); - - for (const server of servers) { - const { appName, serverId } = server; - if (serverId) { - scheduleJob(serverId, "0 0 * * *", async () => { - console.log( - `SERVER-BACKUP[${new Date().toLocaleString()}] Running Cleanup ${appName}`, - ); - await cleanUpUnusedImages(serverId); - await cleanUpDockerBuilder(serverId); - await cleanUpSystemPrune(serverId); - }); - } - } - - const pgs = await db.query.postgres.findMany({ - with: { - backups: { - with: { - destination: true, - postgres: true, - mariadb: true, - mysql: true, - mongo: true, - }, - }, - }, - }); - for (const pg of pgs) { - for (const backup of pg.backups) { - const { schedule, backupId, enabled } = backup; - if (enabled) { - scheduleJob(backupId, schedule, async () => { - console.log( - `PG-SERVER[${new Date().toLocaleString()}] Running Backup ${backupId}`, - ); - runPostgresBackup(pg, backup); - }); - } - } - } - - const mariadbs = await db.query.mariadb.findMany({ - with: { - backups: { - with: { - destination: true, - postgres: true, - mariadb: true, - mysql: true, - mongo: true, - }, - }, - }, - }); - - for (const maria of mariadbs) { - for (const backup of maria.backups) { - const { schedule, backupId, enabled } = backup; - if (enabled) { - scheduleJob(backupId, schedule, async () => { - console.log( - `MARIADB-SERVER[${new Date().toLocaleString()}] Running Backup ${backupId}`, - ); - await runMariadbBackup(maria, backup); - }); - } - } - } - - const mongodbs = await db.query.mongo.findMany({ - with: { - backups: { - with: { - destination: true, - postgres: true, - mariadb: true, - mysql: true, - mongo: true, - }, - }, - }, - }); - - for (const mongo of mongodbs) { - for (const backup of mongo.backups) { - const { schedule, backupId, enabled } = backup; - if (enabled) { - scheduleJob(backupId, schedule, async () => { - console.log( - `MONGO-SERVER[${new Date().toLocaleString()}] Running Backup ${backupId}`, - ); - await runMongoBackup(mongo, backup); - }); - } - } - } - - const mysqls = await db.query.mysql.findMany({ - with: { - backups: { - with: { - destination: true, - postgres: true, - mariadb: true, - mysql: true, - mongo: true, - }, - }, - }, - }); - - for (const mysql of mysqls) { - for (const backup of mysql.backups) { - const { schedule, backupId, enabled } = backup; - if (enabled) { - scheduleJob(backupId, schedule, async () => { - console.log( - `MYSQL-SERVER[${new Date().toLocaleString()}] Running Backup ${backupId}`, - ); - await runMySqlBackup(mysql, backup); - }); - } - } - } -}; diff --git a/apps/dokploy/server/utils/backups/mariadb.ts b/apps/dokploy/server/utils/backups/mariadb.ts deleted file mode 100644 index 84c52047..00000000 --- a/apps/dokploy/server/utils/backups/mariadb.ts +++ /dev/null @@ -1,65 +0,0 @@ -import path from "node:path"; -import type { BackupSchedule } from "@/server/api/services/backup"; -import type { Mariadb } from "@/server/api/services/mariadb"; -import { findProjectById } from "@/server/api/services/project"; -import { - getRemoteServiceContainer, - getServiceContainer, -} from "../docker/utils"; -import { sendDatabaseBackupNotifications } from "../notifications/database-backup"; -import { execAsync, execAsyncRemote } from "../process/execAsync"; -import { getS3Credentials } from "./utils"; - -export const runMariadbBackup = async ( - mariadb: Mariadb, - backup: BackupSchedule, -) => { - const { appName, databasePassword, databaseUser, projectId, name } = mariadb; - const project = await findProjectById(projectId); - const { prefix, database } = backup; - const destination = backup.destination; - const backupFileName = `${new Date().toISOString()}.sql.gz`; - const bucketDestination = path.join(prefix, backupFileName); - - try { - const rcloneFlags = getS3Credentials(destination); - const rcloneDestination = `:s3:${destination.bucket}/${bucketDestination}`; - - const rcloneCommand = `rclone rcat ${rcloneFlags.join(" ")} "${rcloneDestination}"`; - if (mariadb.serverId) { - const { Id: containerId } = await getRemoteServiceContainer( - mariadb.serverId, - appName, - ); - const mariadbDumpCommand = `docker exec ${containerId} sh -c "mariadb-dump --user='${databaseUser}' --password='${databasePassword}' --databases ${database} | gzip"`; - - await execAsyncRemote( - mariadb.serverId, - `${mariadbDumpCommand} | ${rcloneCommand}`, - ); - } else { - const { Id: containerId } = await getServiceContainer(appName); - const mariadbDumpCommand = `docker exec ${containerId} sh -c "mariadb-dump --user='${databaseUser}' --password='${databasePassword}' --databases ${database} | gzip"`; - - await execAsync(`${mariadbDumpCommand} | ${rcloneCommand}`); - } - - await sendDatabaseBackupNotifications({ - applicationName: name, - projectName: project.name, - databaseType: "mariadb", - type: "success", - }); - } catch (error) { - console.log(error); - await sendDatabaseBackupNotifications({ - applicationName: name, - projectName: project.name, - databaseType: "mariadb", - type: "error", - // @ts-ignore - errorMessage: error?.message || "Error message not provided", - }); - throw error; - } -}; diff --git a/apps/dokploy/server/utils/backups/mongo.ts b/apps/dokploy/server/utils/backups/mongo.ts deleted file mode 100644 index 1ce59293..00000000 --- a/apps/dokploy/server/utils/backups/mongo.ts +++ /dev/null @@ -1,63 +0,0 @@ -import path from "node:path"; -import type { BackupSchedule } from "@/server/api/services/backup"; -import type { Mongo } from "@/server/api/services/mongo"; -import { findProjectById } from "@/server/api/services/project"; -import { - getRemoteServiceContainer, - getServiceContainer, -} from "../docker/utils"; -import { sendDatabaseBackupNotifications } from "../notifications/database-backup"; -import { execAsync, execAsyncRemote } from "../process/execAsync"; -import { getS3Credentials } from "./utils"; - -// mongodb://mongo:Bqh7AQl-PRbnBu@localhost:27017/?tls=false&directConnection=true -export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => { - const { appName, databasePassword, databaseUser, projectId, name } = mongo; - const project = await findProjectById(projectId); - const { prefix, database } = backup; - const destination = backup.destination; - const backupFileName = `${new Date().toISOString()}.dump.gz`; - const bucketDestination = path.join(prefix, backupFileName); - - try { - const rcloneFlags = getS3Credentials(destination); - const rcloneDestination = `:s3:${destination.bucket}/${bucketDestination}`; - - const rcloneCommand = `rclone rcat ${rcloneFlags.join(" ")} "${rcloneDestination}"`; - if (mongo.serverId) { - const { Id: containerId } = await getRemoteServiceContainer( - mongo.serverId, - appName, - ); - const mongoDumpCommand = `docker exec ${containerId} sh -c "mongodump -d '${database}' -u '${databaseUser}' -p '${databasePassword}' --authenticationDatabase=admin --gzip"`; - - await execAsyncRemote( - mongo.serverId, - `${mongoDumpCommand} | ${rcloneCommand}`, - ); - } else { - const { Id: containerId } = await getServiceContainer(appName); - const mongoDumpCommand = `docker exec ${containerId} sh -c "mongodump -d '${database}' -u '${databaseUser}' -p '${databasePassword}' --authenticationDatabase=admin --gzip"`; - await execAsync(`${mongoDumpCommand} | ${rcloneCommand}`); - } - - await sendDatabaseBackupNotifications({ - applicationName: name, - projectName: project.name, - databaseType: "mongodb", - type: "success", - }); - } catch (error) { - console.log(error); - await sendDatabaseBackupNotifications({ - applicationName: name, - projectName: project.name, - databaseType: "mongodb", - type: "error", - // @ts-ignore - errorMessage: error?.message || "Error message not provided", - }); - throw error; - } -}; -// mongorestore -d monguito -u mongo -p Bqh7AQl-PRbnBu --authenticationDatabase admin --gzip --archive=2024-04-13T05:03:58.937Z.dump.gz diff --git a/apps/dokploy/server/utils/backups/mysql.ts b/apps/dokploy/server/utils/backups/mysql.ts deleted file mode 100644 index a2de6321..00000000 --- a/apps/dokploy/server/utils/backups/mysql.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { unlink } from "node:fs/promises"; -import path from "node:path"; -import type { BackupSchedule } from "@/server/api/services/backup"; -import type { MySql } from "@/server/api/services/mysql"; -import { findProjectById } from "@/server/api/services/project"; -import { - getRemoteServiceContainer, - getServiceContainer, -} from "../docker/utils"; -import { sendDatabaseBackupNotifications } from "../notifications/database-backup"; -import { execAsync, execAsyncRemote } from "../process/execAsync"; -import { getS3Credentials } from "./utils"; - -export const runMySqlBackup = async (mysql: MySql, backup: BackupSchedule) => { - const { appName, databaseRootPassword, projectId, name } = mysql; - const project = await findProjectById(projectId); - const { prefix, database } = backup; - const destination = backup.destination; - const backupFileName = `${new Date().toISOString()}.sql.gz`; - const bucketDestination = path.join(prefix, backupFileName); - - try { - const rcloneFlags = getS3Credentials(destination); - const rcloneDestination = `:s3:${destination.bucket}/${bucketDestination}`; - - const rcloneCommand = `rclone rcat ${rcloneFlags.join(" ")} "${rcloneDestination}"`; - if (mysql.serverId) { - const { Id: containerId } = await getRemoteServiceContainer( - mysql.serverId, - appName, - ); - const mysqlDumpCommand = `docker exec ${containerId} sh -c "mysqldump --default-character-set=utf8mb4 -u 'root' --password='${databaseRootPassword}' --single-transaction --no-tablespaces --quick '${database}' | gzip"`; - - await execAsyncRemote( - mysql.serverId, - `${mysqlDumpCommand} | ${rcloneCommand}`, - ); - } else { - const { Id: containerId } = await getServiceContainer(appName); - const mysqlDumpCommand = `docker exec ${containerId} sh -c "mysqldump --default-character-set=utf8mb4 -u 'root' --password='${databaseRootPassword}' --single-transaction --no-tablespaces --quick '${database}' | gzip"`; - - await execAsync(`${mysqlDumpCommand} | ${rcloneCommand}`); - } - await sendDatabaseBackupNotifications({ - applicationName: name, - projectName: project.name, - databaseType: "mysql", - type: "success", - }); - } catch (error) { - console.log(error); - await sendDatabaseBackupNotifications({ - applicationName: name, - projectName: project.name, - databaseType: "mysql", - type: "error", - // @ts-ignore - errorMessage: error?.message || "Error message not provided", - }); - throw error; - } -}; diff --git a/apps/dokploy/server/utils/backups/postgres.ts b/apps/dokploy/server/utils/backups/postgres.ts deleted file mode 100644 index 236fec58..00000000 --- a/apps/dokploy/server/utils/backups/postgres.ts +++ /dev/null @@ -1,69 +0,0 @@ -import path from "node:path"; -import type { BackupSchedule } from "@/server/api/services/backup"; -import type { Postgres } from "@/server/api/services/postgres"; -import { findProjectById } from "@/server/api/services/project"; -import { - getRemoteServiceContainer, - getServiceContainer, -} from "../docker/utils"; -import { sendDatabaseBackupNotifications } from "../notifications/database-backup"; -import { execAsync, execAsyncRemote } from "../process/execAsync"; -import { getS3Credentials } from "./utils"; - -export const runPostgresBackup = async ( - postgres: Postgres, - backup: BackupSchedule, -) => { - const { appName, databaseUser, name, projectId } = postgres; - const project = await findProjectById(projectId); - - const { prefix, database } = backup; - const destination = backup.destination; - const backupFileName = `${new Date().toISOString()}.sql.gz`; - const bucketDestination = path.join(prefix, backupFileName); - try { - const rcloneFlags = getS3Credentials(destination); - const rcloneDestination = `:s3:${destination.bucket}/${bucketDestination}`; - - const rcloneCommand = `rclone rcat ${rcloneFlags.join(" ")} "${rcloneDestination}"`; - if (postgres.serverId) { - const { Id: containerId } = await getRemoteServiceContainer( - postgres.serverId, - appName, - ); - const pgDumpCommand = `docker exec ${containerId} sh -c "pg_dump -Fc --no-acl --no-owner -h localhost -U ${databaseUser} --no-password '${database}' | gzip"`; - - await execAsyncRemote( - postgres.serverId, - `${pgDumpCommand} | ${rcloneCommand}`, - ); - } else { - const { Id: containerId } = await getServiceContainer(appName); - - const pgDumpCommand = `docker exec ${containerId} sh -c "pg_dump -Fc --no-acl --no-owner -h localhost -U ${databaseUser} --no-password '${database}' | gzip"`; - await execAsync(`${pgDumpCommand} | ${rcloneCommand}`); - } - - await sendDatabaseBackupNotifications({ - applicationName: name, - projectName: project.name, - databaseType: "postgres", - type: "success", - }); - } catch (error) { - await sendDatabaseBackupNotifications({ - applicationName: name, - projectName: project.name, - databaseType: "postgres", - type: "error", - // @ts-ignore - errorMessage: error?.message || "Error message not provided", - }); - - throw error; - } finally { - } -}; - -// Restore -// /Applications/pgAdmin 4.app/Contents/SharedSupport/pg_restore --host "localhost" --port "5432" --username "mauricio" --no-password --dbname "postgres" --verbose "/Users/mauricio/Downloads/_databases_2024-04-12T07_02_05.234Z.sql" diff --git a/apps/dokploy/server/utils/backups/utils.ts b/apps/dokploy/server/utils/backups/utils.ts deleted file mode 100644 index 41ba1535..00000000 --- a/apps/dokploy/server/utils/backups/utils.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { BackupSchedule } from "@/server/api/services/backup"; -import type { Destination } from "@/server/api/services/destination"; -import { scheduleJob, scheduledJobs } from "node-schedule"; -import { runMariadbBackup } from "./mariadb"; -import { runMongoBackup } from "./mongo"; -import { runMySqlBackup } from "./mysql"; -import { runPostgresBackup } from "./postgres"; - -export const scheduleBackup = (backup: BackupSchedule) => { - const { schedule, backupId, databaseType, postgres, mysql, mongo, mariadb } = - backup; - scheduleJob(backupId, schedule, async () => { - if (databaseType === "postgres" && postgres) { - await runPostgresBackup(postgres, backup); - } else if (databaseType === "mysql" && mysql) { - await runMySqlBackup(mysql, backup); - } else if (databaseType === "mongo" && mongo) { - await runMongoBackup(mongo, backup); - } else if (databaseType === "mariadb" && mariadb) { - await runMariadbBackup(mariadb, backup); - } - }); -}; - -export const removeScheduleBackup = (backupId: string) => { - const currentJob = scheduledJobs[backupId]; - currentJob?.cancel(); -}; - -export const getS3Credentials = (destination: Destination) => { - const { accessKey, secretAccessKey, bucket, region, endpoint } = destination; - const rcloneFlags = [ - // `--s3-provider=Cloudflare`, - `--s3-access-key-id=${accessKey}`, - `--s3-secret-access-key=${secretAccessKey}`, - `--s3-region=${region}`, - `--s3-endpoint=${endpoint}`, - "--s3-no-check-bucket", - "--s3-force-path-style", - ]; - - return rcloneFlags; -}; diff --git a/apps/dokploy/server/utils/builders/compose.ts b/apps/dokploy/server/utils/builders/compose.ts deleted file mode 100644 index 47ec1520..00000000 --- a/apps/dokploy/server/utils/builders/compose.ts +++ /dev/null @@ -1,216 +0,0 @@ -import { - createWriteStream, - existsSync, - mkdirSync, - writeFileSync, -} from "node:fs"; -import { dirname, join } from "node:path"; -import { paths } from "@/server/constants"; -import type { InferResultType } from "@/server/types/with"; -import boxen from "boxen"; -import { - writeDomainsToCompose, - writeDomainsToComposeRemote, -} from "../docker/domain"; -import { encodeBase64, prepareEnvironmentVariables } from "../docker/utils"; -import { execAsyncRemote } from "../process/execAsync"; -import { spawnAsync } from "../process/spawnAsync"; - -export type ComposeNested = InferResultType< - "compose", - { project: true; mounts: true; domains: true } ->; -export const buildCompose = async (compose: ComposeNested, logPath: string) => { - const writeStream = createWriteStream(logPath, { flags: "a" }); - const { sourceType, appName, mounts, composeType, domains } = compose; - try { - const { COMPOSE_PATH } = paths(); - const command = createCommand(compose); - await writeDomainsToCompose(compose, domains); - createEnvFile(compose); - - const logContent = ` -App Name: ${appName} -Build Compose 🐳 -Detected: ${mounts.length} mounts 📂 -Command: docker ${command} -Source Type: docker ${sourceType} ✅ -Compose Type: ${composeType} ✅`; - const logBox = boxen(logContent, { - padding: { - left: 1, - right: 1, - bottom: 1, - }, - width: 80, - borderStyle: "double", - }); - writeStream.write(`\n${logBox}\n`); - - const projectPath = join(COMPOSE_PATH, compose.appName, "code"); - await spawnAsync( - "docker", - [...command.split(" ")], - (data) => { - if (writeStream.writable) { - writeStream.write(data.toString()); - } - }, - { - cwd: projectPath, - env: { - NODE_ENV: process.env.NODE_ENV, - PATH: process.env.PATH, - }, - }, - ); - - writeStream.write("Docker Compose Deployed: ✅"); - } catch (error) { - writeStream.write("Error ❌"); - throw error; - } finally { - writeStream.end(); - } -}; - -export const getBuildComposeCommand = async ( - compose: ComposeNested, - logPath: string, -) => { - const { COMPOSE_PATH } = paths(true); - const { sourceType, appName, mounts, composeType, domains, composePath } = - compose; - const command = createCommand(compose); - const envCommand = getCreateEnvFileCommand(compose); - const projectPath = join(COMPOSE_PATH, compose.appName, "code"); - - const newCompose = await writeDomainsToComposeRemote( - compose, - domains, - logPath, - ); - const logContent = ` -App Name: ${appName} -Build Compose 🐳 -Detected: ${mounts.length} mounts 📂 -Command: docker ${command} -Source Type: docker ${sourceType} ✅ -Compose Type: ${composeType} ✅`; - - const logBox = boxen(logContent, { - padding: { - left: 1, - right: 1, - bottom: 1, - }, - width: 80, - borderStyle: "double", - }); - - const bashCommand = ` - set -e - { - echo "${logBox}" >> "${logPath}" - - ${newCompose} - - ${envCommand} - - cd "${projectPath}"; - - docker ${command.split(" ").join(" ")} >> "${logPath}" 2>&1 || { echo "Error: ❌ Docker command failed" >> "${logPath}"; exit 1; } - - echo "Docker Compose Deployed: ✅" >> "${logPath}" - } || { - echo "Error: ❌ Script execution failed" >> "${logPath}" - exit 1 - } - `; - - return await execAsyncRemote(compose.serverId, bashCommand); -}; - -const sanitizeCommand = (command: string) => { - const sanitizedCommand = command.trim(); - - const parts = sanitizedCommand.split(/\s+/); - - const restCommand = parts.map((arg) => arg.replace(/^"(.*)"$/, "$1")); - - return restCommand.join(" "); -}; - -export const createCommand = (compose: ComposeNested) => { - const { composeType, appName, sourceType } = compose; - - const path = - sourceType === "raw" ? "docker-compose.yml" : compose.composePath; - let command = ""; - - if (composeType === "docker-compose") { - command = `compose -p ${appName} -f ${path} up -d --build --remove-orphans`; - } else if (composeType === "stack") { - command = `stack deploy -c ${path} ${appName} --prune`; - } - - const customCommand = sanitizeCommand(compose.command); - - if (customCommand) { - command = `${command} ${customCommand}`; - } - - return command; -}; - -const createEnvFile = (compose: ComposeNested) => { - const { COMPOSE_PATH } = paths(); - const { env, composePath, appName } = compose; - const composeFilePath = - join(COMPOSE_PATH, appName, "code", composePath) || - join(COMPOSE_PATH, appName, "code", "docker-compose.yml"); - - const envFilePath = join(dirname(composeFilePath), ".env"); - let envContent = env || ""; - if (!envContent.includes("DOCKER_CONFIG")) { - envContent += "\nDOCKER_CONFIG=/root/.docker/config.json"; - } - - if (compose.randomize) { - envContent += `\nCOMPOSE_PREFIX=${compose.suffix}`; - } - - const envFileContent = prepareEnvironmentVariables(envContent).join("\n"); - - if (!existsSync(dirname(envFilePath))) { - mkdirSync(dirname(envFilePath), { recursive: true }); - } - writeFileSync(envFilePath, envFileContent); -}; - -export const getCreateEnvFileCommand = (compose: ComposeNested) => { - const { COMPOSE_PATH } = paths(true); - const { env, composePath, appName } = compose; - const composeFilePath = - join(COMPOSE_PATH, appName, "code", composePath) || - join(COMPOSE_PATH, appName, "code", "docker-compose.yml"); - - const envFilePath = join(dirname(composeFilePath), ".env"); - - let envContent = env || ""; - if (!envContent.includes("DOCKER_CONFIG")) { - envContent += "\nDOCKER_CONFIG=/root/.docker/config.json"; - } - - if (compose.randomize) { - envContent += `\nCOMPOSE_PREFIX=${compose.suffix}`; - } - - const envFileContent = prepareEnvironmentVariables(envContent).join("\n"); - - const encodedContent = encodeBase64(envFileContent); - return ` -touch ${envFilePath}; -echo "${encodedContent}" | base64 -d > "${envFilePath}"; - `; -}; diff --git a/apps/dokploy/server/utils/builders/docker-file.ts b/apps/dokploy/server/utils/builders/docker-file.ts deleted file mode 100644 index ab5cc066..00000000 --- a/apps/dokploy/server/utils/builders/docker-file.ts +++ /dev/null @@ -1,116 +0,0 @@ -import type { WriteStream } from "node:fs"; -import { prepareEnvironmentVariables } from "@/server/utils/docker/utils"; -import type { ApplicationNested } from "."; -import { - getBuildAppDirectory, - getDockerContextPath, -} from "../filesystem/directory"; -import { spawnAsync } from "../process/spawnAsync"; -import { createEnvFile, createEnvFileCommand } from "./utils"; - -export const buildCustomDocker = async ( - application: ApplicationNested, - writeStream: WriteStream, -) => { - const { appName, env, publishDirectory, buildArgs, dockerBuildStage } = - application; - const dockerFilePath = getBuildAppDirectory(application); - try { - const image = `${appName}`; - - const defaultContextPath = - dockerFilePath.substring(0, dockerFilePath.lastIndexOf("/") + 1) || "."; - const args = prepareEnvironmentVariables(buildArgs); - - const dockerContextPath = getDockerContextPath(application); - - const commandArgs = ["build", "-t", image, "-f", dockerFilePath, "."]; - - if (dockerBuildStage) { - commandArgs.push("--target", dockerBuildStage); - } - - for (const arg of args) { - commandArgs.push("--build-arg", arg); - } - /* - Do not generate an environment file when publishDirectory is specified, - as it could be publicly exposed. - */ - if (!publishDirectory) { - createEnvFile(dockerFilePath, env); - } - - await spawnAsync( - "docker", - commandArgs, - (data) => { - if (writeStream.writable) { - writeStream.write(data); - } - }, - { - cwd: dockerContextPath || defaultContextPath, - }, - ); - } catch (error) { - throw error; - } -}; - -export const getDockerCommand = ( - application: ApplicationNested, - logPath: string, -) => { - const { appName, env, publishDirectory, buildArgs, dockerBuildStage } = - application; - const dockerFilePath = getBuildAppDirectory(application); - - try { - const image = `${appName}`; - - const defaultContextPath = - dockerFilePath.substring(0, dockerFilePath.lastIndexOf("/") + 1) || "."; - const args = prepareEnvironmentVariables(buildArgs); - - const dockerContextPath = - getDockerContextPath(application) || defaultContextPath; - - const commandArgs = ["build", "-t", image, "-f", dockerFilePath, "."]; - - if (dockerBuildStage) { - commandArgs.push("--target", dockerBuildStage); - } - - for (const arg of args) { - commandArgs.push("--build-arg", arg); - } - - /* - Do not generate an environment file when publishDirectory is specified, - as it could be publicly exposed. - */ - let command = ""; - if (!publishDirectory) { - command += createEnvFileCommand(dockerFilePath, env); - } - - command = ` -echo "Building ${appName}" >> ${logPath}; -cd ${dockerContextPath} >> ${logPath} 2>> ${logPath} || { - echo "❌ The path ${dockerContextPath} does not exist" >> ${logPath}; - exit 1; -} - -docker ${commandArgs.join(" ")} >> ${logPath} 2>> ${logPath} || { - echo "❌ Docker build failed" >> ${logPath}; - exit 1; -} -echo "✅ Docker build completed." >> ${logPath}; - `; - - return command; - } catch (error) { - throw error; - } -}; diff --git a/apps/dokploy/server/utils/builders/drop.ts b/apps/dokploy/server/utils/builders/drop.ts deleted file mode 100644 index b376251a..00000000 --- a/apps/dokploy/server/utils/builders/drop.ts +++ /dev/null @@ -1,124 +0,0 @@ -import fs from "node:fs/promises"; -import path, { join } from "node:path"; -import type { Application } from "@/server/api/services/application"; -import { findServerById } from "@/server/api/services/server"; -import { paths } from "@/server/constants"; -import AdmZip from "adm-zip"; -import { Client, type SFTPWrapper } from "ssh2"; -import { - recreateDirectory, - recreateDirectoryRemote, -} from "../filesystem/directory"; -import { readSSHKey } from "../filesystem/ssh"; -import { execAsyncRemote } from "../process/execAsync"; - -export const unzipDrop = async (zipFile: File, application: Application) => { - let sftp: SFTPWrapper | null = null; - - try { - const { appName } = application; - const { APPLICATIONS_PATH } = paths(!!application.serverId); - const outputPath = join(APPLICATIONS_PATH, appName, "code"); - if (application.serverId) { - await recreateDirectoryRemote(outputPath, application.serverId); - } else { - await recreateDirectory(outputPath); - } - const arrayBuffer = await zipFile.arrayBuffer(); - const buffer = Buffer.from(arrayBuffer); - - const zip = new AdmZip(buffer); - const zipEntries = zip.getEntries(); - - const rootEntries = zipEntries.filter( - (entry) => - entry.entryName.split("/").length === 1 || - (entry.entryName.split("/").length === 2 && - entry.entryName.endsWith("/")), - ); - - const hasSingleRootFolder = !!( - rootEntries.length === 1 && rootEntries[0]?.isDirectory - ); - const rootFolderName = hasSingleRootFolder - ? rootEntries[0]?.entryName.split("/")[0] - : ""; - - if (application.serverId) { - sftp = await getSFTPConnection(application.serverId); - } - for (const entry of zipEntries) { - let filePath = entry.entryName; - - if ( - hasSingleRootFolder && - rootFolderName && - filePath.startsWith(`${rootFolderName}/`) - ) { - filePath = filePath.slice(rootFolderName?.length + 1); - } - - if (!filePath) continue; - - const fullPath = path.join(outputPath, filePath); - - if (application.serverId) { - if (entry.isDirectory) { - await execAsyncRemote(application.serverId, `mkdir -p ${fullPath}`); - } else { - if (sftp === null) throw new Error("No SFTP connection available"); - await uploadFileToServer(sftp, entry.getData(), fullPath); - } - } else { - if (entry.isDirectory) { - await fs.mkdir(fullPath, { recursive: true }); - } else { - await fs.mkdir(path.dirname(fullPath), { recursive: true }); - await fs.writeFile(fullPath, entry.getData()); - } - } - } - } catch (error) { - console.error("Error processing ZIP file:", error); - throw error; - } finally { - sftp?.end(); - } -}; - -const getSFTPConnection = async (serverId: string): Promise => { - const server = await findServerById(serverId); - if (!server.sshKeyId) throw new Error("No SSH key available for this server"); - - const keys = await readSSHKey(server.sshKeyId); - return new Promise((resolve, reject) => { - const conn = new Client(); - conn - .on("ready", () => { - conn.sftp((err, sftp) => { - if (err) return reject(err); - resolve(sftp); - }); - }) - .connect({ - host: server.ipAddress, - port: server.port, - username: server.username, - privateKey: keys.privateKey, - timeout: 99999, - }); - }); -}; - -const uploadFileToServer = ( - sftp: SFTPWrapper, - data: Buffer, - remotePath: string, -): Promise => { - return new Promise((resolve, reject) => { - sftp.writeFile(remotePath, data, (err) => { - if (err) return reject(err); - resolve(); - }); - }); -}; diff --git a/apps/dokploy/server/utils/builders/heroku.ts b/apps/dokploy/server/utils/builders/heroku.ts deleted file mode 100644 index be7f7877..00000000 --- a/apps/dokploy/server/utils/builders/heroku.ts +++ /dev/null @@ -1,73 +0,0 @@ -import type { WriteStream } from "node:fs"; -import type { ApplicationNested } from "."; -import { prepareEnvironmentVariables } from "../docker/utils"; -import { getBuildAppDirectory } from "../filesystem/directory"; -import { spawnAsync } from "../process/spawnAsync"; - -// TODO: integrate in the vps sudo chown -R $(whoami) ~/.docker -export const buildHeroku = async ( - application: ApplicationNested, - writeStream: WriteStream, -) => { - const { env, appName } = application; - const buildAppDirectory = getBuildAppDirectory(application); - const envVariables = prepareEnvironmentVariables(env); - try { - const args = [ - "build", - appName, - "--path", - buildAppDirectory, - "--builder", - "heroku/builder:24", - ]; - - for (const env of envVariables) { - args.push("--env", env); - } - - await spawnAsync("pack", args, (data) => { - if (writeStream.writable) { - writeStream.write(data); - } - }); - return true; - } catch (e) { - throw e; - } -}; - -export const getHerokuCommand = ( - application: ApplicationNested, - logPath: string, -) => { - const { env, appName } = application; - - const buildAppDirectory = getBuildAppDirectory(application); - const envVariables = prepareEnvironmentVariables(env); - - const args = [ - "build", - appName, - "--path", - buildAppDirectory, - "--builder", - "heroku/builder:24", - ]; - - for (const env of envVariables) { - args.push("--env", env); - } - - const command = `pack ${args.join(" ")}`; - const bashCommand = ` -echo "Starting heroku build..." >> ${logPath}; -${command} >> ${logPath} 2>> ${logPath} || { - echo "❌ Heroku build failed" >> ${logPath}; - exit 1; -} -echo "✅ Heroku build completed." >> ${logPath}; - `; - - return bashCommand; -}; diff --git a/apps/dokploy/server/utils/builders/index.ts b/apps/dokploy/server/utils/builders/index.ts deleted file mode 100644 index 4f7262a8..00000000 --- a/apps/dokploy/server/utils/builders/index.ts +++ /dev/null @@ -1,216 +0,0 @@ -import { createWriteStream } from "node:fs"; -import type { InferResultType } from "@/server/types/with"; -import type { CreateServiceOptions } from "dockerode"; -import { uploadImage } from "../cluster/upload"; -import { - calculateResources, - generateBindMounts, - generateConfigContainer, - generateFileMounts, - generateVolumeMounts, - prepareEnvironmentVariables, -} from "../docker/utils"; -import { getRemoteDocker } from "../servers/remote-docker"; -import { buildCustomDocker, getDockerCommand } from "./docker-file"; -import { buildHeroku, getHerokuCommand } from "./heroku"; -import { buildNixpacks, getNixpacksCommand } from "./nixpacks"; -import { buildPaketo, getPaketoCommand } from "./paketo"; -import { buildStatic, getStaticCommand } from "./static"; - -// NIXPACKS codeDirectory = where is the path of the code directory -// HEROKU codeDirectory = where is the path of the code directory -// PAKETO codeDirectory = where is the path of the code directory -// DOCKERFILE codeDirectory = where is the exact path of the (Dockerfile) -export type ApplicationNested = InferResultType< - "applications", - { mounts: true; security: true; redirects: true; ports: true; registry: true } ->; -export const buildApplication = async ( - application: ApplicationNested, - logPath: string, -) => { - const writeStream = createWriteStream(logPath, { flags: "a" }); - const { buildType, sourceType } = application; - try { - writeStream.write( - `\nBuild ${buildType}: ✅\nSource Type: ${sourceType}: ✅\n`, - ); - console.log(`Build ${buildType}: ✅`); - if (buildType === "nixpacks") { - await buildNixpacks(application, writeStream); - } else if (buildType === "heroku_buildpacks") { - await buildHeroku(application, writeStream); - } else if (buildType === "paketo_buildpacks") { - await buildPaketo(application, writeStream); - } else if (buildType === "dockerfile") { - await buildCustomDocker(application, writeStream); - } else if (buildType === "static") { - await buildStatic(application, writeStream); - } - - if (application.registryId) { - await uploadImage(application, writeStream); - } - await mechanizeDockerContainer(application); - writeStream.write("Docker Deployed: ✅"); - } catch (error) { - if (error instanceof Error) { - writeStream.write(`Error ❌\n${error?.message}`); - } else { - writeStream.write("Error ❌"); - } - throw error; - } finally { - writeStream.end(); - } -}; - -export const getBuildCommand = ( - application: ApplicationNested, - logPath: string, -) => { - const { buildType } = application; - switch (buildType) { - case "nixpacks": - return getNixpacksCommand(application, logPath); - case "heroku_buildpacks": - return getHerokuCommand(application, logPath); - case "paketo_buildpacks": - return getPaketoCommand(application, logPath); - case "static": - return getStaticCommand(application, logPath); - case "dockerfile": - return getDockerCommand(application, logPath); - } -}; - -export const mechanizeDockerContainer = async ( - application: ApplicationNested, -) => { - const { - appName, - env, - mounts, - cpuLimit, - memoryLimit, - memoryReservation, - cpuReservation, - command, - ports, - } = application; - - const resources = calculateResources({ - memoryLimit, - memoryReservation, - cpuLimit, - cpuReservation, - }); - - const volumesMount = generateVolumeMounts(mounts); - - const { - HealthCheck, - RestartPolicy, - Placement, - Labels, - Mode, - RollbackConfig, - UpdateConfig, - Networks, - } = generateConfigContainer(application); - - const bindsMount = generateBindMounts(mounts); - const filesMount = generateFileMounts(appName, application); - const envVariables = prepareEnvironmentVariables(env); - - const image = getImageName(application); - const authConfig = getAuthConfig(application); - const docker = await getRemoteDocker(application.serverId); - - const settings: CreateServiceOptions = { - authconfig: authConfig, - Name: appName, - TaskTemplate: { - ContainerSpec: { - HealthCheck, - Image: image, - Env: envVariables, - Mounts: [...volumesMount, ...bindsMount, ...filesMount], - ...(command - ? { - Command: ["/bin/sh"], - Args: ["-c", command], - } - : {}), - Labels, - }, - Networks, - RestartPolicy, - Placement, - Resources: { - ...resources, - }, - }, - Mode, - RollbackConfig, - EndpointSpec: { - Ports: ports.map((port) => ({ - Protocol: port.protocol, - TargetPort: port.targetPort, - PublishedPort: port.publishedPort, - })), - }, - UpdateConfig, - }; - - try { - const service = docker.getService(appName); - const inspect = await service.inspect(); - await service.update({ - version: Number.parseInt(inspect.Version.Index), - ...settings, - TaskTemplate: { - ...settings.TaskTemplate, - ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1, - }, - }); - } catch (error) { - await docker.createService(settings); - } -}; - -const getImageName = (application: ApplicationNested) => { - const { appName, sourceType, dockerImage, registry } = application; - - if (sourceType === "docker") { - return dockerImage || "ERROR-NO-IMAGE-PROVIDED"; - } - - const registryUrl = registry?.registryUrl || ""; - const imagePrefix = registry?.imagePrefix ? `${registry.imagePrefix}/` : ""; - return registry - ? `${registryUrl}/${imagePrefix}${appName}` - : `${appName}:latest`; -}; - -const getAuthConfig = (application: ApplicationNested) => { - const { registry, username, password, sourceType } = application; - - if (sourceType === "docker") { - if (username && password) { - return { - password, - username, - serveraddress: "https://index.docker.io/v1/", - }; - } - } else if (registry) { - return { - password: registry.password, - username: registry.username, - serveraddress: registry.registryUrl, - }; - } - - return undefined; -}; diff --git a/apps/dokploy/server/utils/builders/nixpacks.ts b/apps/dokploy/server/utils/builders/nixpacks.ts deleted file mode 100644 index 0f408743..00000000 --- a/apps/dokploy/server/utils/builders/nixpacks.ts +++ /dev/null @@ -1,124 +0,0 @@ -import type { WriteStream } from "node:fs"; -import path from "node:path"; -import { buildStatic, getStaticCommand } from "@/server/utils/builders/static"; -import { nanoid } from "nanoid"; -import type { ApplicationNested } from "."; -import { prepareEnvironmentVariables } from "../docker/utils"; -import { getBuildAppDirectory } from "../filesystem/directory"; -import { spawnAsync } from "../process/spawnAsync"; - -export const buildNixpacks = async ( - application: ApplicationNested, - writeStream: WriteStream, -) => { - const { env, appName, publishDirectory, serverId } = application; - - const buildAppDirectory = getBuildAppDirectory(application); - const buildContainerId = `${appName}-${nanoid(10)}`; - const envVariables = prepareEnvironmentVariables(env); - - const writeToStream = (data: string) => { - if (writeStream.writable) { - writeStream.write(data); - } - }; - - try { - const args = ["build", buildAppDirectory, "--name", appName]; - - for (const env of envVariables) { - args.push("--env", env); - } - - if (publishDirectory) { - /* No need for any start command, since we'll use nginx later on */ - args.push("--no-error-without-start"); - } - - await spawnAsync("nixpacks", args, writeToStream); - - /* - Run the container with the image created by nixpacks, - and copy the artifacts on the host filesystem. - Then, remove the container and create a static build. - */ - - if (publishDirectory) { - await spawnAsync( - "docker", - ["create", "--name", buildContainerId, appName], - writeToStream, - ); - - await spawnAsync( - "docker", - [ - "cp", - `${buildContainerId}:/app/${publishDirectory}`, - path.join(buildAppDirectory, publishDirectory), - ], - writeToStream, - ); - - await spawnAsync("docker", ["rm", buildContainerId], writeToStream); - - await buildStatic(application, writeStream); - } - return true; - } catch (e) { - await spawnAsync("docker", ["rm", buildContainerId], writeToStream); - - throw e; - } -}; - -export const getNixpacksCommand = ( - application: ApplicationNested, - logPath: string, -) => { - const { env, appName, publishDirectory, serverId } = application; - - const buildAppDirectory = getBuildAppDirectory(application); - const buildContainerId = `${appName}-${nanoid(10)}`; - const envVariables = prepareEnvironmentVariables(env); - - const args = ["build", buildAppDirectory, "--name", appName]; - - for (const env of envVariables) { - args.push("--env", env); - } - - if (publishDirectory) { - /* No need for any start command, since we'll use nginx later on */ - args.push("--no-error-without-start"); - } - const command = `nixpacks ${args.join(" ")}`; - let bashCommand = ` -echo "Starting nixpacks build..." >> ${logPath}; -${command} >> ${logPath} 2>> ${logPath} || { - echo "❌ Nixpacks build failed" >> ${logPath}; - exit 1; -} -echo "✅ Nixpacks build completed." >> ${logPath}; - `; - - /* - Run the container with the image created by nixpacks, - and copy the artifacts on the host filesystem. - Then, remove the container and create a static build. - */ - if (publishDirectory) { - bashCommand += ` -docker create --name ${buildContainerId} ${appName} -docker cp ${buildContainerId}:/app/${publishDirectory} ${path.join(buildAppDirectory, publishDirectory)} >> ${logPath} 2>> ${logPath} || { - docker rm ${buildContainerId} - echo "❌ Copying ${publishDirectory} to ${path.join(buildAppDirectory, publishDirectory)} failed" >> ${logPath}; - exit 1; -} -docker rm ${buildContainerId} -${getStaticCommand(application, logPath)} - `; - } - - return bashCommand; -}; diff --git a/apps/dokploy/server/utils/builders/paketo.ts b/apps/dokploy/server/utils/builders/paketo.ts deleted file mode 100644 index 1faf42ae..00000000 --- a/apps/dokploy/server/utils/builders/paketo.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { WriteStream } from "node:fs"; -import type { ApplicationNested } from "."; -import { prepareEnvironmentVariables } from "../docker/utils"; -import { getBuildAppDirectory } from "../filesystem/directory"; -import { spawnAsync } from "../process/spawnAsync"; - -export const buildPaketo = async ( - application: ApplicationNested, - writeStream: WriteStream, -) => { - const { env, appName } = application; - const buildAppDirectory = getBuildAppDirectory(application); - const envVariables = prepareEnvironmentVariables(env); - try { - const args = [ - "build", - appName, - "--path", - buildAppDirectory, - "--builder", - "paketobuildpacks/builder-jammy-full", - ]; - - for (const env of envVariables) { - args.push("--env", env); - } - - await spawnAsync("pack", args, (data) => { - if (writeStream.writable) { - writeStream.write(data); - } - }); - return true; - } catch (e) { - throw e; - } -}; - -export const getPaketoCommand = ( - application: ApplicationNested, - logPath: string, -) => { - const { env, appName } = application; - - const buildAppDirectory = getBuildAppDirectory(application); - const envVariables = prepareEnvironmentVariables(env); - - const args = [ - "build", - appName, - "--path", - buildAppDirectory, - "--builder", - "paketobuildpacks/builder-jammy-full", - ]; - - for (const env of envVariables) { - args.push("--env", env); - } - - const command = `pack ${args.join(" ")}`; - const bashCommand = ` -echo "Starting Paketo build..." >> ${logPath}; -${command} >> ${logPath} 2>> ${logPath} || { - echo "❌ Paketo build failed" >> ${logPath}; - exit 1; -} -echo "✅ Paketo build completed." >> ${logPath}; - `; - - return bashCommand; -}; diff --git a/apps/dokploy/server/utils/builders/static.ts b/apps/dokploy/server/utils/builders/static.ts deleted file mode 100644 index 2f3d4bc2..00000000 --- a/apps/dokploy/server/utils/builders/static.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { WriteStream } from "node:fs"; -import { - buildCustomDocker, - getDockerCommand, -} from "@/server/utils/builders/docker-file"; -import type { ApplicationNested } from "."; -import { createFile, getCreateFileCommand } from "../docker/utils"; -import { getBuildAppDirectory } from "../filesystem/directory"; - -export const buildStatic = async ( - application: ApplicationNested, - writeStream: WriteStream, -) => { - const { publishDirectory } = application; - const buildAppDirectory = getBuildAppDirectory(application); - - try { - createFile( - buildAppDirectory, - "Dockerfile", - [ - "FROM nginx:alpine", - "WORKDIR /usr/share/nginx/html/", - `COPY ${publishDirectory || "."} .`, - ].join("\n"), - ); - - await buildCustomDocker( - { - ...application, - buildType: "dockerfile", - dockerfile: "Dockerfile", - }, - writeStream, - ); - - return true; - } catch (e) { - throw e; - } -}; - -export const getStaticCommand = ( - application: ApplicationNested, - logPath: string, -) => { - const { publishDirectory } = application; - const buildAppDirectory = getBuildAppDirectory(application); - - let command = getCreateFileCommand( - buildAppDirectory, - "Dockerfile", - [ - "FROM nginx:alpine", - "WORKDIR /usr/share/nginx/html/", - `COPY ${publishDirectory || "."} .`, - ].join("\n"), - ); - - command += getDockerCommand( - { - ...application, - buildType: "dockerfile", - dockerfile: "Dockerfile", - }, - logPath, - ); - return command; -}; diff --git a/apps/dokploy/server/utils/builders/utils.ts b/apps/dokploy/server/utils/builders/utils.ts deleted file mode 100644 index 3eeb4522..00000000 --- a/apps/dokploy/server/utils/builders/utils.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { existsSync, mkdirSync, writeFileSync } from "node:fs"; -import { dirname, join } from "node:path"; -import { prepareEnvironmentVariables } from "../docker/utils"; - -export const createEnvFile = (directory: string, env: string | null) => { - const envFilePath = join(dirname(directory), ".env"); - if (!existsSync(dirname(envFilePath))) { - mkdirSync(dirname(envFilePath), { recursive: true }); - } - const envFileContent = prepareEnvironmentVariables(env).join("\n"); - writeFileSync(envFilePath, envFileContent); -}; - -export const createEnvFileCommand = (directory: string, env: string | null) => { - const envFilePath = join(dirname(directory), ".env"); - if (!existsSync(dirname(envFilePath))) { - mkdirSync(dirname(envFilePath), { recursive: true }); - } - const envFileContent = prepareEnvironmentVariables(env).join("\n"); - return `echo "${envFileContent}" > ${envFilePath}`; -}; diff --git a/apps/dokploy/server/utils/cluster/upload.ts b/apps/dokploy/server/utils/cluster/upload.ts deleted file mode 100644 index 8cca9e3d..00000000 --- a/apps/dokploy/server/utils/cluster/upload.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { WriteStream } from "node:fs"; -import type { ApplicationNested } from "../builders"; -import { spawnAsync } from "../process/spawnAsync"; - -export const uploadImage = async ( - application: ApplicationNested, - writeStream: WriteStream, -) => { - const registry = application.registry; - - if (!registry) { - throw new Error("Registry not found"); - } - - const { registryUrl, imagePrefix, registryType } = registry; - const { appName } = application; - const imageName = `${appName}:latest`; - - const finalURL = - registryType === "selfHosted" - ? process.env.NODE_ENV === "development" - ? "localhost:5000" - : registryUrl - : registryUrl; - - const registryTag = imagePrefix - ? `${finalURL}/${imagePrefix}/${imageName}` - : `${finalURL}/${imageName}`; - - try { - console.log(finalURL, registryTag); - writeStream.write( - `📦 [Enabled Registry] Uploading image to ${registry.registryType} | ${registryTag} | ${finalURL}\n`, - ); - - await spawnAsync( - "docker", - ["login", finalURL, "-u", registry.username, "-p", registry.password], - (data) => { - if (writeStream.writable) { - writeStream.write(data); - } - }, - ); - - await spawnAsync("docker", ["tag", imageName, registryTag], (data) => { - if (writeStream.writable) { - writeStream.write(data); - } - }); - - await spawnAsync("docker", ["push", registryTag], (data) => { - if (writeStream.writable) { - writeStream.write(data); - } - }); - } catch (error) { - console.log(error); - throw error; - } -}; -// docker: -// endpoint: "unix:///var/run/docker.sock" -// exposedByDefault: false -// swarmMode: true diff --git a/apps/dokploy/server/utils/databases/mariadb.ts b/apps/dokploy/server/utils/databases/mariadb.ts deleted file mode 100644 index 9465d47e..00000000 --- a/apps/dokploy/server/utils/databases/mariadb.ts +++ /dev/null @@ -1,98 +0,0 @@ -import type { InferResultType } from "@/server/types/with"; -import type { CreateServiceOptions } from "dockerode"; -import { - calculateResources, - generateBindMounts, - generateFileMounts, - generateVolumeMounts, - prepareEnvironmentVariables, -} from "../docker/utils"; -import { getRemoteDocker } from "../servers/remote-docker"; - -export type MariadbNested = InferResultType<"mariadb", { mounts: true }>; -export const buildMariadb = async (mariadb: MariadbNested) => { - const { - appName, - env, - externalPort, - dockerImage, - memoryLimit, - memoryReservation, - databaseName, - databaseUser, - databasePassword, - databaseRootPassword, - cpuLimit, - cpuReservation, - command, - mounts, - } = mariadb; - - const defaultMariadbEnv = `MARIADB_DATABASE=${databaseName}\nMARIADB_USER=${databaseUser}\nMARIADB_PASSWORD=${databasePassword}\nMARIADB_ROOT_PASSWORD=${databaseRootPassword}${ - env ? `\n${env}` : "" - }`; - const resources = calculateResources({ - memoryLimit, - memoryReservation, - cpuLimit, - cpuReservation, - }); - const envVariables = prepareEnvironmentVariables(defaultMariadbEnv); - const volumesMount = generateVolumeMounts(mounts); - const bindsMount = generateBindMounts(mounts); - const filesMount = generateFileMounts(appName, mariadb); - - const docker = await getRemoteDocker(mariadb.serverId); - - const settings: CreateServiceOptions = { - Name: appName, - TaskTemplate: { - ContainerSpec: { - Image: dockerImage, - Env: envVariables, - Mounts: [...volumesMount, ...bindsMount, ...filesMount], - ...(command - ? { - Command: ["/bin/sh"], - Args: ["-c", command], - } - : {}), - }, - Networks: [{ Target: "dokploy-network" }], - Resources: { - ...resources, - }, - Placement: { - Constraints: ["node.role==manager"], - }, - }, - Mode: { - Replicated: { - Replicas: 1, - }, - }, - EndpointSpec: { - Mode: "dnsrr", - Ports: externalPort - ? [ - { - Protocol: "tcp", - TargetPort: 3306, - PublishedPort: externalPort, - PublishMode: "host", - }, - ] - : [], - }, - }; - try { - const service = docker.getService(appName); - const inspect = await service.inspect(); - await service.update({ - version: Number.parseInt(inspect.Version.Index), - ...settings, - }); - } catch (error) { - await docker.createService(settings); - } -}; diff --git a/apps/dokploy/server/utils/databases/mongo.ts b/apps/dokploy/server/utils/databases/mongo.ts deleted file mode 100644 index 6b02da86..00000000 --- a/apps/dokploy/server/utils/databases/mongo.ts +++ /dev/null @@ -1,97 +0,0 @@ -import type { InferResultType } from "@/server/types/with"; -import type { CreateServiceOptions } from "dockerode"; -import { - calculateResources, - generateBindMounts, - generateFileMounts, - generateVolumeMounts, - prepareEnvironmentVariables, -} from "../docker/utils"; -import { getRemoteDocker } from "../servers/remote-docker"; - -export type MongoNested = InferResultType<"mongo", { mounts: true }>; - -export const buildMongo = async (mongo: MongoNested) => { - const { - appName, - env, - externalPort, - dockerImage, - memoryLimit, - memoryReservation, - cpuLimit, - cpuReservation, - databaseUser, - databasePassword, - command, - mounts, - } = mongo; - - const defaultMongoEnv = `MONGO_INITDB_ROOT_USERNAME=${databaseUser}\nMONGO_INITDB_ROOT_PASSWORD=${databasePassword}${ - env ? `\n${env}` : "" - }`; - const resources = calculateResources({ - memoryLimit, - memoryReservation, - cpuLimit, - cpuReservation, - }); - const envVariables = prepareEnvironmentVariables(defaultMongoEnv); - const volumesMount = generateVolumeMounts(mounts); - const bindsMount = generateBindMounts(mounts); - const filesMount = generateFileMounts(appName, mongo); - - const docker = await getRemoteDocker(mongo.serverId); - - const settings: CreateServiceOptions = { - Name: appName, - TaskTemplate: { - ContainerSpec: { - Image: dockerImage, - Env: envVariables, - Mounts: [...volumesMount, ...bindsMount, ...filesMount], - ...(command - ? { - Command: ["/bin/sh"], - Args: ["-c", command], - } - : {}), - }, - Networks: [{ Target: "dokploy-network" }], - Resources: { - ...resources, - }, - Placement: { - Constraints: ["node.role==manager"], - }, - }, - Mode: { - Replicated: { - Replicas: 1, - }, - }, - EndpointSpec: { - Mode: "dnsrr", - Ports: externalPort - ? [ - { - Protocol: "tcp", - TargetPort: 27017, - PublishedPort: externalPort, - PublishMode: "host", - }, - ] - : [], - }, - }; - try { - const service = docker.getService(appName); - const inspect = await service.inspect(); - await service.update({ - version: Number.parseInt(inspect.Version.Index), - ...settings, - }); - } catch (error) { - await docker.createService(settings); - } -}; diff --git a/apps/dokploy/server/utils/databases/mysql.ts b/apps/dokploy/server/utils/databases/mysql.ts deleted file mode 100644 index 3ad266b7..00000000 --- a/apps/dokploy/server/utils/databases/mysql.ts +++ /dev/null @@ -1,104 +0,0 @@ -import type { InferResultType } from "@/server/types/with"; -import type { CreateServiceOptions } from "dockerode"; -import { - calculateResources, - generateBindMounts, - generateFileMounts, - generateVolumeMounts, - prepareEnvironmentVariables, -} from "../docker/utils"; -import { getRemoteDocker } from "../servers/remote-docker"; - -export type MysqlNested = InferResultType<"mysql", { mounts: true }>; - -export const buildMysql = async (mysql: MysqlNested) => { - const { - appName, - env, - externalPort, - dockerImage, - memoryLimit, - memoryReservation, - databaseName, - databaseUser, - databasePassword, - databaseRootPassword, - cpuLimit, - cpuReservation, - command, - mounts, - } = mysql; - - const defaultMysqlEnv = - databaseUser !== "root" - ? `MYSQL_USER=${databaseUser}\nMYSQL_DATABASE=${databaseName}\nMYSQL_PASSWORD=${databasePassword}\nMYSQL_ROOT_PASSWORD=${databaseRootPassword}${ - env ? `\n${env}` : "" - }` - : `MYSQL_DATABASE=${databaseName}\nMYSQL_ROOT_PASSWORD=${databaseRootPassword}${ - env ? `\n${env}` : "" - }`; - const resources = calculateResources({ - memoryLimit, - memoryReservation, - cpuLimit, - cpuReservation, - }); - const envVariables = prepareEnvironmentVariables(defaultMysqlEnv); - const volumesMount = generateVolumeMounts(mounts); - const bindsMount = generateBindMounts(mounts); - const filesMount = generateFileMounts(appName, mysql); - - const docker = await getRemoteDocker(mysql.serverId); - - const settings: CreateServiceOptions = { - Name: appName, - TaskTemplate: { - ContainerSpec: { - Image: dockerImage, - Env: envVariables, - Mounts: [...volumesMount, ...bindsMount, ...filesMount], - ...(command - ? { - Command: ["/bin/sh"], - Args: ["-c", command], - } - : {}), - }, - Networks: [{ Target: "dokploy-network" }], - Resources: { - ...resources, - }, - Placement: { - Constraints: ["node.role==manager"], - }, - }, - Mode: { - Replicated: { - Replicas: 1, - }, - }, - EndpointSpec: { - Mode: "dnsrr", - Ports: externalPort - ? [ - { - Protocol: "tcp", - TargetPort: 3306, - PublishedPort: externalPort, - PublishMode: "host", - }, - ] - : [], - }, - }; - try { - const service = docker.getService(appName); - const inspect = await service.inspect(); - await service.update({ - version: Number.parseInt(inspect.Version.Index), - ...settings, - }); - } catch (error) { - await docker.createService(settings); - } -}; diff --git a/apps/dokploy/server/utils/databases/postgres.ts b/apps/dokploy/server/utils/databases/postgres.ts deleted file mode 100644 index f7984fbe..00000000 --- a/apps/dokploy/server/utils/databases/postgres.ts +++ /dev/null @@ -1,98 +0,0 @@ -import type { InferResultType } from "@/server/types/with"; -import type { CreateServiceOptions } from "dockerode"; -import { - calculateResources, - generateBindMounts, - generateFileMounts, - generateVolumeMounts, - prepareEnvironmentVariables, -} from "../docker/utils"; -import { getRemoteDocker } from "../servers/remote-docker"; - -export type PostgresNested = InferResultType<"postgres", { mounts: true }>; -export const buildPostgres = async (postgres: PostgresNested) => { - const { - appName, - env, - externalPort, - dockerImage, - memoryLimit, - memoryReservation, - cpuLimit, - cpuReservation, - databaseName, - databaseUser, - databasePassword, - command, - mounts, - } = postgres; - - const defaultPostgresEnv = `POSTGRES_DB=${databaseName}\nPOSTGRES_USER=${databaseUser}\nPOSTGRES_PASSWORD=${databasePassword}${ - env ? `\n${env}` : "" - }`; - const resources = calculateResources({ - memoryLimit, - memoryReservation, - cpuLimit, - cpuReservation, - }); - const envVariables = prepareEnvironmentVariables(defaultPostgresEnv); - const volumesMount = generateVolumeMounts(mounts); - const bindsMount = generateBindMounts(mounts); - const filesMount = generateFileMounts(appName, postgres); - - const docker = await getRemoteDocker(postgres.serverId); - - const settings: CreateServiceOptions = { - Name: appName, - TaskTemplate: { - ContainerSpec: { - Image: dockerImage, - Env: envVariables, - Mounts: [...volumesMount, ...bindsMount, ...filesMount], - ...(command - ? { - Command: ["/bin/sh"], - Args: ["-c", command], - } - : {}), - }, - Networks: [{ Target: "dokploy-network" }], - Resources: { - ...resources, - }, - Placement: { - Constraints: ["node.role==manager"], - }, - }, - Mode: { - Replicated: { - Replicas: 1, - }, - }, - EndpointSpec: { - Mode: "dnsrr", - Ports: externalPort - ? [ - { - Protocol: "tcp", - TargetPort: 5432, - PublishedPort: externalPort, - PublishMode: "host", - }, - ] - : [], - }, - }; - try { - const service = docker.getService(appName); - const inspect = await service.inspect(); - await service.update({ - version: Number.parseInt(inspect.Version.Index), - ...settings, - }); - } catch (error) { - console.log("error", error); - await docker.createService(settings); - } -}; diff --git a/apps/dokploy/server/utils/databases/redis.ts b/apps/dokploy/server/utils/databases/redis.ts deleted file mode 100644 index 62d972a7..00000000 --- a/apps/dokploy/server/utils/databases/redis.ts +++ /dev/null @@ -1,95 +0,0 @@ -import type { InferResultType } from "@/server/types/with"; -import type { CreateServiceOptions } from "dockerode"; -import { - calculateResources, - generateBindMounts, - generateFileMounts, - generateVolumeMounts, - prepareEnvironmentVariables, -} from "../docker/utils"; -import { getRemoteDocker } from "../servers/remote-docker"; - -export type RedisNested = InferResultType<"redis", { mounts: true }>; -export const buildRedis = async (redis: RedisNested) => { - const { - appName, - env, - externalPort, - dockerImage, - memoryLimit, - memoryReservation, - databasePassword, - cpuLimit, - cpuReservation, - command, - mounts, - } = redis; - - const defaultRedisEnv = `REDIS_PASSWORD=${databasePassword}${ - env ? `\n${env}` : "" - }`; - const resources = calculateResources({ - memoryLimit, - memoryReservation, - cpuLimit, - cpuReservation, - }); - const envVariables = prepareEnvironmentVariables(defaultRedisEnv); - const volumesMount = generateVolumeMounts(mounts); - const bindsMount = generateBindMounts(mounts); - const filesMount = generateFileMounts(appName, redis); - - const docker = await getRemoteDocker(redis.serverId); - - const settings: CreateServiceOptions = { - Name: appName, - TaskTemplate: { - ContainerSpec: { - Image: dockerImage, - Env: envVariables, - Mounts: [...volumesMount, ...bindsMount, ...filesMount], - Command: ["/bin/sh"], - Args: [ - "-c", - command ? command : `redis-server --requirepass ${databasePassword}`, - ], - }, - Networks: [{ Target: "dokploy-network" }], - Resources: { - ...resources, - }, - Placement: { - Constraints: ["node.role==manager"], - }, - }, - Mode: { - Replicated: { - Replicas: 1, - }, - }, - EndpointSpec: { - Mode: "dnsrr", - Ports: externalPort - ? [ - { - Protocol: "tcp", - TargetPort: 6379, - PublishedPort: externalPort, - PublishMode: "host", - }, - ] - : [], - }, - }; - - try { - const service = docker.getService(appName); - const inspect = await service.inspect(); - await service.update({ - version: Number.parseInt(inspect.Version.Index), - ...settings, - }); - } catch (error) { - await docker.createService(settings); - } -}; diff --git a/apps/dokploy/server/utils/process/execAsync.ts b/apps/dokploy/server/utils/process/execAsync.ts index d6df6551..2fa846d7 100644 --- a/apps/dokploy/server/utils/process/execAsync.ts +++ b/apps/dokploy/server/utils/process/execAsync.ts @@ -1,6 +1,6 @@ import { exec } from "node:child_process"; import util from "node:util"; -import { findServerById } from "@/server/api/services/server"; +import { findServerById } from "@dokploy/builders"; import { Client } from "ssh2"; import { readSSHKey } from "../filesystem/ssh"; export const execAsync = util.promisify(exec); diff --git a/apps/dokploy/server/utils/providers/bitbucket.ts b/apps/dokploy/server/utils/providers/bitbucket.ts deleted file mode 100644 index c6eb0bbd..00000000 --- a/apps/dokploy/server/utils/providers/bitbucket.ts +++ /dev/null @@ -1,361 +0,0 @@ -import { createWriteStream } from "node:fs"; -import { join } from "node:path"; -import { findBitbucketById } from "@/server/api/services/bitbucket"; -import type { Compose } from "@/server/api/services/compose"; -import { paths } from "@/server/constants"; -import type { - apiBitbucketTestConnection, - apiFindBitbucketBranches, -} from "@/server/db/schema"; -import type { InferResultType } from "@/server/types/with"; -import { TRPCError } from "@trpc/server"; -import { recreateDirectory } from "../filesystem/directory"; -import { execAsyncRemote } from "../process/execAsync"; -import { spawnAsync } from "../process/spawnAsync"; - -export type ApplicationWithBitbucket = InferResultType< - "applications", - { bitbucket: true } ->; - -export type ComposeWithBitbucket = InferResultType< - "compose", - { bitbucket: true } ->; - -export const cloneBitbucketRepository = async ( - entity: ApplicationWithBitbucket | ComposeWithBitbucket, - logPath: string, - isCompose = false, -) => { - const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(); - const writeStream = createWriteStream(logPath, { flags: "a" }); - const { - appName, - bitbucketRepository, - bitbucketOwner, - bitbucketBranch, - bitbucketId, - bitbucket, - } = entity; - - if (!bitbucketId) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Bitbucket Provider not found", - }); - } - - const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH; - const outputPath = join(basePath, appName, "code"); - await recreateDirectory(outputPath); - const repoclone = `bitbucket.org/${bitbucketOwner}/${bitbucketRepository}.git`; - const cloneUrl = `https://${bitbucket?.bitbucketUsername}:${bitbucket?.appPassword}@${repoclone}`; - try { - writeStream.write(`\nCloning Repo ${repoclone} to ${outputPath}: ✅\n`); - await spawnAsync( - "git", - [ - "clone", - "--branch", - bitbucketBranch!, - "--depth", - "1", - cloneUrl, - outputPath, - "--progress", - ], - (data) => { - if (writeStream.writable) { - writeStream.write(data); - } - }, - ); - writeStream.write(`\nCloned ${repoclone} to ${outputPath}: ✅\n`); - } catch (error) { - writeStream.write(`ERROR Clonning: ${error}: ❌`); - throw error; - } finally { - writeStream.end(); - } -}; - -export const cloneRawBitbucketRepository = async (entity: Compose) => { - const { COMPOSE_PATH } = paths(); - const { - appName, - bitbucketRepository, - bitbucketOwner, - bitbucketBranch, - bitbucketId, - } = entity; - - if (!bitbucketId) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Bitbucket Provider not found", - }); - } - - const bitbucketProvider = await findBitbucketById(bitbucketId); - const basePath = COMPOSE_PATH; - const outputPath = join(basePath, appName, "code"); - await recreateDirectory(outputPath); - const repoclone = `bitbucket.org/${bitbucketOwner}/${bitbucketRepository}.git`; - const cloneUrl = `https://${bitbucketProvider?.bitbucketUsername}:${bitbucketProvider?.appPassword}@${repoclone}`; - - try { - await spawnAsync("git", [ - "clone", - "--branch", - bitbucketBranch!, - "--depth", - "1", - cloneUrl, - outputPath, - "--progress", - ]); - } catch (error) { - throw error; - } -}; - -export const cloneRawBitbucketRepositoryRemote = async (compose: Compose) => { - const { COMPOSE_PATH } = paths(true); - const { - appName, - bitbucketRepository, - bitbucketOwner, - bitbucketBranch, - bitbucketId, - serverId, - } = compose; - - if (!serverId) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Server not found", - }); - } - if (!bitbucketId) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Bitbucket Provider not found", - }); - } - - const bitbucketProvider = await findBitbucketById(bitbucketId); - const basePath = COMPOSE_PATH; - const outputPath = join(basePath, appName, "code"); - await recreateDirectory(outputPath); - const repoclone = `bitbucket.org/${bitbucketOwner}/${bitbucketRepository}.git`; - const cloneUrl = `https://${bitbucketProvider?.bitbucketUsername}:${bitbucketProvider?.appPassword}@${repoclone}`; - - try { - await execAsyncRemote( - serverId, - `git clone --branch ${bitbucketBranch} --depth 1 ${cloneUrl} ${outputPath}`, - ); - } catch (error) { - throw error; - } -}; - -export const getBitbucketCloneCommand = async ( - entity: ApplicationWithBitbucket | ComposeWithBitbucket, - logPath: string, - isCompose = false, -) => { - const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(true); - const { - appName, - bitbucketRepository, - bitbucketOwner, - bitbucketBranch, - bitbucketId, - serverId, - bitbucket, - } = entity; - - if (!serverId) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Server not found", - }); - } - - if (!bitbucketId) { - const command = ` - echo "Error: ❌ Bitbucket Provider not found" >> ${logPath}; - exit 1; - `; - await execAsyncRemote(serverId, command); - throw new TRPCError({ - code: "NOT_FOUND", - message: "Bitbucket Provider not found", - }); - } - - const bitbucketProvider = await findBitbucketById(bitbucketId); - const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH; - const outputPath = join(basePath, appName, "code"); - await recreateDirectory(outputPath); - const repoclone = `bitbucket.org/${bitbucketOwner}/${bitbucketRepository}.git`; - const cloneUrl = `https://${bitbucketProvider?.bitbucketUsername}:${bitbucketProvider?.appPassword}@${repoclone}`; - - const cloneCommand = ` -rm -rf ${outputPath}; -mkdir -p ${outputPath}; -if ! git clone --branch ${bitbucketBranch} --depth 1 --progress ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then - echo "❌ [ERROR] Fail to clone the repository ${repoclone}" >> ${logPath}; - exit 1; -fi -echo "Cloned ${repoclone} to ${outputPath}: ✅" >> ${logPath}; - `; - - return cloneCommand; -}; - -export const getBitbucketRepositories = async (bitbucketId?: string) => { - if (!bitbucketId) { - return []; - } - const bitbucketProvider = await findBitbucketById(bitbucketId); - - const username = - bitbucketProvider.bitbucketWorkspaceName || - bitbucketProvider.bitbucketUsername; - const url = `https://api.bitbucket.org/2.0/repositories/${username}`; - - try { - const response = await fetch(url, { - method: "GET", - headers: { - Authorization: `Basic ${Buffer.from(`${bitbucketProvider.bitbucketUsername}:${bitbucketProvider.appPassword}`).toString("base64")}`, - }, - }); - - if (!response.ok) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: `Failed to fetch repositories: ${response.statusText}`, - }); - } - - const data = await response.json(); - - const mappedData = data.values.map((repo: any) => { - return { - name: repo.name, - url: repo.links.html.href, - owner: { - username: repo.workspace.slug, - }, - }; - }); - - return mappedData as { - name: string; - url: string; - owner: { - username: string; - }; - }[]; - } catch (error) { - throw error; - } -}; - -export const getBitbucketBranches = async ( - input: typeof apiFindBitbucketBranches._type, -) => { - if (!input.bitbucketId) { - return []; - } - const bitbucketProvider = await findBitbucketById(input.bitbucketId); - const { owner, repo } = input; - const url = `https://api.bitbucket.org/2.0/repositories/${owner}/${repo}/refs/branches`; - - try { - const response = await fetch(url, { - method: "GET", - headers: { - Authorization: `Basic ${Buffer.from(`${bitbucketProvider.bitbucketUsername}:${bitbucketProvider.appPassword}`).toString("base64")}`, - }, - }); - - if (!response.ok) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: `HTTP error! status: ${response.status}`, - }); - } - - const data = await response.json(); - - const mappedData = data.values.map((branch: any) => { - return { - name: branch.name, - commit: { - sha: branch.target.hash, - }, - }; - }); - - return mappedData as { - name: string; - commit: { - sha: string; - }; - }[]; - } catch (error) { - throw error; - } -}; - -export const testBitbucketConnection = async ( - input: typeof apiBitbucketTestConnection._type, -) => { - const bitbucketProvider = await findBitbucketById(input.bitbucketId); - - if (!bitbucketProvider) { - throw new Error("Bitbucket provider not found"); - } - - const { bitbucketUsername, workspaceName } = input; - - const username = workspaceName || bitbucketUsername; - - const url = `https://api.bitbucket.org/2.0/repositories/${username}`; - try { - const response = await fetch(url, { - method: "GET", - headers: { - Authorization: `Basic ${Buffer.from(`${bitbucketProvider.bitbucketUsername}:${bitbucketProvider.appPassword}`).toString("base64")}`, - }, - }); - - if (!response.ok) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: `Failed to fetch repositories: ${response.statusText}`, - }); - } - - const data = await response.json(); - - const mappedData = data.values.map((repo: any) => { - return { - name: repo.name, - url: repo.links.html.href, - owner: { - username: repo.workspace.slug, - }, - }; - }) as []; - - return mappedData.length; - } catch (error) { - throw error; - } -}; diff --git a/apps/dokploy/server/utils/providers/docker.ts b/apps/dokploy/server/utils/providers/docker.ts deleted file mode 100644 index 77c4db57..00000000 --- a/apps/dokploy/server/utils/providers/docker.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { createWriteStream } from "node:fs"; -import { type ApplicationNested, mechanizeDockerContainer } from "../builders"; -import { pullImage } from "../docker/utils"; - -interface RegistryAuth { - username: string; - password: string; - serveraddress: string; -} - -export const buildDocker = async ( - application: ApplicationNested, - logPath: string, -): Promise => { - const { buildType, dockerImage, username, password } = application; - const authConfig: Partial = { - username: username || "", - password: password || "", - }; - - const writeStream = createWriteStream(logPath, { flags: "a" }); - - writeStream.write(`\nBuild ${buildType}\n`); - - writeStream.write(`Pulling ${dockerImage}: ✅\n`); - - try { - if (!dockerImage) { - throw new Error("Docker image not found"); - } - - await pullImage( - dockerImage, - (data) => { - if (writeStream.writable) { - writeStream.write(`${data.status}\n`); - } - }, - authConfig, - ); - await mechanizeDockerContainer(application); - writeStream.write("\nDocker Deployed: ✅\n"); - } catch (error) { - writeStream.write(`ERROR: ${error}: ❌`); - throw error; - } finally { - writeStream.end(); - } -}; - -export const buildRemoteDocker = async ( - application: ApplicationNested, - logPath: string, -) => { - const { sourceType, dockerImage, username, password } = application; - - try { - if (!dockerImage) { - throw new Error("Docker image not found"); - } - let command = ` -echo "Pulling ${dockerImage}" >> ${logPath}; - `; - - if (username && password) { - command += ` -if ! docker login --username ${username} --password ${password} https://index.docker.io/v1/ >> ${logPath} 2>&1; then - echo "❌ Login failed" >> ${logPath}; - exit 1; -fi -`; - } - - command += ` -docker pull ${dockerImage} >> ${logPath} 2>> ${logPath} || { - echo "❌ Pulling image failed" >> ${logPath}; - exit 1; -} - -echo "✅ Pulling image completed." >> ${logPath}; -`; - return command; - } catch (error) { - throw error; - } -}; diff --git a/apps/dokploy/server/utils/providers/git.ts b/apps/dokploy/server/utils/providers/git.ts deleted file mode 100644 index d348c0e5..00000000 --- a/apps/dokploy/server/utils/providers/git.ts +++ /dev/null @@ -1,347 +0,0 @@ -import { createWriteStream } from "node:fs"; -import path, { join } from "node:path"; -import type { Compose } from "@/server/api/services/compose"; -import { updateSSHKeyById } from "@/server/api/services/ssh-key"; -import { paths } from "@/server/constants"; -import { TRPCError } from "@trpc/server"; -import { recreateDirectory } from "../filesystem/directory"; -import { execAsync, execAsyncRemote } from "../process/execAsync"; -import { spawnAsync } from "../process/spawnAsync"; - -export const cloneGitRepository = async ( - entity: { - appName: string; - customGitUrl?: string | null; - customGitBranch?: string | null; - customGitSSHKeyId?: string | null; - }, - logPath: string, - isCompose = false, -) => { - const { SSH_PATH, COMPOSE_PATH, APPLICATIONS_PATH } = paths(); - const { appName, customGitUrl, customGitBranch, customGitSSHKeyId } = entity; - - if (!customGitUrl || !customGitBranch) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error: Repository not found", - }); - } - - const writeStream = createWriteStream(logPath, { flags: "a" }); - const keyPath = path.join(SSH_PATH, `${customGitSSHKeyId}_rsa`); - const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH; - const outputPath = join(basePath, appName, "code"); - const knownHostsPath = path.join(SSH_PATH, "known_hosts"); - - try { - if (!isHttpOrHttps(customGitUrl)) { - await addHostToKnownHosts(customGitUrl); - } - await recreateDirectory(outputPath); - // const command = `GIT_SSH_COMMAND="ssh -i ${keyPath} -o UserKnownHostsFile=${knownHostsPath}" git clone --branch ${customGitBranch} --depth 1 ${customGitUrl} ${gitCopyPath} --progress`; - // const { stdout, stderr } = await execAsync(command); - writeStream.write( - `\nCloning Repo Custom ${customGitUrl} to ${outputPath}: ✅\n`, - ); - - if (customGitSSHKeyId) { - await updateSSHKeyById({ - sshKeyId: customGitSSHKeyId, - lastUsedAt: new Date().toISOString(), - }); - } - - await spawnAsync( - "git", - [ - "clone", - "--branch", - customGitBranch, - "--depth", - "1", - "--recurse-submodules", - customGitUrl, - outputPath, - "--progress", - ], - (data) => { - if (writeStream.writable) { - writeStream.write(data); - } - }, - { - env: { - ...process.env, - ...(customGitSSHKeyId && { - GIT_SSH_COMMAND: `ssh -i ${keyPath} -o UserKnownHostsFile=${knownHostsPath}`, - }), - }, - }, - ); - - writeStream.write(`\nCloned Custom Git ${customGitUrl}: ✅\n`); - } catch (error) { - writeStream.write(`\nERROR Cloning Custom Git: ${error}: ❌\n`); - throw error; - } finally { - writeStream.end(); - } -}; - -export const getCustomGitCloneCommand = async ( - entity: { - appName: string; - customGitUrl?: string | null; - customGitBranch?: string | null; - customGitSSHKeyId?: string | null; - serverId: string | null; - }, - logPath: string, - isCompose = false, -) => { - const { SSH_PATH, COMPOSE_PATH, APPLICATIONS_PATH } = paths(true); - const { - appName, - customGitUrl, - customGitBranch, - customGitSSHKeyId, - serverId, - } = entity; - - if (!customGitUrl || !customGitBranch) { - const command = ` - echo "Error: ❌ Repository not found" >> ${logPath}; - exit 1; - `; - - await execAsyncRemote(serverId, command); - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error: Repository not found", - }); - } - - const keyPath = path.join(SSH_PATH, `${customGitSSHKeyId}_rsa`); - const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH; - const outputPath = join(basePath, appName, "code"); - const knownHostsPath = path.join(SSH_PATH, "known_hosts"); - - if (customGitSSHKeyId) { - await updateSSHKeyById({ - sshKeyId: customGitSSHKeyId, - lastUsedAt: new Date().toISOString(), - }); - } - try { - const command = []; - if (!isHttpOrHttps(customGitUrl)) { - command.push(addHostToKnownHostsCommand(customGitUrl)); - } - command.push(`rm -rf ${outputPath};`); - command.push(`mkdir -p ${outputPath};`); - command.push( - `echo "Cloning Custom Git ${customGitUrl}" to ${outputPath}: ✅ >> ${logPath};`, - ); - if (customGitSSHKeyId) { - command.push( - `GIT_SSH_COMMAND="ssh -i ${keyPath} -o UserKnownHostsFile=${knownHostsPath}"`, - ); - } - - command.push( - `if ! git clone --branch ${customGitBranch} --depth 1 --progress ${customGitUrl} ${outputPath} >> ${logPath} 2>&1; then - echo "❌ [ERROR] Fail to clone the repository ${customGitUrl}" >> ${logPath}; - exit 1; - fi - `, - ); - command.push(`echo "Cloned Custom Git ${customGitUrl}: ✅" >> ${logPath};`); - return command.join("\n"); - } catch (error) { - console.log(error); - throw error; - } -}; - -const isHttpOrHttps = (url: string): boolean => { - const regex = /^https?:\/\//; - return regex.test(url); -}; - -const addHostToKnownHosts = async (repositoryURL: string) => { - const { SSH_PATH } = paths(); - const { domain, port } = sanitizeRepoPathSSH(repositoryURL); - const knownHostsPath = path.join(SSH_PATH, "known_hosts"); - - const command = `ssh-keyscan -p ${port} ${domain} >> ${knownHostsPath}`; - try { - await execAsync(command); - } catch (error) { - console.error(`Error adding host to known_hosts: ${error}`); - throw error; - } -}; - -const addHostToKnownHostsCommand = (repositoryURL: string) => { - const { SSH_PATH } = paths(); - const { domain, port } = sanitizeRepoPathSSH(repositoryURL); - const knownHostsPath = path.join(SSH_PATH, "known_hosts"); - - return `ssh-keyscan -p ${port} ${domain} >> ${knownHostsPath};`; -}; -const sanitizeRepoPathSSH = (input: string) => { - const SSH_PATH_RE = new RegExp( - [ - /^\s*/, - /(?:(?[a-z]+):\/\/)?/, - /(?:(?[a-z_][a-z0-9_-]+)@)?/, - /(?[^\s\/\?#:]+)/, - /(?::(?[0-9]{1,5}))?/, - /(?:[\/:](?[^\s\/\?#:]+))?/, - /(?:[\/:](?(?:[^\s\?#:.]|\.(?!git\/?\s*$))+))/, - /(?:.git)?\/?\s*$/, - ] - .map((r) => r.source) - .join(""), - "i", - ); - - const found = input.match(SSH_PATH_RE); - if (!found) { - throw new Error(`Malformatted SSH path: ${input}`); - } - - return { - user: found.groups?.user ?? "git", - domain: found.groups?.domain, - port: Number(found.groups?.port ?? 22), - owner: found.groups?.owner ?? "", - repo: found.groups?.repo, - get repoPath() { - return `ssh://${this.user}@${this.domain}:${this.port}/${this.owner}${ - this.owner && "/" - }${this.repo}.git`; - }, - }; -}; - -export const cloneGitRawRepository = async (entity: { - appName: string; - customGitUrl?: string | null; - customGitBranch?: string | null; - customGitSSHKeyId?: string | null; -}) => { - const { appName, customGitUrl, customGitBranch, customGitSSHKeyId } = entity; - - if (!customGitUrl || !customGitBranch) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error: Repository not found", - }); - } - - const { SSH_PATH, COMPOSE_PATH } = paths(); - const keyPath = path.join(SSH_PATH, `${customGitSSHKeyId}_rsa`); - const basePath = COMPOSE_PATH; - const outputPath = join(basePath, appName, "code"); - const knownHostsPath = path.join(SSH_PATH, "known_hosts"); - - try { - await addHostToKnownHosts(customGitUrl); - await recreateDirectory(outputPath); - - if (customGitSSHKeyId) { - await updateSSHKeyById({ - sshKeyId: customGitSSHKeyId, - lastUsedAt: new Date().toISOString(), - }); - } - - await spawnAsync( - "git", - [ - "clone", - "--branch", - customGitBranch, - "--depth", - "1", - customGitUrl, - outputPath, - "--progress", - ], - (data) => {}, - { - env: { - ...process.env, - ...(customGitSSHKeyId && { - GIT_SSH_COMMAND: `ssh -i ${keyPath} -o UserKnownHostsFile=${knownHostsPath}`, - }), - }, - }, - ); - } catch (error) { - throw error; - } -}; - -export const cloneRawGitRepositoryRemote = async (compose: Compose) => { - const { - appName, - customGitBranch, - customGitUrl, - customGitSSHKeyId, - serverId, - } = compose; - - if (!serverId) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Server not found", - }); - } - if (!customGitUrl) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Git Provider not found", - }); - } - - const { SSH_PATH, COMPOSE_PATH } = paths(true); - const keyPath = path.join(SSH_PATH, `${customGitSSHKeyId}_rsa`); - const basePath = COMPOSE_PATH; - const outputPath = join(basePath, appName, "code"); - const knownHostsPath = path.join(SSH_PATH, "known_hosts"); - - if (customGitSSHKeyId) { - await updateSSHKeyById({ - sshKeyId: customGitSSHKeyId, - lastUsedAt: new Date().toISOString(), - }); - } - try { - const command = []; - if (!isHttpOrHttps(customGitUrl)) { - command.push(addHostToKnownHostsCommand(customGitUrl)); - } - command.push(`rm -rf ${outputPath};`); - command.push(`mkdir -p ${outputPath};`); - if (customGitSSHKeyId) { - command.push( - `GIT_SSH_COMMAND="ssh -i ${keyPath} -o UserKnownHostsFile=${knownHostsPath}"`, - ); - } - - command.push( - `if ! git clone --branch ${customGitBranch} --depth 1 --progress ${customGitUrl} ${outputPath} ; then - echo "[ERROR] Fail to clone the repository "; - exit 1; - fi - `, - ); - - await execAsyncRemote(serverId, command.join("\n")); - } catch (error) { - throw error; - } -}; diff --git a/apps/dokploy/server/utils/providers/github.ts b/apps/dokploy/server/utils/providers/github.ts deleted file mode 100644 index 68a401b2..00000000 --- a/apps/dokploy/server/utils/providers/github.ts +++ /dev/null @@ -1,336 +0,0 @@ -import { createWriteStream } from "node:fs"; -import { join } from "node:path"; -import { paths } from "@/server/constants"; -import type { InferResultType } from "@/server/types/with"; -import { createAppAuth } from "@octokit/auth-app"; -import { TRPCError } from "@trpc/server"; -import { Octokit } from "octokit"; -import { recreateDirectory } from "../filesystem/directory"; -import { spawnAsync } from "../process/spawnAsync"; - -import type { Compose } from "@/server/api/services/compose"; -import { type Github, findGithubById } from "@/server/api/services/github"; -import type { apiFindGithubBranches } from "@/server/db/schema"; -import { execAsyncRemote } from "../process/execAsync"; - -export const authGithub = (githubProvider: Github) => { - if (!haveGithubRequirements(githubProvider)) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Github Account not configured correctly", - }); - } - - const octokit = new Octokit({ - authStrategy: createAppAuth, - auth: { - appId: githubProvider?.githubAppId || 0, - privateKey: githubProvider?.githubPrivateKey || "", - installationId: githubProvider?.githubInstallationId, - }, - }); - - return octokit; -}; - -export const getGithubToken = async ( - octokit: ReturnType, -) => { - const installation = (await octokit.auth({ - type: "installation", - })) as { - token: string; - }; - - return installation.token; -}; - -export const haveGithubRequirements = (githubProvider: Github) => { - return !!( - githubProvider?.githubAppId && - githubProvider?.githubPrivateKey && - githubProvider?.githubInstallationId - ); -}; - -const getErrorCloneRequirements = (entity: { - repository?: string | null; - owner?: string | null; - branch?: string | null; -}) => { - const reasons: string[] = []; - const { repository, owner, branch } = entity; - - if (!repository) reasons.push("1. Repository not assigned."); - if (!owner) reasons.push("2. Owner not specified."); - if (!branch) reasons.push("3. Branch not defined."); - - return reasons; -}; - -export type ApplicationWithGithub = InferResultType< - "applications", - { github: true } ->; - -export type ComposeWithGithub = InferResultType<"compose", { github: true }>; -export const cloneGithubRepository = async ( - entity: ApplicationWithGithub | ComposeWithGithub, - logPath: string, - isCompose = false, -) => { - const { APPLICATIONS_PATH, COMPOSE_PATH } = paths(); - const writeStream = createWriteStream(logPath, { flags: "a" }); - const { appName, repository, owner, branch, githubId } = entity; - - if (!githubId) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "GitHub Provider not found", - }); - } - - const requirements = getErrorCloneRequirements(entity); - - // Check if requirements are met - if (requirements.length > 0) { - writeStream.write( - `\nGitHub Repository configuration failed for application: ${appName}\n`, - ); - writeStream.write("Reasons:\n"); - writeStream.write(requirements.join("\n")); - writeStream.end(); - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error: GitHub repository information is incomplete.", - }); - } - - const githubProvider = await findGithubById(githubId); - const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH; - const outputPath = join(basePath, appName, "code"); - const octokit = authGithub(githubProvider); - const token = await getGithubToken(octokit); - const repoclone = `github.com/${owner}/${repository}.git`; - await recreateDirectory(outputPath); - const cloneUrl = `https://oauth2:${token}@${repoclone}`; - - try { - writeStream.write(`\nClonning Repo ${repoclone} to ${outputPath}: ✅\n`); - await spawnAsync( - "git", - [ - "clone", - "--branch", - branch!, - "--depth", - "1", - cloneUrl, - outputPath, - "--progress", - ], - (data) => { - if (writeStream.writable) { - writeStream.write(data); - } - }, - ); - writeStream.write(`\nCloned ${repoclone}: ✅\n`); - } catch (error) { - writeStream.write(`ERROR Clonning: ${error}: ❌`); - throw error; - } finally { - writeStream.end(); - } -}; - -export const getGithubCloneCommand = async ( - entity: ApplicationWithGithub | ComposeWithGithub, - logPath: string, - isCompose = false, -) => { - const { appName, repository, owner, branch, githubId, serverId } = entity; - - if (!serverId) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Server not found", - }); - } - - if (!githubId) { - const command = ` - echo "Error: ❌ Github Provider not found" >> ${logPath}; - exit 1; - `; - - await execAsyncRemote(serverId, command); - throw new TRPCError({ - code: "NOT_FOUND", - message: "GitHub Provider not found", - }); - } - - const requirements = getErrorCloneRequirements(entity); - - // Build log messages - let logMessages = ""; - if (requirements.length > 0) { - logMessages += `\nGitHub Repository configuration failed for application: ${appName}\n`; - logMessages += "Reasons:\n"; - logMessages += requirements.join("\n"); - const escapedLogMessages = logMessages - .replace(/\\/g, "\\\\") - .replace(/"/g, '\\"') - .replace(/\n/g, "\\n"); - - const bashCommand = ` - echo "${escapedLogMessages}" >> ${logPath}; - exit 1; # Exit with error code - `; - - await execAsyncRemote(serverId, bashCommand); - return; - } - const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(true); - const githubProvider = await findGithubById(githubId); - const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH; - const outputPath = join(basePath, appName, "code"); - const octokit = authGithub(githubProvider); - const token = await getGithubToken(octokit); - const repoclone = `github.com/${owner}/${repository}.git`; - const cloneUrl = `https://oauth2:${token}@${repoclone}`; - - const cloneCommand = ` -rm -rf ${outputPath}; -mkdir -p ${outputPath}; -if ! git clone --branch ${branch} --depth 1 --progress ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then - echo "❌ [ERROR] Fallo al clonar el repositorio ${repoclone}" >> ${logPath}; - exit 1; -fi -echo "Cloned ${repoclone} to ${outputPath}: ✅" >> ${logPath}; - `; - - return cloneCommand; -}; - -export const cloneRawGithubRepository = async (entity: Compose) => { - const { appName, repository, owner, branch, githubId } = entity; - - if (!githubId) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "GitHub Provider not found", - }); - } - const { COMPOSE_PATH } = paths(); - const githubProvider = await findGithubById(githubId); - const basePath = COMPOSE_PATH; - const outputPath = join(basePath, appName, "code"); - const octokit = authGithub(githubProvider); - const token = await getGithubToken(octokit); - const repoclone = `github.com/${owner}/${repository}.git`; - await recreateDirectory(outputPath); - const cloneUrl = `https://oauth2:${token}@${repoclone}`; - try { - await spawnAsync("git", [ - "clone", - "--branch", - branch!, - "--depth", - "1", - cloneUrl, - outputPath, - "--progress", - ]); - } catch (error) { - throw error; - } -}; - -export const cloneRawGithubRepositoryRemote = async (compose: Compose) => { - const { appName, repository, owner, branch, githubId, serverId } = compose; - - if (!serverId) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Server not found", - }); - } - if (!githubId) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "GitHub Provider not found", - }); - } - - const { COMPOSE_PATH } = paths(true); - const githubProvider = await findGithubById(githubId); - const basePath = COMPOSE_PATH; - const outputPath = join(basePath, appName, "code"); - const octokit = authGithub(githubProvider); - const token = await getGithubToken(octokit); - const repoclone = `github.com/${owner}/${repository}.git`; - await recreateDirectory(outputPath); - const cloneUrl = `https://oauth2:${token}@${repoclone}`; - try { - await execAsyncRemote( - serverId, - `git clone --branch ${branch} --depth 1 ${cloneUrl} ${outputPath}`, - ); - } catch (error) { - throw error; - } -}; - -export const getGithubRepositories = async (githubId?: string) => { - if (!githubId) { - return []; - } - - const githubProvider = await findGithubById(githubId); - - const octokit = new Octokit({ - authStrategy: createAppAuth, - auth: { - appId: githubProvider.githubAppId, - privateKey: githubProvider.githubPrivateKey, - installationId: githubProvider.githubInstallationId, - }, - }); - - const repositories = (await octokit.paginate( - octokit.rest.apps.listReposAccessibleToInstallation, - )) as unknown as Awaited< - ReturnType - >["data"]["repositories"]; - - return repositories; -}; - -export const getGithubBranches = async ( - input: typeof apiFindGithubBranches._type, -) => { - if (!input.githubId) { - return []; - } - const githubProvider = await findGithubById(input.githubId); - - const octokit = new Octokit({ - authStrategy: createAppAuth, - auth: { - appId: githubProvider.githubAppId, - privateKey: githubProvider.githubPrivateKey, - installationId: githubProvider.githubInstallationId, - }, - }); - - const branches = (await octokit.paginate(octokit.rest.repos.listBranches, { - owner: input.owner, - repo: input.repo, - })) as unknown as Awaited< - ReturnType - >["data"]; - - return branches; -}; diff --git a/apps/dokploy/server/utils/providers/gitlab.ts b/apps/dokploy/server/utils/providers/gitlab.ts deleted file mode 100644 index 95649f5d..00000000 --- a/apps/dokploy/server/utils/providers/gitlab.ts +++ /dev/null @@ -1,447 +0,0 @@ -import { createWriteStream } from "node:fs"; -import { join } from "node:path"; -import type { Compose } from "@/server/api/services/compose"; -import { - type Gitlab, - findGitlabById, - updateGitlab, -} from "@/server/api/services/gitlab"; -import { paths } from "@/server/constants"; -import type { apiGitlabTestConnection } from "@/server/db/schema"; -import type { InferResultType } from "@/server/types/with"; -import { TRPCError } from "@trpc/server"; -import { recreateDirectory } from "../filesystem/directory"; -import { execAsyncRemote } from "../process/execAsync"; -import { spawnAsync } from "../process/spawnAsync"; - -export const refreshGitlabToken = async (gitlabProviderId: string) => { - const gitlabProvider = await findGitlabById(gitlabProviderId); - const currentTime = Math.floor(Date.now() / 1000); - - const safetyMargin = 60; - if ( - gitlabProvider.expiresAt && - currentTime + safetyMargin < gitlabProvider.expiresAt - ) { - return; - } - - const response = await fetch("https://gitlab.com/oauth/token", { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: new URLSearchParams({ - grant_type: "refresh_token", - refresh_token: gitlabProvider.refreshToken as string, - client_id: gitlabProvider.applicationId as string, - client_secret: gitlabProvider.secret as string, - }), - }); - - if (!response.ok) { - throw new Error(`Failed to refresh token: ${response.statusText}`); - } - - const data = await response.json(); - - const expiresAt = Math.floor(Date.now() / 1000) + data.expires_in; - - console.log("Refreshed token"); - - await updateGitlab(gitlabProviderId, { - accessToken: data.access_token, - refreshToken: data.refresh_token, - expiresAt, - }); - return data; -}; - -export const haveGitlabRequirements = (gitlabProvider: Gitlab) => { - return !!(gitlabProvider?.accessToken && gitlabProvider?.refreshToken); -}; - -const getErrorCloneRequirements = (entity: { - gitlabRepository?: string | null; - gitlabOwner?: string | null; - gitlabBranch?: string | null; - gitlabPathNamespace?: string | null; -}) => { - const reasons: string[] = []; - const { gitlabBranch, gitlabOwner, gitlabRepository, gitlabPathNamespace } = - entity; - - if (!gitlabRepository) reasons.push("1. Repository not assigned."); - if (!gitlabOwner) reasons.push("2. Owner not specified."); - if (!gitlabBranch) reasons.push("3. Branch not defined."); - if (!gitlabPathNamespace) reasons.push("4. Path namespace not defined."); - - return reasons; -}; - -export type ApplicationWithGitlab = InferResultType< - "applications", - { gitlab: true } ->; - -export type ComposeWithGitlab = InferResultType<"compose", { gitlab: true }>; - -export const cloneGitlabRepository = async ( - entity: ApplicationWithGitlab | ComposeWithGitlab, - logPath: string, - isCompose = false, -) => { - const writeStream = createWriteStream(logPath, { flags: "a" }); - const { appName, gitlabBranch, gitlabId, gitlab, gitlabPathNamespace } = - entity; - - if (!gitlabId) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Gitlab Provider not found", - }); - } - - await refreshGitlabToken(gitlabId); - - const requirements = getErrorCloneRequirements(entity); - - // Check if requirements are met - if (requirements.length > 0) { - writeStream.write( - `\nGitLab Repository configuration failed for application: ${appName}\n`, - ); - writeStream.write("Reasons:\n"); - writeStream.write(requirements.join("\n")); - writeStream.end(); - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error: GitLab repository information is incomplete.", - }); - } - - const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(); - const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH; - const outputPath = join(basePath, appName, "code"); - await recreateDirectory(outputPath); - const repoclone = `gitlab.com/${gitlabPathNamespace}.git`; - const cloneUrl = `https://oauth2:${gitlab?.accessToken}@${repoclone}`; - - try { - writeStream.write(`\nClonning Repo ${repoclone} to ${outputPath}: ✅\n`); - await spawnAsync( - "git", - [ - "clone", - "--branch", - gitlabBranch!, - "--depth", - "1", - cloneUrl, - outputPath, - "--progress", - ], - (data) => { - if (writeStream.writable) { - writeStream.write(data); - } - }, - ); - writeStream.write(`\nCloned ${repoclone}: ✅\n`); - } catch (error) { - writeStream.write(`ERROR Clonning: ${error}: ❌`); - throw error; - } finally { - writeStream.end(); - } -}; - -export const getGitlabCloneCommand = async ( - entity: ApplicationWithGitlab | ComposeWithGitlab, - logPath: string, - isCompose = false, -) => { - const { - appName, - gitlabRepository, - gitlabOwner, - gitlabPathNamespace, - gitlabBranch, - gitlabId, - serverId, - gitlab, - } = entity; - - if (!serverId) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Server not found", - }); - } - - if (!gitlabId) { - const command = ` - echo "Error: ❌ Gitlab Provider not found" >> ${logPath}; - exit 1; - `; - - await execAsyncRemote(serverId, command); - throw new TRPCError({ - code: "NOT_FOUND", - message: "Gitlab Provider not found", - }); - } - - const requirements = getErrorCloneRequirements(entity); - - // Build log messages - let logMessages = ""; - if (requirements.length > 0) { - logMessages += `\nGitLab Repository configuration failed for application: ${appName}\n`; - logMessages += "Reasons:\n"; - logMessages += requirements.join("\n"); - const escapedLogMessages = logMessages - .replace(/\\/g, "\\\\") - .replace(/"/g, '\\"') - .replace(/\n/g, "\\n"); - - const bashCommand = ` - echo "${escapedLogMessages}" >> ${logPath}; - exit 1; # Exit with error code - `; - - await execAsyncRemote(serverId, bashCommand); - return; - } - - const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(true); - await refreshGitlabToken(gitlabId); - const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH; - const outputPath = join(basePath, appName, "code"); - await recreateDirectory(outputPath); - const repoclone = `gitlab.com/${gitlabPathNamespace}.git`; - const cloneUrl = `https://oauth2:${gitlab?.accessToken}@${repoclone}`; - - const cloneCommand = ` -rm -rf ${outputPath}; -mkdir -p ${outputPath}; -if ! git clone --branch ${gitlabBranch} --depth 1 --progress ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then - echo "❌ [ERROR] Fail to clone the repository ${repoclone}" >> ${logPath}; - exit 1; -fi -echo "Cloned ${repoclone} to ${outputPath}: ✅" >> ${logPath}; - `; - - return cloneCommand; -}; - -export const getGitlabRepositories = async (gitlabId?: string) => { - if (!gitlabId) { - return []; - } - - await refreshGitlabToken(gitlabId); - - const gitlabProvider = await findGitlabById(gitlabId); - - const response = await fetch( - `https://gitlab.com/api/v4/projects?membership=true&owned=true&page=${0}&per_page=${100}`, - { - headers: { - Authorization: `Bearer ${gitlabProvider.accessToken}`, - }, - }, - ); - - if (!response.ok) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: `Failed to fetch repositories: ${response.statusText}`, - }); - } - - const repositories = await response.json(); - - const filteredRepos = repositories.filter((repo: any) => { - const { full_path, kind } = repo.namespace; - const groupName = gitlabProvider.groupName?.toLowerCase(); - - if (groupName) { - return full_path.toLowerCase().includes(groupName) && kind === "group"; - } - return kind === "user"; - }); - const mappedRepositories = filteredRepos.map((repo: any) => { - return { - id: repo.id, - name: repo.name, - url: repo.path_with_namespace, - owner: { - username: repo.namespace.path, - }, - }; - }); - - return mappedRepositories as { - id: number; - name: string; - url: string; - owner: { - username: string; - }; - }[]; -}; - -export const getGitlabBranches = async (input: { - id?: number; - gitlabId?: string; - owner: string; - repo: string; -}) => { - if (!input.gitlabId || !input.id || input.id === 0) { - return []; - } - - const gitlabProvider = await findGitlabById(input.gitlabId); - - const branchesResponse = await fetch( - `https://gitlab.com/api/v4/projects/${input.id}/repository/branches`, - { - headers: { - Authorization: `Bearer ${gitlabProvider.accessToken}`, - }, - }, - ); - - if (!branchesResponse.ok) { - throw new Error(`Failed to fetch branches: ${branchesResponse.statusText}`); - } - - const branches = await branchesResponse.json(); - - return branches as { - id: string; - name: string; - commit: { - id: string; - }; - }[]; -}; - -export const cloneRawGitlabRepository = async (entity: Compose) => { - const { - appName, - gitlabRepository, - gitlabOwner, - gitlabBranch, - gitlabId, - gitlabPathNamespace, - } = entity; - - if (!gitlabId) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Gitlab Provider not found", - }); - } - - const gitlabProvider = await findGitlabById(gitlabId); - const { COMPOSE_PATH } = paths(); - await refreshGitlabToken(gitlabId); - const basePath = COMPOSE_PATH; - const outputPath = join(basePath, appName, "code"); - await recreateDirectory(outputPath); - const repoclone = `gitlab.com/${gitlabPathNamespace}.git`; - const cloneUrl = `https://oauth2:${gitlabProvider?.accessToken}@${repoclone}`; - - try { - await spawnAsync("git", [ - "clone", - "--branch", - gitlabBranch!, - "--depth", - "1", - cloneUrl, - outputPath, - "--progress", - ]); - } catch (error) { - throw error; - } -}; - -export const cloneRawGitlabRepositoryRemote = async (compose: Compose) => { - const { appName, gitlabPathNamespace, branch, gitlabId, serverId } = compose; - - if (!serverId) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Server not found", - }); - } - if (!gitlabId) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Gitlab Provider not found", - }); - } - const gitlabProvider = await findGitlabById(gitlabId); - const { COMPOSE_PATH } = paths(true); - await refreshGitlabToken(gitlabId); - const basePath = COMPOSE_PATH; - const outputPath = join(basePath, appName, "code"); - await recreateDirectory(outputPath); - const repoclone = `gitlab.com/${gitlabPathNamespace}.git`; - const cloneUrl = `https://oauth2:${gitlabProvider?.accessToken}@${repoclone}`; - try { - await execAsyncRemote( - serverId, - `git clone --branch ${branch} --depth 1 ${cloneUrl} ${outputPath}`, - ); - } catch (error) { - throw error; - } -}; - -export const testGitlabConnection = async ( - input: typeof apiGitlabTestConnection._type, -) => { - const { gitlabId, groupName } = input; - - if (!gitlabId) { - throw new Error("Gitlab provider not found"); - } - - await refreshGitlabToken(gitlabId); - - const gitlabProvider = await findGitlabById(gitlabId); - - const response = await fetch( - `https://gitlab.com/api/v4/projects?membership=true&owned=true&page=${0}&per_page=${100}`, - { - headers: { - Authorization: `Bearer ${gitlabProvider.accessToken}`, - }, - }, - ); - - if (!response.ok) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: `Failed to fetch repositories: ${response.statusText}`, - }); - } - - const repositories = await response.json(); - - const filteredRepos = repositories.filter((repo: any) => { - const { full_path, kind } = repo.namespace; - - if (groupName) { - return full_path.toLowerCase().includes(groupName) && kind === "group"; - } - return kind === "user"; - }); - - return filteredRepos.length; -}; diff --git a/apps/dokploy/server/utils/providers/raw.ts b/apps/dokploy/server/utils/providers/raw.ts deleted file mode 100644 index f0fdcb5c..00000000 --- a/apps/dokploy/server/utils/providers/raw.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { createWriteStream } from "node:fs"; -import { writeFile } from "node:fs/promises"; -import { join } from "node:path"; -import type { Compose } from "@/server/api/services/compose"; -import { paths } from "@/server/constants"; -import { encodeBase64 } from "../docker/utils"; -import { recreateDirectory } from "../filesystem/directory"; -import { execAsyncRemote } from "../process/execAsync"; - -export const createComposeFile = async (compose: Compose, logPath: string) => { - const { COMPOSE_PATH } = paths(); - const { appName, composeFile } = compose; - const writeStream = createWriteStream(logPath, { flags: "a" }); - const outputPath = join(COMPOSE_PATH, appName, "code"); - - try { - await recreateDirectory(outputPath); - writeStream.write( - `\nCreating File 'docker-compose.yml' to ${outputPath}: ✅\n`, - ); - - await writeFile(join(outputPath, "docker-compose.yml"), composeFile); - - writeStream.write(`\nFile 'docker-compose.yml' created: ✅\n`); - } catch (error) { - writeStream.write(`\nERROR Creating Compose File: ${error}: ❌\n`); - throw error; - } finally { - writeStream.end(); - } -}; - -export const getCreateComposeFileCommand = ( - compose: Compose, - logPath: string, -) => { - const { COMPOSE_PATH } = paths(true); - const { appName, composeFile } = compose; - const outputPath = join(COMPOSE_PATH, appName, "code"); - const filePath = join(outputPath, "docker-compose.yml"); - const encodedContent = encodeBase64(composeFile); - const bashCommand = ` - rm -rf ${outputPath}; - mkdir -p ${outputPath}; - echo "${encodedContent}" | base64 -d > "${filePath}"; - echo "File 'docker-compose.yml' created: ✅" >> ${logPath}; - `; - return bashCommand; -}; - -export const createComposeFileRaw = async (compose: Compose) => { - const { COMPOSE_PATH } = paths(); - const { appName, composeFile } = compose; - const outputPath = join(COMPOSE_PATH, appName, "code"); - const filePath = join(outputPath, "docker-compose.yml"); - try { - await recreateDirectory(outputPath); - await writeFile(filePath, composeFile); - } catch (error) { - throw error; - } -}; - -export const createComposeFileRawRemote = async (compose: Compose) => { - const { COMPOSE_PATH } = paths(true); - const { appName, composeFile, serverId } = compose; - const outputPath = join(COMPOSE_PATH, appName, "code"); - const filePath = join(outputPath, "docker-compose.yml"); - - try { - const encodedContent = encodeBase64(composeFile); - const command = ` - mkdir -p ${outputPath}; - echo "${encodedContent}" | base64 -d > "${filePath}"; - `; - await execAsyncRemote(serverId, command); - } catch (error) { - throw error; - } -}; diff --git a/apps/dokploy/server/utils/servers/remote-docker.ts b/apps/dokploy/server/utils/servers/remote-docker.ts index f7704b68..b8bc4920 100644 --- a/apps/dokploy/server/utils/servers/remote-docker.ts +++ b/apps/dokploy/server/utils/servers/remote-docker.ts @@ -1,4 +1,4 @@ -import { findServerById } from "@/server/api/services/server"; +import { findServerById } from "@dokploy/builders"; import { docker } from "@/server/constants"; import Dockerode from "dockerode"; import { readSSHKey } from "../filesystem/ssh"; diff --git a/apps/dokploy/server/wss/docker-container-logs.ts b/apps/dokploy/server/wss/docker-container-logs.ts index a55951c5..f1e767d9 100644 --- a/apps/dokploy/server/wss/docker-container-logs.ts +++ b/apps/dokploy/server/wss/docker-container-logs.ts @@ -2,7 +2,7 @@ import type http from "node:http"; import { spawn } from "node-pty"; import { Client } from "ssh2"; import { WebSocketServer } from "ws"; -import { findServerById } from "../api/services/server"; +import { findServerById } from "@dokploy/builders"; import { validateWebSocketRequest } from "../auth/auth"; import { readSSHKey } from "../utils/filesystem/ssh"; import { getShell } from "./utils"; diff --git a/apps/dokploy/server/wss/docker-container-terminal.ts b/apps/dokploy/server/wss/docker-container-terminal.ts index a7b72892..4ecd7f48 100644 --- a/apps/dokploy/server/wss/docker-container-terminal.ts +++ b/apps/dokploy/server/wss/docker-container-terminal.ts @@ -2,7 +2,7 @@ import type http from "node:http"; import { spawn } from "node-pty"; import { Client } from "ssh2"; import { WebSocketServer } from "ws"; -import { findServerById } from "../api/services/server"; +import { findServerById } from "@dokploy/builders"; import { validateWebSocketRequest } from "../auth/auth"; import { readSSHKey } from "../utils/filesystem/ssh"; import { getShell } from "./utils"; diff --git a/apps/dokploy/server/wss/listen-deployment.ts b/apps/dokploy/server/wss/listen-deployment.ts index 1e38bcdf..9bcda985 100644 --- a/apps/dokploy/server/wss/listen-deployment.ts +++ b/apps/dokploy/server/wss/listen-deployment.ts @@ -2,7 +2,7 @@ import { spawn } from "node:child_process"; import type http from "node:http"; import { Client } from "ssh2"; import { WebSocketServer } from "ws"; -import { findServerById } from "../api/services/server"; +import { findServerById } from "@dokploy/builders"; import { validateWebSocketRequest } from "../auth/auth"; import { readSSHKey } from "../utils/filesystem/ssh"; diff --git a/apps/dokploy/server/wss/terminal.ts b/apps/dokploy/server/wss/terminal.ts index 4d8f03f6..e43415d4 100644 --- a/apps/dokploy/server/wss/terminal.ts +++ b/apps/dokploy/server/wss/terminal.ts @@ -3,7 +3,7 @@ import path from "node:path"; import { spawn } from "node-pty"; import { publicIpv4, publicIpv6 } from "public-ip"; import { WebSocketServer } from "ws"; -import { findServerById } from "../api/services/server"; +import { findServerById } from "@dokploy/builders"; import { validateWebSocketRequest } from "../auth/auth"; import { paths } from "../constants";