Merge branch 'canary' into Traefik--Enable-dashboard-dokploy-traefik-container-gone,all-services-domains-down

This commit is contained in:
Mauricio Siu
2025-12-06 13:09:06 -06:00
145 changed files with 60719 additions and 4821 deletions

View File

@@ -58,7 +58,11 @@ import {
applications,
} from "@/server/db/schema";
import type { DeploymentJob } from "@/server/queues/queue-types";
import { cleanQueuesByApplication, myQueue } from "@/server/queues/queueSetup";
import {
cleanQueuesByApplication,
killDockerBuild,
myQueue,
} from "@/server/queues/queueSetup";
import { cancelDeployment, deploy } from "@/server/utils/deploy";
import { uploadFileSchema } from "@/utils/schema";
@@ -725,7 +729,21 @@ export const applicationRouter = createTRPCRouter({
}
await cleanQueuesByApplication(input.applicationId);
}),
killBuild: protectedProcedure
.input(apiFindOneApplication)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
if (
application.environment.project.organizationId !==
ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to kill this build",
});
}
await killDockerBuild("application", application.serverId);
}),
readTraefikConfig: protectedProcedure
.input(apiFindOneApplication)
.query(async ({ input, ctx }) => {

View File

@@ -3,13 +3,14 @@ import {
addNewService,
checkServiceAccess,
cloneCompose,
cloneComposeRemote,
createCommand,
createCompose,
createComposeByTemplate,
createDomain,
createMount,
deleteMount,
execAsync,
execAsyncRemote,
findComposeById,
findDomainsByComposeId,
findEnvironmentById,
@@ -58,7 +59,11 @@ import {
compose as composeTable,
} from "@/server/db/schema";
import type { DeploymentJob } from "@/server/queues/queue-types";
import { cleanQueuesByCompose, myQueue } from "@/server/queues/queueSetup";
import {
cleanQueuesByCompose,
killDockerBuild,
myQueue,
} from "@/server/queues/queueSetup";
import { cancelDeployment, deploy } from "@/server/utils/deploy";
import { generatePassword } from "@/templates/utils";
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
@@ -247,6 +252,21 @@ export const composeRouter = createTRPCRouter({
await cleanQueuesByCompose(input.composeId);
return { success: true, message: "Queues cleaned successfully" };
}),
killBuild: protectedProcedure
.input(apiFindCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
if (
compose.environment.project.organizationId !==
ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to kill this build",
});
}
await killDockerBuild("compose", compose.serverId);
}),
loadServices: protectedProcedure
.input(apiFetchServices)
@@ -302,10 +322,12 @@ export const composeRouter = createTRPCRouter({
message: "You are not authorized to fetch this compose",
});
}
const command = await cloneCompose(compose);
if (compose.serverId) {
await cloneComposeRemote(compose);
await execAsyncRemote(compose.serverId, command);
} else {
await cloneCompose(compose);
await execAsync(command);
}
return compose.sourceType;
} catch (err) {

View File

@@ -47,15 +47,19 @@ export const destinationRouter = createTRPCRouter({
input;
try {
const rcloneFlags = [
`--s3-access-key-id=${accessKey}`,
`--s3-secret-access-key=${secretAccessKey}`,
`--s3-region=${region}`,
`--s3-endpoint=${endpoint}`,
`--s3-access-key-id="${accessKey}"`,
`--s3-secret-access-key="${secretAccessKey}"`,
`--s3-region="${region}"`,
`--s3-endpoint="${endpoint}"`,
"--s3-no-check-bucket",
"--s3-force-path-style",
"--retries 1",
"--low-level-retries 1",
"--timeout 10s",
"--contimeout 5s",
];
if (provider) {
rcloneFlags.unshift(`--s3-provider=${provider}`);
rcloneFlags.unshift(`--s3-provider="${provider}"`);
}
const rcloneDestination = `:s3:${bucket}`;
const rcloneCommand = `rclone ls ${rcloneFlags.join(" ")} "${rcloneDestination}"`;

View File

@@ -111,7 +111,7 @@ export const notificationRouter = createTRPCRouter({
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error testing the notification",
message: `${error instanceof Error ? error.message : "Unknown error"}`,
cause: error,
});
}
@@ -228,7 +228,7 @@ export const notificationRouter = createTRPCRouter({
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error testing the notification",
message: `${error instanceof Error ? error.message : "Unknown error"}`,
cause: error,
});
}
@@ -285,7 +285,7 @@ export const notificationRouter = createTRPCRouter({
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error testing the notification",
message: `${error instanceof Error ? error.message : "Unknown error"}`,
cause: error,
});
}

View File

@@ -8,6 +8,7 @@ import {
findEnvironmentById,
findPostgresById,
findProjectById,
getMountPath,
IS_CLOUD,
rebuildDatabase,
removePostgresById,
@@ -37,6 +38,7 @@ import {
postgres as postgresTable,
} from "@/server/db/schema";
import { cancelJobs } from "@/server/utils/backup";
export const postgresRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreatePostgres)
@@ -79,11 +81,13 @@ export const postgresRouter = createTRPCRouter({
);
}
const mountPath = getMountPath(input.dockerImage);
await createMount({
serviceId: newPostgres.postgresId,
serviceType: "postgres",
volumeName: `${newPostgres.appName}-data`,
mountPath: "/var/lib/postgresql/data",
mountPath: mountPath,
type: "volume",
});
@@ -282,12 +286,16 @@ export const postgresRouter = createTRPCRouter({
const backups = await findBackupsByDbId(input.postgresId, "postgres");
const cleanupOperations = [
removeService(postgres.appName, postgres.serverId),
cancelJobs(backups),
removePostgresById(input.postgresId),
async () => await removeService(postgres?.appName, postgres.serverId),
async () => await cancelJobs(backups),
async () => await removePostgresById(input.postgresId),
];
await Promise.allSettled(cleanupOperations);
for (const operation of cleanupOperations) {
try {
await operation();
} catch (_) {}
}
return postgres;
}),
@@ -363,6 +371,7 @@ export const postgresRouter = createTRPCRouter({
message: "You are not authorized to update this Postgres",
});
}
const service = await updatePostgresById(postgresId, {
...rest,
});

