Merge branch 'refs/heads/canary' into feature/custom-entrypoint

# Conflicts:
#	apps/dokploy/drizzle/meta/0131_snapshot.json
#	apps/dokploy/drizzle/meta/_journal.json
This commit is contained in:
mkarpats
2025-12-12 18:24:15 +02:00
40 changed files with 14556 additions and 167 deletions

View File

@@ -181,6 +181,7 @@ export const applications = pgTable("application", {
herokuVersion: text("herokuVersion").default("24"),
publishDirectory: text("publishDirectory"),
isStaticSpa: boolean("isStaticSpa"),
createEnvFile: boolean("createEnvFile").notNull().default(true),
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
@@ -332,6 +333,7 @@ const createSchema = createInsertSchema(applications, {
herokuVersion: z.string().optional(),
publishDirectory: z.string().optional(),
isStaticSpa: z.boolean().optional(),
createEnvFile: z.boolean().optional(),
owner: z.string(),
healthCheckSwarm: HealthCheckSwarmSchema.nullable(),
restartPolicySwarm: RestartPolicySwarmSchema.nullable(),
@@ -501,6 +503,7 @@ export const apiSaveEnvironmentVariables = createSchema
env: true,
buildArgs: true,
buildSecrets: true,
createEnvFile: true,
})
.required();

View File

@@ -1,5 +1,5 @@
import { relations } from "drizzle-orm";
import { pgTable, text } from "drizzle-orm/pg-core";
import { boolean, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
@@ -26,6 +26,7 @@ export const environments = pgTable("environment", {
projectId: text("projectId")
.notNull()
.references(() => projects.projectId, { onDelete: "cascade" }),
isDefault: boolean("isDefault").notNull().default(false),
});
export const environmentRelations = relations(
@@ -69,9 +70,14 @@ export const apiRemoveEnvironment = createSchema
})
.required();
export const apiUpdateEnvironment = createSchema.partial().extend({
environmentId: z.string().min(1),
});
export const apiUpdateEnvironment = createSchema
.partial()
.extend({
environmentId: z.string().min(1),
})
.omit({
isDefault: true,
});
export const apiDuplicateEnvironment = createSchema
.pick({

View File

@@ -390,6 +390,7 @@ export const apiCreateCustom = notificationsSchema
.pick({
appBuildError: true,
databaseBackup: true,
volumeBackup: true,
dokployRestart: true,
name: true,
appDeploy: true,
@@ -416,6 +417,7 @@ export const apiCreateLark = notificationsSchema
.pick({
appBuildError: true,
databaseBackup: true,
volumeBackup: true,
dokployRestart: true,
name: true,
appDeploy: true,

View File

@@ -103,10 +103,10 @@ export const findEnvironmentsByProjectId = async (projectId: string) => {
export const deleteEnvironment = async (environmentId: string) => {
const currentEnvironment = await findEnvironmentById(environmentId);
if (currentEnvironment.name === "production") {
if (currentEnvironment.isDefault) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "You cannot delete the production environment",
message: "You cannot delete the default environment",
});
}
const deletedEnvironment = await db
@@ -162,9 +162,23 @@ export const duplicateEnvironment = async (
};
export const createProductionEnvironment = async (projectId: string) => {
return createEnvironment({
name: "production",
description: "Production environment",
projectId,
});
const newEnvironment = await db
.insert(environments)
.values({
name: "production",
description: "Production environment",
projectId,
isDefault: true,
})
.returning()
.then((value) => value[0]);
if (!newEnvironment) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error creating the production environment",
});
}
return newEnvironment;
};

View File

@@ -653,6 +653,7 @@ export const updateCustomNotification = async (
appDeploy: input.appDeploy,
appBuildError: input.appBuildError,
databaseBackup: input.databaseBackup,
volumeBackup: input.volumeBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
organizationId: input.organizationId,
@@ -772,6 +773,7 @@ export const updateLarkNotification = async (
appDeploy: input.appDeploy,
appBuildError: input.appBuildError,
databaseBackup: input.databaseBackup,
volumeBackup: input.volumeBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
organizationId: input.organizationId,

View File

@@ -99,6 +99,11 @@ export function parseRawConfig(
.compact()
.value();
// Filter out Dokploy dashboard requests
parsedLogs = parsedLogs.filter(
(log) => log.ServiceName !== "dokploy-service-app@file",
);
// Apply date range filter if provided
if (dateRange?.start || dateRange?.end) {
parsedLogs = parsedLogs.filter((log) => {

View File

@@ -25,7 +25,7 @@ export const initCronJobs = async () => {
return;
}
if (admin.user.enableDockerCleanup) {
if (admin?.user?.enableDockerCleanup) {
scheduleJob("docker-cleanup", "0 0 * * *", async () => {
console.log(
`Docker Cleanup ${new Date().toLocaleString()}] Running docker cleanup`,
@@ -82,7 +82,7 @@ export const initCronJobs = async () => {
}
}
if (admin?.user.logCleanupCron) {
if (admin?.user?.logCleanupCron) {
console.log("Starting log requests cleanup", admin.user.logCleanupCron);
await startLogCleanup(admin.user.logCleanupCron);
}

View File

@@ -8,6 +8,7 @@ import {
getDockerContextPath,
} from "../filesystem/directory";
import type { ApplicationNested } from ".";
import { createEnvFileCommand } from "./utils";
export const getDockerCommand = (application: ApplicationNested) => {
const {
@@ -18,6 +19,7 @@ export const getDockerCommand = (application: ApplicationNested) => {
buildSecrets,
dockerBuildStage,
cleanCache,
createEnvFile,
} = application;
const dockerFilePath = getBuildAppDirectory(application);
@@ -60,6 +62,21 @@ export const getDockerCommand = (application: ApplicationNested) => {
.map(([key, value]) => `${key}=${quote([value])}`)
.join(" ");
/*
Do not generate an environment file when publishDirectory is specified,
as it could be publicly exposed.
Also respect the createEnvFile flag.
*/
let command = "";
if (!publishDirectory && createEnvFile) {
command += createEnvFileCommand(
dockerFilePath,
env,
application.environment.project.env,
application.environment.env,
);
}
for (const key in secrets) {
// Although buildx is smart enough to know we may be referring to an environment variable name,
// we still make sure it doesn't fall back to `type=file`.
@@ -67,7 +84,7 @@ export const getDockerCommand = (application: ApplicationNested) => {
commandArgs.push("--secret", `type=env,id=${key}`);
}
const command = `
command += `
echo "Building ${appName}" ;
cd ${dockerContextPath} || {
echo "❌ The path ${dockerContextPath} does not exist" ;

View File

@@ -74,11 +74,40 @@ export const uploadImageRemoteCommand = async (
throw error;
}
};
/**
* Extract the repository name from imageName by taking the last part after '/'
* Examples:
* - "nginx" -> "nginx"
* - "nginx:latest" -> "nginx:latest"
* - "myuser/myrepo" -> "myrepo"
* - "myuser/myrepo:tag" -> "myrepo:tag"
* - "docker.io/myuser/myrepo" -> "myrepo"
*/
const extractRepositoryName = (imageName: string): string => {
const lastSlashIndex = imageName.lastIndexOf("/");
// If no '/', return the imageName as is
if (lastSlashIndex === -1) {
return imageName;
}
// Extract everything after the last '/'
return imageName.substring(lastSlashIndex + 1);
};
export const getRegistryTag = (registry: Registry, imageName: string) => {
const { registryUrl, imagePrefix, username } = registry;
return imagePrefix
? `${registryUrl ? `${registryUrl}/` : ""}${imagePrefix}/${imageName}`
: `${registryUrl ? `${registryUrl}/` : ""}${username}/${imageName}`;
// Extract the repository name (last part after '/')
const repositoryName = extractRepositoryName(imageName);
// Build the final tag using registry's username/prefix
const targetPrefix = imagePrefix || username;
const finalRegistry = registryUrl || "";
return finalRegistry
? `${finalRegistry}/${targetPrefix}/${repositoryName}`
: `${targetPrefix}/${repositoryName}`;
};
const getRegistryCommands = (

View File

@@ -162,6 +162,7 @@ export const readMonitoringConfig = async (readAll = false) => {
trimmed.endsWith("}")
) {
const log = JSON.parse(trimmed);
// Exclude Dokploy service app and Dashboard requests
if (log.ServiceName !== "dokploy-service-app@file") {
content += `${line}\n`;
validCount++;