Compare commits

...

16 Commits

Author SHA1 Message Date
Mauricio Siu
e79f8c4b72 feat: add Tolgee sponsorship banner to README 2025-06-27 00:11:02 -06:00
Mauricio Siu
26e2a24f63 chore: update version in package.json to v0.23.4 2025-06-26 23:57:36 -06:00
Mauricio Siu
830feabd70 Merge pull request #2072 from dscamargo/feat/database-name-database-backup-notification
feat: add database name in database backup notification
2025-06-26 23:55:57 -06:00
Mauricio Siu
122a3d110d Merge pull request #2090 from Dokploy/fix/correct-database-type-on-compose-backups
fix(backups): enhance database type handling in compose backup process
2025-06-26 23:34:33 -06:00
Mauricio Siu
c05edb313f fix(backups): enhance database type handling in compose backup process
- Added a new function to determine the database type based on the backup configuration.
- Updated notifications to use the correct database type instead of a hardcoded value.
2025-06-26 23:32:18 -06:00
Mauricio Siu
1ec2853862 Merge pull request #2089 from Dokploy/2080-invalid-name-when-trying-to-deploy-an-app-with-dockerfile-to-ghcrio
fix(upload): refactor registry tag construction for image uploads
2025-06-26 23:02:36 -06:00
Mauricio Siu
5c2709248c fix(upload): refactor registry tag construction for image uploads
- Updated the logic for constructing the registry tag in the uploadImage and uploadImageRemoteCommand functions to ensure correct formatting.
- Removed unnecessary path imports and streamlined the code for better readability.
2025-06-26 23:00:51 -06:00
autofix-ci[bot]
bc79074441 [autofix.ci] apply automated fixes 2025-06-27 03:13:41 +00:00
Mauricio Siu
5ada451916 Merge pull request #2087 from Dokploy/feat/add-dokploy-cloud-endpoints
feat(settings): add query to retrieve Dokploy cloud IPs
2025-06-26 21:01:50 -06:00
Mauricio Siu
6b0d9240dd feat(settings): add query to retrieve Dokploy cloud IPs
- Implemented a new admin procedure to fetch cloud IPs from environment variables.
- Returns an empty array if not in cloud mode.
2025-06-26 21:00:51 -06:00
Douglas Camargo
499022a328 feat: add database name in database backup notification 2025-06-23 22:35:40 -03:00
Mauricio Siu
2a89be6efc Merge pull request #2069 from Dokploy/2065-rollback-feature-dns-issues
feat(rollbacks): enhance fullContext type and refactor createRollback…
2025-06-22 18:01:21 +02:00
autofix-ci[bot]
412bb9e874 [autofix.ci] apply automated fixes 2025-06-22 16:00:36 +00:00
Mauricio Siu
6290c217f1 feat(rollbacks): add alert for storage usage in rollback settings
- Introduced an AlertBlock component to inform users about increased storage usage when rollbacks are enabled.
- Added cautionary note regarding the potential deletion of rollback images during manual cache cleaning.
2025-06-22 10:00:14 -06:00
Mauricio Siu
4babdd45ea chore: update version in package.json to v0.23.3 2025-06-22 09:58:35 -06:00
Mauricio Siu
24bff96898 feat(rollbacks): enhance fullContext type and refactor createRollback logic
- Updated fullContext type in rollbacks schema to include Application and Project types.
- Refactored createRollback function to separate fullContext from input and handle it more efficiently.
- Integrated environment variable preparation into the rollback process.
2025-06-22 09:56:36 -06:00
15 changed files with 113 additions and 39 deletions

View File

@@ -99,6 +99,10 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
<img src=".github/sponsors/american-cloud.png" alt="AmericanCloud" height="70"/>
</a>
<a href="https://tolgee.io/?utm_source=github_dokploy&utm_medium=banner&utm_campaign=dokploy" target="_blank" style="display: inline-block; margin-right: 10px;">
<img src="https://dokploy.com/tolgee-logo.png" alt="Tolgee" height="80"/>
</a>
</div>
<!-- Elite Contributors 🥈 -->

View File