View File

@@ -81,8 +81,10 @@ export const serverRouter = createTRPCRouter({
}),
getDefaultCommand: protectedProcedure
.input(apiFindOneServer)
.query(async () => {
return defaultCommand();
.query(async ({ input }) => {
const server = await findServerById(input.serverId);
const isBuildServer = server.serverType === "build";
return defaultCommand(isBuildServer);
}),
all: protectedProcedure.query(async ({ ctx }) => {
const result = await db
@@ -124,10 +126,30 @@ export const serverRouter = createTRPCRouter({
isNotNull(server.sshKeyId),
eq(server.organizationId, ctx.session.activeOrganizationId),
eq(server.serverStatus, "active"),
eq(server.serverType, "deploy"),
)
: and(
isNotNull(server.sshKeyId),
eq(server.organizationId, ctx.session.activeOrganizationId),
eq(server.serverType, "deploy"),
),
});
return result;
}),
buildServers: protectedProcedure.query(async ({ ctx }) => {
const result = await db.query.server.findMany({
orderBy: desc(server.createdAt),
where: IS_CLOUD
? and(
isNotNull(server.sshKeyId),
eq(server.organizationId, ctx.session.activeOrganizationId),
eq(server.serverStatus, "active"),
eq(server.serverType, "build"),
)
: and(
isNotNull(server.sshKeyId),
eq(server.organizationId, ctx.session.activeOrganizationId),
eq(server.serverType, "build"),
),
});
return result;
@@ -383,6 +405,15 @@ export const serverRouter = createTRPCRouter({
const ip = await getPublicIpWithFallback();
return ip;
}),
getServerTime: protectedProcedure.query(() => {
if (IS_CLOUD) {
return null;
}
return {
time: new Date(),
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
};
}),
getServerMetrics: protectedProcedure
.input(
z.object({

View File

@@ -599,7 +599,7 @@ export const settingsRouter = createTRPCRouter({
return ports.some((port) => port.targetPort === 8080);
}),
readStatsLogs: adminProcedure
readStatsLogs: protectedProcedure
.meta({
openapi: {
path: "/read-stats-logs",
@@ -662,7 +662,7 @@ export const settingsRouter = createTRPCRouter({
const processedLogs = processLogs(rawConfig as string, input?.dateRange);
return processedLogs || [];
}),
haveActivateRequests: adminProcedure.query(async () => {
haveActivateRequests: protectedProcedure.query(async () => {
if (IS_CLOUD) {
return true;
}
@@ -677,7 +677,7 @@ export const settingsRouter = createTRPCRouter({
return !!parsedConfig?.accessLog?.filePath;
}),
toggleRequests: adminProcedure
toggleRequests: protectedProcedure
.input(
z.object({
enable: z.boolean(),
@@ -847,7 +847,7 @@ export const settingsRouter = createTRPCRouter({
const ports = await readPorts("dokploy-traefik", input?.serverId);
return ports;
}),
updateLogCleanup: adminProcedure
updateLogCleanup: protectedProcedure
.input(
z.object({
cronExpression: z.string().nullable(),
@@ -863,7 +863,7 @@ export const settingsRouter = createTRPCRouter({
return stopLogCleanup();
}),
getLogCleanupStatus: adminProcedure.query(async () => {
getLogCleanupStatus: protectedProcedure.query(async () => {
return getLogCleanupStatus();
}),

View File

@@ -4,6 +4,7 @@ import {
findNotificationById,
findOrganizationById,
findUserById,
getDokployUrl,
getUserByToken,
IS_CLOUD,
removeUserById,
@@ -419,11 +420,10 @@ export const userRouter = createTRPCRouter({
});
}
const admin = await findAdmin();
const host =
process.env.NODE_ENV === "development"
? "http://localhost:3000"
: admin.user.host;
: await getDokployUrl();
const inviteLink = `${host}/invitation?token=${input.invitationId}`;
const organization = await findOrganizationById(