mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-01 12:05:23 +02:00
Merge branch 'canary' of github.com:amirhmoradi/dokploy into claude/update-dockerfile-deps-WD7Lw
This commit is contained in:
@@ -35,3 +35,4 @@ export * from "./ssh-key";
|
||||
export * from "./user";
|
||||
export * from "./utils";
|
||||
export * from "./volume-backups";
|
||||
export * from "./web-server-settings";
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
@@ -334,29 +216,4 @@ export const apiUpdateUser = createSchema.partial().extend({
|
||||
currentPassword: z.string().optional(),
|
||||
name: 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(),
|
||||
});
|
||||
|
||||
178
packages/server/src/db/schema/web-server-settings.ts
Normal file
178
packages/server/src/db/schema/web-server-settings.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
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.string().email().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(),
|
||||
});
|
||||
@@ -41,6 +41,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";
|
||||
|
||||
@@ -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,14 @@ 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?.user) {
|
||||
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}`] : []),
|
||||
];
|
||||
},
|
||||
}),
|
||||
emailVerification: {
|
||||
@@ -122,7 +117,7 @@ const { handler, api } = betterAuth({
|
||||
});
|
||||
|
||||
if (!IS_CLOUD) {
|
||||
await updateUser(user.id, {
|
||||
await updateWebServerSettings({
|
||||
serverIp: await getPublicIpWithFallback(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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}`;
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -3,10 +3,10 @@ import { promisify } from "node:util";
|
||||
import { db } from "@dokploy/server/db";
|
||||
import { generateRandomDomain } from "@dokploy/server/templates";
|
||||
import { manageDomain } from "@dokploy/server/utils/traefik/domain";
|
||||
import { getWebServerSettings } from "@dokploy/server/services/web-server-settings";
|
||||
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,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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(".", "-");
|
||||
|
||||
44
packages/server/src/services/web-server-settings.ts
Normal file
44
packages/server/src/services/web-server-settings.ts
Normal file
@@ -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<typeof webServerSettings.$inferInsert>,
|
||||
) => {
|
||||
const current = await getWebServerSettings();
|
||||
|
||||
const [updated] = await db
|
||||
.update(webServerSettings)
|
||||
.set({
|
||||
...updates,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(webServerSettings.id, current?.id ?? ""))
|
||||
.returning();
|
||||
|
||||
return updated;
|
||||
};
|
||||
@@ -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();
|
||||
|
||||
@@ -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<boolean> => {
|
||||
}
|
||||
|
||||
// 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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -146,17 +146,17 @@ export const getContainerByName = (name: string): Promise<ContainerInfo> => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 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,14 +169,15 @@ 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",
|
||||
volumes: "docker volume prune --all --force",
|
||||
};
|
||||
|
||||
export const cleanupContainers = async (serverId?: string) => {
|
||||
@@ -257,24 +258,48 @@ 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.values(cleanupCommands).map(async (command) => {
|
||||
try {
|
||||
(
|
||||
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));
|
||||
}
|
||||
} catch (error) {}
|
||||
}),
|
||||
}),
|
||||
)
|
||||
.then((results) => {
|
||||
const failed = results.filter((r) => r.status === "rejected");
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user