@@ -69,10 +77,11 @@ const Postgresql = (
{ name: "Projects", href: "/dashboard/projects" },
{
name: data?.environment?.project?.name || "",
+ href: `/dashboard/project/${projectId}/environment/${environmentId}`,
},
{
name: data?.environment?.name || "",
- href: `/dashboard/project/${projectId}/environment/${environmentId}`,
+ dropdownItems: environmentDropdownItems,
},
{
name: data?.name || "",
@@ -146,7 +155,9 @@ const Postgresql = (
diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/redis/[redisId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/redis/[redisId].tsx
index 14c873094..47eb82a74 100644
--- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/redis/[redisId].tsx
+++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/redis/[redisId].tsx
@@ -60,6 +60,14 @@ const Redis = (
const { data: auth } = api.user.get.useQuery();
const { data: isCloud } = api.settings.isCloud.useQuery();
+ const { data: environments } = api.environment.byProjectId.useQuery({
+ projectId: data?.environment?.projectId || "",
+ });
+ const environmentDropdownItems =
+ environments?.map((env) => ({
+ name: env.name,
+ href: `/dashboard/project/${projectId}/environment/${env.environmentId}`,
+ })) || [];
return (
@@ -69,10 +77,11 @@ const Redis = (
{ name: "Projects", href: "/dashboard/projects" },
{
name: data?.environment?.project?.name || "",
+ href: `/dashboard/project/${projectId}/environment/${environmentId}`,
},
{
name: data?.environment?.name || "",
- href: `/dashboard/project/${projectId}/environment/${environmentId}`,
+ dropdownItems: environmentDropdownItems,
},
{
name: data?.name || "",
@@ -146,7 +155,9 @@ const Redis = (
- {(auth?.role === "owner" || auth?.canDeleteServices) && (
+ {(auth?.role === "owner" ||
+ auth?.role === "admin" ||
+ auth?.canDeleteServices) && (
)}
diff --git a/apps/dokploy/pages/dashboard/settings/invoices.tsx b/apps/dokploy/pages/dashboard/settings/invoices.tsx
new file mode 100644
index 000000000..a37c3607c
--- /dev/null
+++ b/apps/dokploy/pages/dashboard/settings/invoices.tsx
@@ -0,0 +1,63 @@
+import { IS_CLOUD } from "@dokploy/server/constants";
+import { validateRequest } from "@dokploy/server/lib/auth";
+import { createServerSideHelpers } from "@trpc/react-query/server";
+import type { GetServerSidePropsContext } from "next";
+import type { ReactElement } from "react";
+import superjson from "superjson";
+import { ShowBillingInvoices } from "@/components/dashboard/settings/billing/show-billing-invoices";
+import { DashboardLayout } from "@/components/layouts/dashboard-layout";
+import { appRouter } from "@/server/api/root";
+
+const Page = () => {
+ return
;
+};
+
+export default Page;
+
+Page.getLayout = (page: ReactElement) => {
+ return
{page};
+};
+export async function getServerSideProps(
+ ctx: GetServerSidePropsContext<{ serviceId: string }>,
+) {
+ if (!IS_CLOUD) {
+ return {
+ redirect: {
+ permanent: true,
+ destination: "/dashboard/projects",
+ },
+ };
+ }
+ const { req, res } = ctx;
+ const { user, session } = await validateRequest(req);
+ if (!user || user.role !== "owner") {
+ return {
+ redirect: {
+ permanent: true,
+ destination: "/",
+ },
+ };
+ }
+
+ const helpers = createServerSideHelpers({
+ router: appRouter,
+ ctx: {
+ req: req as any,
+ res: res as any,
+ db: null as any,
+ session: session as any,
+ user: user as any,
+ },
+ transformer: superjson,
+ });
+
+ await helpers.user.get.prefetch();
+
+ await helpers.settings.isCloud.prefetch();
+
+ return {
+ props: {
+ trpcState: helpers.dehydrate(),
+ },
+ };
+}
diff --git a/apps/dokploy/proprietary/README.md b/apps/dokploy/proprietary/README.md
new file mode 100644
index 000000000..b1af288e6
--- /dev/null
+++ b/apps/dokploy/proprietary/README.md
@@ -0,0 +1,18 @@
+# Proprietary Features
+
+This directory contains all proprietary functionality of Dokploy.
+
+## Purpose
+
+This folder will house all **paid features** and premium functionality that are not part of the open source code.
+
+## License
+
+The code in this directory is under Dokploy's proprietary license. See [LICENSE_PROPRIETARY.md](../../../LICENSE_PROPRIETARY.md) for more details.
+
+## Contact
+
+If you want to learn more about our paid features or have any questions, please contact us at:
+
+- Email: [sales@dokploy.com](mailto:sales@dokploy.com)
+- Contact Form: [https://dokploy.com/contact](https://dokploy.com/contact)
diff --git a/apps/dokploy/server/api/routers/admin.ts b/apps/dokploy/server/api/routers/admin.ts
index 6a3be31b4..4323d9e47 100644
--- a/apps/dokploy/server/api/routers/admin.ts
+++ b/apps/dokploy/server/api/routers/admin.ts
@@ -1,8 +1,8 @@
import {
- findUserById,
+ getWebServerSettings,
IS_CLOUD,
setupWebMonitoring,
- updateUser,
+ updateWebServerSettings,
} from "@dokploy/server";
import { TRPCError } from "@trpc/server";
import { apiUpdateWebServerMonitoring } from "@/server/db/schema";
@@ -11,7 +11,7 @@ import { adminProcedure, createTRPCRouter } from "../trpc";
export const adminRouter = createTRPCRouter({
setupMonitoring: adminProcedure
.input(apiUpdateWebServerMonitoring)
- .mutation(async ({ input, ctx }) => {
+ .mutation(async ({ input }) => {
try {
if (IS_CLOUD) {
throw new TRPCError({
@@ -19,15 +19,8 @@ export const adminRouter = createTRPCRouter({
message: "Feature disabled on cloud",
});
}
- const user = await findUserById(ctx.user.ownerId);
- if (user.id !== ctx.user.ownerId) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "You are not authorized to setup the monitoring",
- });
- }
- await updateUser(user.id, {
+ await updateWebServerSettings({
metricsConfig: {
server: {
type: "Dokploy",
@@ -52,8 +45,9 @@ export const adminRouter = createTRPCRouter({
},
});
- const currentServer = await setupWebMonitoring(user.id);
- return currentServer;
+ await setupWebMonitoring();
+ const settings = await getWebServerSettings();
+ return settings;
} catch (error) {
throw error;
}
diff --git a/apps/dokploy/server/api/routers/ai.ts b/apps/dokploy/server/api/routers/ai.ts
index e03da905d..ff2d1ee8a 100644
--- a/apps/dokploy/server/api/routers/ai.ts
+++ b/apps/dokploy/server/api/routers/ai.ts
@@ -68,6 +68,40 @@ export const aiRouter = createTRPCRouter({
{ headers: {} },
);
break;
+ case "perplexity":
+ // Perplexity doesn't have a /models endpoint, return hardcoded list
+ return [
+ {
+ id: "sonar-deep-research",
+ object: "model",
+ created: Date.now(),
+ owned_by: "perplexity",
+ },
+ {
+ id: "sonar-reasoning-pro",
+ object: "model",
+ created: Date.now(),
+ owned_by: "perplexity",
+ },
+ {
+ id: "sonar-reasoning",
+ object: "model",
+ created: Date.now(),
+ owned_by: "perplexity",
+ },
+ {
+ id: "sonar-pro",
+ object: "model",
+ created: Date.now(),
+ owned_by: "perplexity",
+ },
+ {
+ id: "sonar",
+ object: "model",
+ created: Date.now(),
+ owned_by: "perplexity",
+ },
+ ] as Model[];
default:
if (!input.apiKey)
throw new TRPCError({
diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts
index 4149c79f0..b494fdf36 100644
--- a/apps/dokploy/server/api/routers/application.ts
+++ b/apps/dokploy/server/api/routers/application.ts
@@ -336,7 +336,9 @@ export const applicationRouter = createTRPCRouter({
if (IS_CLOUD && application.serverId) {
jobData.serverId = application.serverId;
- await deploy(jobData);
+ deploy(jobData).catch((error) => {
+ console.error("Background deployment failed:", error);
+ });
return true;
}
await myQueue.add(
@@ -467,6 +469,7 @@ export const applicationRouter = createTRPCRouter({
}
await updateApplication(input.applicationId, {
bitbucketRepository: input.bitbucketRepository,
+ bitbucketRepositorySlug: input.bitbucketRepositorySlug,
bitbucketOwner: input.bitbucketOwner,
bitbucketBranch: input.bitbucketBranch,
bitbucketBuildPath: input.bitbucketBuildPath,
@@ -701,7 +704,9 @@ export const applicationRouter = createTRPCRouter({
};
if (IS_CLOUD && application.serverId) {
jobData.serverId = application.serverId;
- await deploy(jobData);
+ deploy(jobData).catch((error) => {
+ console.error("Background deployment failed:", error);
+ });
return true;
}
@@ -813,7 +818,9 @@ export const applicationRouter = createTRPCRouter({
};
if (IS_CLOUD && app.serverId) {
jobData.serverId = app.serverId;
- await deploy(jobData);
+ deploy(jobData).catch((error) => {
+ console.error("Background deployment failed:", error);
+ });
return true;
}
diff --git a/apps/dokploy/server/api/routers/backup.ts b/apps/dokploy/server/api/routers/backup.ts
index 68067f9df..600fa5f51 100644
--- a/apps/dokploy/server/api/routers/backup.ts
+++ b/apps/dokploy/server/api/routers/backup.ts
@@ -285,6 +285,7 @@ export const backupRouter = createTRPCRouter({
.mutation(async ({ input }) => {
const backup = await findBackupById(input.backupId);
await runWebServerBackup(backup);
+ await keepLatestNBackups(backup);
return true;
}),
listBackupFiles: protectedProcedure
diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts
index e233dc6ca..9354988a8 100644
--- a/apps/dokploy/server/api/routers/compose.ts
+++ b/apps/dokploy/server/api/routers/compose.ts
@@ -17,8 +17,8 @@ import {
findGitProviderById,
findProjectById,
findServerById,
- findUserById,
getComposeContainer,
+ getWebServerSettings,
IS_CLOUD,
loadServices,
randomizeComposeFile,
@@ -417,7 +417,9 @@ export const composeRouter = createTRPCRouter({
if (IS_CLOUD && compose.serverId) {
jobData.serverId = compose.serverId;
- await deploy(jobData);
+ deploy(jobData).catch((error) => {
+ console.error("Background deployment failed:", error);
+ });
return true;
}
await myQueue.add(
@@ -428,7 +430,11 @@ export const composeRouter = createTRPCRouter({
removeOnFail: true,
},
);
- return { success: true, message: "Deployment queued" };
+ return {
+ success: true,
+ message: "Deployment queued",
+ composeId: compose.composeId,
+ };
}),
redeploy: protectedProcedure
.input(apiRedeployCompose)
@@ -453,7 +459,9 @@ export const composeRouter = createTRPCRouter({
};
if (IS_CLOUD && compose.serverId) {
jobData.serverId = compose.serverId;
- await deploy(jobData);
+ deploy(jobData).catch((error) => {
+ console.error("Background deployment failed:", error);
+ });
return true;
}
await myQueue.add(
@@ -464,7 +472,11 @@ export const composeRouter = createTRPCRouter({
removeOnFail: true,
},
);
- return { success: true, message: "Redeployment queued" };
+ return {
+ success: true,
+ message: "Redeployment queued",
+ composeId: compose.composeId,
+ };
}),
stop: protectedProcedure
.input(apiFindCompose)
@@ -565,8 +577,7 @@ export const composeRouter = createTRPCRouter({
const template = await fetchTemplateFiles(input.id, input.baseUrl);
- const admin = await findUserById(ctx.user.ownerId);
- let serverIp = admin.serverIp || "127.0.0.1";
+ let serverIp = "127.0.0.1";
const project = await findProjectById(environment.projectId);
@@ -575,6 +586,9 @@ export const composeRouter = createTRPCRouter({
serverIp = server.ipAddress;
} else if (process.env.NODE_ENV === "development") {
serverIp = "127.0.0.1";
+ } else {
+ const settings = await getWebServerSettings();
+ serverIp = settings?.serverIp || "127.0.0.1";
}
const projectName = slugify(`${project.name} ${input.id}`);
@@ -799,14 +813,16 @@ export const composeRouter = createTRPCRouter({
const decodedData = Buffer.from(input.base64, "base64").toString(
"utf-8",
);
- const admin = await findUserById(ctx.user.ownerId);
- let serverIp = admin.serverIp || "127.0.0.1";
+ let serverIp = "127.0.0.1";
if (compose.serverId) {
const server = await findServerById(compose.serverId);
serverIp = server.ipAddress;
} else if (process.env.NODE_ENV === "development") {
serverIp = "127.0.0.1";
+ } else {
+ const settings = await getWebServerSettings();
+ serverIp = settings?.serverIp || "127.0.0.1";
}
const templateData = JSON.parse(decodedData);
const config = parse(templateData.config) as CompleteTemplate;
@@ -876,14 +892,16 @@ export const composeRouter = createTRPCRouter({
await removeDomainById(domain.domainId);
}
- const admin = await findUserById(ctx.user.ownerId);
- let serverIp = admin.serverIp || "127.0.0.1";
+ let serverIp = "127.0.0.1";
if (compose.serverId) {
const server = await findServerById(compose.serverId);
serverIp = server.ipAddress;
} else if (process.env.NODE_ENV === "development") {
serverIp = "127.0.0.1";
+ } else {
+ const settings = await getWebServerSettings();
+ serverIp = settings?.serverIp || "127.0.0.1";
}
const templateData = JSON.parse(decodedData);
diff --git a/apps/dokploy/server/api/routers/domain.ts b/apps/dokploy/server/api/routers/domain.ts
index 1f6264351..5767a38a9 100644
--- a/apps/dokploy/server/api/routers/domain.ts
+++ b/apps/dokploy/server/api/routers/domain.ts
@@ -9,6 +9,7 @@ import {
findPreviewDeploymentById,
findServerById,
generateTraefikMeDomain,
+ getWebServerSettings,
manageDomain,
removeDomain,
removeDomainById,
@@ -107,16 +108,13 @@ export const domainRouter = createTRPCRouter({
}),
canGenerateTraefikMeDomains: protectedProcedure
.input(z.object({ serverId: z.string() }))
- .query(async ({ input, ctx }) => {
- const organization = await findOrganizationById(
- ctx.session.activeOrganizationId,
- );
-
+ .query(async ({ input }) => {
if (input.serverId) {
const server = await findServerById(input.serverId);
return server.ipAddress;
}
- return organization?.owner.serverIp;
+ const settings = await getWebServerSettings();
+ return settings?.serverIp || "";
}),
update: protectedProcedure
diff --git a/apps/dokploy/server/api/routers/mariadb.ts b/apps/dokploy/server/api/routers/mariadb.ts
index 18950b7a3..7d4bd2e50 100644
--- a/apps/dokploy/server/api/routers/mariadb.ts
+++ b/apps/dokploy/server/api/routers/mariadb.ts
@@ -87,7 +87,7 @@ export const mariadbRouter = createTRPCRouter({
type: "volume",
});
- return true;
+ return newMariadb;
} catch (error) {
if (error instanceof TRPCError) {
throw error;
diff --git a/apps/dokploy/server/api/routers/mongo.ts b/apps/dokploy/server/api/routers/mongo.ts
index 51b830fc8..ae0fa4741 100644
--- a/apps/dokploy/server/api/routers/mongo.ts
+++ b/apps/dokploy/server/api/routers/mongo.ts
@@ -87,7 +87,7 @@ export const mongoRouter = createTRPCRouter({
type: "volume",
});
- return true;
+ return newMongo;
} catch (error) {
if (error instanceof TRPCError) {
throw error;
diff --git a/apps/dokploy/server/api/routers/mysql.ts b/apps/dokploy/server/api/routers/mysql.ts
index 5edb27da4..5204fedc8 100644
--- a/apps/dokploy/server/api/routers/mysql.ts
+++ b/apps/dokploy/server/api/routers/mysql.ts
@@ -89,7 +89,7 @@ export const mysqlRouter = createTRPCRouter({
type: "volume",
});
- return true;
+ return newMysql;
} catch (error) {
if (error instanceof TRPCError) {
throw error;
diff --git a/apps/dokploy/server/api/routers/notification.ts b/apps/dokploy/server/api/routers/notification.ts
index b32278465..c22ce7aa5 100644
--- a/apps/dokploy/server/api/routers/notification.ts
+++ b/apps/dokploy/server/api/routers/notification.ts
@@ -5,9 +5,11 @@ import {
createGotifyNotification,
createLarkNotification,
createNtfyNotification,
+ createPushoverNotification,
createSlackNotification,
createTelegramNotification,
findNotificationById,
+ getWebServerSettings,
IS_CLOUD,
removeNotificationById,
sendCustomNotification,
@@ -16,6 +18,7 @@ import {
sendGotifyNotification,
sendLarkNotification,
sendNtfyNotification,
+ sendPushoverNotification,
sendServerThresholdNotifications,
sendSlackNotification,
sendTelegramNotification,
@@ -25,6 +28,7 @@ import {
updateGotifyNotification,
updateLarkNotification,
updateNtfyNotification,
+ updatePushoverNotification,
updateSlackNotification,
updateTelegramNotification,
} from "@dokploy/server";
@@ -45,6 +49,7 @@ import {
apiCreateGotify,
apiCreateLark,
apiCreateNtfy,
+ apiCreatePushover,
apiCreateSlack,
apiCreateTelegram,
apiFindOneNotification,
@@ -54,6 +59,7 @@ import {
apiTestGotifyConnection,
apiTestLarkConnection,
apiTestNtfyConnection,
+ apiTestPushoverConnection,
apiTestSlackConnection,
apiTestTelegramConnection,
apiUpdateCustom,
@@ -62,11 +68,11 @@ import {
apiUpdateGotify,
apiUpdateLark,
apiUpdateNtfy,
+ apiUpdatePushover,
apiUpdateSlack,
apiUpdateTelegram,
notifications,
server,
- user,
} from "@/server/db/schema";
export const notificationRouter = createTRPCRouter({
@@ -342,6 +348,7 @@ export const notificationRouter = createTRPCRouter({
ntfy: true,
custom: true,
lark: true,
+ pushover: true,
},
orderBy: desc(notifications.createdAt),
where: eq(notifications.organizationId, ctx.session.activeOrganizationId),
@@ -364,21 +371,20 @@ export const notificationRouter = createTRPCRouter({
let organizationId = "";
let ServerName = "";
if (input.ServerType === "Dokploy") {
- const result = await db
- .select()
- .from(user)
- .where(
- sql`${user.metricsConfig}::jsonb -> 'server' ->> 'token' = ${input.Token}`,
- );
-
- if (!result?.[0]?.id) {
+ const settings = await getWebServerSettings();
+ if (
+ !settings?.metricsConfig?.server?.token ||
+ settings.metricsConfig.server.token !== input.Token
+ ) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Token not found",
});
}
- organizationId = result?.[0]?.id;
+ // For Dokploy server type, we don't have a specific organizationId
+ // This might need to be adjusted based on your business logic
+ organizationId = "";
ServerName = "Dokploy";
} else {
const result = await db
@@ -635,6 +641,62 @@ export const notificationRouter = createTRPCRouter({
});
}
}),
+ createPushover: adminProcedure
+ .input(apiCreatePushover)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ return await createPushoverNotification(
+ input,
+ ctx.session.activeOrganizationId,
+ );
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error creating the notification",
+ cause: error,
+ });
+ }
+ }),
+ updatePushover: adminProcedure
+ .input(apiUpdatePushover)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ const notification = await findNotificationById(input.notificationId);
+ if (
+ IS_CLOUD &&
+ notification.organizationId !== ctx.session.activeOrganizationId
+ ) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to update this notification",
+ });
+ }
+ return await updatePushoverNotification({
+ ...input,
+ organizationId: ctx.session.activeOrganizationId,
+ });
+ } catch (error) {
+ throw error;
+ }
+ }),
+ testPushoverConnection: adminProcedure
+ .input(apiTestPushoverConnection)
+ .mutation(async ({ input }) => {
+ try {
+ await sendPushoverNotification(
+ input,
+ "Test Notification",
+ "Hi, From Dokploy 👋",
+ );
+ return true;
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error testing the notification",
+ cause: error,
+ });
+ }
+ }),
getEmailProviders: adminProcedure.query(async ({ ctx }) => {
return await db.query.notifications.findMany({
where: eq(notifications.organizationId, ctx.session.activeOrganizationId),
diff --git a/apps/dokploy/server/api/routers/postgres.ts b/apps/dokploy/server/api/routers/postgres.ts
index 3112beb66..e1718bff1 100644
--- a/apps/dokploy/server/api/routers/postgres.ts
+++ b/apps/dokploy/server/api/routers/postgres.ts
@@ -91,7 +91,7 @@ export const postgresRouter = createTRPCRouter({
type: "volume",
});
- return true;
+ return newPostgres;
} catch (error) {
if (error instanceof TRPCError) {
throw error;
diff --git a/apps/dokploy/server/api/routers/preview-deployment.ts b/apps/dokploy/server/api/routers/preview-deployment.ts
index 49b781101..0c325a9c6 100644
--- a/apps/dokploy/server/api/routers/preview-deployment.ts
+++ b/apps/dokploy/server/api/routers/preview-deployment.ts
@@ -2,11 +2,15 @@ import {
findApplicationById,
findPreviewDeploymentById,
findPreviewDeploymentsByApplicationId,
+ IS_CLOUD,
removePreviewDeployment,
} from "@dokploy/server";
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { apiFindAllByApplication } from "@/server/db/schema";
+import type { DeploymentJob } from "@/server/queues/queue-types";
+import { myQueue } from "@/server/queues/queueSetup";
+import { deploy } from "@/server/utils/deploy";
import { createTRPCRouter, protectedProcedure } from "../trpc";
export const previewDeploymentRouter = createTRPCRouter({
@@ -60,4 +64,55 @@ export const previewDeploymentRouter = createTRPCRouter({
}
return previewDeployment;
}),
+ redeploy: protectedProcedure
+ .input(
+ z.object({
+ previewDeploymentId: z.string(),
+ title: z.string().optional(),
+ description: z.string().optional(),
+ }),
+ )
+ .mutation(async ({ input, ctx }) => {
+ const previewDeployment = await findPreviewDeploymentById(
+ input.previewDeploymentId,
+ );
+ if (
+ previewDeployment.application.environment.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to redeploy this preview deployment",
+ });
+ }
+ const application = await findApplicationById(
+ previewDeployment.applicationId,
+ );
+ const jobData: DeploymentJob = {
+ applicationId: previewDeployment.applicationId,
+ titleLog: input.title || "Rebuild Preview Deployment",
+ descriptionLog: input.description || "",
+ type: "redeploy",
+ applicationType: "application-preview",
+ previewDeploymentId: input.previewDeploymentId,
+ server: !!application.serverId,
+ };
+
+ if (IS_CLOUD && application.serverId) {
+ jobData.serverId = application.serverId;
+ deploy(jobData).catch((error) => {
+ console.error("Background deployment failed:", error);
+ });
+ return true;
+ }
+ await myQueue.add(
+ "deployments",
+ { ...jobData },
+ {
+ removeOnComplete: true,
+ removeOnFail: true,
+ },
+ );
+ return true;
+ }),
});
diff --git a/apps/dokploy/server/api/routers/registry.ts b/apps/dokploy/server/api/routers/registry.ts
index 082fbafff..14155edfc 100644
--- a/apps/dokploy/server/api/routers/registry.ts
+++ b/apps/dokploy/server/api/routers/registry.ts
@@ -15,6 +15,7 @@ import {
apiFindOneRegistry,
apiRemoveRegistry,
apiTestRegistry,
+ apiTestRegistryById,
apiUpdateRegistry,
registry,
} from "@/server/db/schema";
@@ -109,6 +110,67 @@ export const registryRouter = createTRPCRouter({
});
}
+ return true;
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message:
+ error instanceof Error
+ ? error.message
+ : "Error testing the registry",
+ cause: error,
+ });
+ }
+ }),
+ testRegistryById: protectedProcedure
+ .input(apiTestRegistryById)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ // Get the full registry with password from database
+ const registryData = await db.query.registry.findFirst({
+ where: eq(registry.registryId, input.registryId ?? ""),
+ });
+
+ if (!registryData) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Registry not found",
+ });
+ }
+
+ if (registryData.organizationId !== ctx.session.activeOrganizationId) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not allowed to test this registry",
+ });
+ }
+
+ const args = [
+ "login",
+ registryData.registryUrl,
+ "--username",
+ registryData.username,
+ "--password-stdin",
+ ];
+
+ if (IS_CLOUD && !input.serverId) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Select a server to test the registry",
+ });
+ }
+
+ if (input.serverId && input.serverId !== "none") {
+ await execAsyncRemote(
+ input.serverId,
+ `echo ${registryData.password} | docker ${args.join(" ")}`,
+ );
+ } else {
+ await execFileAsync("docker", args, {
+ input: Buffer.from(registryData.password).toString(),
+ });
+ }
+
return true;
} catch (error) {
throw new TRPCError({
diff --git a/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts
index 00584bf2d..c9d21e515 100644
--- a/apps/dokploy/server/api/routers/settings.ts
+++ b/apps/dokploy/server/api/routers/settings.ts
@@ -3,6 +3,7 @@ import {
checkGPUStatus,
checkPortInUse,
cleanupAll,
+ cleanupAllBackground,
cleanupBuilders,
cleanupContainers,
cleanupImages,
@@ -11,11 +12,11 @@ import {
DEFAULT_UPDATE_DATA,
execAsync,
findServerById,
- findUserById,
getDokployImage,
getDokployImageTag,
getLogCleanupStatus,
getUpdateData,
+ getWebServerSettings,
IS_CLOUD,
parseRawConfig,
paths,
@@ -39,7 +40,7 @@ import {
updateLetsEncryptEmail,
updateServerById,
updateServerTraefik,
- updateUser,
+ updateWebServerSettings,
writeConfig,
writeMainConfig,
writeTraefikConfigInPath,
@@ -76,11 +77,18 @@ import {
} from "../trpc";
export const settingsRouter = createTRPCRouter({
+ getWebServerSettings: protectedProcedure.query(async () => {
+ if (IS_CLOUD) {
+ return null;
+ }
+ const settings = await getWebServerSettings();
+ return settings;
+ }),
reloadServer: adminProcedure.mutation(async () => {
if (IS_CLOUD) {
return true;
}
- await reloadDockerResource("dokploy");
+ await reloadDockerResource("dokploy", undefined, packageInfo.version);
return true;
}),
cleanRedis: adminProcedure.mutation(async () => {
@@ -193,9 +201,10 @@ export const settingsRouter = createTRPCRouter({
cleanAll: adminProcedure
.input(apiServerSchema)
.mutation(async ({ input }) => {
- await cleanupAll(input?.serverId);
+ // Execute cleanup in background and return immediately to avoid gateway timeouts
+ const result = await cleanupAllBackground(input?.serverId);
- return true;
+ return result;
}),
cleanMonitoring: adminProcedure.mutation(async () => {
if (IS_CLOUD) {
@@ -207,11 +216,11 @@ export const settingsRouter = createTRPCRouter({
}),
saveSSHPrivateKey: adminProcedure
.input(apiSaveSSHKey)
- .mutation(async ({ input, ctx }) => {
+ .mutation(async ({ input }) => {
if (IS_CLOUD) {
return true;
}
- await updateUser(ctx.user.ownerId, {
+ await updateWebServerSettings({
sshPrivateKey: input.sshPrivateKey,
});
@@ -219,36 +228,36 @@ export const settingsRouter = createTRPCRouter({
}),
assignDomainServer: adminProcedure
.input(apiAssignDomain)
- .mutation(async ({ ctx, input }) => {
+ .mutation(async ({ input }) => {
if (IS_CLOUD) {
return true;
}
- const user = await updateUser(ctx.user.ownerId, {
+ const settings = await updateWebServerSettings({
host: input.host,
letsEncryptEmail: input.letsEncryptEmail,
certificateType: input.certificateType,
https: input.https,
});
- if (!user) {
+ if (!settings) {
throw new TRPCError({
code: "NOT_FOUND",
- message: "User not found",
+ message: "Web server settings not found",
});
}
- updateServerTraefik(user, input.host);
+ updateServerTraefik(settings, input.host);
if (input.letsEncryptEmail) {
updateLetsEncryptEmail(input.letsEncryptEmail);
}
- return user;
+ return settings;
}),
- cleanSSHPrivateKey: adminProcedure.mutation(async ({ ctx }) => {
+ cleanSSHPrivateKey: adminProcedure.mutation(async () => {
if (IS_CLOUD) {
return true;
}
- await updateUser(ctx.user.ownerId, {
+ await updateWebServerSettings({
sshPrivateKey: null,
});
return true;
@@ -308,11 +317,11 @@ export const settingsRouter = createTRPCRouter({
}
}
} else if (!IS_CLOUD) {
- const userUpdated = await updateUser(ctx.user.ownerId, {
+ const settingsUpdated = await updateWebServerSettings({
enableDockerCleanup: input.enableDockerCleanup,
});
- if (userUpdated?.enableDockerCleanup) {
+ if (settingsUpdated?.enableDockerCleanup) {
scheduleJob("docker-cleanup", "0 0 * * *", async () => {
console.log(
`Docker Cleanup ${new Date().toLocaleString()}] Running...`,
@@ -390,7 +399,7 @@ export const settingsRouter = createTRPCRouter({
return DEFAULT_UPDATE_DATA;
}
- return await getUpdateData();
+ return await getUpdateData(packageInfo.version);
}),
updateServer: adminProcedure.mutation(async () => {
if (IS_CLOUD) {
@@ -486,13 +495,28 @@ export const settingsRouter = createTRPCRouter({
return readConfigInPath(input.path, input.serverId);
}),
- getIp: protectedProcedure.query(async ({ ctx }) => {
+ getIp: protectedProcedure.query(async () => {
if (IS_CLOUD) {
- return true;
+ return "";
}
- const user = await findUserById(ctx.user.ownerId);
- return user.serverIp;
+ const settings = await getWebServerSettings();
+ return settings?.serverIp || "";
}),
+ updateServerIp: adminProcedure
+ .input(
+ z.object({
+ serverIp: z.string(),
+ }),
+ )
+ .mutation(async ({ input }) => {
+ if (IS_CLOUD) {
+ return true;
+ }
+ const settings = await updateWebServerSettings({
+ serverIp: input.serverIp,
+ });
+ return settings;
+ }),
getOpenApiDocument: protectedProcedure.query(
async ({ ctx }): Promise
=> {
diff --git a/apps/dokploy/server/api/routers/stripe.ts b/apps/dokploy/server/api/routers/stripe.ts
index d2a000324..be1e94d4a 100644
--- a/apps/dokploy/server/api/routers/stripe.ts
+++ b/apps/dokploy/server/api/routers/stripe.ts
@@ -75,9 +75,9 @@ export const stripeRouter = createTRPCRouter({
const session = await stripe.checkout.sessions.create({
mode: "subscription",
line_items: items,
- ...(stripeCustomerId && {
- customer: stripeCustomerId,
- }),
+ ...(stripeCustomerId
+ ? { customer: stripeCustomerId }
+ : { customer_email: owner.email }),
metadata: {
adminId: owner.id,
},
@@ -128,4 +128,39 @@ export const stripeRouter = createTRPCRouter({
return servers.length < user.serversQuantity;
}),
+
+ getInvoices: adminProcedure.query(async ({ ctx }) => {
+ const user = await findUserById(ctx.user.ownerId);
+ const stripeCustomerId = user.stripeCustomerId;
+
+ if (!stripeCustomerId) {
+ return [];
+ }
+
+ const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
+ apiVersion: "2024-09-30.acacia",
+ });
+
+ try {
+ const invoices = await stripe.invoices.list({
+ customer: stripeCustomerId,
+ limit: 100,
+ });
+
+ return invoices.data.map((invoice) => ({
+ id: invoice.id,
+ number: invoice.number,
+ status: invoice.status,
+ amountDue: invoice.amount_due,
+ amountPaid: invoice.amount_paid,
+ currency: invoice.currency,
+ created: invoice.created,
+ dueDate: invoice.due_date,
+ hostedInvoiceUrl: invoice.hosted_invoice_url,
+ invoicePdf: invoice.invoice_pdf,
+ }));
+ } catch (_) {
+ return [];
+ }
+ }),
});
diff --git a/apps/dokploy/server/api/routers/user.ts b/apps/dokploy/server/api/routers/user.ts
index a6bd81a01..e801a5adb 100644
--- a/apps/dokploy/server/api/routers/user.ts
+++ b/apps/dokploy/server/api/routers/user.ts
@@ -5,6 +5,7 @@ import {
findUserById,
getDokployUrl,
getUserByToken,
+ getWebServerSettings,
IS_CLOUD,
removeUserById,
sendEmailNotification,
@@ -214,10 +215,11 @@ export const userRouter = createTRPCRouter({
}),
getMetricsToken: protectedProcedure.query(async ({ ctx }) => {
const user = await findUserById(ctx.user.ownerId);
+ const settings = await getWebServerSettings();
return {
- serverIp: user.serverIp,
+ serverIp: settings?.serverIp,
enabledFeatures: user.enablePaidFeatures,
- metricsConfig: user?.metricsConfig,
+ metricsConfig: settings?.metricsConfig,
};
}),
remove: protectedProcedure
diff --git a/apps/dokploy/server/db/drizzle.config.ts b/apps/dokploy/server/db/drizzle.config.ts
index 60a3bb937..8f6a4a60a 100644
--- a/apps/dokploy/server/db/drizzle.config.ts
+++ b/apps/dokploy/server/db/drizzle.config.ts
@@ -1,10 +1,11 @@
+import { dbUrl } from "@dokploy/server/db";
import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./server/db/schema/index.ts",
dialect: "postgresql",
dbCredentials: {
- url: process.env.DATABASE_URL!,
+ url: dbUrl,
},
out: "drizzle",
migrations: {
diff --git a/apps/dokploy/server/db/index.ts b/apps/dokploy/server/db/index.ts
index 55d6d3a46..2112c4f67 100644
--- a/apps/dokploy/server/db/index.ts
+++ b/apps/dokploy/server/db/index.ts
@@ -1,3 +1,4 @@
+import { dbUrl } from "@dokploy/server/db/constants";
import { drizzle, type PostgresJsDatabase } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import * as schema from "./schema";
@@ -6,10 +7,6 @@ declare global {
var db: PostgresJsDatabase | undefined;
}
-const dbUrl =
- process.env.DATABASE_URL ||
- "postgres://dokploy:amukds4wi9001583845717ad2@dokploy-postgres:5432/dokploy";
-
export let db: PostgresJsDatabase;
if (process.env.NODE_ENV === "production") {
db = drizzle(postgres(dbUrl!), {
diff --git a/apps/dokploy/server/db/migration.ts b/apps/dokploy/server/db/migration.ts
index fa2e1a80f..8a24afdc5 100644
--- a/apps/dokploy/server/db/migration.ts
+++ b/apps/dokploy/server/db/migration.ts
@@ -1,10 +1,9 @@
+import { dbUrl } from "@dokploy/server/db";
import { drizzle } from "drizzle-orm/postgres-js";
import { migrate } from "drizzle-orm/postgres-js/migrator";
import postgres from "postgres";
-const connectionString = process.env.DATABASE_URL!;
-
-const sql = postgres(connectionString, { max: 1 });
+const sql = postgres(dbUrl, { max: 1 });
const db = drizzle(sql);
export const migration = async () =>
diff --git a/apps/dokploy/server/db/reset.ts b/apps/dokploy/server/db/reset.ts
index c22291478..4c6e3736e 100644
--- a/apps/dokploy/server/db/reset.ts
+++ b/apps/dokploy/server/db/reset.ts
@@ -1,11 +1,10 @@
+import { dbUrl } from "@dokploy/server/db";
import { sql } from "drizzle-orm";
// Credits to Louistiti from Drizzle Discord: https://discord.com/channels/1043890932593987624/1130802621750448160/1143083373535973406
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
-const connectionString = process.env.DATABASE_URL!;
-
-const pg = postgres(connectionString, { max: 1 });
+const pg = postgres(dbUrl, { max: 1 });
const db = drizzle(pg);
const clearDb = async (): Promise => {
diff --git a/apps/dokploy/server/queues/deployments-queue.ts b/apps/dokploy/server/queues/deployments-queue.ts
index 4c117e7e3..0474b63e2 100644
--- a/apps/dokploy/server/queues/deployments-queue.ts
+++ b/apps/dokploy/server/queues/deployments-queue.ts
@@ -4,6 +4,7 @@ import {
deployPreviewApplication,
rebuildApplication,
rebuildCompose,
+ rebuildPreviewApplication,
updateApplicationStatus,
updateCompose,
updatePreviewDeployment,
@@ -54,7 +55,14 @@ export const deploymentWorker = new Worker(
previewStatus: "running",
});
- if (job.data.type === "deploy") {
+ if (job.data.type === "redeploy") {
+ await rebuildPreviewApplication({
+ applicationId: job.data.applicationId,
+ titleLog: job.data.titleLog,
+ descriptionLog: job.data.descriptionLog,
+ previewDeploymentId: job.data.previewDeploymentId,
+ });
+ } else if (job.data.type === "deploy") {
await deployPreviewApplication({
applicationId: job.data.applicationId,
titleLog: job.data.titleLog,
diff --git a/apps/dokploy/server/queues/queue-types.ts b/apps/dokploy/server/queues/queue-types.ts
index ef8df6943..1000725ad 100644
--- a/apps/dokploy/server/queues/queue-types.ts
+++ b/apps/dokploy/server/queues/queue-types.ts
@@ -22,7 +22,7 @@ type DeployJob =
titleLog: string;
descriptionLog: string;
server?: boolean;
- type: "deploy";
+ type: "deploy" | "redeploy";
applicationType: "application-preview";
previewDeploymentId: string;
serverId?: string;
diff --git a/apps/dokploy/server/wss/docker-container-logs.ts b/apps/dokploy/server/wss/docker-container-logs.ts
index eaefa21f1..c3f902475 100644
--- a/apps/dokploy/server/wss/docker-container-logs.ts
+++ b/apps/dokploy/server/wss/docker-container-logs.ts
@@ -1,9 +1,9 @@
import type http from "node:http";
-import { findServerById, validateRequest } from "@dokploy/server";
+import { findServerById, IS_CLOUD, validateRequest } from "@dokploy/server";
import { spawn } from "node-pty";
import { Client } from "ssh2";
import { WebSocketServer } from "ws";
-import { getShell } from "./utils";
+import { getShell, isValidContainerId } from "./utils";
export const setupDockerContainerLogsWebSocketServer = (
server: http.Server,
@@ -42,6 +42,12 @@ export const setupDockerContainerLogsWebSocketServer = (
return;
}
+ // Security: Validate containerId to prevent command injection
+ if (!isValidContainerId(containerId)) {
+ ws.close(4000, "Invalid container ID format");
+ return;
+ }
+
if (!user || !session) {
ws.close();
return;
@@ -111,6 +117,11 @@ export const setupDockerContainerLogsWebSocketServer = (
client.end();
});
} else {
+ if (IS_CLOUD) {
+ ws.send("This feature is not available in the cloud version.");
+ ws.close();
+ return;
+ }
const shell = getShell();
const baseCommand = `docker ${runType === "swarm" ? "service" : "container"} logs --timestamps ${
runType === "swarm" ? "--raw" : ""
diff --git a/apps/dokploy/server/wss/docker-container-terminal.ts b/apps/dokploy/server/wss/docker-container-terminal.ts
index 155d7f0cc..a2c242d95 100644
--- a/apps/dokploy/server/wss/docker-container-terminal.ts
+++ b/apps/dokploy/server/wss/docker-container-terminal.ts
@@ -1,9 +1,9 @@
import type http from "node:http";
-import { findServerById, validateRequest } from "@dokploy/server";
+import { findServerById, IS_CLOUD, validateRequest } from "@dokploy/server";
import { spawn } from "node-pty";
import { Client } from "ssh2";
import { WebSocketServer } from "ws";
-import { getShell } from "./utils";
+import { isValidContainerId, isValidShell } from "./utils";
export const setupDockerContainerTerminalWebSocketServer = (
server: http.Server,
@@ -35,10 +35,25 @@ export const setupDockerContainerTerminalWebSocketServer = (
const { user, session } = await validateRequest(req);
if (!containerId) {
- ws.close(4000, "containerId no provided");
+ ws.close(4000, "containerId not provided");
return;
}
+ // Security: Validate containerId to prevent command injection
+ if (!isValidContainerId(containerId)) {
+ ws.close(4000, "Invalid container ID format");
+ return;
+ }
+
+ // Security: Validate shell to prevent command injection
+ if (activeWay && !isValidShell(activeWay)) {
+ ws.close(4000, "Invalid shell specified");
+ return;
+ }
+
+ // Default to 'sh' if no shell specified
+ const shell = activeWay || "sh";
+
if (!user || !session) {
ws.close();
return;
@@ -54,55 +69,61 @@ export const setupDockerContainerTerminalWebSocketServer = (
let _stderr = "";
conn
.once("ready", () => {
- conn.exec(
- `docker exec -it -w / ${containerId} ${activeWay}`,
- { pty: true },
- (err, stream) => {
- if (err) {
- console.error("SSH exec error:", err);
- ws.close();
+ // Use array-style arguments to prevent shell injection
+ const dockerCommand = [
+ "docker",
+ "exec",
+ "-it",
+ "-w",
+ "/",
+ containerId,
+ shell,
+ ].join(" ");
+ conn.exec(dockerCommand, { pty: true }, (err, stream) => {
+ if (err) {
+ console.error("SSH exec error:", err);
+ ws.close();
+ conn.end();
+ return;
+ }
+
+ stream
+ .on("close", (code: number, _signal: string) => {
+ ws.send(`\nContainer closed with code: ${code}\n`);
conn.end();
- return;
- }
+ })
+ .on("data", (data: string) => {
+ _stdout += data.toString();
+ ws.send(data.toString());
+ })
+ .stderr.on("data", (data) => {
+ _stderr += data.toString();
+ ws.send(data.toString());
+ console.error("Error: ", data.toString());
+ });
- stream
- .on("close", (code: number, _signal: string) => {
- ws.send(`\nContainer closed with code: ${code}\n`);
- conn.end();
- })
- .on("data", (data: string) => {
- _stdout += data.toString();
- ws.send(data.toString());
- })
- .stderr.on("data", (data) => {
- _stderr += data.toString();
- ws.send(data.toString());
- console.error("Error: ", data.toString());
- });
-
- ws.on("message", (message) => {
- try {
- let command: string | Buffer[] | Buffer | ArrayBuffer;
- if (Buffer.isBuffer(message)) {
- command = message.toString("utf8");
- } else {
- command = message;
- }
- stream.write(command.toString());
- } catch (error) {
- // @ts-ignore
- const errorMessage = error?.message as unknown as string;
- ws.send(errorMessage);
+ ws.on("message", (message) => {
+ try {
+ let command: string | Buffer[] | Buffer | ArrayBuffer;
+ if (Buffer.isBuffer(message)) {
+ command = message.toString("utf8");
+ } else {
+ command = message;
}
- });
+ stream.write(command.toString());
+ } catch (error) {
+ // @ts-ignore
+ const errorMessage = error?.message as unknown as string;
+ ws.send(errorMessage);
+ }
+ });
- ws.on("close", () => {
- stream.end();
- // Ensure SSH connection is closed when WebSocket closes
- conn.end();
- });
- },
- );
+ ws.on("close", () => {
+ stream.end();
+ // Ensure SSH connection is closed when WebSocket closes
+ conn.end();
+ });
+ });
})
.on("error", (err) => {
console.error("SSH connection error:", err);
@@ -119,10 +140,14 @@ export const setupDockerContainerTerminalWebSocketServer = (
privateKey: server.sshKey?.privateKey,
});
} else {
- const shell = getShell();
+ if (IS_CLOUD) {
+ ws.send("This feature is not available in the cloud version.");
+ ws.close();
+ return;
+ }
const ptyProcess = spawn(
- shell,
- ["-c", `docker exec -it -w / ${containerId} ${activeWay}`],
+ "docker",
+ ["exec", "-it", "-w", "/", containerId, shell],
{},
);
diff --git a/apps/dokploy/server/wss/docker-stats.ts b/apps/dokploy/server/wss/docker-stats.ts
index bd740e976..b5f2439bf 100644
--- a/apps/dokploy/server/wss/docker-stats.ts
+++ b/apps/dokploy/server/wss/docker-stats.ts
@@ -4,6 +4,7 @@ import {
execAsync,
getHostSystemStats,
getLastAdvancedStatsFile,
+ IS_CLOUD,
recordAdvancedStats,
validateRequest,
} from "@dokploy/server";
@@ -32,6 +33,12 @@ export const setupDockerStatsMonitoringSocketServer = (
wssTerm.on("connection", async (ws, req) => {
const url = new URL(req.url || "", `http://${req.headers.host}`);
+
+ if (IS_CLOUD) {
+ ws.send("This feature is not available in the cloud version.");
+ ws.close();
+ return;
+ }
const appName = url.searchParams.get("appName");
const appType = (url.searchParams.get("appType") || "application") as
| "application"
diff --git a/apps/dokploy/server/wss/listen-deployment.ts b/apps/dokploy/server/wss/listen-deployment.ts
index ca49cea29..8aeee2410 100644
--- a/apps/dokploy/server/wss/listen-deployment.ts
+++ b/apps/dokploy/server/wss/listen-deployment.ts
@@ -1,8 +1,9 @@
import { spawn } from "node:child_process";
import type http from "node:http";
-import { findServerById, validateRequest } from "@dokploy/server";
+import { findServerById, IS_CLOUD, validateRequest } from "@dokploy/server";
import { Client } from "ssh2";
import { WebSocketServer } from "ws";
+import { readValidDirectory } from "./utils";
export const setupDeploymentLogsWebSocketServer = (
server: http.Server,
@@ -40,6 +41,11 @@ export const setupDeploymentLogsWebSocketServer = (
return;
}
+ if (!readValidDirectory(logPath)) {
+ ws.close(4000, "Invalid log path");
+ return;
+ }
+
if (!user || !session) {
ws.close();
return;
@@ -108,6 +114,11 @@ export const setupDeploymentLogsWebSocketServer = (
}
});
} else {
+ if (IS_CLOUD) {
+ ws.send("This feature is not available in the cloud version.");
+ ws.close();
+ return;
+ }
tailProcess = spawn("tail", ["-n", "+1", "-f", logPath]);
const stdout = tailProcess.stdout;
diff --git a/apps/dokploy/server/wss/terminal.ts b/apps/dokploy/server/wss/terminal.ts
index fa37d492b..00b0e2c2c 100644
--- a/apps/dokploy/server/wss/terminal.ts
+++ b/apps/dokploy/server/wss/terminal.ts
@@ -97,7 +97,12 @@ export const setupTerminalWebSocketServer = (
const isLocalServer = serverId === "local";
- if (isLocalServer && !IS_CLOUD) {
+ if (isLocalServer) {
+ if (IS_CLOUD) {
+ ws.send("This feature is not available in the cloud version.");
+ ws.close();
+ return;
+ }
const port = Number(url.searchParams.get("port"));
const username = url.searchParams.get("username");
diff --git a/apps/dokploy/server/wss/utils.ts b/apps/dokploy/server/wss/utils.ts
index 1a65fc520..c749fbc51 100644
--- a/apps/dokploy/server/wss/utils.ts
+++ b/apps/dokploy/server/wss/utils.ts
@@ -1,9 +1,52 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
-import { execAsync, paths } from "@dokploy/server";
+import { execAsync, IS_CLOUD, paths } from "@dokploy/server";
+/**
+ * Validates that the container ID matches Docker's expected format.
+ * Docker container IDs are 64-character hex strings (or 12-char short form).
+ * Also allows container names: alphanumeric, underscores, hyphens, and dots.
+ */
+export const isValidContainerId = (id: string): boolean => {
+ // Match full ID (64 hex chars), short ID (12 hex chars), or container name
+ const hexPattern = /^[a-f0-9]{12,64}$/i;
+ const namePattern = /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/;
+ return hexPattern.test(id) || (namePattern.test(id) && id.length <= 128);
+};
+
+/**
+ * Validates that the shell is one of the allowed shells.
+ */
+export const isValidShell = (shell: string): boolean => {
+ const allowedShells = [
+ "sh",
+ "bash",
+ "zsh",
+ "ash",
+ "/bin/sh",
+ "/bin/bash",
+ "/bin/zsh",
+ "/bin/ash",
+ ];
+ return allowedShells.includes(shell);
+};
+
+export const readValidDirectory = (directory: string) => {
+ const { BASE_PATH } = paths();
+
+ const resolvedBase = path.resolve(BASE_PATH);
+ const resolvedDir = path.resolve(directory);
+
+ return (
+ resolvedDir === resolvedBase ||
+ resolvedDir.startsWith(resolvedBase + path.sep)
+ );
+};
export const getShell = () => {
+ if (IS_CLOUD) {
+ return "NO_AVAILABLE";
+ }
switch (os.platform()) {
case "win32":
return "powershell.exe";
diff --git a/apps/dokploy/setup.ts b/apps/dokploy/setup.ts
index e0ccb86d8..20a0b430f 100644
--- a/apps/dokploy/setup.ts
+++ b/apps/dokploy/setup.ts
@@ -22,7 +22,7 @@ import {
await initializeNetwork();
createDefaultTraefikConfig();
createDefaultServerTraefikConfig();
- await execAsync("docker pull traefik:v3.6.1");
+ await execAsync("docker pull traefik:v3.6.7");
await initializeStandaloneTraefik();
await initializeRedis();
await initializePostgres();
diff --git a/apps/monitoring/database/containers.go b/apps/monitoring/database/containers.go
index 568ad12e5..4e41f5fae 100644
--- a/apps/monitoring/database/containers.go
+++ b/apps/monitoring/database/containers.go
@@ -58,7 +58,7 @@ func (db *DB) GetLastNContainerMetrics(containerName string, limit int) ([]Conta
WITH recent_metrics AS (
SELECT metrics_json
FROM container_metrics
- WHERE container_name LIKE ? || '%'
+ WHERE container_name = ?
ORDER BY timestamp DESC
LIMIT ?
)
@@ -98,7 +98,7 @@ func (db *DB) GetAllMetricsContainer(containerName string) ([]ContainerMetric, e
WITH recent_metrics AS (
SELECT metrics_json
FROM container_metrics
- WHERE container_name LIKE ? || '%'
+ WHERE container_name = ?
ORDER BY timestamp DESC
)
SELECT metrics_json FROM recent_metrics ORDER BY json_extract(metrics_json, '$.timestamp') ASC
diff --git a/lefthook.yml b/lefthook.yml
deleted file mode 100644
index 3f5a6d09f..000000000
--- a/lefthook.yml
+++ /dev/null
@@ -1,45 +0,0 @@
-# EXAMPLE USAGE:
-#
-# Refer for explanation to following link:
-# https://github.com/evilmartians/lefthook/blob/master/docs/configuration.md
-#
-# pre-push:
-# commands:
-# packages-audit:
-# tags: frontend security
-# run: yarn audit
-# gems-audit:
-# tags: backend security
-# run: bundle audit
-#
-# pre-commit:
-# parallel: true
-# commands:
-# eslint:
-# glob: "*.{js,ts,jsx,tsx}"
-# run: yarn eslint {staged_files}
-# rubocop:
-# tags: backend style
-# glob: "*.rb"
-# exclude: '(^|/)(application|routes)\.rb$'
-# run: bundle exec rubocop --force-exclusion {all_files}
-# govet:
-# tags: backend style
-# files: git ls-files -m
-# glob: "*.go"
-# run: go vet {files}
-# scripts:
-# "hello.js":
-# runner: node
-# "any.go":
-# runner: go run
-
-commit-msg:
- commands:
- commitlint:
- # run: "npx commitlint --edit $1"
-
-pre-commit:
- commands:
- check:
- # run: "pnpm check"
diff --git a/package.json b/package.json
index 1f59cc661..9a920c59c 100644
--- a/package.json
+++ b/package.json
@@ -24,12 +24,9 @@
},
"devDependencies": {
"@biomejs/biome": "2.1.1",
- "@commitlint/cli": "^19.8.1",
- "@commitlint/config-conventional": "^19.8.1",
"@types/node": "^18.19.104",
"dotenv": "16.4.5",
"esbuild": "0.20.2",
- "lefthook": "1.8.4",
"lint-staged": "^15.5.2",
"tsx": "4.16.2"
},
@@ -43,11 +40,6 @@
"biome check --write --no-errors-on-unmatched --files-ignore-unknown=true"
]
},
- "commitlint": {
- "extends": [
- "@commitlint/config-conventional"
- ]
- },
"resolutions": {
"@types/react": "18.3.5",
"@types/react-dom": "18.3.0"
diff --git a/packages/server/package.json b/packages/server/package.json
index e23fa6d8b..820300b15 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -57,7 +57,6 @@
"drizzle-dbml-generator": "0.10.0",
"drizzle-orm": "^0.39.3",
"drizzle-zod": "0.5.1",
- "hi-base32": "^0.5.1",
"yaml": "2.8.1",
"lodash": "4.17.21",
"micromatch": "4.0.8",
@@ -67,7 +66,6 @@
"node-schedule": "2.1.1",
"nodemailer": "6.9.14",
"octokit": "3.1.2",
- "otpauth": "^9.4.0",
"pino": "9.4.0",
"pino-pretty": "11.2.2",
"postgres": "3.4.4",
@@ -75,15 +73,16 @@
"qrcode": "^1.5.4",
"react": "18.2.0",
"react-dom": "18.2.0",
- "rotating-file-stream": "3.2.3",
"shell-quote": "^1.8.1",
"slugify": "^1.6.6",
"ssh2": "1.15.0",
"toml": "3.0.0",
"ws": "8.16.0",
- "zod": "^3.25.32"
+ "zod": "^3.25.32",
+ "semver": "7.7.3"
},
"devDependencies": {
+ "@types/semver": "7.7.1",
"@types/adm-zip": "^0.5.7",
"@types/bcrypt": "5.0.2",
"@types/dockerode": "3.3.23",
@@ -112,4 +111,4 @@
"node": "^20.16.0",
"pnpm": ">=9.12.0"
}
-}
\ No newline at end of file
+}
diff --git a/packages/server/schema.dbml b/packages/server/schema.dbml
index ef1814c00..0fe7c05e8 100644
--- a/packages/server/schema.dbml
+++ b/packages/server/schema.dbml
@@ -277,7 +277,7 @@ table application {
replicas integer [not null, default: 1]
applicationStatus applicationStatus [not null, default: 'idle']
buildType buildType [not null, default: 'nixpacks']
- railpackVersion text [default: '0.2.2']
+ railpackVersion text [default: '0.15.4']
herokuVersion text [default: '24']
publishDirectory text
isStaticSpa boolean
diff --git a/packages/server/src/db/constants.ts b/packages/server/src/db/constants.ts
new file mode 100644
index 000000000..1d4ec2f1f
--- /dev/null
+++ b/packages/server/src/db/constants.ts
@@ -0,0 +1,39 @@
+import fs from "node:fs";
+
+export const {
+ DATABASE_URL,
+ POSTGRES_PASSWORD_FILE,
+ POSTGRES_USER = "dokploy",
+ POSTGRES_DB = "dokploy",
+ POSTGRES_HOST = "dokploy-postgres",
+ POSTGRES_PORT = "5432",
+} = process.env;
+
+function readSecret(path: string): string {
+ try {
+ return fs.readFileSync(path, "utf8").trim();
+ } catch {
+ throw new Error(`Cannot read secret at ${path}`);
+ }
+}
+export let dbUrl: string;
+if (DATABASE_URL) {
+ // Compatibilidad legacy / overrides
+ dbUrl = DATABASE_URL;
+} else if (POSTGRES_PASSWORD_FILE) {
+ const password = readSecret(POSTGRES_PASSWORD_FILE);
+ dbUrl = `postgres://${POSTGRES_USER}:${encodeURIComponent(
+ password,
+ )}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}`;
+} else {
+ console.warn(`
+ ⚠️ [DEPRECATED DATABASE CONFIG]
+ You are using the legacy hardcoded database credentials.
+ This mode WILL BE REMOVED in a future release.
+
+ Please migrate to Docker Secrets using POSTGRES_PASSWORD_FILE.
+ Please execute this command in your server: curl -sSL https://dokploy.com/security/0.26.6.sh | bash
+ `);
+ dbUrl =
+ "postgres://dokploy:amukds4wi9001583845717ad2@dokploy-postgres:5432/dokploy";
+}
diff --git a/packages/server/src/db/index.ts b/packages/server/src/db/index.ts
index 3ac6e3940..e17002de9 100644
--- a/packages/server/src/db/index.ts
+++ b/packages/server/src/db/index.ts
@@ -1,5 +1,6 @@
import { drizzle, type PostgresJsDatabase } from "drizzle-orm/postgres-js";
import postgres from "postgres";
+import { dbUrl } from "./constants";
import * as schema from "./schema";
declare global {
@@ -8,14 +9,16 @@ declare global {
export let db: PostgresJsDatabase;
if (process.env.NODE_ENV === "production") {
- db = drizzle(postgres(process.env.DATABASE_URL!), {
+ db = drizzle(postgres(dbUrl), {
schema,
});
} else {
if (!global.db)
- global.db = drizzle(postgres(process.env.DATABASE_URL!), {
+ global.db = drizzle(postgres(dbUrl), {
schema,
});
db = global.db;
}
+
+export { dbUrl };
diff --git a/packages/server/src/db/schema/application.ts b/packages/server/src/db/schema/application.ts
index 787ec55b3..489c7cb0e 100644
--- a/packages/server/src/db/schema/application.ts
+++ b/packages/server/src/db/schema/application.ts
@@ -136,6 +136,7 @@ export const applications = pgTable("application", {
giteaBuildPath: text("giteaBuildPath").default("/"),
// Bitbucket
bitbucketRepository: text("bitbucketRepository"),
+ bitbucketRepositorySlug: text("bitbucketRepositorySlug"),
bitbucketOwner: text("bitbucketOwner"),
bitbucketBranch: text("bitbucketBranch"),
bitbucketBuildPath: text("bitbucketBuildPath").default("/"),
@@ -177,7 +178,7 @@ export const applications = pgTable("application", {
.notNull()
.default("idle"),
buildType: buildType("buildType").notNull().default("nixpacks"),
- railpackVersion: text("railpackVersion").default("0.2.2"),
+ railpackVersion: text("railpackVersion").default("0.15.4"),
herokuVersion: text("herokuVersion").default("24"),
publishDirectory: text("publishDirectory"),
isStaticSpa: boolean("isStaticSpa"),
@@ -451,6 +452,7 @@ export const apiSaveBitbucketProvider = createSchema
bitbucketBuildPath: true,
bitbucketOwner: true,
bitbucketRepository: true,
+ bitbucketRepositorySlug: true,
bitbucketId: true,
applicationId: true,
watchPaths: true,
diff --git a/packages/server/src/db/schema/compose.ts b/packages/server/src/db/schema/compose.ts
index 7b8e93c7a..95264f52f 100644
--- a/packages/server/src/db/schema/compose.ts
+++ b/packages/server/src/db/schema/compose.ts
@@ -56,6 +56,7 @@ export const compose = pgTable("compose", {
gitlabPathNamespace: text("gitlabPathNamespace"),
// Bitbucket
bitbucketRepository: text("bitbucketRepository"),
+ bitbucketRepositorySlug: text("bitbucketRepositorySlug"),
bitbucketOwner: text("bitbucketOwner"),
bitbucketBranch: text("bitbucketBranch"),
// Gitea
diff --git a/packages/server/src/db/schema/index.ts b/packages/server/src/db/schema/index.ts
index c16ef1452..ee3c03e93 100644
--- a/packages/server/src/db/schema/index.ts
+++ b/packages/server/src/db/schema/index.ts
@@ -35,3 +35,4 @@ export * from "./ssh-key";
export * from "./user";
export * from "./utils";
export * from "./volume-backups";
+export * from "./web-server-settings";
diff --git a/packages/server/src/db/schema/notification.ts b/packages/server/src/db/schema/notification.ts
index 44dadac8f..3075459ba 100644
--- a/packages/server/src/db/schema/notification.ts
+++ b/packages/server/src/db/schema/notification.ts
@@ -19,6 +19,7 @@ export const notificationType = pgEnum("notificationType", [
"email",
"gotify",
"ntfy",
+ "pushover",
"custom",
"lark",
]);
@@ -64,6 +65,9 @@ export const notifications = pgTable("notification", {
larkId: text("larkId").references(() => lark.larkId, {
onDelete: "cascade",
}),
+ pushoverId: text("pushoverId").references(() => pushover.pushoverId, {
+ onDelete: "cascade",
+ }),
organizationId: text("organizationId")
.notNull()
.references(() => organization.id, { onDelete: "cascade" }),
@@ -149,6 +153,18 @@ export const lark = pgTable("lark", {
webhookUrl: text("webhookUrl").notNull(),
});
+export const pushover = pgTable("pushover", {
+ pushoverId: text("pushoverId")
+ .notNull()
+ .primaryKey()
+ .$defaultFn(() => nanoid()),
+ userKey: text("userKey").notNull(),
+ apiToken: text("apiToken").notNull(),
+ priority: integer("priority").notNull().default(0),
+ retry: integer("retry"),
+ expire: integer("expire"),
+});
+
export const notificationsRelations = relations(notifications, ({ one }) => ({
slack: one(slack, {
fields: [notifications.slackId],
@@ -182,6 +198,10 @@ export const notificationsRelations = relations(notifications, ({ one }) => ({
fields: [notifications.larkId],
references: [lark.larkId],
}),
+ pushover: one(pushover, {
+ fields: [notifications.pushoverId],
+ references: [pushover.pushoverId],
+ }),
organization: one(organization, {
fields: [notifications.organizationId],
references: [organization.id],
@@ -439,6 +459,69 @@ export const apiTestLarkConnection = apiCreateLark.pick({
webhookUrl: true,
});
+export const apiCreatePushover = notificationsSchema
+ .pick({
+ appBuildError: true,
+ databaseBackup: true,
+ volumeBackup: true,
+ dokployRestart: true,
+ name: true,
+ appDeploy: true,
+ dockerCleanup: true,
+ serverThreshold: true,
+ })
+ .extend({
+ userKey: z.string().min(1),
+ apiToken: z.string().min(1),
+ priority: z.number().min(-2).max(2).default(0),
+ retry: z.number().min(30).nullish(),
+ expire: z.number().min(1).max(10800).nullish(),
+ })
+ .refine(
+ (data) =>
+ data.priority !== 2 || (data.retry != null && data.expire != null),
+ {
+ message: "Retry and expire are required for emergency priority (2)",
+ path: ["retry"],
+ },
+ );
+
+export const apiUpdatePushover = z.object({
+ notificationId: z.string().min(1),
+ pushoverId: z.string().min(1),
+ organizationId: z.string().optional(),
+ userKey: z.string().min(1).optional(),
+ apiToken: z.string().min(1).optional(),
+ priority: z.number().min(-2).max(2).optional(),
+ retry: z.number().min(30).nullish(),
+ expire: z.number().min(1).max(10800).nullish(),
+ appBuildError: z.boolean().optional(),
+ databaseBackup: z.boolean().optional(),
+ volumeBackup: z.boolean().optional(),
+ dokployRestart: z.boolean().optional(),
+ name: z.string().optional(),
+ appDeploy: z.boolean().optional(),
+ dockerCleanup: z.boolean().optional(),
+ serverThreshold: z.boolean().optional(),
+});
+
+export const apiTestPushoverConnection = z
+ .object({
+ userKey: z.string().min(1),
+ apiToken: z.string().min(1),
+ priority: z.number().min(-2).max(2),
+ retry: z.number().min(30).nullish(),
+ expire: z.number().min(1).max(10800).nullish(),
+ })
+ .refine(
+ (data) =>
+ data.priority !== 2 || (data.retry != null && data.expire != null),
+ {
+ message: "Retry and expire are required for emergency priority (2)",
+ path: ["retry"],
+ },
+ );
+
export const apiSendTest = notificationsSchema
.extend({
botToken: z.string(),
diff --git a/packages/server/src/db/schema/postgres.ts b/packages/server/src/db/schema/postgres.ts
index d4ad7ad83..9d4224006 100644
--- a/packages/server/src/db/schema/postgres.ts
+++ b/packages/server/src/db/schema/postgres.ts
@@ -102,7 +102,7 @@ const createSchema = createInsertSchema(postgres, {
}),
databaseName: z.string().min(1),
databaseUser: z.string().min(1),
- dockerImage: z.string().default("postgres:15"),
+ dockerImage: z.string().default("postgres:18"),
command: z.string().optional(),
args: z.array(z.string()).optional(),
env: z.string().optional(),
diff --git a/packages/server/src/db/schema/registry.ts b/packages/server/src/db/schema/registry.ts
index 425ce768c..dd00bf19d 100644
--- a/packages/server/src/db/schema/registry.ts
+++ b/packages/server/src/db/schema/registry.ts
@@ -80,6 +80,14 @@ export const apiTestRegistry = createSchema.pick({}).extend({
serverId: z.string().optional(),
});
+export const apiTestRegistryById = createSchema
+ .pick({
+ registryId: true,
+ })
+ .extend({
+ serverId: z.string().optional(),
+ });
+
export const apiRemoveRegistry = createSchema
.pick({
registryId: true,
diff --git a/packages/server/src/db/schema/user.ts b/packages/server/src/db/schema/user.ts
index 5a96aa3eb..088f68efa 100644
--- a/packages/server/src/db/schema/user.ts
+++ b/packages/server/src/db/schema/user.ts
@@ -3,7 +3,6 @@ import { relations } from "drizzle-orm";
import {
boolean,
integer,
- jsonb,
pgTable,
text,
timestamp,
@@ -15,7 +14,6 @@ import { account, apikey, organization } from "./account";
import { backups } from "./backups";
import { projects } from "./project";
import { schedules } from "./schedule";
-import { certificateType } from "./shared";
/**
* This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same
* database instance for multiple projects.
@@ -51,73 +49,10 @@ export const user = pgTable("user", {
banExpires: timestamp("ban_expires"),
updatedAt: timestamp("updated_at").notNull(),
// Admin
- serverIp: text("serverIp"),
- certificateType: certificateType("certificateType").notNull().default("none"),
- https: boolean("https").notNull().default(false),
- host: text("host"),
- letsEncryptEmail: text("letsEncryptEmail"),
- sshPrivateKey: text("sshPrivateKey"),
- enableDockerCleanup: boolean("enableDockerCleanup").notNull().default(true),
- logCleanupCron: text("logCleanupCron").default("0 0 * * *"),
role: text("role").notNull().default("user"),
// Metrics
enablePaidFeatures: boolean("enablePaidFeatures").notNull().default(false),
allowImpersonation: boolean("allowImpersonation").notNull().default(false),
- metricsConfig: jsonb("metricsConfig")
- .$type<{
- server: {
- type: "Dokploy" | "Remote";
- refreshRate: number;
- port: number;
- token: string;
- urlCallback: string;
- retentionDays: number;
- cronJob: string;
- thresholds: {
- cpu: number;
- memory: number;
- };
- };
- containers: {
- refreshRate: number;
- services: {
- include: string[];
- exclude: string[];
- };
- };
- }>()
- .notNull()
- .default({
- server: {
- type: "Dokploy",
- refreshRate: 60,
- port: 4500,
- token: "",
- retentionDays: 2,
- cronJob: "",
- urlCallback: "",
- thresholds: {
- cpu: 0,
- memory: 0,
- },
- },
- containers: {
- refreshRate: 60,
- services: {
- include: [],
- exclude: [],
- },
- },
- }),
- cleanupCacheApplications: boolean("cleanupCacheApplications")
- .notNull()
- .default(false),
- cleanupCacheOnPreviews: boolean("cleanupCacheOnPreviews")
- .notNull()
- .default(false),
- cleanupCacheOnCompose: boolean("cleanupCacheOnCompose")
- .notNull()
- .default(false),
stripeCustomerId: text("stripeCustomerId"),
stripeSubscriptionId: text("stripeSubscriptionId"),
serversQuantity: integer("serversQuantity").notNull().default(0),
@@ -203,33 +138,6 @@ export const apiFindOneUserByAuth = createSchema
// authId: true,
})
.required();
-export const apiSaveSSHKey = createSchema
- .pick({
- sshPrivateKey: true,
- })
- .required();
-
-export const apiAssignDomain = createSchema
- .pick({
- host: true,
- certificateType: true,
- letsEncryptEmail: true,
- https: true,
- })
- .required()
- .partial({
- letsEncryptEmail: true,
- https: true,
- });
-
-export const apiUpdateDockerCleanup = createSchema
- .pick({
- enableDockerCleanup: true,
- })
- .required()
- .extend({
- serverId: z.string().optional(),
- });
export const apiTraefikConfig = z.object({
traefikConfig: z.string().min(1),
@@ -298,32 +206,6 @@ export const apiReadStatsLogs = z.object({
.optional(),
});
-export const apiUpdateWebServerMonitoring = z.object({
- metricsConfig: z
- .object({
- server: z.object({
- refreshRate: z.number().min(2),
- port: z.number().min(1),
- token: z.string(),
- urlCallback: z.string().url(),
- retentionDays: z.number().min(1),
- cronJob: z.string().min(1),
- thresholds: z.object({
- cpu: z.number().min(0),
- memory: z.number().min(0),
- }),
- }),
- containers: z.object({
- refreshRate: z.number().min(2),
- services: z.object({
- include: z.array(z.string()).optional(),
- exclude: z.array(z.string()).optional(),
- }),
- }),
- })
- .required(),
-});
-
export const apiUpdateUser = createSchema.partial().extend({
email: z
.string()
@@ -332,31 +214,6 @@ export const apiUpdateUser = createSchema.partial().extend({
.optional(),
password: z.string().optional(),
currentPassword: z.string().optional(),
- name: z.string().optional(),
+ firstName: z.string().optional(),
lastName: z.string().optional(),
- metricsConfig: z
- .object({
- server: z.object({
- type: z.enum(["Dokploy", "Remote"]),
- refreshRate: z.number(),
- port: z.number(),
- token: z.string(),
- urlCallback: z.string(),
- retentionDays: z.number(),
- cronJob: z.string(),
- thresholds: z.object({
- cpu: z.number(),
- memory: z.number(),
- }),
- }),
- containers: z.object({
- refreshRate: z.number(),
- services: z.object({
- include: z.array(z.string()),
- exclude: z.array(z.string()),
- }),
- }),
- })
- .optional(),
- logCleanupCron: z.string().optional().nullable(),
});
diff --git a/packages/server/src/db/schema/web-server-settings.ts b/packages/server/src/db/schema/web-server-settings.ts
new file mode 100644
index 000000000..fe5cc5ad1
--- /dev/null
+++ b/packages/server/src/db/schema/web-server-settings.ts
@@ -0,0 +1,181 @@
+import { relations } from "drizzle-orm";
+import { boolean, jsonb, pgTable, text, timestamp } from "drizzle-orm/pg-core";
+import { createInsertSchema } from "drizzle-zod";
+import { nanoid } from "nanoid";
+import { z } from "zod";
+import { certificateType } from "./shared";
+
+export const webServerSettings = pgTable("webServerSettings", {
+ id: text("id")
+ .notNull()
+ .primaryKey()
+ .$defaultFn(() => nanoid()),
+ // Web Server Configuration
+ serverIp: text("serverIp"),
+ certificateType: certificateType("certificateType").notNull().default("none"),
+ https: boolean("https").notNull().default(false),
+ host: text("host"),
+ letsEncryptEmail: text("letsEncryptEmail"),
+ sshPrivateKey: text("sshPrivateKey"),
+ enableDockerCleanup: boolean("enableDockerCleanup").notNull().default(true),
+ logCleanupCron: text("logCleanupCron").default("0 0 * * *"),
+ // Metrics Configuration
+ metricsConfig: jsonb("metricsConfig")
+ .$type<{
+ server: {
+ type: "Dokploy" | "Remote";
+ refreshRate: number;
+ port: number;
+ token: string;
+ urlCallback: string;
+ retentionDays: number;
+ cronJob: string;
+ thresholds: {
+ cpu: number;
+ memory: number;
+ };
+ };
+ containers: {
+ refreshRate: number;
+ services: {
+ include: string[];
+ exclude: string[];
+ };
+ };
+ }>()
+ .notNull()
+ .default({
+ server: {
+ type: "Dokploy",
+ refreshRate: 60,
+ port: 4500,
+ token: "",
+ retentionDays: 2,
+ cronJob: "",
+ urlCallback: "",
+ thresholds: {
+ cpu: 0,
+ memory: 0,
+ },
+ },
+ containers: {
+ refreshRate: 60,
+ services: {
+ include: [],
+ exclude: [],
+ },
+ },
+ }),
+ // Cache Cleanup Configuration
+ cleanupCacheApplications: boolean("cleanupCacheApplications")
+ .notNull()
+ .default(false),
+ cleanupCacheOnPreviews: boolean("cleanupCacheOnPreviews")
+ .notNull()
+ .default(false),
+ cleanupCacheOnCompose: boolean("cleanupCacheOnCompose")
+ .notNull()
+ .default(false),
+ createdAt: timestamp("created_at").defaultNow(),
+ updatedAt: timestamp("updated_at").notNull().defaultNow(),
+});
+
+export const webServerSettingsRelations = relations(
+ webServerSettings,
+ () => ({}),
+);
+
+const createSchema = createInsertSchema(webServerSettings, {
+ id: z.string().min(1),
+});
+
+export const apiUpdateWebServerSettings = createSchema.partial().extend({
+ serverIp: z.string().optional(),
+ certificateType: z.enum(["letsencrypt", "none", "custom"]).optional(),
+ https: z.boolean().optional(),
+ host: z.string().optional(),
+ letsEncryptEmail: z.string().email().optional().nullable(),
+ sshPrivateKey: z.string().optional(),
+ enableDockerCleanup: z.boolean().optional(),
+ logCleanupCron: z.string().optional().nullable(),
+ metricsConfig: z
+ .object({
+ server: z.object({
+ type: z.enum(["Dokploy", "Remote"]),
+ refreshRate: z.number(),
+ port: z.number(),
+ token: z.string(),
+ urlCallback: z.string(),
+ retentionDays: z.number(),
+ cronJob: z.string(),
+ thresholds: z.object({
+ cpu: z.number(),
+ memory: z.number(),
+ }),
+ }),
+ containers: z.object({
+ refreshRate: z.number(),
+ services: z.object({
+ include: z.array(z.string()),
+ exclude: z.array(z.string()),
+ }),
+ }),
+ })
+ .optional(),
+ cleanupCacheApplications: z.boolean().optional(),
+ cleanupCacheOnPreviews: z.boolean().optional(),
+ cleanupCacheOnCompose: z.boolean().optional(),
+});
+
+export const apiAssignDomain = z
+ .object({
+ host: z.string(),
+ certificateType: z.enum(["letsencrypt", "none", "custom"]),
+ letsEncryptEmail: z
+ .union([z.string().email(), z.literal("")])
+ .optional()
+ .nullable(),
+ https: z.boolean().optional(),
+ })
+ .required()
+ .partial({
+ letsEncryptEmail: true,
+ https: true,
+ });
+
+export const apiSaveSSHKey = z
+ .object({
+ sshPrivateKey: z.string(),
+ })
+ .required();
+
+export const apiUpdateDockerCleanup = z.object({
+ enableDockerCleanup: z.boolean(),
+ serverId: z.string().optional(),
+});
+
+export const apiUpdateWebServerMonitoring = z.object({
+ metricsConfig: z
+ .object({
+ server: z.object({
+ refreshRate: z.number().min(2),
+ port: z.number().min(1),
+ token: z.string(),
+ urlCallback: z.string().url(),
+ retentionDays: z.number().min(1),
+ cronJob: z.string().min(1),
+ thresholds: z.object({
+ cpu: z.number().min(0),
+ memory: z.number().min(0),
+ }),
+ }),
+ containers: z.object({
+ refreshRate: z.number().min(2),
+ services: z.object({
+ include: z.array(z.string()).optional(),
+ exclude: z.array(z.string()).optional(),
+ }),
+ }),
+ })
+ .required(),
+});
diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts
index e6d753293..c05ac1ab7 100644
--- a/packages/server/src/index.ts
+++ b/packages/server/src/index.ts
@@ -1,5 +1,6 @@
export * from "./auth/random-password";
export * from "./constants/index";
+export * from "./db/constants";
export * from "./db/validations/domain";
export * from "./db/validations/index";
export * from "./lib/auth";
@@ -41,6 +42,7 @@ export * from "./services/settings";
export * from "./services/ssh-key";
export * from "./services/user";
export * from "./services/volume-backups";
+export * from "./services/web-server-settings";
export * from "./setup/config-paths";
export * from "./setup/monitoring-setup";
export * from "./setup/postgres-setup";
diff --git a/packages/server/src/lib/auth.ts b/packages/server/src/lib/auth.ts
index 9c56a7d59..d952e1f6a 100644
--- a/packages/server/src/lib/auth.ts
+++ b/packages/server/src/lib/auth.ts
@@ -9,7 +9,10 @@ import { IS_CLOUD } from "../constants";
import { db } from "../db";
import * as schema from "../db/schema";
import { getUserByToken } from "../services/admin";
-import { updateUser } from "../services/user";
+import {
+ getWebServerSettings,
+ updateWebServerSettings,
+} from "../services/web-server-settings";
import { getHubSpotUTK, submitToHubSpot } from "../utils/tracking/hubspot";
import { sendEmail } from "../verification/send-verification-email";
import { getPublicIpWithFallback } from "../wss/utils";
@@ -35,22 +38,20 @@ const { handler, api } = betterAuth({
},
...(!IS_CLOUD && {
async trustedOrigins() {
- const admin = await db.query.member.findFirst({
- where: eq(schema.member.role, "owner"),
- with: {
- user: true,
- },
- });
-
- if (admin) {
- return [
- ...(admin.user.serverIp
- ? [`http://${admin.user.serverIp}:3000`]
- : []),
- ...(admin.user.host ? [`https://${admin.user.host}`] : []),
- ];
+ const settings = await getWebServerSettings();
+ if (!settings) {
+ return [];
}
- return [];
+ return [
+ ...(settings?.serverIp ? [`http://${settings?.serverIp}:3000`] : []),
+ ...(settings?.host ? [`https://${settings?.host}`] : []),
+ ...(process.env.NODE_ENV === "development"
+ ? [
+ "http://localhost:3000",
+ "https://absolutely-handy-falcon.ngrok-free.app",
+ ]
+ : []),
+ ];
},
}),
emailVerification: {
@@ -122,7 +123,7 @@ const { handler, api } = betterAuth({
});
if (!IS_CLOUD) {
- await updateUser(user.id, {
+ await updateWebServerSettings({
serverIp: await getPublicIpWithFallback(),
});
}
diff --git a/packages/server/src/services/admin.ts b/packages/server/src/services/admin.ts
index 0e8612415..323d0177e 100644
--- a/packages/server/src/services/admin.ts
+++ b/packages/server/src/services/admin.ts
@@ -8,6 +8,7 @@ import {
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { IS_CLOUD } from "../constants";
+import { getWebServerSettings } from "./web-server-settings";
export const findUserById = async (userId: string) => {
const userResult = await db.query.user.findFirst({
@@ -107,11 +108,11 @@ export const getDokployUrl = async () => {
if (IS_CLOUD) {
return "https://app.dokploy.com";
}
- const owner = await findOwner();
+ const settings = await getWebServerSettings();
- if (owner.user.host) {
- const protocol = owner.user.https ? "https" : "http";
- return `${protocol}://${owner.user.host}`;
+ if (settings?.host) {
+ const protocol = settings?.https ? "https" : "http";
+ return `${protocol}://${settings?.host}`;
}
- return `http://${owner.user.serverIp}:${process.env.PORT}`;
+ return `http://${settings?.serverIp}:${process.env.PORT}`;
};
diff --git a/packages/server/src/services/ai.ts b/packages/server/src/services/ai.ts
index 277ec6c39..fefc82eae 100644
--- a/packages/server/src/services/ai.ts
+++ b/packages/server/src/services/ai.ts
@@ -6,8 +6,8 @@ import { generateObject } from "ai";
import { desc, eq } from "drizzle-orm";
import { z } from "zod";
import { IS_CLOUD } from "../constants";
-import { findOrganizationById } from "./admin";
import { findServerById } from "./server";
+import { getWebServerSettings } from "./web-server-settings";
export const getAiSettingsByOrganizationId = async (organizationId: string) => {
const aiSettings = await db.query.ai.findMany({
@@ -79,8 +79,8 @@ export const suggestVariants = async ({
let ip = "";
if (!IS_CLOUD) {
- const organization = await findOrganizationById(organizationId);
- ip = organization?.owner.serverIp || "";
+ const settings = await getWebServerSettings();
+ ip = settings?.serverIp || "";
}
if (serverId) {
diff --git a/packages/server/src/services/application.ts b/packages/server/src/services/application.ts
index 61a77ae5a..24a5ae79c 100644
--- a/packages/server/src/services/application.ts
+++ b/packages/server/src/services/application.ts
@@ -253,7 +253,11 @@ export const deployApplication = async ({
} finally {
// Only extract commit info for non-docker sources
if (application.sourceType !== "docker") {
- const commitInfo = await getGitCommitInfo(application);
+ const commitInfo = await getGitCommitInfo({
+ appName: application.appName,
+ type: "application",
+ serverId: serverId,
+ });
if (commitInfo) {
await updateDeployment(deployment.deploymentId, {
@@ -452,6 +456,137 @@ export const deployPreviewApplication = async ({
return true;
};
+export const rebuildPreviewApplication = async ({
+ applicationId,
+ titleLog = "Rebuild Preview Deployment",
+ descriptionLog = "",
+ previewDeploymentId,
+}: {
+ applicationId: string;
+ titleLog: string;
+ descriptionLog: string;
+ previewDeploymentId: string;
+}) => {
+ const application = await findApplicationById(applicationId);
+ const previewDeployment =
+ await findPreviewDeploymentById(previewDeploymentId);
+
+ const deployment = await createDeploymentPreview({
+ title: titleLog,
+ description: descriptionLog,
+ previewDeploymentId: previewDeploymentId,
+ });
+
+ const previewDomain = getDomainHost(previewDeployment?.domain as Domain);
+ const issueParams = {
+ owner: application?.owner || "",
+ repository: application?.repository || "",
+ issue_number: previewDeployment.pullRequestNumber,
+ comment_id: Number.parseInt(previewDeployment.pullRequestCommentId),
+ githubId: application?.githubId || "",
+ };
+
+ try {
+ const commentExists = await issueCommentExists({
+ ...issueParams,
+ });
+ if (!commentExists) {
+ const result = await createPreviewDeploymentComment({
+ ...issueParams,
+ previewDomain,
+ appName: previewDeployment.appName,
+ githubId: application?.githubId || "",
+ previewDeploymentId,
+ });
+
+ if (!result) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Pull request comment not found",
+ });
+ }
+
+ issueParams.comment_id = Number.parseInt(result?.pullRequestCommentId);
+ }
+
+ const buildingComment = getIssueComment(
+ application.name,
+ "running",
+ previewDomain,
+ );
+ await updateIssueComment({
+ ...issueParams,
+ body: `### Dokploy Preview Deployment\n\n${buildingComment}`,
+ });
+
+ // Set application properties for preview deployment
+ application.appName = previewDeployment.appName;
+ application.env = `${application.previewEnv}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain?.host}`;
+ application.buildArgs = `${application.previewBuildArgs}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain?.host}`;
+ application.buildSecrets = `${application.previewBuildSecrets}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain?.host}`;
+ application.rollbackActive = false;
+ application.buildRegistry = null;
+ application.rollbackRegistry = null;
+ application.registry = null;
+
+ const serverId = application.serverId;
+ let command = "set -e;";
+ // Only rebuild, don't clone repository
+ command += await getBuildCommand(application);
+ const commandWithLog = `(${command}) >> ${deployment.logPath} 2>&1`;
+ if (serverId) {
+ await execAsyncRemote(serverId, commandWithLog);
+ } else {
+ await execAsync(commandWithLog);
+ }
+ await mechanizeDockerContainer(application);
+
+ const successComment = getIssueComment(
+ application.name,
+ "success",
+ previewDomain,
+ );
+ await updateIssueComment({
+ ...issueParams,
+ body: `### Dokploy Preview Deployment\n\n${successComment}`,
+ });
+ await updateDeploymentStatus(deployment.deploymentId, "done");
+ await updatePreviewDeployment(previewDeploymentId, {
+ previewStatus: "done",
+ });
+ } catch (error) {
+ let command = "";
+
+ // Only log details for non-ExecError errors
+ if (!(error instanceof ExecError)) {
+ const message = error instanceof Error ? error.message : String(error);
+ const encodedMessage = encodeBase64(message);
+ command += `echo "${encodedMessage}" | base64 -d >> "${deployment.logPath}";`;
+ }
+
+ command += `echo "\nError occurred ❌, check the logs for details." >> ${deployment.logPath};`;
+ const serverId = application.buildServerId || application.serverId;
+ if (serverId) {
+ await execAsyncRemote(serverId, command);
+ } else {
+ await execAsync(command);
+ }
+
+ const comment = getIssueComment(application.name, "error", previewDomain);
+ await updateIssueComment({
+ ...issueParams,
+ body: `### Dokploy Preview Deployment\n\n${comment}`,
+ });
+ await updateDeploymentStatus(deployment.deploymentId, "error");
+ await updatePreviewDeployment(previewDeploymentId, {
+ previewStatus: "error",
+ });
+ throw error;
+ }
+
+ return true;
+};
+
export const getApplicationStats = async (appName: string) => {
if (appName === "dokploy") {
return await getAdvancedStats(appName);
diff --git a/packages/server/src/services/domain.ts b/packages/server/src/services/domain.ts
index 50888e546..b2e15ed91 100644
--- a/packages/server/src/services/domain.ts
+++ b/packages/server/src/services/domain.ts
@@ -1,12 +1,12 @@
import dns from "node:dns";
import { promisify } from "node:util";
import { db } from "@dokploy/server/db";
+import { getWebServerSettings } from "@dokploy/server/services/web-server-settings";
import { generateRandomDomain } from "@dokploy/server/templates";
import { manageDomain } from "@dokploy/server/utils/traefik/domain";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { type apiCreateDomain, domains } from "../db/schema";
-import { findUserById } from "./admin";
import { findApplicationById } from "./application";
import { detectCDNProvider } from "./cdn";
import { findServerById } from "./server";
@@ -61,9 +61,9 @@ export const generateTraefikMeDomain = async (
projectName: appName,
});
}
- const admin = await findUserById(userId);
+ const settings = await getWebServerSettings();
return generateRandomDomain({
- serverIp: admin?.serverIp || "",
+ serverIp: settings?.serverIp || "",
projectName: appName,
});
};
diff --git a/packages/server/src/services/notification.ts b/packages/server/src/services/notification.ts
index ca6b4ded6..453a61ca0 100644
--- a/packages/server/src/services/notification.ts
+++ b/packages/server/src/services/notification.ts
@@ -6,6 +6,7 @@ import {
type apiCreateGotify,
type apiCreateLark,
type apiCreateNtfy,
+ type apiCreatePushover,
type apiCreateSlack,
type apiCreateTelegram,
type apiUpdateCustom,
@@ -14,6 +15,7 @@ import {
type apiUpdateGotify,
type apiUpdateLark,
type apiUpdateNtfy,
+ type apiUpdatePushover,
type apiUpdateSlack,
type apiUpdateTelegram,
custom,
@@ -23,6 +25,7 @@ import {
lark,
notifications,
ntfy,
+ pushover,
slack,
telegram,
} from "@dokploy/server/db/schema";
@@ -694,6 +697,7 @@ export const findNotificationById = async (notificationId: string) => {
ntfy: true,
custom: true,
lark: true,
+ pushover: true,
},
});
if (!notification) {
@@ -817,3 +821,99 @@ export const updateNotificationById = async (
return result[0];
};
+
+export const createPushoverNotification = async (
+ input: typeof apiCreatePushover._type,
+ organizationId: string,
+) => {
+ await db.transaction(async (tx) => {
+ const newPushover = await tx
+ .insert(pushover)
+ .values({
+ userKey: input.userKey,
+ apiToken: input.apiToken,
+ priority: input.priority,
+ retry: input.retry,
+ expire: input.expire,
+ })
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newPushover) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error input: Inserting pushover",
+ });
+ }
+
+ const newDestination = await tx
+ .insert(notifications)
+ .values({
+ pushoverId: newPushover.pushoverId,
+ name: input.name,
+ appDeploy: input.appDeploy,
+ appBuildError: input.appBuildError,
+ databaseBackup: input.databaseBackup,
+ volumeBackup: input.volumeBackup,
+ dokployRestart: input.dokployRestart,
+ dockerCleanup: input.dockerCleanup,
+ serverThreshold: input.serverThreshold,
+ notificationType: "pushover",
+ organizationId: organizationId,
+ })
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newDestination) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error input: Inserting notification",
+ });
+ }
+
+ return newDestination;
+ });
+};
+
+export const updatePushoverNotification = async (
+ input: typeof apiUpdatePushover._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,
+ volumeBackup: input.volumeBackup,
+ dokployRestart: input.dokployRestart,
+ dockerCleanup: input.dockerCleanup,
+ organizationId: input.organizationId,
+ serverThreshold: input.serverThreshold,
+ })
+ .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(pushover)
+ .set({
+ userKey: input.userKey,
+ apiToken: input.apiToken,
+ priority: input.priority,
+ retry: input.retry,
+ expire: input.expire,
+ })
+ .where(eq(pushover.pushoverId, input.pushoverId));
+
+ return newDestination;
+ });
+};
diff --git a/packages/server/src/services/preview-deployment.ts b/packages/server/src/services/preview-deployment.ts
index 5ee763b08..1ece3bc53 100644
--- a/packages/server/src/services/preview-deployment.ts
+++ b/packages/server/src/services/preview-deployment.ts
@@ -13,11 +13,11 @@ import { removeDirectoryCode } from "../utils/filesystem/directory";
import { authGithub } from "../utils/providers/github";
import { removeTraefikConfig } from "../utils/traefik/application";
import { manageDomain } from "../utils/traefik/domain";
-import { findUserById } from "./admin";
import { findApplicationById } from "./application";
import { removeDeploymentsByPreviewDeploymentId } from "./deployment";
import { createDomain } from "./domain";
import { type Github, getIssueComment } from "./github";
+import { getWebServerSettings } from "./web-server-settings";
export type PreviewDeployment = typeof previewDeployments.$inferSelect;
@@ -253,8 +253,8 @@ const generateWildcardDomain = async (
}
if (!ip) {
- const admin = await findUserById(userId);
- ip = admin?.serverIp || "";
+ const settings = await getWebServerSettings();
+ ip = settings?.serverIp || "";
}
const slugIp = ip.replaceAll(".", "-");
diff --git a/packages/server/src/services/settings.ts b/packages/server/src/services/settings.ts
index 277008bd3..4235376d9 100644
--- a/packages/server/src/services/settings.ts
+++ b/packages/server/src/services/settings.ts
@@ -5,12 +5,12 @@ import {
execAsync,
execAsyncRemote,
} from "@dokploy/server/utils/process/execAsync";
+import semver from "semver";
import {
initializeStandaloneTraefik,
initializeTraefikService,
type TraefikOptions,
} from "../setup/traefik-setup";
-
export interface IUpdateData {
latestVersion: string | null;
updateAvailable: boolean;
@@ -55,56 +55,95 @@ export const getServiceImageDigest = async () => {
};
/** Returns latest version number and information whether server update is available by comparing current image's digest against digest for provided image tag via Docker hub API. */
-export const getUpdateData = async (): Promise => {
- let currentDigest: string;
+export const getUpdateData = async (
+ currentVersion: string,
+): Promise => {
try {
- currentDigest = await getServiceImageDigest();
- } catch (error) {
- // TODO: Docker versions 29.0.0 change the way to get the service image digest, so we need to update this in the future we upgrade to that version.
- return DEFAULT_UPDATE_DATA;
- }
+ const baseUrl =
+ "https://hub.docker.com/v2/repositories/dokploy/dokploy/tags";
+ let url: string | null = `${baseUrl}?page_size=100`;
+ let allResults: { digest: string; name: string }[] = [];
- const baseUrl = "https://hub.docker.com/v2/repositories/dokploy/dokploy/tags";
- let url: string | null = `${baseUrl}?page_size=100`;
- let allResults: { digest: string; name: string }[] = [];
- while (url) {
- const response = await fetch(url, {
- method: "GET",
- headers: { "Content-Type": "application/json" },
- });
+ // Fetch all tags from Docker Hub
+ while (url) {
+ const response = await fetch(url, {
+ method: "GET",
+ headers: { "Content-Type": "application/json" },
+ });
- const data = (await response.json()) as {
- next: string | null;
- results: { digest: string; name: string }[];
- };
+ const data = (await response.json()) as {
+ next: string | null;
+ results: { digest: string; name: string }[];
+ };
- allResults = allResults.concat(data.results);
- url = data?.next;
- }
+ allResults = allResults.concat(data.results);
+ url = data?.next;
+ }
- const imageTag = getDokployImageTag();
- const searchedDigest = allResults.find((t) => t.name === imageTag)?.digest;
+ const currentImageTag = getDokployImageTag();
- if (!searchedDigest) {
- return DEFAULT_UPDATE_DATA;
- }
+ // Special handling for canary and feature branches
+ // For development versions (canary/feature), don't perform update checks
+ // These are unstable versions that change frequently, and users on these
+ // branches are expected to manually manage updates
+ if (currentImageTag === "canary" || currentImageTag === "feature") {
+ const currentDigest = await getServiceImageDigest();
+ const latestDigest = allResults.find(
+ (t) => t.name === currentImageTag,
+ )?.digest;
+ if (!latestDigest) {
+ return DEFAULT_UPDATE_DATA;
+ }
+ if (currentDigest !== latestDigest) {
+ return {
+ latestVersion: currentImageTag,
+ updateAvailable: true,
+ };
+ }
+ return {
+ latestVersion: currentImageTag,
+ updateAvailable: false,
+ };
+ }
- if (imageTag === "latest") {
- const versionedTag = allResults.find(
- (t) => t.digest === searchedDigest && t.name.startsWith("v"),
- );
+ // For stable versions, use semver comparison
+ // Find the "latest" tag and get its digest
+ const latestTag = allResults.find((t) => t.name === "latest");
- if (!versionedTag) {
+ if (!latestTag) {
return DEFAULT_UPDATE_DATA;
}
- const { name: latestVersion, digest } = versionedTag;
- const updateAvailable = digest !== currentDigest;
+ // Find the versioned tag (v0.x.x) that has the same digest as "latest"
+ const latestVersionTag = allResults.find(
+ (t) => t.digest === latestTag.digest && t.name.startsWith("v"),
+ );
- return { latestVersion, updateAvailable };
+ if (!latestVersionTag) {
+ return DEFAULT_UPDATE_DATA;
+ }
+
+ const latestVersion = latestVersionTag.name;
+
+ // Use semver to compare versions for stable releases
+ const cleanedCurrent = semver.clean(currentVersion);
+ const cleanedLatest = semver.clean(latestVersion);
+
+ if (!cleanedCurrent || !cleanedLatest) {
+ return DEFAULT_UPDATE_DATA;
+ }
+
+ // Check if the latest version is greater than the current version
+ const updateAvailable = semver.gt(cleanedLatest, cleanedCurrent);
+
+ return {
+ latestVersion,
+ updateAvailable,
+ };
+ } catch (error) {
+ console.error("Error fetching update data:", error);
+ return DEFAULT_UPDATE_DATA;
}
- const updateAvailable = searchedDigest !== currentDigest;
- return { latestVersion: imageTag, updateAvailable };
};
interface TreeDataItem {
@@ -254,11 +293,22 @@ fi`;
export const reloadDockerResource = async (
resourceName: string,
serverId?: string,
+ version?: string,
) => {
const resourceType = await getDockerResourceType(resourceName, serverId);
let command = "";
if (resourceType === "service") {
- command = `docker service update --force ${resourceName}`;
+ if (resourceName === "dokploy") {
+ const currentImageTag = getDokployImageTag();
+ let imageTag = version;
+ if (currentImageTag === "canary" || currentImageTag === "feature") {
+ imageTag = currentImageTag;
+ }
+
+ command = `docker service update --force --image dokploy/dokploy:${imageTag} ${resourceName}`;
+ } else {
+ command = `docker service update --force ${resourceName}`;
+ }
} else if (resourceType === "standalone") {
command = `docker restart ${resourceName}`;
} else {
diff --git a/packages/server/src/services/web-server-settings.ts b/packages/server/src/services/web-server-settings.ts
new file mode 100644
index 000000000..289d119c9
--- /dev/null
+++ b/packages/server/src/services/web-server-settings.ts
@@ -0,0 +1,44 @@
+import { db } from "@dokploy/server/db";
+import { webServerSettings } from "@dokploy/server/db/schema";
+import { eq } from "drizzle-orm";
+
+/**
+ * Get the web server settings (singleton - only one row should exist)
+ */
+export const getWebServerSettings = async () => {
+ const settings = await db.query.webServerSettings.findFirst({
+ orderBy: (settings, { asc }) => [asc(settings.createdAt)],
+ });
+
+ if (!settings) {
+ // Create default settings if none exist
+ const [newSettings] = await db
+ .insert(webServerSettings)
+ .values({})
+ .returning();
+
+ return newSettings;
+ }
+
+ return settings;
+};
+
+/**
+ * Update web server settings
+ */
+export const updateWebServerSettings = async (
+ updates: Partial,
+) => {
+ const current = await getWebServerSettings();
+
+ const [updated] = await db
+ .update(webServerSettings)
+ .set({
+ ...updates,
+ updatedAt: new Date(),
+ })
+ .where(eq(webServerSettings.id, current?.id ?? ""))
+ .returning();
+
+ return updated;
+};
diff --git a/packages/server/src/setup/monitoring-setup.ts b/packages/server/src/setup/monitoring-setup.ts
index 20055be9a..287894c99 100644
--- a/packages/server/src/setup/monitoring-setup.ts
+++ b/packages/server/src/setup/monitoring-setup.ts
@@ -1,7 +1,7 @@
import { findServerById } from "@dokploy/server/services/server";
+import { getWebServerSettings } from "@dokploy/server/services/web-server-settings";
import type { ContainerCreateOptions } from "dockerode";
import { IS_CLOUD } from "../constants";
-import { findUserById } from "../services/admin";
import { getDokployImageTag } from "../services/settings";
import { pullImage, pullRemoteImage } from "../utils/docker/utils";
import { execAsync, execAsyncRemote } from "../utils/process/execAsync";
@@ -83,8 +83,8 @@ export const setupMonitoring = async (serverId: string) => {
}
};
-export const setupWebMonitoring = async (userId: string) => {
- const user = await findUserById(userId);
+export const setupWebMonitoring = async () => {
+ const webServerSettings = await getWebServerSettings();
const containerName = "dokploy-monitoring";
let imageName = "dokploy/monitoring:latest";
@@ -99,7 +99,7 @@ export const setupWebMonitoring = async (userId: string) => {
const settings: ContainerCreateOptions = {
name: containerName,
- Env: [`METRICS_CONFIG=${JSON.stringify(user?.metricsConfig)}`],
+ Env: [`METRICS_CONFIG=${JSON.stringify(webServerSettings?.metricsConfig)}`],
Image: imageName,
HostConfig: {
// Memory: 100 * 1024 * 1024, // 100MB en bytes
@@ -110,9 +110,9 @@ export const setupWebMonitoring = async (userId: string) => {
Name: "always",
},
PortBindings: {
- [`${user?.metricsConfig?.server?.port}/tcp`]: [
+ [`${webServerSettings?.metricsConfig?.server?.port}/tcp`]: [
{
- HostPort: user?.metricsConfig?.server?.port.toString(),
+ HostPort: webServerSettings?.metricsConfig?.server?.port.toString(),
},
],
},
@@ -126,7 +126,7 @@ export const setupWebMonitoring = async (userId: string) => {
// NetworkMode: "host",
},
ExposedPorts: {
- [`${user?.metricsConfig?.server?.port}/tcp`]: {},
+ [`${webServerSettings?.metricsConfig?.server?.port}/tcp`]: {},
},
};
const docker = await getRemoteDocker();
diff --git a/packages/server/src/setup/server-setup.ts b/packages/server/src/setup/server-setup.ts
index 54e740583..32e5e4a7e 100644
--- a/packages/server/src/setup/server-setup.ts
+++ b/packages/server/src/setup/server-setup.ts
@@ -1,10 +1,14 @@
import path from "node:path";
-import { paths } from "@dokploy/server/constants";
+import { IS_CLOUD, paths } from "@dokploy/server/constants";
+import { getDokployUrl } from "@dokploy/server/services/admin";
import {
createServerDeployment,
updateDeploymentStatus,
} from "@dokploy/server/services/deployment";
-import { findServerById } from "@dokploy/server/services/server";
+import {
+ findServerById,
+ updateServerById,
+} from "@dokploy/server/services/server";
import {
getDefaultMiddlewares,
getDefaultServerTraefikConfig,
@@ -16,6 +20,15 @@ import {
import slug from "slugify";
import { Client } from "ssh2";
import { recreateDirectory } from "../utils/filesystem/directory";
+import { setupMonitoring } from "./monitoring-setup";
+
+const generateToken = () => {
+ const array = new Uint8Array(64);
+ crypto.getRandomValues(array);
+ return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join(
+ "",
+ );
+};
export const slugify = (text: string | undefined) => {
if (!text) {
@@ -59,6 +72,29 @@ export const serverSetup = async (
);
await installRequirements(serverId, onData);
+ if (IS_CLOUD) {
+ onData?.("\nConfiguring Monitoring: 🔄\n");
+
+ const baseUrl = await getDokployUrl();
+ const token = generateToken();
+ const urlCallback = `${baseUrl}/api/trpc/notification.receiveNotification`;
+
+ // Update server with monitoring configuration
+ await updateServerById(serverId, {
+ metricsConfig: {
+ server: {
+ ...server.metricsConfig.server,
+ token: token,
+ urlCallback: urlCallback,
+ },
+ containers: server.metricsConfig.containers,
+ },
+ });
+
+ await setupMonitoring(serverId);
+ onData?.("\nMonitoring Configured: ✅\n");
+ }
+
await updateDeploymentStatus(deployment.deploymentId, "done");
onData?.("\nSetup Server: ✅\n");
@@ -629,7 +665,7 @@ const installNixpacks = () => `
if command_exists nixpacks; then
echo "Nixpacks already installed ✅"
else
- export NIXPACKS_VERSION=1.39.0
+ export NIXPACKS_VERSION=1.41.0
bash -c "$(curl -fsSL https://nixpacks.com/install.sh)"
echo "Nixpacks version $NIXPACKS_VERSION installed ✅"
fi
@@ -639,7 +675,7 @@ const installRailpack = () => `
if command_exists railpack; then
echo "Railpack already installed ✅"
else
- export RAILPACK_VERSION=0.2.2
+ export RAILPACK_VERSION=0.15.4
bash -c "$(curl -fsSL https://railpack.com/install.sh)"
echo "Railpack version $RAILPACK_VERSION installed ✅"
fi
@@ -653,8 +689,8 @@ const installBuildpacks = () => `
if command_exists pack; then
echo "Buildpacks already installed ✅"
else
- BUILDPACKS_VERSION=0.35.0
- curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.35.0/pack-v$BUILDPACKS_VERSION-linux$SUFFIX.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack
+ BUILDPACKS_VERSION=0.39.1
+ curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.39.1/pack-v$BUILDPACKS_VERSION-linux$SUFFIX.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack
echo "Buildpacks version $BUILDPACKS_VERSION installed ✅"
fi
`;
diff --git a/packages/server/src/setup/traefik-setup.ts b/packages/server/src/setup/traefik-setup.ts
index 73cff0b1c..ad3abe33b 100644
--- a/packages/server/src/setup/traefik-setup.ts
+++ b/packages/server/src/setup/traefik-setup.ts
@@ -20,7 +20,7 @@ export const TRAEFIK_PORT =
Number.parseInt(process.env.TRAEFIK_PORT!, 10) || 80;
export const TRAEFIK_HTTP3_PORT =
Number.parseInt(process.env.TRAEFIK_HTTP3_PORT!, 10) || 443;
-export const TRAEFIK_VERSION = process.env.TRAEFIK_VERSION || "3.6.1";
+export const TRAEFIK_VERSION = process.env.TRAEFIK_VERSION || "3.6.4";
export interface TraefikOptions {
env?: string[];
diff --git a/packages/server/src/templates/processors.ts b/packages/server/src/templates/processors.ts
index 9e73fb1f4..ce1553095 100644
--- a/packages/server/src/templates/processors.ts
+++ b/packages/server/src/templates/processors.ts
@@ -170,12 +170,12 @@ export function processValue(
}
// If not a utility function, try to get from variables
- return variables[varName] || match;
+ return varName in variables ? (variables[varName] ?? match) : match;
});
// Then replace any remaining ${var} with their values from variables
processedValue = processedValue.replace(/\${([^}]+)}/g, (match, varName) => {
- return variables[varName] || match;
+ return varName in variables ? (variables[varName] ?? match) : match;
});
return processedValue;
diff --git a/packages/server/src/utils/access-log/handler.ts b/packages/server/src/utils/access-log/handler.ts
index 237a68f17..13a52c4b1 100644
--- a/packages/server/src/utils/access-log/handler.ts
+++ b/packages/server/src/utils/access-log/handler.ts
@@ -1,6 +1,8 @@
import { paths } from "@dokploy/server/constants";
-import { findOwner } from "@dokploy/server/services/admin";
-import { updateUser } from "@dokploy/server/services/user";
+import {
+ getWebServerSettings,
+ updateWebServerSettings,
+} from "@dokploy/server/services/web-server-settings";
import { scheduledJobs, scheduleJob } from "node-schedule";
import { execAsync } from "../process/execAsync";
@@ -29,12 +31,9 @@ export const startLogCleanup = async (
}
});
- const owner = await findOwner();
- if (owner) {
- await updateUser(owner.user.id, {
- logCleanupCron: cronExpression,
- });
- }
+ await updateWebServerSettings({
+ logCleanupCron: cronExpression,
+ });
return true;
} catch (error) {
@@ -51,12 +50,9 @@ export const stopLogCleanup = async (): Promise => {
}
// Update database
- const owner = await findOwner();
- if (owner) {
- await updateUser(owner.user.id, {
- logCleanupCron: null,
- });
- }
+ await updateWebServerSettings({
+ logCleanupCron: null,
+ });
return true;
} catch (error) {
@@ -69,8 +65,8 @@ export const getLogCleanupStatus = async (): Promise<{
enabled: boolean;
cronExpression: string | null;
}> => {
- const owner = await findOwner();
- const cronExpression = owner?.user.logCleanupCron ?? null;
+ const settings = await getWebServerSettings();
+ const cronExpression = settings?.logCleanupCron ?? null;
return {
enabled: cronExpression !== null,
cronExpression,
diff --git a/packages/server/src/utils/ai/select-ai-provider.ts b/packages/server/src/utils/ai/select-ai-provider.ts
index 1967ff834..8b3173137 100644
--- a/packages/server/src/utils/ai/select-ai-provider.ts
+++ b/packages/server/src/utils/ai/select-ai-provider.ts
@@ -71,8 +71,9 @@ export function selectAIProvider(config: { apiUrl: string; apiKey: string }) {
return createOpenAICompatible({
name: "gemini",
baseURL: config.apiUrl,
- queryParams: { key: config.apiKey },
- headers: {},
+ headers: {
+ Authorization: `Bearer ${config.apiKey}`,
+ },
});
case "custom":
return createOpenAICompatible({
diff --git a/packages/server/src/utils/backups/index.ts b/packages/server/src/utils/backups/index.ts
index dfdcd2cac..14d38ddf0 100644
--- a/packages/server/src/utils/backups/index.ts
+++ b/packages/server/src/utils/backups/index.ts
@@ -2,6 +2,7 @@ import path from "node:path";
import { member } from "@dokploy/server/db/schema";
import type { BackupSchedule } from "@dokploy/server/services/backup";
import { getAllServers } from "@dokploy/server/services/server";
+import { getWebServerSettings } from "@dokploy/server/services/web-server-settings";
import { eq } from "drizzle-orm";
import { scheduleJob } from "node-schedule";
import { db } from "../../db/index";
@@ -25,7 +26,9 @@ export const initCronJobs = async () => {
return;
}
- if (admin?.user?.enableDockerCleanup) {
+ const webServerSettings = await getWebServerSettings();
+
+ if (webServerSettings?.enableDockerCleanup) {
scheduleJob("docker-cleanup", "0 0 * * *", async () => {
console.log(
`Docker Cleanup ${new Date().toLocaleString()}] Running docker cleanup`,
@@ -82,9 +85,12 @@ export const initCronJobs = async () => {
}
}
- if (admin?.user?.logCleanupCron) {
- console.log("Starting log requests cleanup", admin.user.logCleanupCron);
- await startLogCleanup(admin.user.logCleanupCron);
+ if (webServerSettings?.logCleanupCron) {
+ console.log(
+ "Starting log requests cleanup",
+ webServerSettings.logCleanupCron,
+ );
+ await startLogCleanup(webServerSettings.logCleanupCron);
}
};
diff --git a/packages/server/src/utils/builders/compose.ts b/packages/server/src/utils/builders/compose.ts
index fe5417ea5..5eede59d5 100644
--- a/packages/server/src/utils/builders/compose.ts
+++ b/packages/server/src/utils/builders/compose.ts
@@ -90,7 +90,7 @@ export const createCommand = (compose: ComposeNested) => {
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`;
+ command = `stack deploy -c ${path} ${appName} --prune --with-registry-auth`;
}
return command;
@@ -134,6 +134,7 @@ const getExportEnvCommand = (compose: ComposeNested) => {
const envVars = getEnviromentVariablesObject(
compose.env,
compose.environment.project.env,
+ compose.environment.env,
);
const exports = Object.entries(envVars)
.map(([key, value]) => `${key}=${quote([value])}`)
diff --git a/packages/server/src/utils/cluster/upload.ts b/packages/server/src/utils/cluster/upload.ts
index e2cf4a4a4..aa014a05c 100644
--- a/packages/server/src/utils/cluster/upload.ts
+++ b/packages/server/src/utils/cluster/upload.ts
@@ -117,7 +117,7 @@ const getRegistryCommands = (
): string => {
return `
echo "📦 [Enabled Registry] Uploading image to '${registry.registryType}' | '${registryTag}'" ;
-echo "${registry.password}" | docker login ${registry.registryUrl} -u ${registry.username} --password-stdin || {
+echo "${registry.password}" | docker login ${registry.registryUrl} -u '${registry.username}' --password-stdin || {
echo "❌ DockerHub Failed" ;
exit 1;
}
diff --git a/packages/server/src/utils/databases/mongo.ts b/packages/server/src/utils/databases/mongo.ts
index 9cb8a69f4..556878fe2 100644
--- a/packages/server/src/utils/databases/mongo.ts
+++ b/packages/server/src/utils/databases/mongo.ts
@@ -54,7 +54,7 @@ if [ "$REPLICA_STATUS" != "1" ]; then
mongosh --eval '
rs.initiate({
_id: "rs0",
- members: [{ _id: 0, host: "localhost:27017", priority: 1 }]
+ members: [{ _id: 0, host: "${appName}:27017", priority: 1 }]
});
// Wait for the replica set to initialize
diff --git a/packages/server/src/utils/docker/utils.ts b/packages/server/src/utils/docker/utils.ts
index 5c7326e2d..045040061 100644
--- a/packages/server/src/utils/docker/utils.ts
+++ b/packages/server/src/utils/docker/utils.ts
@@ -146,17 +146,17 @@ export const getContainerByName = (name: string): Promise => {
};
/**
- * Docker commands passed through this method are held during Docker's build or pull process.
+ * Docker commands sent using this method are held in a hold when Docker is busy.
*
* https://github.com/Dokploy/dokploy/pull/3064
- * https://github.com/fir4tozden
*/
-export const dockerSafeExec = (exec: string) => `CHECK_INTERVAL=10
+export const dockerSafeExec = (exec: string) => `
+CHECK_INTERVAL=10
echo "Preparing for execution..."
while true; do
- PROCESSES=$(ps aux | grep -E "docker build|docker pull" | grep -v grep)
+ PROCESSES=$(ps aux | grep -E "^.*docker [A-Za-z]" | grep -v grep)
if [ -z "$PROCESSES" ]; then
echo "Docker is idle. Starting execution..."
@@ -169,11 +169,20 @@ done
${exec}
-echo "Execution completed."`;
+echo "Execution completed."
+`;
+
+const cleanupCommands = {
+ containers: "docker container prune --force",
+ images: "docker image prune --all --force",
+ volumes: "docker volume prune --all --force",
+ builders: "docker builder prune --all --force",
+ system: "docker system prune --all --force",
+};
export const cleanupContainers = async (serverId?: string) => {
try {
- const command = "docker container prune --force";
+ const command = cleanupCommands.containers;
if (serverId) {
await execAsyncRemote(serverId, dockerSafeExec(command));
@@ -189,7 +198,7 @@ export const cleanupContainers = async (serverId?: string) => {
export const cleanupImages = async (serverId?: string) => {
try {
- const command = "docker image prune --all --force";
+ const command = cleanupCommands.images;
if (serverId) {
await execAsyncRemote(serverId, dockerSafeExec(command));
@@ -203,7 +212,7 @@ export const cleanupImages = async (serverId?: string) => {
export const cleanupVolumes = async (serverId?: string) => {
try {
- const command = "docker volume prune --all --force";
+ const command = cleanupCommands.volumes;
if (serverId) {
await execAsyncRemote(serverId, dockerSafeExec(command));
@@ -219,7 +228,7 @@ export const cleanupVolumes = async (serverId?: string) => {
export const cleanupBuilders = async (serverId?: string) => {
try {
- const command = "docker builder prune --all --force";
+ const command = cleanupCommands.builders;
if (serverId) {
await execAsyncRemote(serverId, dockerSafeExec(command));
@@ -235,7 +244,7 @@ export const cleanupBuilders = async (serverId?: string) => {
export const cleanupSystem = async (serverId?: string) => {
try {
- const command = "docker system prune --all --force";
+ const command = cleanupCommands.system;
if (serverId) {
await execAsyncRemote(serverId, dockerSafeExec(command));
@@ -249,11 +258,63 @@ export const cleanupSystem = async (serverId?: string) => {
}
};
+/**
+ * Volume cleanup should always be performed manually by the user. The reason is that during automatic cleanup, a volume may be deleted due to a stopped container, which is a dangerous situation.
+ *
+ * https://github.com/Dokploy/dokploy/pull/3267
+ */
+const excludedCleanupAllCommands: (keyof typeof cleanupCommands)[] = [
+ "volumes",
+];
+
export const cleanupAll = async (serverId?: string) => {
- await cleanupContainers(serverId);
- await cleanupImages(serverId);
- await cleanupBuilders(serverId);
- await cleanupSystem(serverId);
+ for (const [key, command] of Object.entries(cleanupCommands) as [
+ keyof typeof cleanupCommands,
+ string,
+ ][]) {
+ if (excludedCleanupAllCommands.includes(key)) continue;
+
+ try {
+ if (serverId) {
+ await execAsyncRemote(serverId, dockerSafeExec(command));
+ } else {
+ await execAsync(dockerSafeExec(command));
+ }
+ } catch {}
+ }
+};
+
+export const cleanupAllBackground = async (serverId?: string) => {
+ Promise.allSettled(
+ (
+ Object.entries(cleanupCommands) as [
+ keyof typeof cleanupCommands,
+ string,
+ ][]
+ )
+ .filter(([key]) => !excludedCleanupAllCommands.includes(key))
+ .map(async ([, command]) => {
+ if (serverId) {
+ await execAsyncRemote(serverId, dockerSafeExec(command));
+ } else {
+ await execAsync(dockerSafeExec(command));
+ }
+ }),
+ )
+ .then((results) => {
+ const failed = results.filter((r) => r.status === "rejected");
+ if (failed.length > 0) {
+ console.error(`Docker cleanup: ${failed.length} operations failed`);
+ } else {
+ console.log("Docker cleanup completed successfully");
+ }
+ })
+ .catch((error) => console.error("Error in cleanup:", error));
+
+ return {
+ status: "scheduled",
+ message: "Docker cleanup has been initiated in the background",
+ };
};
export const startService = async (appName: string) => {
diff --git a/packages/server/src/utils/notifications/build-error.ts b/packages/server/src/utils/notifications/build-error.ts
index f05fa8134..3c2497324 100644
--- a/packages/server/src/utils/notifications/build-error.ts
+++ b/packages/server/src/utils/notifications/build-error.ts
@@ -11,6 +11,7 @@ import {
sendGotifyNotification,
sendLarkNotification,
sendNtfyNotification,
+ sendPushoverNotification,
sendSlackNotification,
sendTelegramNotification,
} from "./utils";
@@ -48,12 +49,22 @@ export const sendBuildErrorNotifications = async ({
ntfy: true,
custom: true,
lark: true,
+ pushover: true,
},
});
for (const notification of notificationList) {
- const { email, discord, telegram, slack, gotify, ntfy, custom, lark } =
- notification;
+ const {
+ email,
+ discord,
+ telegram,
+ slack,
+ gotify,
+ ntfy,
+ custom,
+ lark,
+ pushover,
+ } = notification;
try {
if (email) {
const template = await renderAsync(
@@ -349,6 +360,14 @@ export const sendBuildErrorNotifications = async ({
},
});
}
+
+ if (pushover) {
+ await sendPushoverNotification(
+ pushover,
+ "Build Failed",
+ `Project: ${projectName}\nApplication: ${applicationName}\nType: ${applicationType}\nDate: ${date.toLocaleString()}\nError: ${errorMessage}`,
+ );
+ }
} catch (error) {
console.log(error);
}
diff --git a/packages/server/src/utils/notifications/build-success.ts b/packages/server/src/utils/notifications/build-success.ts
index e120d107b..d1bc04796 100644
--- a/packages/server/src/utils/notifications/build-success.ts
+++ b/packages/server/src/utils/notifications/build-success.ts
@@ -12,6 +12,7 @@ import {
sendGotifyNotification,
sendLarkNotification,
sendNtfyNotification,
+ sendPushoverNotification,
sendSlackNotification,
sendTelegramNotification,
} from "./utils";
@@ -51,12 +52,22 @@ export const sendBuildSuccessNotifications = async ({
ntfy: true,
custom: true,
lark: true,
+ pushover: true,
},
});
for (const notification of notificationList) {
- const { email, discord, telegram, slack, gotify, ntfy, custom, lark } =
- notification;
+ const {
+ email,
+ discord,
+ telegram,
+ slack,
+ gotify,
+ ntfy,
+ custom,
+ lark,
+ pushover,
+ } = notification;
try {
if (email) {
const template = await renderAsync(
@@ -363,6 +374,14 @@ export const sendBuildSuccessNotifications = async ({
},
});
}
+
+ if (pushover) {
+ await sendPushoverNotification(
+ pushover,
+ "Build Success",
+ `Project: ${projectName}\nApplication: ${applicationName}\nEnvironment: ${environmentName}\nType: ${applicationType}\nDate: ${date.toLocaleString()}`,
+ );
+ }
} catch (error) {
console.log(error);
}
diff --git a/packages/server/src/utils/notifications/database-backup.ts b/packages/server/src/utils/notifications/database-backup.ts
index e0754b715..1b2b49bf1 100644
--- a/packages/server/src/utils/notifications/database-backup.ts
+++ b/packages/server/src/utils/notifications/database-backup.ts
@@ -11,6 +11,7 @@ import {
sendGotifyNotification,
sendLarkNotification,
sendNtfyNotification,
+ sendPushoverNotification,
sendSlackNotification,
sendTelegramNotification,
} from "./utils";
@@ -48,12 +49,22 @@ export const sendDatabaseBackupNotifications = async ({
ntfy: true,
custom: true,
lark: true,
+ pushover: true,
},
});
for (const notification of notificationList) {
- const { email, discord, telegram, slack, gotify, ntfy, custom, lark } =
- notification;
+ const {
+ email,
+ discord,
+ telegram,
+ slack,
+ gotify,
+ ntfy,
+ custom,
+ lark,
+ pushover,
+ } = notification;
try {
if (email) {
const template = await renderAsync(
@@ -377,6 +388,14 @@ export const sendDatabaseBackupNotifications = async ({
},
});
}
+
+ if (pushover) {
+ await sendPushoverNotification(
+ pushover,
+ `Database Backup ${type === "success" ? "Successful" : "Failed"}`,
+ `Project: ${projectName}\nApplication: ${applicationName}\nDatabase: ${databaseType}\nDatabase Name: ${databaseName}\nDate: ${date.toLocaleString()}${type === "error" && errorMessage ? `\nError: ${errorMessage}` : ""}`,
+ );
+ }
} catch (error) {
console.log(error);
}
diff --git a/packages/server/src/utils/notifications/docker-cleanup.ts b/packages/server/src/utils/notifications/docker-cleanup.ts
index 061f892ff..834ff489c 100644
--- a/packages/server/src/utils/notifications/docker-cleanup.ts
+++ b/packages/server/src/utils/notifications/docker-cleanup.ts
@@ -11,6 +11,7 @@ import {
sendGotifyNotification,
sendLarkNotification,
sendNtfyNotification,
+ sendPushoverNotification,
sendSlackNotification,
sendTelegramNotification,
} from "./utils";
@@ -35,12 +36,22 @@ export const sendDockerCleanupNotifications = async (
ntfy: true,
custom: true,
lark: true,
+ pushover: true,
},
});
for (const notification of notificationList) {
- const { email, discord, telegram, slack, gotify, ntfy, custom, lark } =
- notification;
+ const {
+ email,
+ discord,
+ telegram,
+ slack,
+ gotify,
+ ntfy,
+ custom,
+ lark,
+ pushover,
+ } = notification;
try {
if (email) {
const template = await renderAsync(
@@ -230,6 +241,14 @@ export const sendDockerCleanupNotifications = async (
},
});
}
+
+ if (pushover) {
+ await sendPushoverNotification(
+ pushover,
+ "Docker Cleanup",
+ `Date: ${date.toLocaleString()}\nMessage: ${message}`,
+ );
+ }
} catch (error) {
console.log(error);
}
diff --git a/packages/server/src/utils/notifications/dokploy-restart.ts b/packages/server/src/utils/notifications/dokploy-restart.ts
index 095ca4a6a..f93f31ac5 100644
--- a/packages/server/src/utils/notifications/dokploy-restart.ts
+++ b/packages/server/src/utils/notifications/dokploy-restart.ts
@@ -11,6 +11,7 @@ import {
sendGotifyNotification,
sendLarkNotification,
sendNtfyNotification,
+ sendPushoverNotification,
sendSlackNotification,
sendTelegramNotification,
} from "./utils";
@@ -29,12 +30,22 @@ export const sendDokployRestartNotifications = async () => {
ntfy: true,
custom: true,
lark: true,
+ pushover: true,
},
});
for (const notification of notificationList) {
- const { email, discord, telegram, slack, gotify, ntfy, custom, lark } =
- notification;
+ const {
+ email,
+ discord,
+ telegram,
+ slack,
+ gotify,
+ ntfy,
+ custom,
+ lark,
+ pushover,
+ } = notification;
try {
if (email) {
@@ -219,6 +230,14 @@ export const sendDokployRestartNotifications = async () => {
},
});
}
+
+ if (pushover) {
+ await sendPushoverNotification(
+ pushover,
+ "Dokploy Server Restarted",
+ `Date: ${date.toLocaleString()}`,
+ );
+ }
} catch (error) {
console.log(error);
}
diff --git a/packages/server/src/utils/notifications/server-threshold.ts b/packages/server/src/utils/notifications/server-threshold.ts
index cb3484c55..bafe95cfa 100644
--- a/packages/server/src/utils/notifications/server-threshold.ts
+++ b/packages/server/src/utils/notifications/server-threshold.ts
@@ -5,6 +5,7 @@ import {
sendCustomNotification,
sendDiscordNotification,
sendLarkNotification,
+ sendPushoverNotification,
sendSlackNotification,
sendTelegramNotification,
} from "./utils";
@@ -38,6 +39,7 @@ export const sendServerThresholdNotifications = async (
slack: true,
custom: true,
lark: true,
+ pushover: true,
},
});
@@ -45,7 +47,7 @@ export const sendServerThresholdNotifications = async (
const typeColor = 0xff0000; // Rojo para indicar alerta
for (const notification of notificationList) {
- const { discord, telegram, slack, custom, lark } = notification;
+ const { discord, telegram, slack, custom, lark, pushover } = notification;
if (discord) {
const decorate = (decoration: string, text: string) =>
@@ -266,5 +268,13 @@ export const sendServerThresholdNotifications = async (
},
});
}
+
+ if (pushover) {
+ await sendPushoverNotification(
+ pushover,
+ `Server ${payload.Type} Alert`,
+ `Server: ${payload.ServerName}\nType: ${payload.Type}\nCurrent: ${payload.Value.toFixed(2)}%\nThreshold: ${payload.Threshold.toFixed(2)}%\nMessage: ${payload.Message}\nTime: ${date.toLocaleString()}`,
+ );
+ }
}
};
diff --git a/packages/server/src/utils/notifications/utils.ts b/packages/server/src/utils/notifications/utils.ts
index 02a226f23..170b90e8a 100644
--- a/packages/server/src/utils/notifications/utils.ts
+++ b/packages/server/src/utils/notifications/utils.ts
@@ -5,6 +5,7 @@ import type {
gotify,
lark,
ntfy,
+ pushover,
slack,
telegram,
} from "@dokploy/server/db/schema";
@@ -223,3 +224,33 @@ export const sendLarkNotification = async (
console.log(err);
}
};
+
+export const sendPushoverNotification = async (
+ connection: typeof pushover.$inferInsert,
+ title: string,
+ message: string,
+) => {
+ const formData = new URLSearchParams();
+ formData.append("token", connection.apiToken);
+ formData.append("user", connection.userKey);
+ formData.append("title", title);
+ formData.append("message", message);
+ formData.append("priority", connection.priority?.toString() || "0");
+
+ // For emergency priority (2), retry and expire are required
+ if (connection.priority === 2) {
+ formData.append("retry", connection.retry?.toString() || "30");
+ formData.append("expire", connection.expire?.toString() || "3600");
+ }
+
+ const response = await fetch("https://api.pushover.net/1/messages.json", {
+ method: "POST",
+ body: formData,
+ });
+
+ if (!response.ok) {
+ throw new Error(
+ `Failed to send Pushover notification: ${response.statusText}`,
+ );
+ }
+};
diff --git a/packages/server/src/utils/notifications/volume-backup.ts b/packages/server/src/utils/notifications/volume-backup.ts
index bec85f399..44e2b5fb3 100644
--- a/packages/server/src/utils/notifications/volume-backup.ts
+++ b/packages/server/src/utils/notifications/volume-backup.ts
@@ -9,6 +9,7 @@ import {
sendEmailNotification,
sendGotifyNotification,
sendNtfyNotification,
+ sendPushoverNotification,
sendSlackNotification,
sendTelegramNotification,
} from "./utils";
@@ -53,11 +54,13 @@ export const sendVolumeBackupNotifications = async ({
slack: true,
gotify: true,
ntfy: true,
+ pushover: true,
},
});
for (const notification of notificationList) {
- const { email, discord, telegram, slack, gotify, ntfy } = notification;
+ const { email, discord, telegram, slack, gotify, ntfy, pushover } =
+ notification;
if (email) {
const subject = `Volume Backup ${type === "success" ? "Successful" : "Failed"} - ${applicationName}`;
@@ -270,5 +273,13 @@ export const sendVolumeBackupNotifications = async ({
],
});
}
+
+ if (pushover) {
+ await sendPushoverNotification(
+ pushover,
+ `Volume Backup ${type === "success" ? "Successful" : "Failed"}`,
+ `Project: ${projectName}\nApplication: ${applicationName}\nVolume: ${volumeName}\nService Type: ${serviceType}${backupSize ? `\nBackup Size: ${backupSize}` : ""}\nDate: ${date.toLocaleString()}${type === "error" && errorMessage ? `\nError: ${errorMessage}` : ""}`,
+ );
+ }
}
};
diff --git a/packages/server/src/utils/providers/bitbucket.ts b/packages/server/src/utils/providers/bitbucket.ts
index 2248baaaf..57d6de3bc 100644
--- a/packages/server/src/utils/providers/bitbucket.ts
+++ b/packages/server/src/utils/providers/bitbucket.ts
@@ -79,6 +79,7 @@ export const getBitbucketHeaders = (bitbucketProvider: Bitbucket) => {
interface CloneBitbucketRepository {
appName: string;
bitbucketRepository: string | null;
+ bitbucketRepositorySlug?: string | null;
bitbucketOwner: string | null;
bitbucketBranch: string | null;
bitbucketId: string | null;
@@ -117,7 +118,8 @@ export const cloneBitbucketRepository = async ({
const outputPath = join(basePath, appName, "code");
command += `rm -rf ${outputPath};`;
command += `mkdir -p ${outputPath};`;
- const repoclone = `bitbucket.org/${bitbucketOwner}/${bitbucketRepository}.git`;
+ const repoToUse = entity.bitbucketRepositorySlug || bitbucketRepository;
+ const repoclone = `bitbucket.org/${bitbucketOwner}/${repoToUse}.git`;
const cloneUrl = getBitbucketCloneUrl(bitbucket, repoclone);
command += `echo "Cloning Repo ${repoclone} to ${outputPath}: ✅";`;
command += `git clone --branch ${bitbucketBranch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} ${cloneUrl} ${outputPath} --progress;`;
@@ -137,6 +139,7 @@ export const getBitbucketRepositories = async (bitbucketId?: string) => {
let repositories: {
name: string;
url: string;
+ slug: string;
owner: { username: string };
}[] = [];
@@ -159,6 +162,7 @@ export const getBitbucketRepositories = async (bitbucketId?: string) => {
const mappedData = data.values.map((repo: any) => ({
name: repo.name,
url: repo.links.html.href,
+ slug: repo.slug,
owner: {
username: repo.workspace.slug,
},
diff --git a/packages/server/src/utils/schedules/utils.ts b/packages/server/src/utils/schedules/utils.ts
index 1a43d2af6..64657c9a6 100644
--- a/packages/server/src/utils/schedules/utils.ts
+++ b/packages/server/src/utils/schedules/utils.ts
@@ -1,6 +1,6 @@
import { createWriteStream } from "node:fs";
import path from "node:path";
-import { paths } from "@dokploy/server/constants";
+import { IS_CLOUD, paths } from "@dokploy/server/constants";
import type { Schedule } from "@dokploy/server/db/schema/schedule";
import {
createDeploymentSchedule,
@@ -93,6 +93,13 @@ export const runCommand = async (scheduleId: string) => {
const writeStream = createWriteStream(deployment.logPath, { flags: "a" });
try {
+ if (IS_CLOUD) {
+ writeStream.write(
+ "This feature is not available in the cloud version.",
+ );
+ writeStream.end();
+ return;
+ }
writeStream.write(
`docker exec ${containerId} ${shellType} -c ${command}\n`,
);
diff --git a/packages/server/src/utils/traefik/web-server.ts b/packages/server/src/utils/traefik/web-server.ts
index 0209d9a21..e5315dab4 100644
--- a/packages/server/src/utils/traefik/web-server.ts
+++ b/packages/server/src/utils/traefik/web-server.ts
@@ -1,7 +1,7 @@
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { paths } from "@dokploy/server/constants";
-import type { User } from "@dokploy/server/services/user";
+import type { webServerSettings } from "@dokploy/server/db/schema/web-server-settings";
import { parse, stringify } from "yaml";
import {
loadOrCreateConfig,
@@ -12,10 +12,10 @@ import type { FileConfig } from "./file-types";
import type { MainTraefikConfig } from "./types";
export const updateServerTraefik = (
- user: User | null,
+ settings: typeof webServerSettings.$inferSelect | null,
newHost: string | null,
) => {
- const { https, certificateType } = user || {};
+ const { https, certificateType } = settings || {};
const appName = "dokploy";
const config: FileConfig = loadOrCreateConfig(appName);
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a1d8e5c0d..d5fc7f074 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -15,12 +15,6 @@ importers:
'@biomejs/biome':
specifier: 2.1.1
version: 2.1.1
- '@commitlint/cli':
- specifier: ^19.8.1
- version: 19.8.1(@types/node@18.19.104)(typescript@5.8.3)
- '@commitlint/config-conventional':
- specifier: ^19.8.1
- version: 19.8.1
'@types/node':
specifier: ^18.19.104
version: 18.19.104
@@ -30,9 +24,6 @@ importers:
esbuild:
specifier: 0.20.2
version: 0.20.2
- lefthook:
- specifier: 1.8.4
- version: 1.8.4
lint-staged:
specifier: ^15.5.2
version: 15.5.2
@@ -51,9 +42,6 @@ importers:
'@hono/zod-validator':
specifier: 0.3.0
version: 0.3.0(hono@4.7.10)(zod@3.25.32)
- '@nerimity/mimiqueue':
- specifier: 1.2.3
- version: 1.2.3(redis@4.7.0)
dotenv:
specifier: ^16.4.5
version: 16.4.5
@@ -313,9 +301,6 @@ importers:
fancy-ansi:
specifier: ^0.1.3
version: 0.1.3
- hi-base32:
- specifier: ^0.5.1
- version: 0.5.1
i18next:
specifier: ^23.16.8
version: 23.16.8
@@ -364,9 +349,6 @@ importers:
octokit:
specifier: 3.1.2
version: 3.1.2
- otpauth:
- specifier: ^9.4.0
- version: 9.4.0
pino:
specifier: 9.4.0
version: 9.4.0
@@ -406,9 +388,9 @@ importers:
recharts:
specifier: ^2.15.3
version: 2.15.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
- rotating-file-stream:
- specifier: 3.2.3
- version: 3.2.3
+ semver:
+ specifier: 7.7.3
+ version: 7.7.3
shell-quote:
specifier: ^1.8.1
version: 1.8.2
@@ -494,6 +476,9 @@ importers:
'@types/react-dom':
specifier: 18.3.0
version: 18.3.0
+ '@types/semver':
+ specifier: 7.7.1
+ version: 7.7.1
'@types/shell-quote':
specifier: ^1.7.5
version: 1.7.5
@@ -681,9 +666,6 @@ importers:
drizzle-zod:
specifier: 0.5.1
version: 0.5.1(drizzle-orm@0.39.3(@opentelemetry/api@1.9.0)(@types/pg@8.6.1)(kysely@0.28.7)(postgres@3.4.4))(zod@3.25.32)
- hi-base32:
- specifier: ^0.5.1
- version: 0.5.1
lodash:
specifier: 4.17.21
version: 4.17.21
@@ -708,9 +690,6 @@ importers:
octokit:
specifier: 3.1.2
version: 3.1.2
- otpauth:
- specifier: ^9.4.0
- version: 9.4.0
pino:
specifier: 9.4.0
version: 9.4.0
@@ -732,9 +711,9 @@ importers:
react-dom:
specifier: 18.2.0
version: 18.2.0(react@18.2.0)
- rotating-file-stream:
- specifier: 3.2.3
- version: 3.2.3
+ semver:
+ specifier: 7.7.3
+ version: 7.7.3
shell-quote:
specifier: ^1.8.1
version: 1.8.2
@@ -790,6 +769,9 @@ importers:
'@types/react-dom':
specifier: 18.3.0
version: 18.3.0
+ '@types/semver':
+ specifier: 7.7.1
+ version: 7.7.1
'@types/shell-quote':
specifier: ^1.7.5
version: 1.7.5
@@ -888,14 +870,6 @@ packages:
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
- '@babel/code-frame@7.27.1':
- resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
- engines: {node: '>=6.9.0'}
-
- '@babel/helper-validator-identifier@7.27.1':
- resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
- engines: {node: '>=6.9.0'}
-
'@babel/runtime-corejs3@7.27.3':
resolution: {integrity: sha512-ZYcgrwb+dkWNcDlsTe4fH1CMdqMDSJ5lWFd1by8Si2pI54XcQjte/+ViIPqAk7EAWisaUxvQ89grv+bNX2x8zg==}
engines: {node: '>=6.9.0'}
@@ -1011,75 +985,6 @@ packages:
'@codemirror/view@6.36.8':
resolution: {integrity: sha512-yoRo4f+FdnD01fFt4XpfpMCcCAo9QvZOtbrXExn4SqzH32YC6LgzqxfLZw/r6Ge65xyY03mK/UfUqrVw1gFiFg==}
- '@commitlint/cli@19.8.1':
- resolution: {integrity: sha512-LXUdNIkspyxrlV6VDHWBmCZRtkEVRpBKxi2Gtw3J54cGWhLCTouVD/Q6ZSaSvd2YaDObWK8mDjrz3TIKtaQMAA==}
- engines: {node: '>=v18'}
- hasBin: true
-
- '@commitlint/config-conventional@19.8.1':
- resolution: {integrity: sha512-/AZHJL6F6B/G959CsMAzrPKKZjeEiAVifRyEwXxcT6qtqbPwGw+iQxmNS+Bu+i09OCtdNRW6pNpBvgPrtMr9EQ==}
- engines: {node: '>=v18'}
-
- '@commitlint/config-validator@19.8.1':
- resolution: {integrity: sha512-0jvJ4u+eqGPBIzzSdqKNX1rvdbSU1lPNYlfQQRIFnBgLy26BtC0cFnr7c/AyuzExMxWsMOte6MkTi9I3SQ3iGQ==}
- engines: {node: '>=v18'}
-
- '@commitlint/ensure@19.8.1':
- resolution: {integrity: sha512-mXDnlJdvDzSObafjYrOSvZBwkD01cqB4gbnnFuVyNpGUM5ijwU/r/6uqUmBXAAOKRfyEjpkGVZxaDsCVnHAgyw==}
- engines: {node: '>=v18'}
-
- '@commitlint/execute-rule@19.8.1':
- resolution: {integrity: sha512-YfJyIqIKWI64Mgvn/sE7FXvVMQER/Cd+s3hZke6cI1xgNT/f6ZAz5heND0QtffH+KbcqAwXDEE1/5niYayYaQA==}
- engines: {node: '>=v18'}
-
- '@commitlint/format@19.8.1':
- resolution: {integrity: sha512-kSJj34Rp10ItP+Eh9oCItiuN/HwGQMXBnIRk69jdOwEW9llW9FlyqcWYbHPSGofmjsqeoxa38UaEA5tsbm2JWw==}
- engines: {node: '>=v18'}
-
- '@commitlint/is-ignored@19.8.1':
- resolution: {integrity: sha512-AceOhEhekBUQ5dzrVhDDsbMaY5LqtN8s1mqSnT2Kz1ERvVZkNihrs3Sfk1Je/rxRNbXYFzKZSHaPsEJJDJV8dg==}
- engines: {node: '>=v18'}
-
- '@commitlint/lint@19.8.1':
- resolution: {integrity: sha512-52PFbsl+1EvMuokZXLRlOsdcLHf10isTPlWwoY1FQIidTsTvjKXVXYb7AvtpWkDzRO2ZsqIgPK7bI98x8LRUEw==}
- engines: {node: '>=v18'}
-
- '@commitlint/load@19.8.1':
- resolution: {integrity: sha512-9V99EKG3u7z+FEoe4ikgq7YGRCSukAcvmKQuTtUyiYPnOd9a2/H9Ak1J9nJA1HChRQp9OA/sIKPugGS+FK/k1A==}
- engines: {node: '>=v18'}
-
- '@commitlint/message@19.8.1':
- resolution: {integrity: sha512-+PMLQvjRXiU+Ae0Wc+p99EoGEutzSXFVwQfa3jRNUZLNW5odZAyseb92OSBTKCu+9gGZiJASt76Cj3dLTtcTdg==}
- engines: {node: '>=v18'}
-
- '@commitlint/parse@19.8.1':
- resolution: {integrity: sha512-mmAHYcMBmAgJDKWdkjIGq50X4yB0pSGpxyOODwYmoexxxiUCy5JJT99t1+PEMK7KtsCtzuWYIAXYAiKR+k+/Jw==}
- engines: {node: '>=v18'}
-
- '@commitlint/read@19.8.1':
- resolution: {integrity: sha512-03Jbjb1MqluaVXKHKRuGhcKWtSgh3Jizqy2lJCRbRrnWpcM06MYm8th59Xcns8EqBYvo0Xqb+2DoZFlga97uXQ==}
- engines: {node: '>=v18'}
-
- '@commitlint/resolve-extends@19.8.1':
- resolution: {integrity: sha512-GM0mAhFk49I+T/5UCYns5ayGStkTt4XFFrjjf0L4S26xoMTSkdCf9ZRO8en1kuopC4isDFuEm7ZOm/WRVeElVg==}
- engines: {node: '>=v18'}
-
- '@commitlint/rules@19.8.1':
- resolution: {integrity: sha512-Hnlhd9DyvGiGwjfjfToMi1dsnw1EXKGJNLTcsuGORHz6SS9swRgkBsou33MQ2n51/boIDrbsg4tIBbRpEWK2kw==}
- engines: {node: '>=v18'}
-
- '@commitlint/to-lines@19.8.1':
- resolution: {integrity: sha512-98Mm5inzbWTKuZQr2aW4SReY6WUukdWXuZhrqf1QdKPZBCCsXuG87c+iP0bwtD6DBnmVVQjgp4whoHRVixyPBg==}
- engines: {node: '>=v18'}
-
- '@commitlint/top-level@19.8.1':
- resolution: {integrity: sha512-Ph8IN1IOHPSDhURCSXBz44+CIu+60duFwRsg6HqaISFHQHbmBtxVw4ZrFNIYUzEP7WwrNPxa2/5qJ//NK1FGcw==}
- engines: {node: '>=v18'}
-
- '@commitlint/types@19.8.1':
- resolution: {integrity: sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==}
- engines: {node: '>=v18'}
-
'@dokploy/trpc-openapi@0.0.4':
resolution: {integrity: sha512-a7VKunKu9arq57bP9MPH7ikJuKfT5SILnNy70vMqf1stm5IrqMG3Y7CIFprFe0DZiw3bwjue0KpETIATBftN6w==}
peerDependencies:
@@ -1957,11 +1862,6 @@ packages:
cpu: [x64]
os: [win32]
- '@nerimity/mimiqueue@1.2.3':
- resolution: {integrity: sha512-WPoGe417P+S0FLfl3psRBI5adcAWXb917vCF1qD2yGZ1ggBEnMH6UrUK464gzJEOpAlGt8BBbIp0tgCEazZ47A==}
- peerDependencies:
- redis: ^4.7.0
-
'@next/env@16.0.10':
resolution: {integrity: sha512-8tuaQkyDVgeONQ1MeT9Mkk8pQmZapMKFh5B+OrFUlG3rVmYTXcXlBetBgTurKXGaIZvkoqRT9JL5K3phXcgang==}
@@ -3946,9 +3846,6 @@ packages:
'@types/connect@3.4.38':
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
- '@types/conventional-commits-parser@5.0.1':
- resolution: {integrity: sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==}
-
'@types/d3-array@3.2.1':
resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==}
@@ -4066,6 +3963,9 @@ packages:
'@types/readable-stream@4.0.20':
resolution: {integrity: sha512-eLgbR5KwUh8+6pngBDxS32MymdCsCHnGtwHTrC0GDorbc7NbcnkZAWptDLgZiRk9VRas+B6TyRgPDucq4zRs8g==}
+ '@types/semver@7.7.1':
+ resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==}
+
'@types/shell-quote@1.7.5':
resolution: {integrity: sha512-+UE8GAGRPbJVQDdxi16dgadcBfQ+KG2vgZhV1+3A1XmHbmwcdwhCUwIdy+d3pAGrbvgRoVSjeI9vOWyq376Yzw==}
@@ -4163,10 +4063,6 @@ packages:
'@xterm/xterm@5.5.0':
resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==}
- JSONStream@1.3.5:
- resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==}
- hasBin: true
-
abbrev@1.1.1:
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
@@ -4226,9 +4122,6 @@ packages:
peerDependencies:
zod: ^3.25.76 || ^4
- ajv@8.17.1:
- resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
-
ansi-align@3.0.1:
resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==}
@@ -4291,9 +4184,6 @@ packages:
resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
engines: {node: '>=10'}
- array-ify@1.0.0:
- resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==}
-
array-union@2.1.0:
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
engines: {node: '>=8'}
@@ -4308,9 +4198,6 @@ packages:
assertion-error@1.1.0:
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
- async-await-queue@2.1.4:
- resolution: {integrity: sha512-3DpDtxkKO0O/FPlWbk/CrbexjuSxWm1CH1bXlVNVyMBIkKHhT5D85gzHmGJokG3ibNGWQ7pHBmStxUW/z/0LYQ==}
-
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
@@ -4437,10 +4324,6 @@ packages:
resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
engines: {node: '>= 0.4'}
- callsites@3.1.0:
- resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
- engines: {node: '>=6'}
-
camelcase-css@2.0.1:
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
engines: {node: '>= 6'}
@@ -4609,9 +4492,6 @@ packages:
resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==}
engines: {node: ^12.20.0 || >=14}
- compare-func@2.0.0:
- resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==}
-
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
@@ -4628,19 +4508,6 @@ packages:
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
engines: {node: '>= 0.6'}
- conventional-changelog-angular@7.0.0:
- resolution: {integrity: sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==}
- engines: {node: '>=16'}
-
- conventional-changelog-conventionalcommits@7.0.2:
- resolution: {integrity: sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==}
- engines: {node: '>=16'}
-
- conventional-commits-parser@5.0.0:
- resolution: {integrity: sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==}
- engines: {node: '>=16'}
- hasBin: true
-
cookie-es@1.2.2:
resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==}
@@ -4657,23 +4524,6 @@ packages:
core-js@3.42.0:
resolution: {integrity: sha512-Sz4PP4ZA+Rq4II21qkNqOEDTDrCvcANId3xpIgB34NDkWc3UduWj2dqEtN9yZIq8Dk3HyPI33x9sqqU5C8sr0g==}
- cosmiconfig-typescript-loader@6.1.0:
- resolution: {integrity: sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g==}
- engines: {node: '>=v18'}
- peerDependencies:
- '@types/node': '*'
- cosmiconfig: '>=9'
- typescript: '>=5'
-
- cosmiconfig@9.0.0:
- resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==}
- engines: {node: '>=14'}
- peerDependencies:
- typescript: '>=4.9.5'
- peerDependenciesMeta:
- typescript:
- optional: true
-
cpu-features@0.0.10:
resolution: {integrity: sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==}
engines: {node: '>=10.0.0'}
@@ -4756,10 +4606,6 @@ packages:
resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
engines: {node: '>=12'}
- dargs@8.1.0:
- resolution: {integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==}
- engines: {node: '>=12'}
-
date-fns@3.6.0:
resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
@@ -4903,10 +4749,6 @@ packages:
domutils@3.2.2:
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
- dot-prop@5.3.0:
- resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==}
- engines: {node: '>=8'}
-
dotenv@16.4.5:
resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==}
engines: {node: '>=12'}
@@ -5050,10 +4892,6 @@ packages:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
- env-paths@2.2.1:
- resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
- engines: {node: '>=6'}
-
env-paths@3.0.0:
resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -5062,9 +4900,6 @@ packages:
resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
engines: {node: '>=18'}
- error-ex@1.3.2:
- resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
-
es-define-property@1.0.1:
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
engines: {node: '>= 0.4'}
@@ -5160,9 +4995,6 @@ packages:
fast-deep-equal@2.0.1:
resolution: {integrity: sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==}
- fast-deep-equal@3.1.3:
- resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
-
fast-equals@5.2.2:
resolution: {integrity: sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==}
engines: {node: '>=6.0.0'}
@@ -5181,9 +5013,6 @@ packages:
fast-safe-stringify@2.1.1:
resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
- fast-uri@3.0.6:
- resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==}
-
fastq@1.19.1:
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
@@ -5198,10 +5027,6 @@ packages:
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
engines: {node: '>=8'}
- find-up@7.0.0:
- resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==}
- engines: {node: '>=18'}
-
follow-redirects@1.15.9:
resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
engines: {node: '>=4.0'}
@@ -5311,11 +5136,6 @@ packages:
get-tsconfig@4.10.1:
resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==}
- git-raw-commits@4.0.0:
- resolution: {integrity: sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==}
- engines: {node: '>=16'}
- hasBin: true
-
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
@@ -5332,10 +5152,6 @@ packages:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Glob versions prior to v9 are no longer supported
- global-directory@4.0.1:
- resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==}
- engines: {node: '>=18'}
-
globby@11.1.0:
resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
engines: {node: '>=10'}
@@ -5395,9 +5211,6 @@ packages:
help-me@5.0.0:
resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==}
- hi-base32@0.5.1:
- resolution: {integrity: sha512-EmBBpvdYh/4XxsnUybsPag6VikPYnN30td+vQk+GI3qpahVEG9+gTkG0aXVxTjBqQ5T6ijbWIu77O+C5WFWsnA==}
-
highlight.js@10.7.3:
resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==}
@@ -5475,16 +5288,9 @@ packages:
resolution: {integrity: sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==}
engines: {node: '>=0.10.0'}
- import-fresh@3.3.1:
- resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
- engines: {node: '>=6'}
-
import-in-the-middle@1.14.2:
resolution: {integrity: sha512-5tCuY9BV8ujfOpwtAGgsTx9CGUapcFMEEyByLv1B+v2+6DhAcw+Zr0nhQT7uwaZ7DiourxFEscghOR8e1aPLQw==}
- import-meta-resolve@4.1.0:
- resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==}
-
indent-string@4.0.0:
resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
engines: {node: '>=8'}
@@ -5507,10 +5313,6 @@ packages:
ini@1.3.8:
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
- ini@4.1.1:
- resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==}
- engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
-
inline-style-parser@0.2.4:
resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==}
@@ -5586,9 +5388,6 @@ packages:
is-alphanumerical@2.0.1:
resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==}
- is-arrayish@0.2.1:
- resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
-
is-binary-path@2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
@@ -5640,10 +5439,6 @@ packages:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
- is-obj@2.0.0:
- resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==}
- engines: {node: '>=8'}
-
is-plain-obj@4.1.0:
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
engines: {node: '>=12'}
@@ -5656,10 +5451,6 @@ packages:
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
- is-text-path@2.0.0:
- resolution: {integrity: sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==}
- engines: {node: '>=8'}
-
is-what@4.1.16:
resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
engines: {node: '>=12.13'}
@@ -5678,10 +5469,6 @@ packages:
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
hasBin: true
- jiti@2.4.2:
- resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
- hasBin: true
-
jose@5.10.0:
resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==}
@@ -5720,22 +5507,12 @@ packages:
json-buffer@3.0.1:
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
- json-parse-even-better-errors@2.3.1:
- resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
-
- json-schema-traverse@1.0.0:
- resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
-
json-schema@0.4.0:
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
json-stringify-safe@5.0.1:
resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
- jsonparse@1.3.1:
- resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==}
- engines: {'0': node >= 0.2.0}
-
jsonwebtoken@9.0.2:
resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==}
engines: {node: '>=12', npm: '>=6'}
@@ -5798,60 +5575,6 @@ packages:
leac@0.6.0:
resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==}
- lefthook-darwin-arm64@1.8.4:
- resolution: {integrity: sha512-OS5MsU0gvd8LYSpuQCHtmDUqwNrJ/LjCO0LGC1wNepY4OkuVl9DfX+rQ506CVUQYZiGVcwy2/qPOOBjNzA5+wQ==}
- cpu: [arm64]
- os: [darwin]
-
- lefthook-darwin-x64@1.8.4:
- resolution: {integrity: sha512-QLRsqK9aTMRcVW8qz4pzI2OWnGCEcaEPJlIiFjwstYsS+wfkooxOS0UkfVMjy+QoGgEcki+cxF/FoY7lE7DDtw==}
- cpu: [x64]
- os: [darwin]
-
- lefthook-freebsd-arm64@1.8.4:
- resolution: {integrity: sha512-chnQ1m/Cmn9c0sLdk5HL2SToE5LBJv5uQMdH1IGRRcw+nEqWqrMnDXvM75caiJAyjmUGvPH3czKTJDzTFV1E+A==}
- cpu: [arm64]
- os: [freebsd]
-
- lefthook-freebsd-x64@1.8.4:
- resolution: {integrity: sha512-KQi+WBUdnGLnK0rHOR58kbMH5TDVN1ZjZLu66Pv9FCG7Y7shR1qtaTXu+wmxdRhMvaLeQIXRsUEPjNRC66yMmA==}
- cpu: [x64]
- os: [freebsd]
-
- lefthook-linux-arm64@1.8.4:
- resolution: {integrity: sha512-CXNcqIskLwTwQARidGdFqmNxpvOU3jsWPK4KA7pq2+QmlWJ64w98ebMvNBoUmRUCXqzmUm7Udf/jpfz2fobewQ==}
- cpu: [arm64]
- os: [linux]
-
- lefthook-linux-x64@1.8.4:
- resolution: {integrity: sha512-pVNITkFBxUCEtamWSM/res2Gd48+m9YKbNyIBndAuZVC5pKV5aGKZy2DNq6PWUPYiUDPx+7hoAtCJg/tlAiqhw==}
- cpu: [x64]
- os: [linux]
-
- lefthook-openbsd-arm64@1.8.4:
- resolution: {integrity: sha512-l+i/Dg5X36kYzhpMGSPE3rMbWy1KSytbLB9lY1PmxYb6LRH6iQTYIoxvLabVUwSBPSq8HtIFa50+bvC5+scfVA==}
- cpu: [arm64]
- os: [openbsd]
-
- lefthook-openbsd-x64@1.8.4:
- resolution: {integrity: sha512-CqhDDPPX8oHzMLgNi/Reba823DRzj+eMNWQ8axvSiIG+zmG1w20xZH5QSs/mD3tjrND90yfDd90mWMt181qPyA==}
- cpu: [x64]
- os: [openbsd]
-
- lefthook-windows-arm64@1.8.4:
- resolution: {integrity: sha512-dvpvorICmVjmw29Aiczg7DcaSzkd86bEBomiGq4UsAEk3+7ExLrlWJDLFsI6xLjMKmTxy+F7eXb2uDtuFC1N4g==}
- cpu: [arm64]
- os: [win32]
-
- lefthook-windows-x64@1.8.4:
- resolution: {integrity: sha512-e+y8Jt4/7PnoplhOuK48twjGVJEsU4T3J5kxD4mWfl6Cbit0YSn4bme9nW41eqCqTUqOm+ky29XlfnPHFX5ZNA==}
- cpu: [x64]
- os: [win32]
-
- lefthook@1.8.4:
- resolution: {integrity: sha512-XNyMaTWNRuADOaocYiHidgNkNDz8SCekpdNJ7lqceFcBT2zjumnb28/o7IMaNROpLBZdQkLkJXSeaQWGqn3kog==}
- hasBin: true
-
lilconfig@3.1.3:
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
engines: {node: '>=14'}
@@ -5876,10 +5599,6 @@ packages:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'}
- locate-path@7.2.0:
- resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
lodash.camelcase@4.3.0:
resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
@@ -5916,30 +5635,12 @@ packages:
lodash.isstring@4.0.1:
resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==}
- lodash.kebabcase@4.1.1:
- resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==}
-
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
- lodash.mergewith@4.6.2:
- resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==}
-
lodash.once@4.1.1:
resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==}
- lodash.snakecase@4.1.1:
- resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==}
-
- lodash.startcase@4.4.0:
- resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==}
-
- lodash.uniq@4.5.0:
- resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==}
-
- lodash.upperfirst@4.3.1:
- resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==}
-
lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
@@ -6035,10 +5736,6 @@ packages:
resolution: {integrity: sha512-NgYhCOWgovOXSzvYgUW0LQ7Qy72rWQMGGFJDoWg4G30RHd3z77VbYdtJ4fembJXBy8pMIUA31XNAupobOQlwdg==}
engines: {node: '>= 4.0.0'}
- meow@12.1.1:
- resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==}
- engines: {node: '>=16.10'}
-
merge-descriptors@1.0.3:
resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
@@ -6432,9 +6129,6 @@ packages:
openapi-types@12.1.3:
resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==}
- otpauth@9.4.0:
- resolution: {integrity: sha512-fHIfzIG5RqCkK9cmV8WU+dPQr9/ebR5QOwGZn2JAr1RQF+lmAuLL2YdtdqvmBjNmgJlYk3KZ4a0XokaEhg1Jsw==}
-
p-cancelable@3.0.0:
resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==}
engines: {node: '>=12.20'}
@@ -6443,10 +6137,6 @@ packages:
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
engines: {node: '>=6'}
- p-limit@4.0.0:
- resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
p-limit@5.0.0:
resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==}
engines: {node: '>=18'}
@@ -6455,10 +6145,6 @@ packages:
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
engines: {node: '>=8'}
- p-locate@6.0.0:
- resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
p-try@2.2.0:
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
engines: {node: '>=6'}
@@ -6466,20 +6152,12 @@ packages:
package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
- parent-module@1.0.1:
- resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
- engines: {node: '>=6'}
-
parse-entities@2.0.0:
resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==}
parse-entities@4.0.2:
resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==}
- parse-json@5.2.0:
- resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
- engines: {node: '>=8'}
-
parseley@0.12.1:
resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==}
@@ -6491,10 +6169,6 @@ packages:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
- path-exists@5.0.0:
- resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
path-is-absolute@1.0.1:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
engines: {node: '>=0.10.0'}
@@ -6999,10 +6673,6 @@ packages:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
- require-from-string@2.0.2:
- resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
- engines: {node: '>=0.10.0'}
-
require-in-the-middle@7.5.2:
resolution: {integrity: sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==}
engines: {node: '>=8.6.0'}
@@ -7019,14 +6689,6 @@ packages:
resolve-alpn@1.2.1:
resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
- resolve-from@4.0.0:
- resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
- engines: {node: '>=4'}
-
- resolve-from@5.0.0:
- resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
- engines: {node: '>=8'}
-
resolve-pkg-maps@1.0.0:
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
@@ -7064,10 +6726,6 @@ packages:
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
- rotating-file-stream@3.2.3:
- resolution: {integrity: sha512-cfmm3tqdnbuYw2FBmRTPBDaohYEbMJ3211T35o6eZdr4d7v69+ZeK1Av84Br7FLj2dlzyeZSbN6qTuXXE6dawQ==}
- engines: {node: '>=14.0'}
-
rou3@0.5.1:
resolution: {integrity: sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ==}
@@ -7097,11 +6755,6 @@ packages:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
- semver@7.7.2:
- resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==}
- engines: {node: '>=10'}
- hasBin: true
-
semver@7.7.3:
resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
engines: {node: '>=10'}
@@ -7383,10 +7036,6 @@ packages:
temporal-spec@0.2.4:
resolution: {integrity: sha512-lDMFv4nKQrSjlkHKAlHVqKrBG4DyFfa9F74cmBZ3Iy3ed8yvWnlWSIdi4IKfSqwmazAohBNwiN64qGx4y5Q3IQ==}
- text-extensions@2.4.0:
- resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==}
- engines: {node: '>=8'}
-
theming@3.3.0:
resolution: {integrity: sha512-u6l4qTJRDaWZsqa8JugaNt7Xd8PPl9+gonZaIe28vAhqgHMIG/DOyFPqiKN/gQLQYj05tHv+YQdNILL4zoiAVA==}
engines: {node: '>=8'}
@@ -7409,9 +7058,6 @@ packages:
thread-stream@3.1.0:
resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
- through@2.3.8:
- resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
-
tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
@@ -7421,9 +7067,6 @@ packages:
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
- tinyexec@1.0.1:
- resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==}
-
tinypool@0.8.4:
resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==}
engines: {node: '>=14.0.0'}
@@ -7556,10 +7199,6 @@ packages:
resolution: {integrity: sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==}
engines: {node: '>=18.17'}
- unicorn-magic@0.1.0:
- resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==}
- engines: {node: '>=18'}
-
unified@11.0.5:
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
@@ -7943,14 +7582,6 @@ snapshots:
'@alloc/quick-lru@5.2.0': {}
- '@babel/code-frame@7.27.1':
- dependencies:
- '@babel/helper-validator-identifier': 7.27.1
- js-tokens: 4.0.0
- picocolors: 1.1.1
-
- '@babel/helper-validator-identifier@7.27.1': {}
-
'@babel/runtime-corejs3@7.27.3':
dependencies:
core-js-pure: 3.42.0
@@ -8087,116 +7718,6 @@ snapshots:
style-mod: 4.1.2
w3c-keyname: 2.2.8
- '@commitlint/cli@19.8.1(@types/node@18.19.104)(typescript@5.8.3)':
- dependencies:
- '@commitlint/format': 19.8.1
- '@commitlint/lint': 19.8.1
- '@commitlint/load': 19.8.1(@types/node@18.19.104)(typescript@5.8.3)
- '@commitlint/read': 19.8.1
- '@commitlint/types': 19.8.1
- tinyexec: 1.0.1
- yargs: 17.7.2
- transitivePeerDependencies:
- - '@types/node'
- - typescript
-
- '@commitlint/config-conventional@19.8.1':
- dependencies:
- '@commitlint/types': 19.8.1
- conventional-changelog-conventionalcommits: 7.0.2
-
- '@commitlint/config-validator@19.8.1':
- dependencies:
- '@commitlint/types': 19.8.1
- ajv: 8.17.1
-
- '@commitlint/ensure@19.8.1':
- dependencies:
- '@commitlint/types': 19.8.1
- lodash.camelcase: 4.3.0
- lodash.kebabcase: 4.1.1
- lodash.snakecase: 4.1.1
- lodash.startcase: 4.4.0
- lodash.upperfirst: 4.3.1
-
- '@commitlint/execute-rule@19.8.1': {}
-
- '@commitlint/format@19.8.1':
- dependencies:
- '@commitlint/types': 19.8.1
- chalk: 5.4.1
-
- '@commitlint/is-ignored@19.8.1':
- dependencies:
- '@commitlint/types': 19.8.1
- semver: 7.7.2
-
- '@commitlint/lint@19.8.1':
- dependencies:
- '@commitlint/is-ignored': 19.8.1
- '@commitlint/parse': 19.8.1
- '@commitlint/rules': 19.8.1
- '@commitlint/types': 19.8.1
-
- '@commitlint/load@19.8.1(@types/node@18.19.104)(typescript@5.8.3)':
- dependencies:
- '@commitlint/config-validator': 19.8.1
- '@commitlint/execute-rule': 19.8.1
- '@commitlint/resolve-extends': 19.8.1
- '@commitlint/types': 19.8.1
- chalk: 5.4.1
- cosmiconfig: 9.0.0(typescript@5.8.3)
- cosmiconfig-typescript-loader: 6.1.0(@types/node@18.19.104)(cosmiconfig@9.0.0(typescript@5.8.3))(typescript@5.8.3)
- lodash.isplainobject: 4.0.6
- lodash.merge: 4.6.2
- lodash.uniq: 4.5.0
- transitivePeerDependencies:
- - '@types/node'
- - typescript
-
- '@commitlint/message@19.8.1': {}
-
- '@commitlint/parse@19.8.1':
- dependencies:
- '@commitlint/types': 19.8.1
- conventional-changelog-angular: 7.0.0
- conventional-commits-parser: 5.0.0
-
- '@commitlint/read@19.8.1':
- dependencies:
- '@commitlint/top-level': 19.8.1
- '@commitlint/types': 19.8.1
- git-raw-commits: 4.0.0
- minimist: 1.2.8
- tinyexec: 1.0.1
-
- '@commitlint/resolve-extends@19.8.1':
- dependencies:
- '@commitlint/config-validator': 19.8.1
- '@commitlint/types': 19.8.1
- global-directory: 4.0.1
- import-meta-resolve: 4.1.0
- lodash.mergewith: 4.6.2
- resolve-from: 5.0.0
-
- '@commitlint/rules@19.8.1':
- dependencies:
- '@commitlint/ensure': 19.8.1
- '@commitlint/message': 19.8.1
- '@commitlint/to-lines': 19.8.1
- '@commitlint/types': 19.8.1
-
- '@commitlint/to-lines@19.8.1': {}
-
- '@commitlint/top-level@19.8.1':
- dependencies:
- find-up: 7.0.0
-
- '@commitlint/types@19.8.1':
- dependencies:
- '@types/conventional-commits-parser': 5.0.1
- chalk: 5.4.1
-
'@dokploy/trpc-openapi@0.0.4(@trpc/server@10.45.2)(@types/node@18.19.104)(zod@3.25.32)':
dependencies:
'@trpc/server': 10.45.2
@@ -8746,7 +8267,7 @@ snapshots:
nopt: 5.0.0
npmlog: 5.0.1
rimraf: 3.0.2
- semver: 7.7.2
+ semver: 7.7.3
tar: 6.2.1
transitivePeerDependencies:
- encoding
@@ -8772,11 +8293,6 @@ snapshots:
'@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3':
optional: true
- '@nerimity/mimiqueue@1.2.3(redis@4.7.0)':
- dependencies:
- async-await-queue: 2.1.4
- redis: 4.7.0
-
'@next/env@16.0.10': {}
'@next/swc-darwin-arm64@16.0.10':
@@ -9337,7 +8853,7 @@ snapshots:
'@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.28.0
forwarded-parse: 2.1.2
- semver: 7.7.2
+ semver: 7.7.3
transitivePeerDependencies:
- supports-color
@@ -9538,7 +9054,7 @@ snapshots:
'@types/shimmer': 1.2.0
import-in-the-middle: 1.14.2
require-in-the-middle: 7.5.2
- semver: 7.7.2
+ semver: 7.7.3
shimmer: 1.2.1
transitivePeerDependencies:
- supports-color
@@ -9683,7 +9199,7 @@ snapshots:
'@opentelemetry/propagator-b3': 1.30.1(@opentelemetry/api@1.9.0)
'@opentelemetry/propagator-jaeger': 1.30.1(@opentelemetry/api@1.9.0)
'@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0)
- semver: 7.7.2
+ semver: 7.7.3
'@opentelemetry/semantic-conventions@1.28.0': {}
@@ -11288,10 +10804,6 @@ snapshots:
dependencies:
'@types/node': 20.17.51
- '@types/conventional-commits-parser@5.0.1':
- dependencies:
- '@types/node': 20.17.51
-
'@types/d3-array@3.2.1': {}
'@types/d3-color@3.1.3': {}
@@ -11431,6 +10943,8 @@ snapshots:
dependencies:
'@types/node': 20.17.51
+ '@types/semver@7.7.1': {}
+
'@types/shell-quote@1.7.5': {}
'@types/shimmer@1.2.0': {}
@@ -11545,11 +11059,6 @@ snapshots:
'@xterm/xterm@5.5.0': {}
- JSONStream@1.3.5:
- dependencies:
- jsonparse: 1.3.1
- through: 2.3.8
-
abbrev@1.1.1: {}
abbrev@2.0.0: {}
@@ -11610,13 +11119,6 @@ snapshots:
'@opentelemetry/api': 1.9.0
zod: 3.25.32
- ajv@8.17.1:
- dependencies:
- fast-deep-equal: 3.1.3
- fast-uri: 3.0.6
- json-schema-traverse: 1.0.0
- require-from-string: 2.0.2
-
ansi-align@3.0.1:
dependencies:
string-width: 4.2.3
@@ -11667,8 +11169,6 @@ snapshots:
dependencies:
tslib: 2.8.1
- array-ify@1.0.0: {}
-
array-union@2.1.0: {}
asn1@0.2.6:
@@ -11683,8 +11183,6 @@ snapshots:
assertion-error@1.1.0: {}
- async-await-queue@2.1.4: {}
-
asynckit@0.4.0: {}
atomic-sleep@1.0.0: {}
@@ -11830,7 +11328,7 @@ snapshots:
lodash: 4.17.21
msgpackr: 1.11.4
node-abort-controller: 3.1.1
- semver: 7.7.2
+ semver: 7.7.3
tslib: 2.8.1
uuid: 9.0.1
transitivePeerDependencies:
@@ -11862,8 +11360,6 @@ snapshots:
call-bind-apply-helpers: 1.0.2
get-intrinsic: 1.3.0
- callsites@3.1.0: {}
-
camelcase-css@2.0.1: {}
camelcase@5.3.1: {}
@@ -12022,11 +11518,6 @@ snapshots:
commander@9.5.0: {}
- compare-func@2.0.0:
- dependencies:
- array-ify: 1.0.0
- dot-prop: 5.3.0
-
concat-map@0.0.1: {}
confbox@0.1.8: {}
@@ -12042,21 +11533,6 @@ snapshots:
dependencies:
safe-buffer: 5.2.1
- conventional-changelog-angular@7.0.0:
- dependencies:
- compare-func: 2.0.0
-
- conventional-changelog-conventionalcommits@7.0.2:
- dependencies:
- compare-func: 2.0.0
-
- conventional-commits-parser@5.0.0:
- dependencies:
- JSONStream: 1.3.5
- is-text-path: 2.0.0
- meow: 12.1.1
- split2: 4.2.0
-
cookie-es@1.2.2: {}
copy-anything@3.0.5:
@@ -12071,22 +11547,6 @@ snapshots:
core-js@3.42.0: {}
- cosmiconfig-typescript-loader@6.1.0(@types/node@18.19.104)(cosmiconfig@9.0.0(typescript@5.8.3))(typescript@5.8.3):
- dependencies:
- '@types/node': 18.19.104
- cosmiconfig: 9.0.0(typescript@5.8.3)
- jiti: 2.4.2
- typescript: 5.8.3
-
- cosmiconfig@9.0.0(typescript@5.8.3):
- dependencies:
- env-paths: 2.2.1
- import-fresh: 3.3.1
- js-yaml: 4.1.0
- parse-json: 5.2.0
- optionalDependencies:
- typescript: 5.8.3
-
cpu-features@0.0.10:
dependencies:
buildcheck: 0.0.6
@@ -12170,8 +11630,6 @@ snapshots:
d3-timer@3.0.1: {}
- dargs@8.1.0: {}
-
date-fns@3.6.0: {}
dateformat@4.6.3: {}
@@ -12295,10 +11753,6 @@ snapshots:
domelementtype: 2.3.0
domhandler: 5.0.3
- dot-prop@5.3.0:
- dependencies:
- is-obj: 2.0.0
-
dotenv@16.4.5: {}
drange@1.1.1: {}
@@ -12346,7 +11800,7 @@ snapshots:
'@one-ini/wasm': 0.1.1
commander: 10.0.1
minimatch: 9.0.1
- semver: 7.7.2
+ semver: 7.7.3
electron-to-chromium@1.5.159: {}
@@ -12362,16 +11816,10 @@ snapshots:
entities@4.5.0: {}
- env-paths@2.2.1: {}
-
env-paths@3.0.0: {}
environment@1.1.0: {}
- error-ex@1.3.2:
- dependencies:
- is-arrayish: 0.2.1
-
es-define-property@1.0.1: {}
es-errors@1.3.0: {}
@@ -12543,8 +11991,6 @@ snapshots:
fast-deep-equal@2.0.1: {}
- fast-deep-equal@3.1.3: {}
-
fast-equals@5.2.2: {}
fast-glob@3.3.3:
@@ -12561,8 +12007,6 @@ snapshots:
fast-safe-stringify@2.1.1: {}
- fast-uri@3.0.6: {}
-
fastq@1.19.1:
dependencies:
reusify: 1.1.0
@@ -12580,12 +12024,6 @@ snapshots:
locate-path: 5.0.0
path-exists: 4.0.0
- find-up@7.0.0:
- dependencies:
- locate-path: 7.2.0
- path-exists: 5.0.0
- unicorn-magic: 0.1.0
-
follow-redirects@1.15.9: {}
foreground-child@3.3.1:
@@ -12660,7 +12098,7 @@ snapshots:
'@petamoriken/float16': 3.9.2
debug: 4.4.1
env-paths: 3.0.0
- semver: 7.7.2
+ semver: 7.7.3
shell-quote: 1.8.2
which: 4.0.0
transitivePeerDependencies:
@@ -12702,12 +12140,6 @@ snapshots:
dependencies:
resolve-pkg-maps: 1.0.0
- git-raw-commits@4.0.0:
- dependencies:
- dargs: 8.1.0
- meow: 12.1.1
- split2: 4.2.0
-
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
@@ -12734,10 +12166,6 @@ snapshots:
once: 1.4.0
path-is-absolute: 1.0.1
- global-directory@4.0.1:
- dependencies:
- ini: 4.1.1
-
globby@11.1.0:
dependencies:
array-union: 2.1.0
@@ -12834,8 +12262,6 @@ snapshots:
help-me@5.0.0: {}
- hi-base32@0.5.1: {}
-
highlight.js@10.7.3: {}
highlightjs-vue@1.0.0: {}
@@ -12918,11 +12344,6 @@ snapshots:
immutable@3.8.2: {}
- import-fresh@3.3.1:
- dependencies:
- parent-module: 1.0.1
- resolve-from: 4.0.0
-
import-in-the-middle@1.14.2:
dependencies:
acorn: 8.14.1
@@ -12930,8 +12351,6 @@ snapshots:
cjs-module-lexer: 1.4.3
module-details-from-path: 1.0.4
- import-meta-resolve@4.1.0: {}
-
indent-string@4.0.0: {}
indent-string@5.0.0: {}
@@ -12947,8 +12366,6 @@ snapshots:
ini@1.3.8: {}
- ini@4.1.1: {}
-
inline-style-parser@0.2.4: {}
inngest@3.40.1(h3@1.15.3)(hono@4.7.10)(next@16.0.10(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.8.3):
@@ -13027,8 +12444,6 @@ snapshots:
is-alphabetical: 2.0.1
is-decimal: 2.0.1
- is-arrayish@0.2.1: {}
-
is-binary-path@2.1.0:
dependencies:
binary-extensions: 2.3.0
@@ -13067,18 +12482,12 @@ snapshots:
is-number@7.0.0: {}
- is-obj@2.0.0: {}
-
is-plain-obj@4.1.0: {}
is-stream@2.0.1: {}
is-stream@3.0.0: {}
- is-text-path@2.0.0:
- dependencies:
- text-extensions: 2.4.0
-
is-what@4.1.16: {}
isexe@2.0.0: {}
@@ -13093,8 +12502,6 @@ snapshots:
jiti@1.21.7: {}
- jiti@2.4.2: {}
-
jose@5.10.0: {}
joycon@3.1.1: {}
@@ -13127,16 +12534,10 @@ snapshots:
json-buffer@3.0.1: {}
- json-parse-even-better-errors@2.3.1: {}
-
- json-schema-traverse@1.0.0: {}
-
json-schema@0.4.0: {}
json-stringify-safe@5.0.1: {}
- jsonparse@1.3.1: {}
-
jsonwebtoken@9.0.2:
dependencies:
jws: 3.2.2
@@ -13148,7 +12549,7 @@ snapshots:
lodash.isstring: 4.0.1
lodash.once: 4.1.1
ms: 2.1.3
- semver: 7.7.2
+ semver: 7.7.3
jss-plugin-camel-case@10.10.0:
dependencies:
@@ -13261,49 +12662,6 @@ snapshots:
leac@0.6.0: {}
- lefthook-darwin-arm64@1.8.4:
- optional: true
-
- lefthook-darwin-x64@1.8.4:
- optional: true
-
- lefthook-freebsd-arm64@1.8.4:
- optional: true
-
- lefthook-freebsd-x64@1.8.4:
- optional: true
-
- lefthook-linux-arm64@1.8.4:
- optional: true
-
- lefthook-linux-x64@1.8.4:
- optional: true
-
- lefthook-openbsd-arm64@1.8.4:
- optional: true
-
- lefthook-openbsd-x64@1.8.4:
- optional: true
-
- lefthook-windows-arm64@1.8.4:
- optional: true
-
- lefthook-windows-x64@1.8.4:
- optional: true
-
- lefthook@1.8.4:
- optionalDependencies:
- lefthook-darwin-arm64: 1.8.4
- lefthook-darwin-x64: 1.8.4
- lefthook-freebsd-arm64: 1.8.4
- lefthook-freebsd-x64: 1.8.4
- lefthook-linux-arm64: 1.8.4
- lefthook-linux-x64: 1.8.4
- lefthook-openbsd-arm64: 1.8.4
- lefthook-openbsd-x64: 1.8.4
- lefthook-windows-arm64: 1.8.4
- lefthook-windows-x64: 1.8.4
-
lilconfig@3.1.3: {}
lines-and-columns@1.2.4: {}
@@ -13341,10 +12699,6 @@ snapshots:
dependencies:
p-locate: 4.1.0
- locate-path@7.2.0:
- dependencies:
- p-locate: 6.0.0
-
lodash.camelcase@4.3.0: {}
lodash.castarray@4.4.0: {}
@@ -13369,22 +12723,10 @@ snapshots:
lodash.isstring@4.0.1: {}
- lodash.kebabcase@4.1.1: {}
-
lodash.merge@4.6.2: {}
- lodash.mergewith@4.6.2: {}
-
lodash.once@4.1.1: {}
- lodash.snakecase@4.1.1: {}
-
- lodash.startcase@4.4.0: {}
-
- lodash.uniq@4.5.0: {}
-
- lodash.upperfirst@4.3.1: {}
-
lodash@4.17.21: {}
log-update@6.1.0:
@@ -13539,8 +12881,6 @@ snapshots:
tree-dump: 1.0.3(tslib@2.8.1)
tslib: 2.8.1
- meow@12.1.1: {}
-
merge-descriptors@1.0.3: {}
merge-stream@2.0.0: {}
@@ -13972,20 +13312,12 @@ snapshots:
openapi-types@12.1.3: {}
- otpauth@9.4.0:
- dependencies:
- '@noble/hashes': 1.7.1
-
p-cancelable@3.0.0: {}
p-limit@2.3.0:
dependencies:
p-try: 2.2.0
- p-limit@4.0.0:
- dependencies:
- yocto-queue: 1.2.1
-
p-limit@5.0.0:
dependencies:
yocto-queue: 1.2.1
@@ -13994,18 +13326,10 @@ snapshots:
dependencies:
p-limit: 2.3.0
- p-locate@6.0.0:
- dependencies:
- p-limit: 4.0.0
-
p-try@2.2.0: {}
package-json-from-dist@1.0.1: {}
- parent-module@1.0.1:
- dependencies:
- callsites: 3.1.0
-
parse-entities@2.0.0:
dependencies:
character-entities: 1.2.4
@@ -14025,13 +13349,6 @@ snapshots:
is-decimal: 2.0.1
is-hexadecimal: 2.0.1
- parse-json@5.2.0:
- dependencies:
- '@babel/code-frame': 7.27.1
- error-ex: 1.3.2
- json-parse-even-better-errors: 2.3.1
- lines-and-columns: 1.2.4
-
parseley@0.12.1:
dependencies:
leac: 0.6.0
@@ -14041,8 +13358,6 @@ snapshots:
path-exists@4.0.0: {}
- path-exists@5.0.0: {}
-
path-is-absolute@1.0.1: {}
path-key@3.1.1: {}
@@ -14585,8 +13900,6 @@ snapshots:
require-directory@2.1.1: {}
- require-from-string@2.0.2: {}
-
require-in-the-middle@7.5.2:
dependencies:
debug: 4.4.1
@@ -14603,10 +13916,6 @@ snapshots:
resolve-alpn@1.2.1: {}
- resolve-from@4.0.0: {}
-
- resolve-from@5.0.0: {}
-
resolve-pkg-maps@1.0.0: {}
resolve@1.22.10:
@@ -14660,8 +13969,6 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.41.1
fsevents: 2.3.3
- rotating-file-stream@3.2.3: {}
-
rou3@0.5.1: {}
run-parallel@1.2.0:
@@ -14686,10 +13993,7 @@ snapshots:
semver@6.3.1: {}
- semver@7.7.2: {}
-
- semver@7.7.3:
- optional: true
+ semver@7.7.3: {}
serialize-error-cjs@0.1.4: {}
@@ -15066,8 +14370,6 @@ snapshots:
temporal-spec@0.2.4: {}
- text-extensions@2.4.0: {}
-
theming@3.3.0(react@18.2.0):
dependencies:
hoist-non-react-statics: 3.3.2
@@ -15092,16 +14394,12 @@ snapshots:
dependencies:
real-require: 0.2.0
- through@2.3.8: {}
-
tiny-invariant@1.3.3: {}
tiny-warning@1.0.3: {}
tinybench@2.9.0: {}
- tinyexec@1.0.1: {}
-
tinypool@0.8.4: {}
tinyspy@2.2.1: {}
@@ -15205,8 +14503,6 @@ snapshots:
undici@6.21.3: {}
- unicorn-magic@0.1.0: {}
-
unified@11.0.5:
dependencies:
'@types/unist': 3.0.3
diff --git a/schema.dbml b/schema.dbml
index 5823a8ff3..d0845f3ed 100644
--- a/schema.dbml
+++ b/schema.dbml
@@ -276,7 +276,7 @@ table application {
replicas integer [not null, default: 1]
applicationStatus applicationStatus [not null, default: 'idle']
buildType buildType [not null, default: 'nixpacks']
- railpackVersion text [default: '0.2.2']
+ railpackVersion text [default: '0.15.4']
herokuVersion text [default: '24']
publishDirectory text
isStaticSpa boolean