@@ -1,3 +1,4 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -79,6 +80,11 @@ export const ShowRollbackSettings = ({ applicationId, children }: Props) => {
<DialogDescription>
Configure how rollbacks work for this application
</DialogDescription>
<AlertBlock>
Having rollbacks enabled increases storage usage. Be careful with
this option. Note that manually cleaning the cache may delete
rollback images, making them unavailable for future rollbacks.
</AlertBlock>
</DialogHeader>
<Form {...form}>

View File

@@ -1,6 +1,6 @@
{
"name": "dokploy",
"version": "v0.23.2",
"version": "v0.23.4",
"private": true,
"license": "Apache-2.0",
"type": "module",

View File

@@ -4,24 +4,24 @@ import { users_temp } from "@dokploy/server/db/schema";
import { eq } from "drizzle-orm";
(async () => {
try {
const result = await findAdmin();
try {
const result = await findAdmin();
const update = await db
.update(users_temp)
.set({
twoFactorEnabled: false,
})
.where(eq(users_temp.id, result.userId));
const update = await db
.update(users_temp)
.set({
twoFactorEnabled: false,
})
.where(eq(users_temp.id, result.userId));
if (update) {
console.log("2FA reset successful");
} else {
console.log("Password reset failed");
}
if (update) {
console.log("2FA reset successful");
} else {
console.log("Password reset failed");
}
process.exit(0);
} catch (error) {
console.log("Error resetting 2FA", error);
}
process.exit(0);
} catch (error) {
console.log("Error resetting 2FA", error);
}
})();

View File

@@ -837,6 +837,14 @@ export const settingsRouter = createTRPCRouter({
getLogCleanupStatus: adminProcedure.query(async () => {
return getLogCleanupStatus();
}),
getDokployCloudIps: adminProcedure.query(async () => {
if (!IS_CLOUD) {
return [];
}
const ips = process.env.DOKPLOY_CLOUD_IPS?.split(",");
return ips;
}),
});
export const getTraefikPorts = async (serverId?: string) => {

View File

@@ -4,6 +4,8 @@ import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { deployments } from "./deployment";
import type { Application } from "@dokploy/server/services/application";
import type { Project } from "@dokploy/server/services/project";
export const rollbacks = pgTable("rollback", {
rollbackId: text("rollbackId")
@@ -20,7 +22,7 @@ export const rollbacks = pgTable("rollback", {
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
fullContext: jsonb("fullContext"),
fullContext: jsonb("fullContext").$type<Application & { project: Project }>(),
});
export type Rollback = typeof rollbacks.$inferSelect;

View File

@@ -12,14 +12,16 @@ import type { ApplicationNested } from "../utils/builders";
import { execAsync, execAsyncRemote } from "../utils/process/execAsync";
import type { CreateServiceOptions } from "dockerode";
import { findDeploymentById } from "./deployment";
import { prepareEnvironmentVariables } from "../utils/docker/utils";
export const createRollback = async (
input: z.infer<typeof createRollbackSchema>,
) => {
await db.transaction(async (tx) => {
const { fullContext, ...other } = input;
const rollback = await tx
.insert(rollbacks)
.values(input)
.values(other)
.returning()
.then((res) => res[0]);
@@ -47,7 +49,7 @@ export const createRollback = async (
.update(rollbacks)
.set({
image: tagImage,
fullContext: JSON.stringify(rest),
fullContext: rest,
})
.where(eq(rollbacks.rollbackId, rollback.rollbackId));
@@ -150,10 +152,16 @@ export const rollback = async (rollbackId: string) => {
const application = await findApplicationById(deployment.applicationId);
const envVariables = prepareEnvironmentVariables(
result?.fullContext?.env || "",
result.fullContext?.project?.env || "",
);
await rollbackApplication(
application.appName,
result.image || "",
application.serverId,
envVariables,
);
};
@@ -161,6 +169,7 @@ const rollbackApplication = async (
appName: string,
image: string,
serverId?: string | null,
env: string[] = [],
) => {
const docker = await getRemoteDocker(serverId);
@@ -169,6 +178,7 @@ const rollbackApplication = async (
TaskTemplate: {
ContainerSpec: {
Image: image,
Env: env,
},
},
};

View File

@@ -15,7 +15,7 @@ export const runComposeBackup = async (
) => {
const { projectId, name } = compose;
const project = await findProjectById(projectId);
const { prefix } = backup;
const { prefix, databaseType } = backup;
const destination = backup.destination;
const backupFileName = `${new Date().toISOString()}.dump.gz`;
const bucketDestination = `${normalizeS3Path(prefix)}${backupFileName}`;
@@ -46,9 +46,10 @@ export const runComposeBackup = async (
await sendDatabaseBackupNotifications({
applicationName: name,
projectName: project.name,
databaseType: "mongodb",
databaseType: getDatabaseType(databaseType),
type: "success",
organizationId: project.organizationId,
databaseName: backup.database,
});
await updateDeploymentStatus(deployment.deploymentId, "done");
@@ -57,14 +58,31 @@ export const runComposeBackup = async (
await sendDatabaseBackupNotifications({
applicationName: name,
projectName: project.name,
databaseType: "mongodb",
databaseType: getDatabaseType(databaseType),
type: "error",
// @ts-ignore
errorMessage: error?.message || "Error message not provided",
organizationId: project.organizationId,
databaseName: backup.database,
});
await updateDeploymentStatus(deployment.deploymentId, "error");
throw error;
}
};
const getDatabaseType = (databaseType: BackupSchedule["databaseType"]) => {
if (databaseType === "mongo") {
return "mongodb";
}
if (databaseType === "postgres") {
return "postgres";
}
if (databaseType === "mariadb") {
return "mariadb";
}
if (databaseType === "mysql") {
return "mysql";
}
return "mongodb";
};

View File

@@ -48,6 +48,7 @@ export const runMariadbBackup = async (
databaseType: "mariadb",
type: "success",
organizationId: project.organizationId,
databaseName: backup.database,
});
await updateDeploymentStatus(deployment.deploymentId, "done");
} catch (error) {
@@ -60,6 +61,7 @@ export const runMariadbBackup = async (
// @ts-ignore
errorMessage: error?.message || "Error message not provided",
organizationId: project.organizationId,
databaseName: backup.database,
});
await updateDeploymentStatus(deployment.deploymentId, "error");
throw error;

View File

@@ -46,6 +46,7 @@ export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => {
databaseType: "mongodb",
type: "success",
organizationId: project.organizationId,
databaseName: backup.database,
});
await updateDeploymentStatus(deployment.deploymentId, "done");
} catch (error) {
@@ -58,6 +59,7 @@ export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => {
// @ts-ignore
errorMessage: error?.message || "Error message not provided",
organizationId: project.organizationId,
databaseName: backup.database,
});
await updateDeploymentStatus(deployment.deploymentId, "error");
throw error;

View File

@@ -47,6 +47,7 @@ export const runMySqlBackup = async (mysql: MySql, backup: BackupSchedule) => {
databaseType: "mysql",
type: "success",
organizationId: project.organizationId,
databaseName: backup.database,
});
await updateDeploymentStatus(deployment.deploymentId, "done");
} catch (error) {
@@ -59,6 +60,7 @@ export const runMySqlBackup = async (mysql: MySql, backup: BackupSchedule) => {
// @ts-ignore
errorMessage: error?.message || "Error message not provided",
organizationId: project.organizationId,
databaseName: backup.database,
});
await updateDeploymentStatus(deployment.deploymentId, "error");
throw error;

View File

@@ -50,6 +50,7 @@ export const runPostgresBackup = async (
databaseType: "postgres",
type: "success",
organizationId: project.organizationId,
databaseName: backup.database,
});
await updateDeploymentStatus(deployment.deploymentId, "done");
@@ -62,6 +63,7 @@ export const runPostgresBackup = async (
// @ts-ignore
errorMessage: error?.message || "Error message not provided",
organizationId: project.organizationId,
databaseName: backup.database,
});
await updateDeploymentStatus(deployment.deploymentId, "error");

View File

@@ -1,5 +1,4 @@
import { createWriteStream } from "node:fs";
import { join } from "node:path";
import type { InferResultType } from "@dokploy/server/types/with";
import type { CreateServiceOptions } from "dockerode";
import { uploadImage, uploadImageRemoteCommand } from "../cluster/upload";
@@ -211,16 +210,20 @@ export const mechanizeDockerContainer = async (
const getImageName = (application: ApplicationNested) => {
const { appName, sourceType, dockerImage, registry } = application;
const imageName = `${appName}:latest`;
if (sourceType === "docker") {
return dockerImage || "ERROR-NO-IMAGE-PROVIDED";
}
if (registry) {
return join(registry.registryUrl, registry.imagePrefix || "", appName);
const { registryUrl, imagePrefix, username } = registry;
const registryTag = imagePrefix
? `${registryUrl}/${imagePrefix}/${imageName}`
: `${registryUrl}/${username}/${imageName}`;
return registryTag;
}
return `${appName}:latest`;
return imageName;
};
const getAuthConfig = (application: ApplicationNested) => {

View File

@@ -1,5 +1,4 @@
import type { WriteStream } from "node:fs";
import path, { join } from "node:path";
import type { ApplicationNested } from "../builders";
import { spawnAsync } from "../process/spawnAsync";
@@ -13,19 +12,22 @@ export const uploadImage = async (
throw new Error("Registry not found");
}
const { registryUrl, imagePrefix } = registry;
const { registryUrl, imagePrefix, username } = registry;
const { appName } = application;
const imageName = `${appName}:latest`;
const finalURL = registryUrl;
const registryTag = path
.join(registryUrl, join(imagePrefix || "", imageName))
.replace(/\/+/g, "/");
// Build registry tag in correct format: registry.com/owner/image:tag
// For ghcr.io: ghcr.io/username/image:tag
// For docker.io: docker.io/username/image:tag
const registryTag = imagePrefix
? `${registryUrl}/${imagePrefix}/${imageName}`
: `${registryUrl}/${username}/${imageName}`;
try {
writeStream.write(
`📦 [Enabled Registry] Uploading image to ${registry.registryType} | ${imageName} | ${finalURL}\n`,
`📦 [Enabled Registry] Uploading image to ${registry.registryType} | ${imageName} | ${finalURL} | ${registryTag}\n`,
);
const loginCommand = spawnAsync(
"docker",
@@ -67,15 +69,16 @@ export const uploadImageRemoteCommand = (
throw new Error("Registry not found");
}
const { registryUrl, imagePrefix } = registry;
const { registryUrl, imagePrefix, username } = registry;
const { appName } = application;
const imageName = `${appName}:latest`;
const finalURL = registryUrl;
const registryTag = path
.join(registryUrl, join(imagePrefix || "", imageName))
.replace(/\/+/g, "/");
// Build registry tag in correct format: registry.com/owner/image:tag
const registryTag = imagePrefix
? `${registryUrl}/${imagePrefix}/${imageName}`
: `${registryUrl}/${username}/${imageName}`;
try {
const command = `

View File

@@ -19,6 +19,7 @@ export const sendDatabaseBackupNotifications = async ({
type,
errorMessage,
organizationId,
databaseName,
}: {
projectName: string;
applicationName: string;
@@ -26,6 +27,7 @@ export const sendDatabaseBackupNotifications = async ({
type: "error" | "success";
organizationId: string;
errorMessage?: string;
databaseName: string;
}) => {
const date = new Date();
const unixDate = ~~(Number(date) / 1000);
@@ -90,6 +92,11 @@ export const sendDatabaseBackupNotifications = async ({
value: databaseType,
inline: true,
},
{
name: decorate("`📂`", "Database Name"),
value: databaseName,
inline: true,
},
{
name: decorate("`📅`", "Date"),
value: `<t:${unixDate}:D>`,
@@ -136,6 +143,7 @@ export const sendDatabaseBackupNotifications = async ({
`${decorate("🛠️", `Project: ${projectName}`)}` +
`${decorate("⚙️", `Application: ${applicationName}`)}` +
`${decorate("❔", `Type: ${databaseType}`)}` +
`${decorate("📂", `Database Name: ${databaseName}`)}` +
`${decorate("🕒", `Date: ${date.toLocaleString()}`)}` +
`${type === "error" && errorMessage ? decorate("❌", `Error:\n${errorMessage}`) : ""}`,
);
@@ -150,7 +158,7 @@ export const sendDatabaseBackupNotifications = async ({
? `\n\n<b>Error:</b>\n<pre>${errorMessage}</pre>`
: "";
const messageText = `<b>${statusEmoji} Database Backup ${typeStatus}</b>\n\n<b>Project:</b> ${projectName}\n<b>Application:</b> ${applicationName}\n<b>Type:</b> ${databaseType}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}${isError ? errorMsg : ""}`;
const messageText = `<b>${statusEmoji} Database Backup ${typeStatus}</b>\n\n<b>Project:</b> ${projectName}\n<b>Application:</b> ${applicationName}\n<b>Type:</b> ${databaseType}\n<b>Database Name:</b> ${databaseName}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}${isError ? errorMsg : ""}`;
await sendTelegramNotification(telegram, messageText);
}
@@ -191,6 +199,10 @@ export const sendDatabaseBackupNotifications = async ({
value: databaseType,
short: true,
},
{
title: "Database Name",
value: databaseName,
},
{
title: "Time",
value: date.toLocaleString(),